Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Merged mainline into my branch to get the newest application. |
|---|---|
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA1: |
d0305b305ad0a15a1aa38dbd1830bc9b |
| User & Date: | aku 2007-12-05 08:07:46.000 |
Context
|
2007-12-06
| ||
| 03:48 | Fixed handling of empty revisions. check-in: bf0b70d5e0 user: aku tags: trunk | |
|
2007-12-05
| ||
| 08:07 | Merged mainline into my branch to get the newest application. check-in: d0305b305a user: aku tags: trunk | |
| 07:58 | Bugfix. Translation implies encoding, not the reverse. This caused problems when parsing files with mixed-mode line-endings. The generated char offsets and lengths were off. Found during expansion. check-in: 6f1c4424b4 user: aku tags: trunk | |
|
2007-12-04
| ||
| 02:47 | Add the timeline display preferences page with the ability to turn on and off block markup in timeline comments and to limit the length of timeline comments. check-in: ebb2765954 user: drh tags: trunk | |
Changes
Added Makefile.w32.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #!/usr/bin/make # #### The toplevel directory of the source tree. Fossil can be built # in a directory that is separate from the source tree. Just change # the following to point from the build directory to the src/ folder. # SRCDIR = ../src #### C Compiler and options for use in building executables that # will run on the platform that is doing the build. This is used # to compile code-generator programs as part of the build process. # See TCC below for the C compiler for building the finished binary. # BCC = gcc -g -O2 #### The suffix to add to executable files. ".exe" for windows. # Nothing for unix. # E = #### C Compile and options for use in building executables that # will run on the target platform. This is usually the same # as BCC, unless you are cross-compiling. This C compiler builds # the finished binary for fossil. The BCC compiler above is used # for building intermediate code-generator tools. # #TCC = gcc -O6 #TCC = gcc -g -O0 -Wall -fprofile-arcs -ftest-coverage #TCC = gcc -g -Os -Wall TCC = gcc -g -Os -Wall -DFOSSIL_I18N=0 -L/usr/local/lib -I/usr/local/include #### Extra arguments for linking the finished binary. Fossil needs # to link against the Z-Lib compression library. There are no # other dependencies. We sometimes add the -static option here # so that we can build a static executable that will run in a # chroot jail. # #LIB = -lz LIB = -lz -lws2_32 #### Tcl shell for use in running the fossil testsuite. # TCLSH = tclsh # You should not need to change anything below this line ############################################################################### include $(SRCDIR)/main.mk |
Changes to ideas.txt.
1 2 | Possible ticket file format: | > < | | < < < < < > | | > | | < > | | > > > > | | | > > > > > | | | | | < < | < < < > > > > > | | | | > > | < < | > > > > < < < < | | | < > > > > > > > < > < > > > > | | < | > | < > | < < > | < < | > | < < > > < | | | < > | | | | | | < | > | | > < > > > > > > > < < < > < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | > > | | > > > > | | < < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
Possible ticket file format:
A uuid name description
D datetime
J field value
K uuid
U user
Z md5sum
FIELDs:
comment cumulative text
title text
assignedto text
status enum
resolution enum
subsystem enum
type enum
priority enum
severity enum
deferuntil datetime
duedate datetime
derivedfrom add or subtract uuid
relatedversions add or subtract associate with manifest
presentin add or subtract uuid
fixedin add or subtract uuid
Other table columns:
origintime
lastchange
Field Types:
text width height
enum width valuelist
datetime width
cumulative-text width height
set-of-uuid width
set-of-checkin width
Tables:
tktrid(rid, tkid, mtime); index(tkid, mtime);
ticket(tkid, tkuuid UNIQUE, starttime, lastmod, ...);
tktfield(fieldname UNIQUE, type, width, other);
tktxref(tkid, mid); index(tkid); index(mid);
Tktformat in the config table.
* Three pages: creation, display, and edit
* Separate global and local versions of each page. Local overwrites
global if it exists.
* HTML
* [[field]] to substitute the appropriate form or display element
Ticket Configuraiton File:
* Format:
ticket-configuration
field <fieldname> <type> <param> ...
template <type> <delimiter>
<text>
* Each repository selects a single ticket config for its own use.
* Rescan all tickets following any config change
* Use the ticket-configuration tag must be on the file
Todo:
* Configuration file parser
+ tkt-new-template
+ tkt-view-template
+ tkt-edit-template
+ Reconstruct the ticket table schema
+ Repopulate tktfield
* Ticket control file parser
+ Create ticket entry if necessary
+ Update fields. Ignore unrecognized fields.
* Transfer tkt control info to ticket table
* Setup pages for selecting and editing ticket configuration
* Ticket display
* New ticket creation display
* Ticket change screen
* Ticket query screens
------------------------------------------------------------------------
Change to wiki:
A uuid filename description
D datetime
P uuid ...
U user
W size \n text \n
Z cksum
Hyperlinks:
[lowercasehex] /info/lowercasehex
[attachment.gif] inline image
[tagname] /info/tagname
[wikipagename] /wiki/wikipagename
[/internal/page] /internal/page
[http:...] external link
Markup:
blank-line paragraph break
_*__ bullet
__ indentation
_#.__ enumeration
*text* bold
_text_ italic
------------------------------------------------------------------------
Random thoughts:
* Plink.isprim changed to record:
+ child is the principal descendent of parent. (1)
+ child is a branch from parent (2)
+ child uses parent as a merge (0)
* website can toggle isprim between principal and branch.
+ How to preserve across rebuild. A new record type?
+ How to share with other repositories
* isprim guessed using userid of parent and child. Change
in id suggests a branch. Same id suggests principal.
For a tie, go with the earliest check-in as the principal'
* Autosync mode
* Notes:
+ Designed to avoid branching in highly collaborative
environments.
* Outstanding:
+ On commit, first pull. If commit baseline is not a tip
prompt user to cancel or branch. Default is cancel.
+ Need an "undo" capability
* Done:
* Set a preferred remote repository to use as a server
= Clone repository is the default
* Push after commit
* Automatically pull prior to update.
* Archeological webpage improvements:
+ Use a small amount of CSS+javascript on timelines so that
branching structure is displayed on mouseover. On mouseover
of a checkin, highlight other checkins that are direct (non-merge)
descendents and ancestors of the mouseover checkin.
+ Timeline showing individual branches
|
| ︙ | ︙ | |||
182 183 184 185 186 187 188 |
P uuid ... -- omitted for first manifest
R repository-md5sum
U user-login
Z manifest-checksum
* Accessory:
A uuid|* attachment-uuid description
| < > < | 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
P uuid ... -- omitted for first manifest
R repository-md5sum
U user-login
Z manifest-checksum
* Accessory:
A uuid|* attachment-uuid description
D date-time
E uuid new-comment
G uuid appended-remark
S repositoryid serial-number
T (+|-)tag uuid
U userid
X uuid-to-surpress
Z this-file-checksum
* Change the comment on a version: -- always a leaf except in cluster
D date-time
E new-comment
P uuid -- baseline whose comment is changed
|
| ︙ | ︙ | |||
223 224 225 226 227 228 229 |
* Set or erase a tag -- most recent date wins
B* (+|-)tag uuid
C? comment
D date-time
V* (+|-) tag uuid -- + to set, - to clear.
Z manifest-cksum
-- Must have at least one B or V.
| | | > | | | | | | | > | | | | | | | | 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
* Set or erase a tag -- most recent date wins
B* (+|-)tag uuid
C? comment
D date-time
V* (+|-) tag uuid -- + to set, - to clear.
Z manifest-cksum
-- Must have at least one B or V.
-- Branch tag "hidden" means do not sync
-- Version tag "closed" means do not display as a leaf
* A cluster
M+ uuid
Z manifest-cksum
* Complete set of cards in a control file:
A filename uuid
B (+|-)branch-tag uuid
C comment
D date-time
E uuid edited-comment
F filename uuid
M uuid
N name
P uuid ...
R repository-md5sum
T uuid
U user-login
V (+|-)version-tag uuid
W uuid
Z manifest-checksum
|
Changes to src/add.c.
| ︙ | ︙ | |||
48 49 50 51 52 53 54 |
db_begin_transaction();
for(i=2; i<g.argc; i++){
char *zName;
char *zPath;
Blob pathname;
int isDir;
| | > > > | 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
db_begin_transaction();
for(i=2; i<g.argc; i++){
char *zName;
char *zPath;
Blob pathname;
int isDir;
zName = mprintf("%/", g.argv[i]);
isDir = file_isdir(zName);
if( isDir==1 ) continue;
if( isDir==0 ){
fossil_fatal("not found: %s", zName);
}
if( isDir==2 && access(zName, R_OK) ){
fossil_fatal("cannot open %s", zName);
}
file_tree_name(zName, &pathname);
zPath = blob_str(&pathname);
if( strcmp(zPath, "manifest")==0 || strcmp(zPath, "_FOSSIL_")==0 ){
fossil_fatal("cannot add %s", zPath);
}
if( !file_is_simple_pathname(zPath) ){
fossil_fatal("filename contains illegal characters: %s", zPath);
}
if( db_exists("SELECT 1 FROM vfile WHERE pathname=%Q", zPath) ){
db_multi_exec("UPDATE vfile SET deleted=0 WHERE pathname=%Q", zPath);
}else{
db_multi_exec(
"INSERT INTO vfile(vid,deleted,rid,mrid,pathname)"
"VALUES(%d,0,0,0,%Q)", vid, zPath);
}
|
| ︙ | ︙ | |||
98 99 100 101 102 103 104 |
}
db_begin_transaction();
for(i=2; i<g.argc; i++){
char *zName;
char *zPath;
Blob pathname;
| | | 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
}
db_begin_transaction();
for(i=2; i<g.argc; i++){
char *zName;
char *zPath;
Blob pathname;
zName = mprintf("%/", g.argv[i]);
file_tree_name(zName, &pathname);
zPath = blob_str(&pathname);
if( !db_exists(
"SELECT 1 FROM vfile WHERE pathname=%Q AND NOT deleted", zPath) ){
fossil_fatal("not in the repository: %s", zName);
}else{
db_multi_exec("UPDATE vfile SET deleted=1 WHERE pathname=%Q", zPath);
|
| ︙ | ︙ |
Changes to src/blob.c.
| ︙ | ︙ | |||
47 48 49 50 51 52 53 54 55 56 57 58 59 60 | #define blob_size(X) ((X)->nUsed) /* ** The buffer holding the blob data */ #define blob_buffer(X) ((X)->aData) #endif /* INTERFACE */ /* ** Make sure a blob is initialized */ #define blob_is_init(x) \ assert((x)->xRealloc==blobReallocMalloc || (x)->xRealloc==blobReallocStatic) | > > > > > > > | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | #define blob_size(X) ((X)->nUsed) /* ** The buffer holding the blob data */ #define blob_buffer(X) ((X)->aData) /* ** Seek whence parameter values */ #define BLOB_SEEK_SET 1 #define BLOB_SEEK_CUR 2 #define BLOB_SEEK_END 3 #endif /* INTERFACE */ /* ** Make sure a blob is initialized */ #define blob_is_init(x) \ assert((x)->xRealloc==blobReallocMalloc || (x)->xRealloc==blobReallocStatic) |
| ︙ | ︙ | |||
337 338 339 340 341 342 343 344 345 346 347 348 349 350 |
/*
** Rewind the cursor on a blob back to the beginning.
*/
void blob_rewind(Blob *p){
p->iCursor = 0;
}
/*
** Extract a single line of text from pFrom beginning at the current
** cursor location and use that line of text to initialize pTo.
** pTo will include the terminating \n. Return the number of bytes
** in the line including the \n at the end. 0 is returned at
** end-of-file.
| > > > > > > > > > > > > > > > > > > > > > > > > > > > | 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 |
/*
** Rewind the cursor on a blob back to the beginning.
*/
void blob_rewind(Blob *p){
p->iCursor = 0;
}
/*
** Seek the cursor in a blob to the indicated offset.
*/
int blob_seek(Blob *p, int offset, int whence){
if( whence==BLOB_SEEK_SET ){
p->iCursor = offset;
}else if( whence==BLOB_SEEK_CUR ){
p->iCursor += offset;
}else if( whence==BLOB_SEEK_END ){
p->iCursor = p->nUsed + offset - 1;
}
if( p->iCursor<0 ){
p->iCursor = 0;
}
if( p->iCursor>p->nUsed ){
p->iCursor = p->nUsed;
}
return p->iCursor;
}
/*
** Return the current offset into the blob
*/
int blob_tell(Blob *p){
return p->iCursor;
}
/*
** Extract a single line of text from pFrom beginning at the current
** cursor location and use that line of text to initialize pTo.
** pTo will include the terminating \n. Return the number of bytes
** in the line including the \n at the end. 0 is returned at
** end-of-file.
|
| ︙ | ︙ | |||
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 |
if( i<n ){
assert( aData[i]=='\n' );
i++;
}
blob_extract(pFrom, i-pFrom->iCursor, pTo);
return pTo->nUsed;
}
/*
** Extract a single token from pFrom and use it to initialize pTo.
** Return the number of bytes in the token. If no token is found,
** return 0.
**
** The cursor of pFrom is left pointing at the first character past
** the end of the token.
**
** pTo will be an ephermeral blob. If pFrom changes, it might alter
** pTo as well.
*/
| > > > > > > > > > > > > > > > > > > | 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
if( i<n ){
assert( aData[i]=='\n' );
i++;
}
blob_extract(pFrom, i-pFrom->iCursor, pTo);
return pTo->nUsed;
}
/*
** Trim whitespace off of the end of a blob. Return the number
** of characters remaining.
**
** All this does is reduce the length counter. This routine does
** not insert a new zero terminator.
*/
int blob_trim(Blob *p){
char *z = p->aData;
int n = p->nUsed;
while( n>0 && isspace(z[n-1]) ){ n--; }
p->nUsed = n;
return n;
}
/*
** Extract a single token from pFrom and use it to initialize pTo.
** Return the number of bytes in the token. If no token is found,
** return 0.
**
** A token consists of one or more non-space characters. Leading
** whitespace is ignored.
**
** The cursor of pFrom is left pointing at the first character past
** the end of the token.
**
** pTo will be an ephermeral blob. If pFrom changes, it might alter
** pTo as well.
*/
|
| ︙ | ︙ | |||
400 401 402 403 404 405 406 407 408 409 410 411 412 413 |
*/
int blob_tail(Blob *pFrom, Blob *pTo){
int iCursor = pFrom->iCursor;
blob_extract(pFrom, pFrom->nUsed-pFrom->iCursor, pTo);
pFrom->iCursor = iCursor;
return pTo->nUsed;
}
/*
** Return true if the blob contains a valid UUID_SIZE-digit base16 identifier.
*/
int blob_is_uuid(Blob *pBlob){
return blob_size(pBlob)==UUID_SIZE
&& validate16(blob_buffer(pBlob), UUID_SIZE);
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 |
*/
int blob_tail(Blob *pFrom, Blob *pTo){
int iCursor = pFrom->iCursor;
blob_extract(pFrom, pFrom->nUsed-pFrom->iCursor, pTo);
pFrom->iCursor = iCursor;
return pTo->nUsed;
}
/*
** Copy N lines of text from pFrom into pTo. The copy begins at the
** current cursor position of pIn. The pIn cursor is left pointing
** at the first character past the last \n copied.
**
** If pTo==NULL then this routine simply skips over N lines.
*/
void blob_copy_lines(Blob *pTo, Blob *pFrom, int N){
char *z = pFrom->aData;
int i = pFrom->iCursor;
int n = pFrom->nUsed;
int cnt = 0;
if( N==0 ) return;
while( i<n ){
if( z[i]=='\n' ){
cnt++;
if( cnt==N ){
i++;
break;
}
}
i++;
}
if( pTo ){
blob_append(pTo, &pFrom->aData[pFrom->iCursor], i - pFrom->iCursor);
}
pFrom->iCursor = i;
}
/*
** Return true if the blob contains a valid UUID_SIZE-digit base16 identifier.
*/
int blob_is_uuid(Blob *pBlob){
return blob_size(pBlob)==UUID_SIZE
&& validate16(blob_buffer(pBlob), UUID_SIZE);
|
| ︙ | ︙ | |||
455 456 457 458 459 460 461 |
*/
int blob_tokenize(Blob *pIn, Blob *aToken, int nToken){
int i;
for(i=0; i<nToken && blob_token(pIn, &aToken[i]); i++){}
return i;
}
| < < < < < < < < < < < | | | 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 |
*/
int blob_tokenize(Blob *pIn, Blob *aToken, int nToken){
int i;
for(i=0; i<nToken && blob_token(pIn, &aToken[i]); i++){}
return i;
}
/*
** Do printf-style string rendering and append the results to a blob.
*/
void blob_appendf(Blob *pBlob, const char *zFormat, ...){
va_list ap;
va_start(ap, zFormat);
vxprintf(pBlob, zFormat, ap);
va_end(ap);
}
void blob_vappendf(Blob *pBlob, const char *zFormat, va_list ap){
vxprintf(pBlob, zFormat, ap);
}
/*
** Initalize a blob to the data on an input channel. Return
** the number of bytes read into the blob. Any prior content
** of the blob is discarded, not freed.
*/
|
| ︙ | ︙ | |||
569 570 571 572 573 574 575 |
zName = zBuf;
strcpy(zName, zFilename);
}
nName = file_simplify_name(zName, nName);
for(i=1; i<nName; i++){
if( zName[i]=='/' ){
zName[i] = 0;
| > > > > > > > > | | | > > > | 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 |
zName = zBuf;
strcpy(zName, zFilename);
}
nName = file_simplify_name(zName, nName);
for(i=1; i<nName; i++){
if( zName[i]=='/' ){
zName[i] = 0;
#ifdef __MINGW32__
/*
** On Windows, local path looks like: C:/develop/project/file.txt
** The if stops us from trying to create a directory of a drive letter
** C: in this example.
*/
if( !(i==2 && zName[1]==':') ){
#endif
if( file_mkdir(zName, 1) ){
fossil_panic("unable to create directory %s", zName);
}
#ifdef __MINGW32__
}
#endif
zName[i] = '/';
}
}
out = fopen(zName, "wb");
if( out==0 ){
fossil_panic("unable to open file \"%s\" for writing", zName);
}
|
| ︙ | ︙ |
Added src/branch.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License version 2 as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to create new branches within a repository.
*/
#include "config.h"
#include "branch.h"
#include <assert.h>
void branch_new(void){
int vid, nvid, noSign;
Stmt q;
char *zBranch, *zUuid, *zDate, *zComment;
const char *zColor;
Blob manifest;
Blob mcksum; /* Self-checksum on the manifest */
Blob cksum1, cksum2; /* Before and after commit checksums */
Blob cksum1b; /* Checksum recorded in the manifest */
noSign = find_option("nosign","",0)!=0;
db_must_be_within_tree();
noSign = db_get_int("omitsign", 0)|noSign;
zColor = find_option("bgcolor","c",1);
verify_all_options();
/* fossil branch new name */
if( g.argc<3 ){
usage("branch new ?-bgcolor COLOR BRANCH-NAME");
}
zBranch = g.argv[3];
if( zBranch==0 || zBranch[0]==0 ){
fossil_panic("branch name cannot be empty");
}
user_select();
db_begin_transaction();
if( unsaved_changes() ){
fossil_panic("there are uncommitted changes. please commit first");
}
vid = db_lget_int("checkout", 0);
vfile_aggregate_checksum_disk(vid, &cksum1);
/* Create our new manifest */
blob_zero(&manifest);
zComment = mprintf("Branch created %s", zBranch);
blob_appendf(&manifest, "C %F\n", zComment);
zDate = db_text(0, "SELECT datetime('now')");
zDate[10] = 'T';
blob_appendf(&manifest, "D %s\n", zDate);
db_prepare(&q,
"SELECT pathname, uuid FROM vfile JOIN blob ON vfile.mrid=blob.rid"
" WHERE NOT deleted AND vfile.vid=%d"
" ORDER BY 1", vid);
while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
const char *zUuid = db_column_text(&q, 1);
blob_appendf(&manifest, "F %F %s\n", zName, zUuid);
}
db_finalize(&q);
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
blob_appendf(&manifest, "P %s\n", zUuid);
blob_appendf(&manifest, "R %b\n", &cksum1);
if( zColor!=0 ){
if( strcmp("bgcolor",zBranch)>=0 ){
blob_appendf(&manifest, "T *%F *\n", zBranch);
blob_appendf(&manifest, "T *bgcolor * %F\n", zColor);
}else{
blob_appendf(&manifest, "T *bgcolor * %F\n", zColor);
blob_appendf(&manifest, "T *%F *\n", zBranch);
}
}else{
blob_appendf(&manifest, "T *%F *\n", zBranch);
}
/* Cancel any tags that propagate */
db_prepare(&q,
"SELECT tagname"
" FROM tagxref JOIN tag ON tagxref.tagid=tag.tagid"
" WHERE rid=%d AND tagtype=2", vid);
while( db_step(&q)==SQLITE_ROW ){
const char *zTagname = db_column_text(&q, 0);
blob_appendf(&manifest, "T -%s *\n", zTagname);
}
db_finalize(&q);
blob_appendf(&manifest, "U %F\n", g.zLogin);
md5sum_blob(&manifest, &mcksum);
blob_appendf(&manifest, "Z %b\n", &mcksum);
if( !noSign && clearsign(&manifest, &manifest) ){
Blob ans;
blob_zero(&ans);
prompt_user("unable to sign manifest. continue [y/N]? ", &ans);
if( blob_str(&ans)[0]!='y' ){
db_end_transaction(1);
exit(1);
}
}
/*blob_write_to_file(&manifest, "manifest.new");*/
nvid = content_put(&manifest, 0, 0);
if( nvid==0 ){
fossil_panic("trouble committing manifest: %s", g.zErrMsg);
}
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid);
manifest_crosslink(nvid, &manifest);
content_deltify(vid, nvid, 0);
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid);
printf("Branch Version: %s\n", zUuid);
printf("\n");
printf("Notice: working copy not updated to the new branch. If\n");
printf(" you wish to work on the new branch, update to\n");
printf(" that branch first:\n");
printf("\n");
printf(" fossil update %s\n", zBranch);
/* Verify that the manifest checksum matches the expected checksum */
vfile_aggregate_checksum_repository(nvid, &cksum2);
vfile_aggregate_checksum_manifest(nvid, &cksum2, &cksum1b);
if( blob_compare(&cksum1, &cksum1b) ){
fossil_panic("manifest checksum does not agree with manifest: "
"%b versus %b", &cksum1, &cksum1b);
}
/* Verify that the commit did not modify any disk images. */
vfile_aggregate_checksum_disk(vid, &cksum2);
if( blob_compare(&cksum1, &cksum2) ){
fossil_panic("tree checksums before and after commit do not match");
}
/* Clear the undo/redo stack */
undo_reset();
/* Commit */
db_end_transaction(0);
/* Do an autosync push, if requested */
autosync(0);
}
/*
** COMMAND: branch
**
** Usage: %fossil branch SUBCOMMAND ... ?-R|--repository FILE?
**
** Run various subcommands on the branches of the open repository o
** of the repository identified by the -R or --repository option.
**
** %fossil branch new ?-bgcolor COLOR BRANCH-NAME
**
** Create a new branch BRANCH-NAME. You can optionally give
** a commit message and branch color.
**
** %fossil branch list
**
** List all branches
**
*/
void branch_cmd(void){
int n;
db_find_and_open_repository();
if( g.argc<3 ){
usage("new|list ...");
}
n = strlen(g.argv[2]);
if( n>=2 && strncmp(g.argv[2],"new",n)==0 ){
branch_new();
}else if( n>=2 && strncmp(g.argv[2],"list",n)==0 ){
fossil_panic("branch list is not yet completed");
}else{
fossil_panic("branch subcommand should be one of: "
"new list");
}
}
|
Changes to src/cgi.c.
| ︙ | ︙ | |||
24 25 26 27 28 29 30 | ** This file contains C functions and procedures that provide useful ** services to CGI programs. There are procedures for parsing and ** dispensing QUERY_STRING parameters and cookies, the "mprintf()" ** formatting function and its cousins, and routines to encode and ** decode strings in HTML or HTTP. */ #include "config.h" | > > > > > > | | | < | | | > > | | | | 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
** This file contains C functions and procedures that provide useful
** services to CGI programs. There are procedures for parsing and
** dispensing QUERY_STRING parameters and cookies, the "mprintf()"
** formatting function and its cousins, and routines to encode and
** decode strings in HTML or HTTP.
*/
#include "config.h"
#ifdef __MINGW32__
# include <windows.h> /* for Sleep once server works again */
# include <winsock2.h> /* socket operations */
# define sleep Sleep /* windows does not have sleep, but Sleep */
# include <ws2tcpip.h>
#else
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <sys/times.h>
# include <sys/time.h>
# include <sys/wait.h>
# include <sys/select.h>
#endif
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "cgi.h"
#if INTERFACE
/*
** Shortcuts for cgi_parameter. P("x") returns the value of query parameter
** or cookie "x", or NULL if there is no such parameter or cookie. PD("x","y")
|
| ︙ | ︙ | |||
141 142 143 144 145 146 147 |
*/
void cgi_set_cookie(
const char *zName, /* Name of the cookie */
const char *zValue, /* Value of the cookie. Automatically escaped */
const char *zPath, /* Path cookie applies to. NULL means "/" */
int lifetime /* Expiration of the cookie in seconds from now */
){
| | | 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
*/
void cgi_set_cookie(
const char *zName, /* Name of the cookie */
const char *zValue, /* Value of the cookie. Automatically escaped */
const char *zPath, /* Path cookie applies to. NULL means "/" */
int lifetime /* Expiration of the cookie in seconds from now */
){
if( zPath==0 ) zPath = g.zTop;
if( lifetime>0 ){
lifetime += (int)time(0);
blob_appendf(&extraHeader,
"Set-Cookie: %s=%t; Path=%s; expires=%s; Version=1\r\n",
zName, zValue, zPath, cgi_rfc822_datestamp(lifetime));
}else{
blob_appendf(&extraHeader,
|
| ︙ | ︙ | |||
286 287 288 289 290 291 292 |
**
** The URL must be relative to the base of the fossil server.
*/
void cgi_redirect(const char *zURL){
char *zLocation;
CGIDEBUG(("redirect to %s\n", zURL));
if( strncmp(zURL,"http:",5)==0 || strncmp(zURL,"https:",6)==0 || *zURL=='/' ){
| | | | > > > > > > > | 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
**
** The URL must be relative to the base of the fossil server.
*/
void cgi_redirect(const char *zURL){
char *zLocation;
CGIDEBUG(("redirect to %s\n", zURL));
if( strncmp(zURL,"http:",5)==0 || strncmp(zURL,"https:",6)==0 || *zURL=='/' ){
zLocation = mprintf("Location: %s\r\n", zURL);
}else{
zLocation = mprintf("Location: %s/%s\r\n", g.zBaseURL, zURL);
}
cgi_append_header(zLocation);
cgi_reset_content();
cgi_printf("<html>\n<p>Redirect to %h</p>\n</html>\n", zURL);
cgi_set_status(302, "Moved Temporarily");
free(zLocation);
cgi_reply();
exit(0);
}
void cgi_redirectf(const char *zFormat, ...){
va_list ap;
va_start(ap, zFormat);
cgi_redirect(vmprintf(zFormat, ap));
va_end(ap);
}
/*
** Information about all query parameters and cookies are stored
** in these variables.
*/
static int nAllocQP = 0; /* Space allocated for aParamQP[] */
|
| ︙ | ︙ | |||
320 321 322 323 324 325 326 | ** Add another query parameter or cookie to the parameter set. ** zName is the name of the query parameter or cookie and zValue ** is its fully decoded value. ** ** zName and zValue are not copied and must not change or be ** deallocated after this routine returns. */ | | | 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 |
** Add another query parameter or cookie to the parameter set.
** zName is the name of the query parameter or cookie and zValue
** is its fully decoded value.
**
** zName and zValue are not copied and must not change or be
** deallocated after this routine returns.
*/
void cgi_set_parameter_nocopy(const char *zName, const char *zValue){
if( nAllocQP<=nUsedQP ){
nAllocQP = nAllocQP*2 + 10;
aParamQP = realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) );
if( aParamQP==0 ) exit(1);
}
aParamQP[nUsedQP].zName = zName;
aParamQP[nUsedQP].zValue = zValue;
|
| ︙ | ︙ | |||
343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
** is its fully decoded value.
**
** Copies are made of both the zName and zValue parameters.
*/
void cgi_set_parameter(const char *zName, const char *zValue){
cgi_set_parameter_nocopy(mprintf("%s",zName), mprintf("%s",zValue));
}
/*
** Add a query parameter. The zName portion is fixed but a copy
** must be made of zValue.
*/
void cgi_setenv(const char *zName, const char *zValue){
cgi_set_parameter_nocopy(zName, mprintf("%s",zValue));
| > > > > > > > > > > > > | 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 |
** is its fully decoded value.
**
** Copies are made of both the zName and zValue parameters.
*/
void cgi_set_parameter(const char *zName, const char *zValue){
cgi_set_parameter_nocopy(mprintf("%s",zName), mprintf("%s",zValue));
}
/*
** Replace a parameter with a new value.
*/
void cgi_replace_parameter(const char *zName, const char *zValue){
int i;
for(i=0; i<nUsedQP; i++){
if( strcmp(aParamQP[i].zName,zName)==0 ){
aParamQP[i].zValue = zValue;
}
}
}
/*
** Add a query parameter. The zName portion is fixed but a copy
** must be made of zValue.
*/
void cgi_setenv(const char *zName, const char *zValue){
cgi_set_parameter_nocopy(zName, mprintf("%s",zValue));
|
| ︙ | ︙ | |||
698 699 700 701 702 703 704 705 706 707 708 709 710 711 |
CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
return zValue;
}
}
CGIDEBUG(("no-match [%s]\n", zName));
return zDefault;
}
/*
** Print CGI debugging messages.
*/
void cgi_debug(const char *zFormat, ...){
va_list ap;
if( g.fDebug ){
| > > > > > > > > > > > > | 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 |
CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
return zValue;
}
}
CGIDEBUG(("no-match [%s]\n", zName));
return zDefault;
}
/*
** Return the name of the i-th CGI parameter. Return NULL if there
** are fewer than i registered CGI parmaeters.
*/
const char *cgi_parameter_name(int i){
if( i>=0 && i<nUsedQP ){
return aParamQP[i].zName;
}else{
return 0;
}
}
/*
** Print CGI debugging messages.
*/
void cgi_debug(const char *zFormat, ...){
va_list ap;
if( g.fDebug ){
|
| ︙ | ︙ | |||
892 893 894 895 896 897 898 |
strcmp(zVal, zD) ? "" : " selected"
);
}
}
cgi_printf("%*s</select>\n", in, "");
}
| < < < < < < < < < < | | | 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 |
strcmp(zVal, zD) ? "" : " selected"
);
}
}
cgi_printf("%*s</select>\n", in, "");
}
/*
** This routine works like "printf" except that it has the
** extra formatting capabilities such as %h and %t.
*/
void cgi_printf(const char *zFormat, ...){
va_list ap;
va_start(ap,zFormat);
vxprintf(&cgiContent,zFormat,ap);
va_end(ap);
}
/*
** This routine works like "vprintf" except that it has the
** extra formatting capabilities such as %h and %t.
*/
void cgi_vprintf(const char *zFormat, va_list ap){
vxprintf(&cgiContent,zFormat,ap);
}
/*
** Send a reply indicating that the HTTP request was malformed
*/
static void malformed_request(void){
|
| ︙ | ︙ | |||
946 947 948 949 950 951 952 |
cgi_reset_content();
cgi_set_status(500, "Internal Server Error");
cgi_printf(
"<html><body><h1>Internal Server Error</h1>\n"
"<plaintext>"
);
va_start(ap, zFormat);
| | | 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 |
cgi_reset_content();
cgi_set_status(500, "Internal Server Error");
cgi_printf(
"<html><body><h1>Internal Server Error</h1>\n"
"<plaintext>"
);
va_start(ap, zFormat);
vxprintf(&cgiContent,zFormat,ap);
va_end(ap);
cgi_reply();
exit(1);
}
/*
** Remove the first space-delimited token from a string and return
|
| ︙ | ︙ | |||
1014 1015 1016 1017 1018 1019 1020 |
malformed_request();
}
cgi_setenv("REQUEST_URI", zToken);
for(i=0; zToken[i] && zToken[i]!='?'; i++){}
if( zToken[i] ) zToken[i++] = 0;
cgi_setenv("PATH_INFO", zToken);
cgi_setenv("QUERY_STRING", &zToken[i]);
| | | 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 |
malformed_request();
}
cgi_setenv("REQUEST_URI", zToken);
for(i=0; zToken[i] && zToken[i]!='?'; i++){}
if( zToken[i] ) zToken[i++] = 0;
cgi_setenv("PATH_INFO", zToken);
cgi_setenv("QUERY_STRING", &zToken[i]);
if( getpeername(fileno(stdin), (struct sockaddr*)&remoteName, (socklen_t*)&size)>=0 ){
char *zIpAddr = inet_ntoa(remoteName.sin_addr);
cgi_setenv("REMOTE_ADDR", zIpAddr);
/* Set the Global.zIpAddr variable to the server we are talking to.
** This is used to populate the ipaddr column of the rcvfrom table,
** if any files are received from the connected client.
*/
|
| ︙ | ︙ | |||
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 |
** Implement an HTTP server daemon listening on port iPort.
**
** As new connections arrive, fork a child and let child return
** out of this procedure call. The child will handle the request.
** The parent never returns from this procedure.
*/
void cgi_http_server(int iPort){
int listener; /* The server socket */
int connection; /* A socket for each individual connection */
fd_set readfds; /* Set of file descriptors for select() */
size_t lenaddr; /* Length of the inaddr structure */
int child; /* PID of the child process */
int nchildren = 0; /* Number of child processes */
struct timeval delay; /* How long to wait inside select() */
| > > > > | 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 |
** Implement an HTTP server daemon listening on port iPort.
**
** As new connections arrive, fork a child and let child return
** out of this procedure call. The child will handle the request.
** The parent never returns from this procedure.
*/
void cgi_http_server(int iPort){
#ifdef __MINGW32__
fprintf(stderr,"server not yet available in windows version of fossil\n");
exit(1);
#else
int listener; /* The server socket */
int connection; /* A socket for each individual connection */
fd_set readfds; /* Set of file descriptors for select() */
size_t lenaddr; /* Length of the inaddr structure */
int child; /* PID of the child process */
int nchildren = 0; /* Number of child processes */
struct timeval delay; /* How long to wait inside select() */
|
| ︙ | ︙ | |||
1113 1114 1115 1116 1117 1118 1119 |
}
delay.tv_sec = 60;
delay.tv_usec = 0;
FD_ZERO(&readfds);
FD_SET( listener, &readfds);
if( select( listener+1, &readfds, 0, 0, &delay) ){
lenaddr = sizeof(inaddr);
| | | 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 |
}
delay.tv_sec = 60;
delay.tv_usec = 0;
FD_ZERO(&readfds);
FD_SET( listener, &readfds);
if( select( listener+1, &readfds, 0, 0, &delay) ){
lenaddr = sizeof(inaddr);
connection = accept(listener, (struct sockaddr*)&inaddr, (socklen_t*) &lenaddr);
if( connection>=0 ){
child = fork();
if( child!=0 ){
if( child>0 ) nchildren++;
close(connection);
}else{
close(0);
|
| ︙ | ︙ | |||
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 |
/* Bury dead children */
while( waitpid(0, 0, WNOHANG)>0 ){
nchildren--;
}
}
/* NOT REACHED */
exit(1);
}
/*
** Name of days and months.
*/
static const char *azDays[] =
{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 0};
| > | 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 |
/* Bury dead children */
while( waitpid(0, 0, WNOHANG)>0 ){
nchildren--;
}
}
/* NOT REACHED */
exit(1);
#endif
}
/*
** Name of days and months.
*/
static const char *azDays[] =
{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 0};
|
| ︙ | ︙ |
Changes to src/checkin.c.
| ︙ | ︙ | |||
42 43 44 45 46 47 48 49 |
"WHERE file_is_selected(id) AND (chnged OR deleted OR rid=0) ORDER BY 1"
);
while( db_step(&q)==SQLITE_ROW ){
const char *zPathname = db_column_text(&q,0);
int isDeleted = db_column_int(&q, 1);
int isChnged = db_column_int(&q,2);
int isNew = db_column_int(&q,3)==0;
blob_append(report, zPrefix, nPrefix);
| > > > | > | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
"WHERE file_is_selected(id) AND (chnged OR deleted OR rid=0) ORDER BY 1"
);
while( db_step(&q)==SQLITE_ROW ){
const char *zPathname = db_column_text(&q,0);
int isDeleted = db_column_int(&q, 1);
int isChnged = db_column_int(&q,2);
int isNew = db_column_int(&q,3)==0;
char *zFullName = mprintf("%s/%s", g.zLocalRoot, zPathname);
blob_append(report, zPrefix, nPrefix);
if( access(zFullName, 0) ){
blob_appendf(report, "MISSING %s\n", zPathname);
}else if( isNew ){
blob_appendf(report, "ADDED %s\n", zPathname);
}else if( isDeleted ){
blob_appendf(report, "DELETED %s\n", zPathname);
}else if( isChnged==2 ){
blob_appendf(report, "UPDATED_BY_MERGE %s\n", zPathname);
}else if( isChnged==3 ){
blob_appendf(report, "ADDED_BY_MERGE %s\n", zPathname);
}else{
blob_appendf(report, "EDITED %s\n", zPathname);
}
free(zFullName);
}
db_finalize(&q);
db_prepare(&q, "SELECT uuid FROM vmerge JOIN blob ON merge=rid"
" WHERE id=0");
while( db_step(&q)==SQLITE_ROW ){
blob_append(report, zPrefix, nPrefix);
blob_appendf(report, "MERGED_WITH %s\n", db_column_text(&q, 0));
|
| ︙ | ︙ | |||
160 161 162 163 164 165 166 |
printf("%s\n", db_column_text(&q, 0));
}
db_finalize(&q);
}
/*
** COMMAND: clean
| | | > > > > > > > | > > > > > > > > > > | 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
printf("%s\n", db_column_text(&q, 0));
}
db_finalize(&q);
}
/*
** COMMAND: clean
** Usage: %fossil clean ?-all
** Delete all "extra" files in the source tree. "Extra" files are
** files that are not officially part of the checkout. See also
** the "extra" command. This operation cannot be undone.
**
** You will be prompted before removing each file. If you are
** sure you wish to remove all "extra" files you can specify the
** optional -all flag.
*/
void clean_cmd(void){
int allFlag;
Blob path;
Stmt q;
allFlag = find_option("all","a",0)!=0;
db_must_be_within_tree();
db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)");
chdir(g.zLocalRoot);
blob_zero(&path);
vfile_scan(0, &path);
db_prepare(&q,
"SELECT %Q || x FROM sfile"
" WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_')"
" ORDER BY 1", g.zLocalRoot);
while( db_step(&q)==SQLITE_ROW ){
if( allFlag ){
unlink(db_column_text(&q, 0));
}else{
Blob ans;
char *prompt = mprintf("remove unmanaged file \"%s\" [y/N]? ",
db_column_text(&q, 0));
blob_zero(&ans);
prompt_user(prompt, &ans);
if( blob_str(&ans)[0]=='y' ){
unlink(db_column_text(&q, 0));
}
}
}
db_finalize(&q);
}
/*
** Prepare a commit comment. Let the user modify it using the
** editor specified in the global_config table or either
|
| ︙ | ︙ | |||
203 204 205 206 207 208 209 |
char *zComment;
int i;
blob_set(&text,
"\n# Enter comments on this commit. Lines beginning with # are ignored\n"
"#\n"
);
status_report(&text, "# ");
| | | | 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
char *zComment;
int i;
blob_set(&text,
"\n# Enter comments on this commit. Lines beginning with # are ignored\n"
"#\n"
);
status_report(&text, "# ");
zEditor = db_get("editor", 0);
if( zEditor==0 ){
zEditor = getenv("VISUAL");
}
if( zEditor==0 ){
zEditor = getenv("EDITOR");
}
if( zEditor==0 ){
zEditor = "ed";
}
zFile = db_text(0, "SELECT '%qci-comment-' || hex(randomblob(6)) || '.txt'",
g.zLocalRoot);
blob_write_to_file(&text, zFile);
zCmd = mprintf("%s \"%s\"", zEditor, zFile);
printf("%s\n", zCmd);
if( system(zCmd) ){
fossil_panic("editor aborted");
}
blob_reset(&text);
blob_read_from_file(&text, zFile);
unlink(zFile);
|
| ︙ | ︙ | |||
296 297 298 299 300 301 302 |
** prompted for your GPG passphrase in order to sign the new manifest
** unless the "--nosign" options is used. All files that have
** changed will be committed unless some subset of files is specified
** on the command line.
*/
void commit_cmd(void){
int rc;
| | > > | | > > > > > | > > > > > > | 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 |
** prompted for your GPG passphrase in order to sign the new manifest
** unless the "--nosign" options is used. All files that have
** changed will be committed unless some subset of files is specified
** on the command line.
*/
void commit_cmd(void){
int rc;
int vid, nrid, nvid, wouldFork=0;
Blob comment;
const char *zComment;
Stmt q;
Stmt q2;
char *zUuid, *zDate;
int noSign = 0; /* True to omit signing the manifest using GPG */
int isAMerge = 0; /* True if checking in a merge */
int forceFlag = 0; /* Force a fork */
char *zManifestFile; /* Name of the manifest file */
Blob manifest;
Blob muuid; /* Manifest uuid */
Blob mcksum; /* Self-checksum on the manifest */
Blob cksum1, cksum2; /* Before and after commit checksums */
Blob cksum1b; /* Checksum recorded in the manifest */
noSign = find_option("nosign","",0)!=0;
zComment = find_option("comment","m",1);
forceFlag = find_option("force", "r", 0)!=0;
db_must_be_within_tree();
noSign = db_get_int("omitsign", 0)|noSign;
verify_all_options();
/*
** Autosync if requested.
*/
autosync(1);
/* There are two ways this command may be executed. If there are
** no arguments following the word "commit", then all modified files
** in the checked out directory are committed. If one or more arguments
** follows "commit", then only those files are committed.
**
** After the following function call has returned, the Global.aCommitFile[]
** array is allocated to contain the "id" field from the vfile table
** for each file to be committed. Or, if aCommitFile is NULL, all files
** should be committed.
*/
select_commit_files();
isAMerge = db_exists("SELECT 1 FROM vmerge");
if( g.aCommitFile && isAMerge ){
fossil_fatal("cannot do a partial commit of a merge");
}
user_select();
db_begin_transaction();
rc = unsaved_changes();
if( rc==0 && !isAMerge && !forceFlag ){
fossil_panic("nothing has changed");
}
/* If one or more files that were named on the command line have not
** been modified, bail out now.
*/
if( g.aCommitFile ){
Blob unmodified;
memset(&unmodified, 0, sizeof(Blob));
blob_init(&unmodified, 0, 0);
db_blob(&unmodified,
"SELECT pathname FROM vfile WHERE chnged = 0 AND file_is_selected(id)"
);
if( strlen(blob_str(&unmodified)) ){
fossil_panic("file %s has not changed", blob_str(&unmodified));
}
}
vid = db_lget_int("checkout", 0);
if( db_exists("SELECT 1 FROM plink WHERE pid=%d", vid) ){
wouldFork=1;
if( forceFlag==0 && db_get_int("safemerge", 0)==0 ){
fossil_fatal("would fork. use -f or --force");
}
}
vfile_aggregate_checksum_disk(vid, &cksum1);
if( zComment ){
blob_zero(&comment);
blob_append(&comment, zComment, -1);
}else{
prepare_commit_comment(&comment);
}
|
| ︙ | ︙ | |||
503 504 505 506 507 508 509 |
fossil_panic("tree checksums before and after commit do not match");
}
/* Clear the undo/redo stack */
undo_reset();
/* Commit */
| | | > > > > > > > > > > > > | 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 |
fossil_panic("tree checksums before and after commit do not match");
}
/* Clear the undo/redo stack */
undo_reset();
/* Commit */
db_end_transaction(0);
if( wouldFork==0 ){
/* Do an autosync push if requested. If wouldFork == 1, then they either
** forced this commit or safe merge is on, and this commit did indeed
** create a fork. In this case, we want the user to merge before sending
** their new commit back to the rest of the world, so do not auto-push.
*/
autosync(0);
}else{
printf("Warning: commit caused a fork to occur. Please merge and push\n");
printf(" your changes as soon as possible.\n");
}
}
|
Changes to src/clearsign.c.
| ︙ | ︙ | |||
32 33 34 35 36 37 38 |
** Clearsign the given blob. Put the signed version in
** pOut.
*/
int clearsign(Blob *pIn, Blob *pOut){
char *zRand;
char *zIn;
char *zOut;
| | | 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
** Clearsign the given blob. Put the signed version in
** pOut.
*/
int clearsign(Blob *pIn, Blob *pOut){
char *zRand;
char *zIn;
char *zOut;
char *zBase = db_get("clear-sign", "gpg --clearsign -o ");
char *zCmd;
int rc;
zRand = db_text(0, "SELECT hex(randomblob(10))");
zOut = mprintf("out-%s", zRand);
zIn = mprintf("in-%z", zRand);
blob_write_to_file(pIn, zOut);
zCmd = mprintf("%s %s %s", zBase, zIn, zOut);
|
| ︙ | ︙ |
Changes to src/clone.c.
| ︙ | ︙ | |||
37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
** Make a clone of a repository specified by URL in the local
** file named FILENAME.
*/
void clone_cmd(void){
if( g.argc!=4 ){
usage("FILE-OR-URL NEW-REPOSITORY");
}
if( file_size(g.argv[3])>0 ){
fossil_panic("file already exists: %s", g.argv[3]);
}
url_parse(g.argv[2]);
db_create_repository(g.argv[3]);
db_open_repository(g.argv[3]);
user_select();
| > > > | | | | > < < > | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
** Make a clone of a repository specified by URL in the local
** file named FILENAME.
*/
void clone_cmd(void){
if( g.argc!=4 ){
usage("FILE-OR-URL NEW-REPOSITORY");
}
db_open_config();
if( file_size(g.argv[3])>0 ){
fossil_panic("file already exists: %s", g.argv[3]);
}
url_parse(g.argv[2]);
db_create_repository(g.argv[3]);
db_open_repository(g.argv[3]);
db_begin_transaction();
db_initial_setup(0, 0);
user_select();
db_set("content-schema", CONTENT_SCHEMA, 0);
db_set("aux-schema", AUX_SCHEMA, 0);
if( !g.urlIsFile ){
db_set("last-sync-url", g.argv[2], 0);
}
db_multi_exec(
"INSERT INTO config(name,value)"
" VALUES('server-code', lower(hex(randomblob(20))));"
);
if( g.urlIsFile ){
Stmt q;
db_multi_exec("ATTACH DATABASE %Q AS orig", g.urlName);
db_prepare(&q,
"SELECT name FROM orig.sqlite_master"
" WHERE type='table'"
);
while( db_step(&q)==SQLITE_ROW ){
const char *zTab = db_column_text(&q, 0);
db_multi_exec("INSERT OR IGNORE INTO %Q SELECT * FROM orig.%Q",
zTab, zTab);
}
db_finalize(&q);
}else{
client_sync(0,0,1);
}
db_end_transaction(0);
}
|
Changes to src/construct.c.
| ︙ | ︙ | |||
138 139 140 141 142 143 144 | /* Create the foundation */ db_create_repository(zRepository); db_open_repository(zRepository); db_open_config(); db_begin_transaction(); | | | | 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
/* Create the foundation */
db_create_repository(zRepository);
db_open_repository(zRepository);
db_open_config();
db_begin_transaction();
db_initial_setup(0, 1);
printf("project-id: %s\n", db_get("project-code", 0));
printf("server-id: %s\n", db_get("server-code", 0));
printf("admin-user: %s (no password set yet!)\n", g.zLogin);
printf("baseline: %s\n", db_text(0, "SELECT uuid FROM blob"));
/* Scan origin and insert all files found inside */
fileCnt = import_origin (zOrigin);
printf("imported: %d %s\n", fileCnt, fileCnt == 1 ?
"file" : "files");
/* Finalize the repository, rebuild the derived tables */
errCnt = rebuild_db(0, 0);
if( errCnt ){
printf("%d %s. Rolling back changes.\n", errCnt, errCnt == 1 ?
"error" : "errors");
db_end_transaction(1);
}else{
db_end_transaction(0);
|
| ︙ | ︙ |
Changes to src/content.c.
| ︙ | ︙ | |||
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
** is a phantom, zero pBlob and return 0.
*/
int content_get(int rid, Blob *pBlob){
Stmt q;
Blob src;
int srcid;
int rc = 0;
assert( g.repositoryOpen );
srcid = findSrcid(rid);
blob_zero(pBlob);
if( srcid ){
if( content_get(srcid, &src) ){
db_prepare(&q, "SELECT content FROM blob WHERE rid=%d AND size>=0", rid);
if( db_step(&q)==SQLITE_ROW ){
Blob delta;
db_ephemeral_blob(&q, 0, &delta);
blob_uncompress(&delta, &delta);
blob_init(pBlob,0,0);
blob_delta_apply(&src, &delta, pBlob);
blob_reset(&delta);
rc = 1;
}
db_finalize(&q);
blob_reset(&src);
}
}else{
db_prepare(&q, "SELECT content FROM blob WHERE rid=%d AND size>=0", rid);
if( db_step(&q)==SQLITE_ROW ){
db_ephemeral_blob(&q, 0, pBlob);
blob_uncompress(pBlob, pBlob);
rc = 1;
}
db_finalize(&q);
}
return rc;
}
/*
** COMMAND: test-content-get
**
** Extract a blob from the database and write it into a file.
*/
void test_content_get_cmd(void){
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
** is a phantom, zero pBlob and return 0.
*/
int content_get(int rid, Blob *pBlob){
Stmt q;
Blob src;
int srcid;
int rc = 0;
static Bag inProcess;
assert( g.repositoryOpen );
srcid = findSrcid(rid);
blob_zero(pBlob);
if( srcid ){
if( bag_find(&inProcess, srcid) ){
db_multi_exec(
"UPDATE blob SET content=NULL, size=-1 WHERE rid=%d;"
"DELETE FROM delta WHERE rid=%d;"
"INSERT OR IGNORE INTO phantom VALUES(%d);",
srcid, srcid, srcid
);
blob_zero(pBlob);
return 0;
}
bag_insert(&inProcess, srcid);
if( content_get(srcid, &src) ){
db_prepare(&q, "SELECT content FROM blob WHERE rid=%d AND size>=0", rid);
if( db_step(&q)==SQLITE_ROW ){
Blob delta;
db_ephemeral_blob(&q, 0, &delta);
blob_uncompress(&delta, &delta);
blob_init(pBlob,0,0);
blob_delta_apply(&src, &delta, pBlob);
blob_reset(&delta);
rc = 1;
}
db_finalize(&q);
blob_reset(&src);
}
bag_remove(&inProcess, srcid);
}else{
db_prepare(&q, "SELECT content FROM blob WHERE rid=%d AND size>=0", rid);
if( db_step(&q)==SQLITE_ROW ){
db_ephemeral_blob(&q, 0, pBlob);
blob_uncompress(pBlob, pBlob);
rc = 1;
}
db_finalize(&q);
}
return rc;
}
/*
** Get the contents of a file within a given revision.
*/
int content_get_historical_file(const char *revision, const char *file, Blob *content){
Blob mfile;
Manifest m;
int i, rid=0;
rid = name_to_rid(revision);
content_get(rid, &mfile);
if( manifest_parse(&m, &mfile) ){
for(i=0; i<m.nFile; i++){
if( strcmp(m.aFile[i].zName, file)==0 ){
rid = uuid_to_rid(m.aFile[i].zUuid, 0);
return content_get(rid, content);
}
}
fossil_panic("file: %s does not exist in revision: %s", file, revision);
}else{
fossil_panic("could not parse manifest for revision: %s", revision);
}
return 0;
}
/*
** COMMAND: test-content-get
**
** Extract a blob from the database and write it into a file.
*/
void test_content_get_cmd(void){
|
| ︙ | ︙ |
Changes to src/db.c.
1 2 3 4 5 6 7 8 9 10 11 | /* ** Copyright (c) 2006 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public ** License version 2 as published by the Free Software Foundation. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. | | | > > > < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | /* ** Copyright (c) 2006 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public ** License version 2 as published by the Free Software Foundation. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. ** ** You should have received a copy of the GNU General Public ** License along with this library; if not, write to the ** Free Software Foundation, Inc., 59 Temple Place - Suite 330, ** Boston, MA 02111-1307, USA. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** Code for interfacing to the various databases. ** ** There are three separate database files that fossil interacts ** with: ** ** (1) The "user" database in ~/.fossil ** ** (2) The "repository" database ** ** (3) A local checkout database named "FOSSIL" and located at the ** root of the local copy of the source tree. ** */ #include "config.h" #ifndef __MINGW32__ # include <pwd.h> #endif #include <sqlite3.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include "db.h" #if INTERFACE /* ** An single SQL statement is represented as an instance of the following ** structure. |
| ︙ | ︙ | |||
68 69 70 71 72 73 74 |
style_footer();
cgi_reply();
}else{
fprintf(stderr, "%s: %s\n", g.argv[0], z);
}
db_force_rollback();
exit(1);
| < > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
style_footer();
cgi_reply();
}else{
fprintf(stderr, "%s: %s\n", g.argv[0], z);
}
db_force_rollback();
exit(1);
}
static int nBegin = 0; /* Nesting depth of BEGIN */
static int doRollback = 0; /* True to force a rollback */
static int nCommitHook = 0; /* Number of commit hooks */
static struct sCommitHook {
int (*xHook)(void); /* Functions to call at db_end_transaction() */
int sequence; /* Call functions in sequence order */
} aHook[5];
/*
** This routine is called by the SQLite commit-hook mechanism
** just prior to each omit. All this routine does is verify
** that nBegin really is zero. That insures that transactions
** cannot commit by any means other than by calling db_end_transaction()
** below.
**
** This is just a safety and sanity check.
*/
static int db_verify_at_commit(void *notUsed){
if( nBegin ){
fossil_panic("illegal commit attempt");
return 1;
}
return 0;
}
/*
** Begin and end a nested transaction
*/
void db_begin_transaction(void){
if( nBegin==0 ){
db_multi_exec("BEGIN");
sqlite3_commit_hook(g.db, db_verify_at_commit, 0);
}
nBegin++;
}
void db_end_transaction(int rollbackFlag){
if( rollbackFlag ) doRollback = 1;
nBegin--;
if( nBegin==0 ){
int i;
for(i=0; doRollback==0 && i<nCommitHook; i++){
doRollback |= aHook[i].xHook();
}
db_multi_exec(doRollback ? "ROLLBACK" : "COMMIT");
doRollback = 0;
}
}
void db_force_rollback(void){
if( nBegin ){
sqlite3_exec(g.db, "ROLLBACK", 0, 0, 0);
}
nBegin = 0;
}
/*
** Install a commit hook. Hooks are installed in sequence order.
** It is an error to install the same commit hook more than once.
**
** Each commit hook is called (in order of accending sequence) at
** each commit operation. If any commit hook returns non-zero,
** the subsequence commit hooks are omitted and the transaction
** rolls back rather than commit. It is the responsibility of the
** hooks themselves to issue any error messages.
*/
void db_commit_hook(int (*x)(void), int sequence){
int i;
assert( nCommitHook < sizeof(aHook)/sizeof(aHook[1]) );
for(i=0; i<nCommitHook; i++){
assert( x!=aHook[i].xHook );
if( aHook[i].sequence>sequence ){
int s = sequence;
int (*xS)(void) = x;
sequence = aHook[i].sequence;
x = aHook[i].xHook;
aHook[i].sequence = s;
aHook[i].xHook = xS;
}
}
aHook[nCommitHook].sequence = sequence;
aHook[nCommitHook].xHook = x;
nCommitHook++;
}
/*
** Prepare or reprepare the sqlite3 statement from the raw SQL text.
*/
static void reprepare(Stmt *pStmt){
sqlite3_stmt *pNew;
if( sqlite3_prepare(g.db, blob_buffer(&pStmt->sql), -1, &pNew, 0)!=0 ){
db_err("%s\n%s", blob_str(&pStmt->sql), sqlite3_errmsg(g.db));
|
| ︙ | ︙ | |||
162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
*/
int db_bind_int(Stmt *pStmt, const char *zParamName, int iValue){
return sqlite3_bind_int(pStmt->pStmt, paramIdx(pStmt, zParamName), iValue);
}
int db_bind_int64(Stmt *pStmt, const char *zParamName, i64 iValue){
return sqlite3_bind_int64(pStmt->pStmt, paramIdx(pStmt, zParamName), iValue);
}
int db_bind_text(Stmt *pStmt, const char *zParamName, const char *zValue){
return sqlite3_bind_text(pStmt->pStmt, paramIdx(pStmt, zParamName), zValue,
-1, SQLITE_STATIC);
}
int db_bind_null(Stmt *pStmt, const char *zParamName){
return sqlite3_bind_null(pStmt->pStmt, paramIdx(pStmt, zParamName));
}
| > > > | 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
*/
int db_bind_int(Stmt *pStmt, const char *zParamName, int iValue){
return sqlite3_bind_int(pStmt->pStmt, paramIdx(pStmt, zParamName), iValue);
}
int db_bind_int64(Stmt *pStmt, const char *zParamName, i64 iValue){
return sqlite3_bind_int64(pStmt->pStmt, paramIdx(pStmt, zParamName), iValue);
}
int db_bind_double(Stmt *pStmt, const char *zParamName, double rValue){
return sqlite3_bind_double(pStmt->pStmt, paramIdx(pStmt, zParamName), rValue);
}
int db_bind_text(Stmt *pStmt, const char *zParamName, const char *zValue){
return sqlite3_bind_text(pStmt->pStmt, paramIdx(pStmt, zParamName), zValue,
-1, SQLITE_STATIC);
}
int db_bind_null(Stmt *pStmt, const char *zParamName){
return sqlite3_bind_null(pStmt->pStmt, paramIdx(pStmt, zParamName));
}
|
| ︙ | ︙ | |||
254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
return sqlite3_column_int64(pStmt->pStmt, N);
}
double db_column_double(Stmt *pStmt, int N){
return sqlite3_column_double(pStmt->pStmt, N);
}
const char *db_column_text(Stmt *pStmt, int N){
return (char*)sqlite3_column_text(pStmt->pStmt, N);
}
char *db_column_malloc(Stmt *pStmt, int N){
return mprintf("%s", db_column_text(pStmt, N));
}
void db_column_blob(Stmt *pStmt, int N, Blob *pBlob){
blob_append(pBlob, sqlite3_column_blob(pStmt->pStmt, N),
sqlite3_column_bytes(pStmt->pStmt, N));
| > > > > > > | 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
return sqlite3_column_int64(pStmt->pStmt, N);
}
double db_column_double(Stmt *pStmt, int N){
return sqlite3_column_double(pStmt->pStmt, N);
}
const char *db_column_text(Stmt *pStmt, int N){
return (char*)sqlite3_column_text(pStmt->pStmt, N);
}
const char *db_column_name(Stmt *pStmt, int N){
return (char*)sqlite3_column_name(pStmt->pStmt, N);
}
int db_column_count(Stmt *pStmt){
return sqlite3_column_count(pStmt->pStmt);
}
char *db_column_malloc(Stmt *pStmt, int N){
return mprintf("%s", db_column_text(pStmt, N));
}
void db_column_blob(Stmt *pStmt, int N, Blob *pBlob){
blob_append(pBlob, sqlite3_column_blob(pStmt->pStmt, N),
sqlite3_column_bytes(pStmt->pStmt, N));
|
| ︙ | ︙ | |||
483 484 485 486 487 488 489 |
/*
** Open the user database in "~/.fossil". Create the database anew if
** it does not already exist.
*/
void db_open_config(void){
char *zDbName;
| | > > > > > > > > > > > > > > > > | 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 |
/*
** Open the user database in "~/.fossil". Create the database anew if
** it does not already exist.
*/
void db_open_config(void){
char *zDbName;
const char *zHome;
#ifdef __MINGW32__
zHome = getenv("LOCALAPPDATA");
if( zHome==0 ){
zHome = getenv("APPDATA");
if( zHome==0 ){
zHome = getenv("HOMEPATH");
}
}
#else
zHome = getenv("HOME");
#endif
if( zHome==0 ){
db_err("cannot local home directory");
}
#ifdef __MINGW32__
/* . filenames give some window systems problems and many apps problems */
zDbName = mprintf("%s/_fossil", zHome);
#else
zDbName = mprintf("%s/.fossil", zHome);
#endif
if( g.configOpen ) return;
if( file_size(zDbName)<1024*3 ){
db_init_database(zDbName, zConfigSchema, (char*)0);
}
db_open_or_attach(zDbName, "configdb");
g.configOpen = 1;
}
|
| ︙ | ︙ | |||
517 518 519 520 521 522 523 | } /* ** Locate the root directory of the local repository tree. The root ** directory is found by searching for a file named "FOSSIL" that contains ** a valid repository database. ** | | > > > > | 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 |
}
/*
** Locate the root directory of the local repository tree. The root
** directory is found by searching for a file named "FOSSIL" that contains
** a valid repository database.
**
** If no valid FOSSIL file is found, we move up one level and try again.
** Once the file is found, the g.zLocalRoot variable is set to the root of
** the repository tree and this routine returns 1. If no database is
** found, then this routine return 0.
**
** This routine always opens the user database regardless of whether or
** not the repository database is found. If the FOSSIL file is found,
** it is attached to the open database connection too.
*/
int db_open_local(void){
int n;
char zPwd[2000];
char *zPwdConv;
if( g.localOpen) return 1;
if( getcwd(zPwd, sizeof(zPwd)-20)==0 ){
db_err("pwd too big: max %d", sizeof(zPwd)-20);
}
n = strlen(zPwd);
zPwdConv = mprintf("%/", zPwd);
strncpy(zPwd, zPwdConv, 2000-20);
free(zPwdConv);
while( n>0 ){
if( access(zPwd, W_OK) ) break;
strcpy(&zPwd[n], "/_FOSSIL_");
if( isValidLocalDb(zPwd) ){
/* Found a valid _FOSSIL_ file */
zPwd[n] = 0;
g.zLocalRoot = mprintf("%s/", zPwd);
|
| ︙ | ︙ | |||
646 647 648 649 650 651 652 |
/*
** Fill an empty repository database with the basic information for a
** repository. This function is shared between 'create_repository_cmd'
** ('new') and 'reconstruct_cmd' ('reconstruct'), both of which create
** new repositories.
**
| | | > < | | | | | | | > | > | > > > > | > > > > | | 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 |
/*
** Fill an empty repository database with the basic information for a
** repository. This function is shared between 'create_repository_cmd'
** ('new') and 'reconstruct_cmd' ('reconstruct'), both of which create
** new repositories.
**
** The makeInitialVersion flag determines whether or not an initial
** manifest is created. The makeServerCodes flag determines whether or
** not server and project codes are invented for this repository.
*/
void db_initial_setup (int makeInitialVersion, int makeServerCodes){
char *zDate;
const char *zUser;
Blob hash;
Blob manifest;
db_set("content-schema", CONTENT_SCHEMA, 0);
db_set("aux-schema", AUX_SCHEMA, 0);
if( makeServerCodes ){
db_multi_exec(
"INSERT INTO config(name,value)"
" VALUES('server-code', lower(hex(randomblob(20))));"
"INSERT INTO config(name,value)"
" VALUES('project-code', lower(hex(randomblob(20))));"
);
}
if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
if( !db_is_global("safemerge") ) db_set_int("safemerge", 0, 0);
if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
zUser = db_get("default-user", 0);
if( zUser==0 ){
#ifdef __MINGW32__
zUser = getenv("USERNAME");
#else
zUser = getenv("USER");
#endif
}
if( zUser==0 ){
zUser = "root";
}
db_multi_exec(
"INSERT INTO user(login, pw, cap, info)"
"VALUES(%Q,'','s','')", zUser
);
db_multi_exec(
"INSERT INTO user(login,pw,cap,info)"
" VALUES('anonymous','anonymous','hjkorw','Anon');"
"INSERT INTO user(login,pw,cap,info)"
" VALUES('nobody','','jor','Nobody');"
);
user_select();
if (makeInitialVersion){
blob_zero(&manifest);
blob_appendf(&manifest, "C initial\\sempty\\sbaseline\n");
zDate = db_text(0, "SELECT datetime('now')");
zDate[10]='T';
blob_appendf(&manifest, "D %s\n", zDate);
blob_appendf(&manifest, "P\n");
md5sum_init();
|
| ︙ | ︙ | |||
715 716 717 718 719 720 721 |
if( g.argc!=3 ){
usage("REPOSITORY-NAME");
}
db_create_repository(g.argv[2]);
db_open_repository(g.argv[2]);
db_open_config();
db_begin_transaction();
| | | 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 |
if( g.argc!=3 ){
usage("REPOSITORY-NAME");
}
db_create_repository(g.argv[2]);
db_open_repository(g.argv[2]);
db_open_config();
db_begin_transaction();
db_initial_setup(1, 1);
db_end_transaction(0);
printf("project-id: %s\n", db_get("project-code", 0));
printf("server-id: %s\n", db_get("server-code", 0));
printf("admin-user: %s (no password set yet!)\n", g.zLogin);
printf("baseline: %s\n", db_text(0, "SELECT uuid FROM blob"));
}
|
| ︙ | ︙ | |||
799 800 801 802 803 804 805 | } } /* ** Get and set values from the CONFIG, GLOBAL_CONFIG and VVAR table in the ** repository and local databases. */ | | | > | | > > > > > > > > | > | > > > > > > > > > > > > > > > > | > > > | | < > > | < < > | | > > | > | | > > | > > > > > > > > > > > > > > > | | | 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 |
}
}
/*
** Get and set values from the CONFIG, GLOBAL_CONFIG and VVAR table in the
** repository and local databases.
*/
char *db_get(const char *zName, char *zDefault){
char *z = 0;
if( g.repositoryOpen ){
z = db_text(0, "SELECT value FROM config WHERE name=%Q", zName);
}
if( z==0 && g.configOpen ){
z = db_text(0, "SELECT value FROM global_config WHERE name=%Q", zName);
}
if( z==0 ){
z = zDefault;
}
return z;
}
void db_set(const char *zName, const char *zValue, int globalFlag){
db_begin_transaction();
db_multi_exec("REPLACE INTO %sconfig(name,value) VALUES(%Q,%Q)",
globalFlag ? "global_" : "", zName, zValue);
if( globalFlag && g.repositoryOpen ){
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
}
db_end_transaction(0);
}
int db_is_global(const char *zName){
if( g.configOpen ){
return db_exists("SELECT 1 FROM global_config WHERE name=%Q", zName);
}else{
return 0;
}
}
int db_get_int(const char *zName, int dflt){
int v;
int rc;
if( g.repositoryOpen ){
Stmt q;
db_prepare(&q, "SELECT value FROM config WHERE name=%Q", zName);
rc = db_step(&q);
if( rc==SQLITE_ROW ){
v = db_column_int(&q, 0);
}
db_finalize(&q);
}else{
rc = SQLITE_DONE;
}
if( rc==SQLITE_DONE && g.configOpen ){
v = db_int(dflt, "SELECT value FROM global_config WHERE name=%Q", zName);
}
return v;
}
void db_set_int(const char *zName, int value, int globalFlag){
db_begin_transaction();
db_multi_exec("REPLACE INTO %sconfig(name,value) VALUES(%Q,%d)",
globalFlag ? "global_" : "", zName, value);
if( globalFlag && g.repositoryOpen ){
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
}
db_end_transaction(0);
}
int db_get_boolean(const char *zName, int dflt){
static const char *azOn[] = { "on", "yes", "true", "1" };
static const char *azOff[] = { "off", "no", "false", "0" };
int i;
char *zVal = db_get(zName, dflt ? "on" : "off");
for(i=0; i<sizeof(azOn)/sizeof(azOn[0]); i++){
if( strcmp(zVal,azOn[i])==0 ) return 1;
}
for(i=0; i<sizeof(azOff)/sizeof(azOff[0]); i++){
if( strcmp(zVal,azOff[i])==0 ) return 0;
}
return dflt;
}
char *db_lget(const char *zName, char *zDefault){
return db_text((char*)zDefault,
"SELECT value FROM vvar WHERE name=%Q", zName);
}
void db_lset(const char *zName, const char *zValue){
db_multi_exec("REPLACE INTO vvar(name,value) VALUES(%Q,%Q)", zName, zValue);
}
int db_lget_int(const char *zName, int dflt){
return db_int(dflt, "SELECT value FROM vvar WHERE name=%Q", zName);
|
| ︙ | ︙ | |||
845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 |
**
** Open a connection to the local repository in FILENAME. A checkout
** for the repository is created with its root at the working directory.
** See also the "close" command.
*/
void cmd_open(void){
Blob path;
if( g.argc!=3 ){
usage("REPOSITORY-FILENAME");
}
if( db_open_local() ){
fossil_panic("already within an open tree rooted at %s", g.zLocalRoot);
}
file_canonical_name(g.argv[2], &path);
db_open_repository(blob_str(&path));
db_init_database("./_FOSSIL_", zLocalSchema, (char*)0);
db_open_local();
db_lset("repository", blob_str(&path));
| > > > > > | > > > > > | > > > > > > > > > > > > > > > > > > | > > > > | < | | < | | < < > | > | | | < < < < < | | | | | | < > > > > > > > > > > > > > > > | < > > > | < | < < < < > > > > > > > | > | > | > | > > | > > > | | < < < | < > > | 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 |
**
** Open a connection to the local repository in FILENAME. A checkout
** for the repository is created with its root at the working directory.
** See also the "close" command.
*/
void cmd_open(void){
Blob path;
int vid;
static char *azNewArgv[] = { 0, "update", "--latest", 0 };
if( g.argc!=3 ){
usage("REPOSITORY-FILENAME");
}
if( db_open_local() ){
fossil_panic("already within an open tree rooted at %s", g.zLocalRoot);
}
file_canonical_name(g.argv[2], &path);
db_open_repository(blob_str(&path));
db_init_database("./_FOSSIL_", zLocalSchema, (char*)0);
db_open_local();
db_lset("repository", blob_str(&path));
vid = db_int(0, "SELECT pid FROM plink y"
" WHERE NOT EXISTS(SELECT 1 FROM plink x WHERE x.cid=y.pid)");
if( vid==0 ){
db_lset_int("checkout", 1);
}else{
db_lset_int("checkout", vid);
g.argv = azNewArgv;
g.argc = 3;
update_cmd();
}
}
/*
** Print the value of a setting named zName
*/
static void print_setting(const char *zName){
Stmt q;
db_prepare(&q,
"SELECT '(local)', value FROM config WHERE name=%Q"
" UNION ALL "
"SELECT '(global)', value FROM global_config WHERE name=%Q",
zName, zName
);
if( db_step(&q)==SQLITE_ROW ){
printf("%-20s %-8s %s\n", zName, db_column_text(&q, 0),
db_column_text(&q, 1));
}else{
printf("%-20s\n", zName);
}
db_finalize(&q);
}
/*
** COMMAND: settings
** %fossil setting ?PROPERTY? ?VALUE? ?-global?
**
** With no arguments, list all properties and their values. With just
** a property name, show the value of that property. With a value
** argument, change the property for the current repository.
**
** autosync If enabled, automatically pull prior to
** commit or update and automatically push
** after commit or tag or branch creation.
**
** clearsign Command used to clear-sign manifests at check-in.
** The default is "gpg --clearsign -o ".
**
** editor Text editor command used for check-in comments.
**
** localauth If enabled, require that HTTP connections from
** 127.0.0.1 be authenticated by password. If
** false, all HTTP requests from localhost have
** unrestricted access to the repository.
**
** omitsign When enabled, fossil will not attempt to sign any
** commit with gpg. All commits will be unsigned.
**
** safemerge If enabled, when commit will cause a fork, the
** commit will not abort with warning. Also update
** will not be allowed if local changes exist.
**
** diff-command External command to run when performing a diff.
** If undefined, the internal text diff will be used.
**
** gdiff-command External command to run when performing a graphical
** diff. If undefined, text diff will be used.
*/
void setting_cmd(void){
static const char *azName[] = {
"autosync",
"clearsign",
"editor",
"localauth",
"omitsign",
"safemerge",
"diff-command",
"gdiff-command",
};
int i;
int globalFlag = find_option("global","g",0)!=0;
db_find_and_open_repository();
if( g.argc==2 ){
for(i=0; i<sizeof(azName)/sizeof(azName[0]); i++){
print_setting(azName[i]);
}
}else if( g.argc==3 || g.argc==4 ){
const char *zName = g.argv[2];
int n = strlen(zName);
for(i=0; i<sizeof(azName)/sizeof(azName[0]); i++){
if( strncmp(azName[i], zName, n)==0 ) break;
}
if( i>=sizeof(azName)/sizeof(azName[0]) ){
fossil_fatal("no such setting: %s", zName);
}
if( g.argc==4 ){
db_set(azName[i], g.argv[3], globalFlag);
}else{
print_setting(azName[i]);
}
}else{
usage("?PROPERTY? ?VALUE?");
}
}
|
Changes to src/delta.c.
| ︙ | ︙ | |||
332 333 334 335 336 337 338 |
/* Begin scanning the target file and generating copy commands and
** literal sections of the delta.
*/
base = 0; /* We have already generated everything before zOut[base] */
while( base+NHASH<lenOut ){
int iSrc, iBlock;
| | | 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 |
/* Begin scanning the target file and generating copy commands and
** literal sections of the delta.
*/
base = 0; /* We have already generated everything before zOut[base] */
while( base+NHASH<lenOut ){
int iSrc, iBlock;
unsigned int bestCnt, bestOfst=0, bestLitsz=0;
hash_init(&h, &zOut[base]);
i = 0; /* Trying to match a landmark against zOut[base+i] */
bestCnt = 0;
while( 1 ){
int hv;
int limit = 250;
|
| ︙ | ︙ |
Changes to src/descendents.c.
| ︙ | ︙ | |||
145 146 147 148 149 150 151 |
base = db_lget_int("checkout", 0);
}else{
base = name_to_rid(g.argv[2]);
}
if( base==0 ) return;
compute_leaves(base);
db_prepare(&q,
| | < < < | | > | < < < | < | > | < < | < | > | 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
base = db_lget_int("checkout", 0);
}else{
base = name_to_rid(g.argv[2]);
}
if( base==0 ) return;
compute_leaves(base);
db_prepare(&q,
"%s"
" AND event.objid IN (SELECT rid FROM leaves)"
" ORDER BY event.mtime DESC",
timeline_query_for_tty()
);
print_timeline(&q, 20);
db_finalize(&q);
}
/*
** COMMAND: leaves
**
** Usage: %fossil leaves
** Find leaves of all branches.
*/
void branches_cmd(void){
Stmt q;
db_must_be_within_tree();
db_prepare(&q,
"%s"
" AND blob.rid IN"
" (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
" ORDER BY event.mtime DESC",
timeline_query_for_tty()
);
print_timeline(&q, 2000);
db_finalize(&q);
}
/*
** WEBPAGE: leaves
**
** Find leaves of all branches.
*/
void leaves_page(void){
Stmt q;
login_check_credentials();
if( !g.okRead ){ login_needed(); return; }
style_header("Leaves");
db_prepare(&q,
"%s"
" AND blob.rid IN"
" (SELECT cid FROM plink EXCEPT SELECT pid FROM plink)"
" ORDER BY event.mtime DESC",
timeline_query_for_www()
);
www_print_timeline(&q, 0, 0, 0, 0);
db_finalize(&q);
@ <script>
@ function xin(id){
@ }
@ function xout(id){
@ }
@ </script>
style_footer();
}
|
Changes to src/diff.c.
| ︙ | ︙ | |||
17 18 19 20 21 22 23 | ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** | | > > > > > > > > > > > > > > < > | > > > > > > | > > > > | > > > > > | > > > > > > > > | > | < | | | < | < | > > | > | | > > > > > > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | | | > | | > > | > > > | | | | < > | | > | > | | | | > > | > > > > > > > > > | | | < | | > > > > > | < < | > > > > > > > > > | > > > > > | > > > > > > > > | > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > | < | > | > > > | > > | | | > > > | < < | | | > | < < < < > | > > > > > | < > | > > > > | > > > > > | > | < < < < < | < | < < < < | | | | > > > | > > > > > | > > > > > > > > > | < | > > > > > > > > > > | < > > | > | > > > > > > | < > > > | | < < < | | < > > > | > > > | > > | | > > | > > > > > > > > > | < < | > | > | < < | < < | > | < < | | > > > > > > > > > > > > > > > > | < | | < > | | | | < < < < < < < < < < < < < < < < < < < < < < | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 |
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to compute a "diff" between two
** text files.
*/
#include "config.h"
#include "diff.h"
#include <assert.h>
#if 0
#define DEBUG(X) X
#else
#define DEBUG(X)
#endif
/*
** Information about each line of a file being diffed.
**
** The lower 20 bits of the hash are the length of the
** line. If any line is longer than 1048575 characters,
** the file is considered binary.
*/
typedef struct DLine DLine;
struct DLine {
const char *z; /* The text of the line */
unsigned int h; /* Hash of the line */
};
#define LENGTH_MASK 0x000fffff
/*
** Return an array of DLine objects containing a pointer to the
** start of each line and a hash of that line. The lower
** bits of the hash store the length of each line.
**
** Trailing whitespace is removed from each line.
**
** Return 0 if the file is binary or contains a line that is
** longer than 1048575 bytes.
*/
static DLine *break_into_lines(char *z, int *pnLine){
int nLine, i, j, k, x;
unsigned int h;
DLine *a;
/* Count the number of lines. Allocate space to hold
** the returned array.
*/
for(i=j=0, nLine=1; z[i]; i++, j++){
int c = z[i];
if( c==0 ){
return 0;
}
if( c=='\n' && z[i+1]!=0 ){
nLine++;
if( j>1048575 ){
return 0;
}
j = 0;
}
}
if( j>1048575 ){
return 0;
}
a = malloc( nLine*sizeof(a[0]) );
if( a==0 ) fossil_panic("out of memory");
/* Fill in the array */
for(i=0; i<nLine; i++){
a[i].z = z;
for(j=0; z[j] && z[j]!='\n'; j++){}
for(k=j; k>0 && isspace(z[k-1]); k--){}
for(h=0, x=0; x<k; x++){
h = h ^ (h<<2) ^ z[x];
}
a[i].h = (h<<20) | k;;
z += j+1;
}
/* Return results */
*pnLine = nLine;
return a;
}
/*
** Return true if two DLine elements are identical.
*/
static int same_dline(DLine *pA, DLine *pB){
return pA->h==pB->h && memcmp(pA->z,pB->z,pA->h & LENGTH_MASK)==0;
}
/*
** Append a single line of "diff" output to pOut.
*/
static void appendDiffLine(Blob *pOut, char *zPrefix, DLine *pLine){
blob_append(pOut, zPrefix, 1);
blob_append(pOut, pLine->z, pLine->h & LENGTH_MASK);
blob_append(pOut, "\n", 1);
}
/*
** Generate a report of the differences between files pA and pB.
** If pOut is not NULL then a unified diff is appended there. It
** is assumed that pOut has already been initialized. If pOut is
** NULL, then a pointer to an array of integers is returned.
** The integers come in triples. For each triple,
** the elements are the number of lines copied, the number of
** lines deleted, and the number of lines inserted. The vector
** is terminated by a triple of all zeros.
**
** This diff utility does not work on binary files. If a binary
** file is encountered, 0 is returned and pOut is written with
** text "cannot compute difference between binary files".
**
****************************************************************************
**
** The core algorithm is a variation on the classic Wagner
** minimum edit distance with enhancements to reduce the runtime
** to be almost linear in the common case where the two files
** have a lot in common. For additional information see
** Eugene W. Myers, "An O(ND) Difference Algorithm And Its
** Variations"
**
** Consider comparing strings A and B. A=abcabba and B=cbabac
** We construct a "Wagner" matrix W with A along the X axis and
** B along the Y axis:
**
** c 6 *
** a 5 *
** b 4 * *
** a 3 *
** b 2 *
** B c 1 *
** 0 * * *
** 0 1 2 3 4 5 6 7
** a b c a b b a
** A
**
** (Note: we draw this Wagner matrix with the origin at the lower
** left whereas Myers uses the origin at the upper left. Otherwise,
** they are the same.)
**
** Let Y be the maximum y value or the number of characters in B.
** 6 in this example. X is the maximum x value or the number of
** elements in A. Here 7.
**
** Our goal is to find a path from (0,0) to (X,Y). The path can
** use horizontal, vertical, or diagonal steps. A diagonal from
** (x-1,y-1) to (x,y) is only allowed if A[x]==B[y]. A vertical
** steps corresponds to an insertion. A horizontal step corresponds
** to a deletion. We want to find the path with the fewest
** horizontal and vertical steps.
**
** Diagonal k consists of all points such that x-y==k. Diagonal
** zero begins at the origin. Diagonal 1 begins at (1,0).
** Diagonal -1 begins at (0,1). All diagonals move up and to the
** right at 45 degrees. Diagonal number increase from upper left
** to lower right.
**
** Myers matrix M is a lower right triangular matrix with indices d
** along the bottom and i vertically:
**
**
** i=4 | +4 \
** 3 | +3 +2 |
** 2 | +2 +1 0 |- k values. k = 2*i-d
** 1 | +1 0 -1 -2 |
** 0 | 0 -1 -2 -3 -4 /
** ---------------
** 0 1 2 3 4 = d
**
** Each element of the Myers matrix corresponds to a diagonal.
** The diagonal is k=2*i-d. The diagonal values are shown
** in the template above.
**
** Each entry in M represents the end-point on a path from (0,0).
** The end-point is on diagonal k. The value stored in M is
** q=x+y where (x,y) is the terminus of the path. A path
** to M[d][i] will come through either M[d-1][i-1] or
** though M[d-1][i], whichever holds the largest value of q.
** If both elements hold the same value, the path comes
** through M[d-1][i-1].
**
** The value of d is the number of insertions and deletions
** made so far on the path. M grows progressively. So the
** size of the M matrix is proportional to d*d. For the
** common case where A and B are similar, d will be small
** compared to X and Y so little memory is required. The
** original Wagner algorithm requires X*Y memory, which for
** larger files (100K lines) is more memory than we have at
** hand.
*/
int *text_diff(
Blob *pA_Blob, /* FROM file */
Blob *pB_Blob, /* TO file */
Blob *pOut, /* Write unified diff here if not NULL */
int nContext /* Amount of context to unified diff */
){
DLine *A, *B; /* Files being compared */
int X, Y; /* Number of elements in A and B */
int x, y; /* Indices: A[x] and B[y] */
int szM = 0; /* Number of rows and columns in M */
int **M = 0; /* Myers matrix */
int i, d; /* Indices on M. M[d][i] */
int k, q; /* Diagonal number and distinct from (0,0) */
int K, D; /* The diagonal and d for the final solution */
int *R = 0; /* Result vector */
int r; /* Loop variables */
int go = 1; /* Outer loop control */
int MAX; /* Largest of X and Y */
/* Break the two files being diffed into individual lines.
** Compute hashes on each line for fast comparison.
*/
A = break_into_lines(blob_str(pA_Blob), &X);
B = break_into_lines(blob_str(pB_Blob), &Y);
if( A==0 || B==0 ){
free(A);
free(B);
if( pOut ){
blob_appendf(pOut, "cannot compute difference between binary files\n");
}
return 0;
}
szM = 0;
MAX = X>Y ? X : Y;
if( MAX>2000 ) MAX = 2000;
for(d=0; go && d<=MAX; d++){
if( szM<d+1 ){
szM += szM + 10;
M = realloc(M, sizeof(M[0])*szM);
if( M==0 ){
fossil_panic("out of memory");
}
}
M[d] = malloc( sizeof(M[d][0])*(d+1) );
if( M[d]==0 ){
fossil_panic("out of memory");
}
for(i=0; i<=d; i++){
k = 2*i - d;
if( d==0 ){
q = 0;
}else if( i==0 ){
q = M[d-1][0];
}else if( i<d-1 && M[d-1][i-1] < M[d-1][i] ){
q = M[d-1][i];
}else{
q = M[d-1][i-1];
}
x = (k + q + 1)/2;
y = x - k;
if( x<0 || x>X || y<0 || y>Y ){
x = y = 0;
}else{
while( x<X && y<Y && same_dline(&A[x],&B[y]) ){ x++; y++; }
}
M[d][i] = x + y;
DEBUG( printf("M[%d][%i] = %d k=%d (%d,%d)\n", d, i, x+y, k, x, y); )
if( x==X && y==Y ){
go = 0;
break;
}
}
}
if( d>MAX ){
R = malloc( sizeof(R[0])*7 );
R[0] = 0;
R[1] = X;
R[2] = Y;
R[3] = 0;
R[4] = 0;
R[5] = 0;
R[6] = 0;
}else{
/* Reuse M[] as follows:
**
** M[d][1] = 1 if a line is inserted or 0 if a line is deleted.
** M[d][0] = number of lines copied after the ins or del above.
**
*/
D = d - 1;
K = X - Y;
for(d=D, i=(K+D)/2; d>0; d--){
DEBUG( printf("d=%d i=%d\n", d, i); )
if( i==d || (i>0 && M[d-1][i-1] > M[d-1][i]) ){
M[d][0] = M[d][i] - M[d-1][i-1] - 1;
M[d][1] = 0;
i--;
}else{
M[d][0] = M[d][i] - M[d-1][i] - 1;
M[d][1] = 1;
}
}
DEBUG(
printf("---------------\nM[0][0] = %5d\n", M[0][0]);
for(d=1; d<=D; d++){
printf("M[%d][0] = %5d M[%d][1] = %d\n",d,M[d][0],d,M[d][1]);
}
)
/* Allocate the output vector
*/
R = malloc( sizeof(R[0])*(D+2)*3 );
if( R==0 ){
fossil_fatal("out of memory");
}
/* Populate the output vector
*/
d = r = 0;
while( d<=D ){
int n;
R[r++] = M[d++][0]/2; /* COPY */
if( d>D ){
R[r++] = 0;
R[r++] = 0;
break;
}
if( M[d][1]==0 ){
n = 1;
while( M[d][0]==0 && d<D && M[d+1][1]==0 ){
d++;
n++;
}
R[r++] = n; /* DELETE */
if( d==D || M[d][0]>0 ){
R[r++] = 0; /* INSERT */
continue;
}
d++;
}else{
R[r++] = 0; /* DELETE */
}
assert( M[d][1]==1 );
n = 1;
while( M[d][0]==0 && d<D && M[d+1][1]==1 ){
d++;
n++;
}
R[r++] = n; /* INSERT */
}
R[r++] = 0;
R[r++] = 0;
R[r++] = 0;
}
/* Free the Myers matrix */
for(d=0; d<=D; d++){
free(M[d]);
}
free(M);
/* If pOut is defined, construct a unified diff into pOut and
** delete R
*/
if( pOut ){
int a = 0; /* Index of next line in A[] */
int b = 0; /* Index of next line in B[] */
int nr; /* Number of COPY/DELETE/INSERT triples to process */
int mxr; /* Maximum value for r */
int na, nb; /* Number of lines shown from A and B */
int i, j; /* Loop counters */
int m; /* Number of lines to output */
int skip; /* Number of lines to skip */
for(mxr=0; R[mxr+1] || R[mxr+2] || R[mxr+3]; mxr += 3){}
for(r=0; r<mxr; r += 3*nr){
/* Figure out how many triples to show in a single block */
for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){}
DEBUG( printf("r=%d nr=%d\n", r, nr); )
/* For the current block comprising nr triples, figure out
** how many lines of A and B are to be displayed
*/
if( R[r]>nContext ){
na = nb = nContext;
skip = R[r] - nContext;
}else{
na = nb = R[r];
skip = 0;
}
for(i=0; i<nr; i++){
na += R[r+i*3+1];
nb += R[r+i*3+2];
}
if( R[r+nr*3]>nContext ){
na += nContext;
nb += nContext;
}else{
na += R[r+nr*3];
nb += R[r+nr*3];
}
for(i=1; i<nr; i++){
na += R[r+i*3];
nb += R[r+i*3];
}
blob_appendf(pOut,"@@ -%d,%d +%d,%d @@\n", a+skip+1, na, b+skip+1, nb);
/* Show the initial common area */
a += skip;
b += skip;
m = R[r] - skip;
for(j=0; j<m; j++){
appendDiffLine(pOut, " ", &A[a+j]);
}
a += m;
b += m;
/* Show the differences */
for(i=0; i<nr; i++){
m = R[r+i*3+1];
for(j=0; j<m; j++){
appendDiffLine(pOut, "-", &A[a+j]);
}
a += m;
m = R[r+i*3+2];
for(j=0; j<m; j++){
appendDiffLine(pOut, "+", &B[b+j]);
}
b += m;
if( i<nr-1 ){
m = R[r+i*3+3];
for(j=0; j<m; j++){
appendDiffLine(pOut, " ", &B[b+j]);
}
b += m;
a += m;
}
}
/* Show the final common area */
assert( nr==i );
m = R[r+nr*3];
if( m>nContext ) m = nContext;
for(j=0; j<m; j++){
appendDiffLine(pOut, " ", &B[b+j]);
}
}
free(R);
R = 0;
}
/* We no longer need the A[] and B[] vectors */
free(A);
free(B);
/* Return the result */
return R;
}
/*
** COMMAND: test-rawdiff
*/
void test_rawdiff_cmd(void){
Blob a, b;
int r;
int i;
int *R;
if( g.argc<4 ) usage("FILE1 FILE2 ...");
blob_read_from_file(&a, g.argv[2]);
for(i=3; i<g.argc; i++){
if( i>3 ) printf("-------------------------------\n");
blob_read_from_file(&b, g.argv[i]);
R = text_diff(&a, &b, 0, 0);
for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
printf(" copy %4d delete %4d insert %4d\n", R[r], R[r+1], R[r+2]);
}
/* free(R); */
blob_reset(&b);
}
}
/*
** COMMAND: test-udiff
*/
void test_udiff_cmd(void){
Blob a, b, out;
if( g.argc!=4 ) usage("FILE1 FILE2");
blob_read_from_file(&a, g.argv[2]);
blob_read_from_file(&b, g.argv[3]);
blob_zero(&out);
text_diff(&a, &b, &out, 3);
blob_write_to_file(&out, "-");
}
|
Changes to src/diffcmd.c.
1 2 3 4 5 6 7 8 9 10 11 | /* ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public ** License version 2 as published by the Free Software Foundation. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /* ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public ** License version 2 as published by the Free Software Foundation. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. ** ** You should have received a copy of the GNU General Public ** License along with this library; if not, write to the ** Free Software Foundation, Inc., 59 Temple Place - Suite 330, ** Boston, MA 02111-1307, USA. ** ** Author contact information: ** drh@hwaci.com |
| ︙ | ︙ | |||
38 39 40 41 42 43 44 |
blob_appendf(pBlob, "\"%s\"", zIn);
z = blob_buffer(pBlob);
for(i=n+1; i<=n+k; i++){
if( z[i]=='"' ) z[i] = '_';
}
}
| < < | | > | > > > > | > > > > > > > > > > > > > > | > > | | | > > | | < | < < | < | | < > | > | < > > | | | | | > > > > > > > > > > | > | > > | < | < > < < | 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
blob_appendf(pBlob, "\"%s\"", zIn);
z = blob_buffer(pBlob);
for(i=n+1; i<=n+k; i++){
if( z[i]=='"' ) z[i] = '_';
}
}
/*
** COMMAND: diff
** COMMAND: gdiff
**
** Usage: %fossil diff|gdiff ?-i? ?-r REVISION? FILE...
**
** Show the difference between the current version of a file (as it
** exists on disk) and that same file as it was checked out.
**
** diff will show a textual diff while gdiff will attempt to run a
** graphical diff command that you have setup. If the choosen command
** is not yet configured, the internal textual diff command will be
** used.
**
** If -i is supplied for either diff or gdiff, the internal textual
** diff command will be executed.
**
** Here are a few external diff command settings, for example:
**
** %fossil setting diff-command diff
**
** %fossil setting gdiff-command tkdiff
** %fossil setting gdiff-command eskill22
** %fossil setting gdiff-command tortoisemerge
** %fossil setting gdiff-command meld
** %fossil setting gdiff-command xxdiff
** %fossil setting gdiff-command kdiff3
*/
void diff_cmd(void){
const char *zFile, *zRevision;
Blob cmd;
Blob fname;
Blob vname;
Blob record;
int cnt=0,internalDiff;
internalDiff = find_option("internal","i",0)!=0;
zRevision = find_option("revision", "r", 1);
verify_all_options();
if( g.argc<3 ){
usage("?OPTIONS? FILE");
}
db_must_be_within_tree();
if( internalDiff==0 ){
const char *zExternalCommand;
if( strcmp(g.argv[1], "diff")==0 ){
zExternalCommand = db_get("diff-command", 0);
}else{
zExternalCommand = db_get("gdiff-command", 0);
}
if( zExternalCommand==0 ){
internalDiff=1;
}
blob_zero(&cmd);
blob_appendf(&cmd, "%s ", zExternalCommand);
}
zFile = g.argv[g.argc-1];
if( !file_tree_name(zFile, &fname) ){
fossil_panic("unknown file: %s", zFile);
}
blob_zero(&vname);
do{
blob_reset(&vname);
blob_appendf(&vname, "%s~%d", zFile, cnt++);
}while( access(blob_str(&vname),0)==0 );
if( zRevision==0 ){
int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B", &fname);
if( rid==0 ){
fossil_panic("no history for file: %b", &fname);
}
content_get(rid, &record);
}else{
historical_version_of_file(zRevision, zFile, &record);
}
if( internalDiff==1 ){
Blob out;
Blob current;
blob_zero(¤t);
blob_read_from_file(¤t, zFile);
blob_zero(&out);
text_diff(&record, ¤t, &out, 5);
printf("%s\n", blob_str(&out));
blob_reset(¤t);
blob_reset(&out);
}else{
blob_write_to_file(&record, blob_str(&vname));
blob_reset(&record);
shell_escape(&cmd, blob_str(&vname));
blob_appendf(&cmd, " ");
shell_escape(&cmd, zFile);
system(blob_str(&cmd));
unlink(blob_str(&vname));
blob_reset(&vname);
blob_reset(&cmd);
}
blob_reset(&fname);
}
|
Changes to src/encode.c.
| ︙ | ︙ | |||
400 401 402 403 404 405 406 |
/*
** Encode a N-digit base-256 in base-16. Return zero on success
** and non-zero if there is an error.
*/
int encode16(const unsigned char *pIn, unsigned char *zOut, int N){
int i;
for(i=0; i<N; i++){
| | | | 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 |
/*
** Encode a N-digit base-256 in base-16. Return zero on success
** and non-zero if there is an error.
*/
int encode16(const unsigned char *pIn, unsigned char *zOut, int N){
int i;
for(i=0; i<N; i++){
*(zOut++) = zEncode[pIn[i]>>4];
*(zOut++) = zEncode[pIn[i]&0xf];
}
*zOut = 0;
return 0;
}
/*
** An array for translating single base-16 characters into a value.
|
| ︙ | ︙ |
Changes to src/file.c.
| ︙ | ︙ | |||
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
int file_mkdir(const char *zName, int forceFlag){
int rc = file_isdir(zName);
if( rc==2 ){
if( !forceFlag ) return 1;
unlink(zName);
}
if( rc!=1 ){
return mkdir(zName, 0755);
}
return 0;
}
/*
** Return true if the filename given is a valid filename for
** a file in a repository. Valid filenames follow all of the
| > > > > | 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
int file_mkdir(const char *zName, int forceFlag){
int rc = file_isdir(zName);
if( rc==2 ){
if( !forceFlag ) return 1;
unlink(zName);
}
if( rc!=1 ){
#ifdef __MINGW32__
return mkdir(zName);
#else
return mkdir(zName, 0755);
#endif
}
return 0;
}
/*
** Return true if the filename given is a valid filename for
** a file in a repository. Valid filenames follow all of the
|
| ︙ | ︙ | |||
176 177 178 179 180 181 182 |
** Compute a canonical pathname for a file or directory.
** Make the name absolute if it is relative.
** Remove redundant / characters
** Remove all /./ path elements.
** Convert /A/../ to just /
*/
void file_canonical_name(const char *zOrigName, Blob *pOut){
| | > | | | 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
** Compute a canonical pathname for a file or directory.
** Make the name absolute if it is relative.
** Remove redundant / characters
** Remove all /./ path elements.
** Convert /A/../ to just /
*/
void file_canonical_name(const char *zOrigName, Blob *pOut){
if( zOrigName[0]=='/'
|| (strlen(zOrigName)>3 && zOrigName[1]==':' && zOrigName[2]=='\\') ){
blob_set(pOut, zOrigName);
blob_materialize(pOut);
}else{
char zPwd[2000];
if( getcwd(zPwd, sizeof(zPwd)-20)==0 ){
fprintf(stderr, "pwd too big: max %d\n", (int)sizeof(zPwd)-20);
exit(1);
}
blob_zero(pOut);
blob_appendf(pOut, "%//%/", zPwd, zOrigName);
}
blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut)));
}
/*
** COMMAND: test-canonical-name
**
|
| ︙ | ︙ | |||
221 222 223 224 225 226 227 |
blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut)));
zPath = blob_buffer(pOut);
if( zPath[0]=='/' ){
int i, j;
Blob tmp;
char zPwd[2000];
if( getcwd(zPwd, sizeof(zPwd)-20)==0 ){
| | | 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut)));
zPath = blob_buffer(pOut);
if( zPath[0]=='/' ){
int i, j;
Blob tmp;
char zPwd[2000];
if( getcwd(zPwd, sizeof(zPwd)-20)==0 ){
fprintf(stderr, "pwd too big: max %d\n", (int)sizeof(zPwd)-20);
exit(1);
}
for(i=1; zPath[i] && zPwd[i]==zPath[i]; i++){}
if( zPath[i]==0 ){
blob_reset(pOut);
if( zPwd[i]==0 ){
blob_append(pOut, ".", 1);
|
| ︙ | ︙ |
Changes to src/http.c.
| ︙ | ︙ | |||
21 22 23 24 25 26 27 | ** ******************************************************************************* ** ** This file contains code that implements the client-side HTTP protocol */ #include "config.h" #include "http.h" | > | > > | < | | | > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
**
*******************************************************************************
**
** This file contains code that implements the client-side HTTP protocol
*/
#include "config.h"
#include "http.h"
#ifdef __MINGW32__
# include <windows.h>
# include <winsock2.h>
#else
# include <arpa/inet.h>
# include <sys/socket.h>
# include <netdb.h>
# include <netinet/in.h>
#endif
#include <assert.h>
#include <sys/types.h>
#include <signal.h>
/*
** Persistent information about the HTTP connection.
*/
#ifdef __MINGW32__
static WSADATA ws_info;
static int pSocket = 0; /* The socket on which we talk to the server on */
#else
static FILE *pSocket = 0; /* The socket filehandle on which we talk to the server */
#endif
/*
** Winsock must be initialize before use. This helper method allows us to
** always call ws_init in our code regardless of platform but only actually
** initialize winsock on the windows platform.
*/
static void ws_init(){
#ifdef __MINGW32__
if (WSAStartup(MAKEWORD(2,0), &ws_info) != 0){
fossil_panic("can't initialize winsock");
}
#endif
}
/*
** Like ws_init, winsock must also be cleaned up after.
*/
static void ws_cleanup(){
#ifdef __MINGW32__
WSACleanup();
#endif
}
/*
** Open a socket connection to the server. Return 0 on success and
** non-zero if an error occurs.
*/
static int http_open_socket(void){
static struct sockaddr_in addr; /* The server address */
static int addrIsInit = 0; /* True once addr is initialized */
int s;
ws_init();
if( !addrIsInit ){
addr.sin_family = AF_INET;
addr.sin_port = htons(g.urlPort);
*(int*)&addr.sin_addr = inet_addr(g.urlName);
if( -1 == *(int*)&addr.sin_addr ){
#ifndef FOSSIL_STATIC_LINK
struct hostent *pHost;
pHost = gethostbyname(g.urlName);
|
| ︙ | ︙ | |||
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
s = socket(AF_INET,SOCK_STREAM,0);
if( s<0 ){
fossil_panic("cannot create a socket");
}
if( connect(s,(struct sockaddr*)&addr,sizeof(addr))<0 ){
fossil_panic("cannot connect to host %s:%d", g.urlName, g.urlPort);
}
pSocket = fdopen(s,"r+");
signal(SIGPIPE, SIG_IGN);
return 0;
}
/*
** Make a single attempt to talk to the server. Return TRUE on success
** and FALSE on a failure.
**
** pHeader contains the HTTP header. pPayload contains the content.
** The content of the reply is written into pReply. pReply is assumed
** to be uninitialized prior to this call.
**
** If an error occurs, this routine return false, resets pReply and
** closes the persistent connection, if any.
*/
static int http_send_recv(Blob *pHeader, Blob *pPayload, Blob *pReply){
int rc;
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | < | > | 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
s = socket(AF_INET,SOCK_STREAM,0);
if( s<0 ){
fossil_panic("cannot create a socket");
}
if( connect(s,(struct sockaddr*)&addr,sizeof(addr))<0 ){
fossil_panic("cannot connect to host %s:%d", g.urlName, g.urlPort);
}
#ifdef __MINGW32__
pSocket = s;
#else
pSocket = fdopen(s,"r+");
signal(SIGPIPE, SIG_IGN);
#endif
return 0;
}
#ifdef __MINGW32__
/*
** Read the socket until a newline '\n' is found. Return the number
** of characters read. pSockId contains the socket handel. pOut
** contains a pointer to the buffer to write to. pOutSize contains
** the maximum size of the line that pOut can handle.
*/
static int socket_recv_line(int pSockId, char* pOut, int pOutSize){
int received=0;
char letter;
memset(pOut,0,pOutSize);
for(; received<pOutSize-1;received++){
if( recv(pSockId,(char*)&letter,1,0)>0 ){
pOut[received]=letter;
if( letter=='\n' ){
break;
}
}else{
break;
}
}
return received;
}
/*
** Initialize a blob to the data on an input socket. return
** the number of bytes read into the blob. Any prior content
** of the blob is discarded, not freed.
**
** The function was placed here in http.c due to it's socket
** nature and we did not want to introduce socket headers into
** the socket netural blob.c file.
*/
int socket_read_blob(Blob *pBlob, int pSockId, int nToRead){
int i=0,read=0;
char rbuf[50];
blob_zero(pBlob);
while ( i<nToRead ){
read = recv(pSockId, rbuf, 50, 0);
i += read;
if( read<0 ){
return 0;
}
blob_append(pBlob, rbuf, read);
}
return blob_size(pBlob);
}
#endif
/*
** Make a single attempt to talk to the server. Return TRUE on success
** and FALSE on a failure.
**
** pHeader contains the HTTP header. pPayload contains the content.
** The content of the reply is written into pReply. pReply is assumed
** to be uninitialized prior to this call.
**
** If an error occurs, this routine return false, resets pReply and
** closes the persistent connection, if any.
*/
static int http_send_recv(Blob *pHeader, Blob *pPayload, Blob *pReply){
int closeConnection=1; /* default to closing the connection */
int rc;
int iLength;
int iHttpVersion;
int i;
int nRead;
char zLine[2000];
if( pSocket==0 && http_open_socket() ){
return 0;
}
iLength = -1;
#ifdef __MINGW32__
/*
** Use recv/send on the windows platform as winsock does not allow
** sockets to be used as FILE handles, thus fdopen, fwrite, fgets
** does not function on windows for sockets.
*/
rc = send(pSocket, blob_buffer(pHeader), blob_size(pHeader), 0);
if( rc!=blob_size(pHeader) ) goto write_err;
rc = send(pSocket, blob_buffer(pPayload), blob_size(pPayload), 0);
if( rc!=blob_size(pPayload) ) goto write_err;
/* Read the response */
while( socket_recv_line(pSocket, zLine, 2000) ){
for( i=0; zLine[i] && zLine[i]!='\n' && zLine[i]!='\r'; i++ ){}
if( i==0 ) break;
zLine[i] = 0;
if( strncasecmp(zLine, "http/1.", 7)==0 ){
if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err;
if( rc!=200 ) goto write_err;
if( iHttpVersion==0 ){
closeConnection = 1;
}else{
closeConnection = 0;
}
} else if( strncasecmp(zLine, "content-length:", 15)==0 ){
iLength = atoi(&zLine[16]);
}else if( strncasecmp(zLine, "connection:", 11)==0 ){
for(i=12; isspace(zLine[i]); i++){}
if( zLine[i]=='c' || zLine[i]=='C' ){
closeConnection = 1;
}else if( zLine[i]=='k' || zLine[i]=='K' ){
closeConnection = 0;
}
}
}
if( iLength<0 ) goto write_err;
nRead = socket_read_blob(pReply, pSocket, iLength);
#else
rc = fwrite(blob_buffer(pHeader), 1, blob_size(pHeader), pSocket);
if( rc!=blob_size(pHeader) ) goto write_err;
rc = fwrite(blob_buffer(pPayload), 1, blob_size(pPayload), pSocket);
if( rc!=blob_size(pPayload) ) goto write_err;
if( fflush(pSocket) ) goto write_err;
if( fgets(zLine, sizeof(zLine), pSocket)==0 ) goto write_err;
if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err;
if( rc!=200 ) goto write_err;
if( iHttpVersion==0 ){
closeConnection = 1; /* Connection: close */
}else{
closeConnection = 0; /* Connection: keep-alive */
}
while( fgets(zLine, sizeof(zLine), pSocket) ){
for(i=0; zLine[i] && zLine[i]!='\n' && zLine[i]!='\r'; i++){}
if( i==0 ) break;
zLine[i] = 0;
if( strncasecmp(zLine,"content-length:",15)==0 ){
iLength = atoi(&zLine[16]);
}else if( strncasecmp(zLine, "connection:", 11)==0 ){
for(i=12; isspace(zLine[i]); i++){}
if( zLine[i]=='c' || zLine[i]=='C' ){
closeConnection = 1; /* Connection: close */
}else if( zLine[i]=='k' || zLine[i]=='K' ){
closeConnection = 0; /* Connection: keep-alive */
}
}
}
if( iLength<0 ) goto write_err;
nRead = blob_read_from_channel(pReply, pSocket, iLength);
#endif
if( nRead!=iLength ){
blob_reset(pReply);
goto write_err;
}
if( closeConnection ){
http_close();
}
|
| ︙ | ︙ | |||
258 259 260 261 262 263 264 265 266 267 268 |
/*
** Make sure the socket to the HTTP server is closed
*/
void http_close(void){
if( pSocket ){
fclose(pSocket);
pSocket = 0;
}
}
| > > > > > > > > > > > > | 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 |
/*
** Make sure the socket to the HTTP server is closed
*/
void http_close(void){
if( pSocket ){
#ifdef __MINGW32__
closesocket(pSocket);
#else
fclose(pSocket);
#endif
pSocket = 0;
}
/*
** This is counter productive. Each time we open a connection we initialize
** winsock and then when closing we cleanup. It would be better to
** initialize winsock once at application start when we know we are going to
** use the socket interface and then cleanup once at application exit when
** we are all done with all socket operations.
*/
ws_cleanup();
}
|
Changes to src/info.c.
| ︙ | ︙ | |||
112 113 114 115 116 117 118 |
** to a depth of N. Return true if descendents are shown and false if not.
*/
static int showDescendents(int pid, int depth, const char *zTitle){
Stmt q;
int cnt = 0;
db_prepare(&q,
"SELECT plink.cid, blob.uuid, datetime(plink.mtime, 'localtime'),"
| > | | | > > | | | > | > | < | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < < | > > > > | > | > | > > > | | > > > | | | < < < > | > > > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < > > > > | | | | 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 |
** to a depth of N. Return true if descendents are shown and false if not.
*/
static int showDescendents(int pid, int depth, const char *zTitle){
Stmt q;
int cnt = 0;
db_prepare(&q,
"SELECT plink.cid, blob.uuid, datetime(plink.mtime, 'localtime'),"
" coalesce(event.euser,event.user),"
" coalesce(event.comment,event.ecomment)"
" FROM plink, blob, event"
" WHERE plink.pid=%d"
" AND blob.rid=plink.cid"
" AND event.objid=plink.cid"
" ORDER BY plink.mtime ASC",
pid
);
while( db_step(&q)==SQLITE_ROW ){
int n;
int cid = db_column_int(&q, 0);
const char *zUuid = db_column_text(&q, 1);
const char *zDate = db_column_text(&q, 2);
const char *zUser = db_column_text(&q, 3);
const char *zCom = db_column_text(&q, 4);
cnt++;
if( cnt==1 ){
if( zTitle ){
@ <div class="section">%s(zTitle)</div>
}
@ <ul>
}
@ <li>
hyperlink_to_uuid(zUuid);
@ %w(zCom) (by %s(zUser) on %s(zDate))
if( depth ){
n = showDescendents(cid, depth-1, 0);
}else{
n = db_int(0, "SELECT 1 FROM plink WHERE pid=%d", cid);
}
if( n==0 ){
db_multi_exec("DELETE FROM leaves WHERE rid=%d", cid);
@ <b>leaf</b>
}
}
db_finalize(&q);
if( cnt ){
@ </ul>
}
return cnt;
}
/*
** Show information about ancestors of a version. Do this recursively
** to a depth of N. Return true if ancestors are shown and false if not.
*/
static void showAncestors(int pid, int depth, const char *zTitle){
Stmt q;
int cnt = 0;
db_prepare(&q,
"SELECT plink.pid, blob.uuid, datetime(event.mtime, 'localtime'),"
" coalesce(event.euser,event.user),"
" coalesce(event.comment,event.ecomment)"
" FROM plink, blob, event"
" WHERE plink.cid=%d"
" AND blob.rid=plink.pid"
" AND event.objid=plink.pid"
" ORDER BY event.mtime DESC",
pid
);
while( db_step(&q)==SQLITE_ROW ){
int cid = db_column_int(&q, 0);
const char *zUuid = db_column_text(&q, 1);
const char *zDate = db_column_text(&q, 2);
const char *zUser = db_column_text(&q, 3);
const char *zCom = db_column_text(&q, 4);
cnt++;
if( cnt==1 ){
if( zTitle ){
@ <div class="section">%s(zTitle)</div>
}
@ <ul>
}
@ <li>
hyperlink_to_uuid(zUuid);
@ %w(zCom) (by %s(zUser) on %s(zDate))
if( depth ){
showAncestors(cid, depth-1, 0);
}
}
db_finalize(&q);
if( cnt ){
@ </ul>
}
}
/*
** Show information about versions mentioned in the "leaves" table.
*/
static void showLeaves(void){
Stmt q;
int cnt = 0;
db_prepare(&q,
"SELECT blob.uuid, datetime(event.mtime, 'localtime'),"
" coalesce(event.euser, event.user),"
" coalesce(event.ecomment,event.comment)"
" FROM leaves, blob, event"
" WHERE blob.rid=leaves.rid"
" AND event.objid=leaves.rid"
" ORDER BY event.mtime DESC"
);
while( db_step(&q)==SQLITE_ROW ){
const char *zUuid = db_column_text(&q, 0);
const char *zDate = db_column_text(&q, 1);
const char *zUser = db_column_text(&q, 2);
const char *zCom = db_column_text(&q, 3);
cnt++;
if( cnt==1 ){
@ <div class="section">Leaves</div>
@ <ul>
}
@ <li>
hyperlink_to_uuid(zUuid);
@ %w(zCom) (by %s(zUser) on %s(zDate))
}
db_finalize(&q);
if( cnt ){
@ </ul>
}
}
/*
** Show information about all tags on a given node.
*/
static void showTags(int rid){
Stmt q;
int cnt = 0;
db_prepare(&q,
"SELECT tag.tagid, tagname, srcid, blob.uuid, value,"
" datetime(tagxref.mtime,'localtime'), tagtype"
" FROM tagxref JOIN tag ON tagxref.tagid=tag.tagid"
" LEFT JOIN blob ON blob.rid=tagxref.srcid"
" WHERE tagxref.rid=%d"
" ORDER BY tagname", rid
);
while( db_step(&q)==SQLITE_ROW ){
const char *zTagname = db_column_text(&q, 1);
int srcid = db_column_int(&q, 2);
const char *zUuid = db_column_text(&q, 3);
const char *zValue = db_column_text(&q, 4);
const char *zDate = db_column_text(&q, 5);
int tagtype = db_column_int(&q, 6);
cnt++;
if( cnt==1 ){
@ <div class="section">Tags And Properties</div>
@ <ul>
}
@ <li>
@ <b>%h(zTagname)</b>
if( zValue ){
@ = %h(zValue)<i>
}else if( tagtype==0 ){
@ <i>Cancelled
}else{
@ <i>
}
if( srcid==0 ){
@ Inherited
}else if( zUuid ){
@ From
hyperlink_to_uuid(zUuid);
}
@ on %s(zDate)</i>
}
db_finalize(&q);
if( cnt ){
@ </ul>
}
}
/*
** WEBPAGE: vinfo
** URL: /vinfo?name=RID|UUID
**
** Return information about a version.
*/
void vinfo_page(void){
Stmt q;
int rid;
int isLeaf;
login_check_credentials();
if( !g.okHistory ){ login_needed(); return; }
rid = name_to_rid(PD("name","0"));
if( rid==0 ){
style_header("Version Information Error");
@ No such object: %h(g.argv[2])
style_footer();
return;
}
isLeaf = !db_exists("SELECT 1 FROM plink WHERE pid=%d", rid);
db_prepare(&q,
"SELECT uuid, datetime(mtime, 'localtime'), user, comment"
" FROM blob, event"
" WHERE blob.rid=%d"
" AND event.objid=%d",
rid, rid
);
if( db_step(&q)==SQLITE_ROW ){
const char *zUuid = db_column_text(&q, 0);
char *zTitle = mprintf("Version: [%.10s]", zUuid);
style_header(zTitle);
free(zTitle);
/*@ <h2>Version %s(zUuid)</h2>*/
@ <div class="section">Overview</div>
@ <p><table class="label-value">
@ <tr><th>Version:</th><td>%s(zUuid)</td></tr>
@ <tr><th>Date:</th><td>%s(db_column_text(&q, 1))</td></tr>
if( g.okSetup ){
@ <tr><th>Record ID:</th><td>%d(rid)</td></tr>
}
@ <tr><th>Original User:</th><td>%h(db_column_text(&q, 2))</td></tr>
@ <tr><th>Original Comment:</th><td>%w(db_column_text(&q,3))</td></tr>
@ </td></tr>
@ <tr><th>Commands:</th>
@ <td>
@ <a href="%s(g.zBaseURL)/vdiff/%d(rid)">diff</a>
@ | <a href="%s(g.zBaseURL)/zip/%s(zUuid).zip">ZIP archive</a>
@ | <a href="%s(g.zBaseURL)/fview/%d(rid)">manifest</a>
@ </td>
@ </tr>
@ </table></p>
}else{
style_header("Version Information");
}
db_finalize(&q);
showTags(rid);
@ <div class="section">Changes</div>
@ <ul>
db_prepare(&q,
"SELECT name, pid, fid"
" FROM mlink, filename"
" WHERE mid=%d"
" AND filename.fnid=mlink.fnid",
rid
);
while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
int pid = db_column_int(&q, 1);
int fid = db_column_int(&q, 2);
@ <li>
if( pid && fid ){
@ <b>Modified:</b>
}else if( fid ){
@ <b>Added:</b>
}else{
@ <b>Deleted:</b>
}
@ <a href="%s(g.zBaseURL)/finfo?name=%T(zName)">%h(zName)</a></li>
}
@ </ul>
compute_leaves(rid);
showDescendents(rid, 2, "Descendents");
showLeaves();
showAncestors(rid, 2, "Ancestors");
style_footer();
}
/*
** WEBPAGE: winfo
** URL: /winfo?name=RID
**
** Return information about a wiki page.
*/
void winfo_page(void){
Stmt q;
int rid;
login_check_credentials();
if( !g.okHistory ){ login_needed(); return; }
rid = name_to_rid(PD("name","0"));
if( rid==0 ){
style_header("Wiki Page Information Error");
@ No such object: %h(g.argv[2])
style_footer();
return;
}
db_prepare(&q,
"SELECT substr(tagname, 6, 1000), uuid,"
" datetime(event.mtime, 'localtime'), user"
" FROM tagxref, tag, blob, event"
" WHERE tagxref.rid=%d"
" AND tag.tagid=tagxref.tagid"
" AND tag.tagname LIKE 'wiki-%%'"
" AND blob.rid=%d"
" AND event.objid=%d",
rid, rid, rid
);
if( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
const char *zUuid = db_column_text(&q, 1);
char *zTitle = mprintf("Wiki Page %s", zName);
style_header(zTitle);
free(zTitle);
@ <div class="section">Overview</div>
@ <p><table class="label-value">
@ <tr><th>Version:</th><td>%s(zUuid)</td></tr>
@ <tr><th>Date:</th><td>%s(db_column_text(&q, 2))</td></tr>
if( g.okSetup ){
@ <tr><th>Record ID:</th><td>%d(rid)</td></tr>
}
@ <tr><th>Original User:</th><td>%s(db_column_text(&q, 3))</td></tr>
@ <tr><th>Commands:</th>
@ <td>
/* @ <a href="%s(g.zBaseURL)/wdiff/%d(rid)">diff</a> | */
@ <a href="%s(g.zBaseURL)/whistory?page=%t(zName)">history</a>
@ | <a href="%s(g.zBaseURL)/fview/%d(rid)">raw-text</a>
@ </td>
@ </tr>
@ </table></p>
}else{
style_header("Wiki Information");
}
db_finalize(&q);
showTags(rid);
style_footer();
}
/*
** WEBPAGE: finfo
** URL: /finfo?name=FILENAME
**
** Show the complete change history for a single file.
*/
void finfo_page(void){
Stmt q;
const char *zFilename;
char zPrevDate[20];
login_check_credentials();
if( !g.okHistory ){ login_needed(); return; }
style_header("File History");
zPrevDate[0] = 0;
zFilename = PD("name","");
db_prepare(&q,
"SELECT a.uuid, substr(b.uuid,1,10), datetime(event.mtime,'localtime'),"
" coalesce(event.ecomment, event.comment),"
" coalesce(event.euser, event.user),"
" mlink.pid, mlink.fid"
" FROM mlink, blob a, blob b, event"
" WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%Q)"
" AND a.rid=mlink.mid"
" AND b.rid=mlink.fid"
" AND event.objid=mlink.mid"
" ORDER BY event.mtime DESC",
zFilename
);
@ <h2>History of %h(zFilename)</h2>
@ <table cellspacing=0 border=0 cellpadding=0>
while( db_step(&q)==SQLITE_ROW ){
const char *zVers = db_column_text(&q, 0);
const char *zUuid = db_column_text(&q, 1);
const char *zDate = db_column_text(&q, 2);
const char *zCom = db_column_text(&q, 3);
const char *zUser = db_column_text(&q, 4);
|
| ︙ | ︙ | |||
359 360 361 362 363 364 365 |
@ <tr><td valign="top">%s(&zDate[11])</td>
@ <td width="20"></td>
@ <td valign="top" align="left">
hyperlink_to_uuid(zVers);
@ %h(zCom) (By: %h(zUser))
@ Id: %s(zUuid)/%d(frid)
@ <a href="%s(g.zBaseURL)/fview/%d(frid)">[view]</a>
| > | > | > | | | | 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 |
@ <tr><td valign="top">%s(&zDate[11])</td>
@ <td width="20"></td>
@ <td valign="top" align="left">
hyperlink_to_uuid(zVers);
@ %h(zCom) (By: %h(zUser))
@ Id: %s(zUuid)/%d(frid)
@ <a href="%s(g.zBaseURL)/fview/%d(frid)">[view]</a>
if( fpid ){
@ <a href="%s(g.zBaseURL)/fdiff?v1=%d(fpid)&v2=%d(frid)">[diff]</a>
}
@ </td>
}
db_finalize(&q);
@ </table>
style_footer();
}
/*
** Append the difference between two RIDs to the output
*/
static void append_diff(int fromid, int toid){
Blob from, to, out;
content_get(fromid, &from);
content_get(toid, &to);
blob_zero(&out);
text_diff(&from, &to, &out, 5);
@ %h(blob_str(&out))
blob_reset(&from);
blob_reset(&to);
blob_reset(&out);
}
/*
** WEBPAGE: vdiff
** URL: /vdiff?name=RID
**
** Show all differences for a particular check-in.
*/
void vdiff_page(void){
int rid;
Stmt q;
char *zUuid;
login_check_credentials();
if( !g.okHistory ){ login_needed(); return; }
style_header("Version Diff");
rid = name_to_rid(PD("name",""));
if( rid==0 ){
cgi_redirect("index");
}
db_prepare(&q,
"SELECT pid, fid, name"
" FROM mlink, filename"
" WHERE mlink.mid=%d"
" AND filename.fnid=mlink.fnid"
" ORDER BY name",
rid
);
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
@ <h2>All Changes In Version
hyperlink_to_uuid(zUuid);
@ </h2>
while( db_step(&q)==SQLITE_ROW ){
int pid = db_column_int(&q,0);
int fid = db_column_int(&q,1);
const char *zName = db_column_text(&q,2);
@ <p><a href="%s(g.zBaseURL)/finfo?name=%T(zName)">%h(zName)</a></p>
@ <blockquote><pre>
append_diff(pid, fid);
@ </pre></blockquote>
}
db_finalize(&q);
style_footer();
}
|
| ︙ | ︙ | |||
445 446 447 448 449 450 451 452 453 |
** * It's uuid
** * date of check-in
** * Comment & user
*/
static void object_description(int rid, int linkToView){
Stmt q;
int cnt = 0;
db_prepare(&q,
"SELECT filename.name, datetime(event.mtime), substr(a.uuid,1,10),"
| > > > | | | > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | < | > | | | | | | > > | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 |
** * It's uuid
** * date of check-in
** * Comment & user
*/
static void object_description(int rid, int linkToView){
Stmt q;
int cnt = 0;
int nWiki = 0;
db_prepare(&q,
"SELECT filename.name, datetime(event.mtime), substr(a.uuid,1,10),"
" coalesce(event.comment,event.ecomment),"
" coalesce(event.euser,event.user),"
" b.uuid"
" FROM mlink, filename, event, blob a, blob b"
" WHERE filename.fnid=mlink.fnid"
" AND event.objid=mlink.mid"
" AND a.rid=mlink.fid"
" AND b.rid=mlink.mid"
" AND mlink.fid=%d",
rid
);
while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
const char *zDate = db_column_text(&q, 1);
const char *zFuuid = db_column_text(&q, 2);
const char *zCom = db_column_text(&q, 3);
const char *zUser = db_column_text(&q, 4);
const char *zVers = db_column_text(&q, 5);
@ File <a href="%s(g.zBaseURL)/finfo?name=%T(zName)">%h(zName)</a>
@ uuid %s(zFuuid) part of check-in
hyperlink_to_uuid(zVers);
@ %w(zCom) by %h(zUser) on %s(zDate)
cnt++;
}
db_finalize(&q);
db_prepare(&q,
"SELECT substr(tagname, 6, 10000), datetime(event.mtime),"
" coalesce(event.euser, event.user), uuid"
" FROM tagxref, tag, event, blob"
" WHERE tagxref.rid=%d"
" AND tag.tagid=tagxref.tagid"
" AND tag.tagname LIKE 'wiki-%%'"
" AND event.objid=tagxref.rid"
" AND blob.rid=tagxref.rid",
rid
);
while( db_step(&q)==SQLITE_ROW ){
const char *zPagename = db_column_text(&q, 0);
const char *zDate = db_column_text(&q, 1);
const char *zUser = db_column_text(&q, 2);
const char *zUuid = db_column_text(&q, 3);
@ Wiki page
@ [<a href="%s(g.zBaseURL)/wiki?page=%t(zPagename)">%h(zPagename)</a>]
@ uuid %s(zUuid) by %h(zUser) on %s(zDate)
nWiki++;
cnt++;
}
db_finalize(&q);
if( nWiki==0 ){
db_prepare(&q,
"SELECT datetime(mtime), user, comment, uuid"
" FROM event, blob"
" WHERE event.objid=%d"
" AND blob.rid=%d",
rid, rid
);
while( db_step(&q)==SQLITE_ROW ){
const char *zDate = db_column_text(&q, 0);
const char *zUuid = db_column_text(&q, 3);
const char *zUser = db_column_text(&q, 1);
const char *zCom = db_column_text(&q, 2);
@ Manifest of version
hyperlink_to_uuid(zUuid);
@ %w(zCom) by %h(zUser) on %s(zDate)
cnt++;
}
db_finalize(&q);
}
if( cnt==0 ){
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
@ Control file %s(zUuid).
}else if( linkToView ){
@ <a href="%s(g.zBaseURL)/fview/%d(rid)">[view]</a>
}
}
/*
** WEBPAGE: fdiff
**
** Two arguments, v1 and v2, are integers. Show the difference between
** the two records.
*/
void diff_page(void){
int v1 = name_to_rid(PD("v1","0"));
int v2 = name_to_rid(PD("v2","0"));
Blob c1, c2, diff;
login_check_credentials();
if( !g.okHistory ){ login_needed(); return; }
style_header("Diff");
@ <h2>Differences From:</h2>
@ <blockquote>
object_description(v1, 1);
@ </blockquote>
@ <h2>To:</h2>
@ <blockquote>
object_description(v2, 1);
@ </blockquote>
@ <hr>
@ <blockquote><pre>
content_get(v1, &c1);
content_get(v2, &c2);
blob_zero(&diff);
text_diff(&c1, &c2, &diff, 4);
blob_reset(&c1);
blob_reset(&c2);
@ %h(blob_str(&diff))
@ </pre></blockquote>
blob_reset(&diff);
style_footer();
}
/*
** WEBPAGE: fview
** URL: /fview?name=UUID
**
** Show the complete content of a file identified by UUID
** as preformatted text.
*/
void fview_page(void){
int rid;
Blob content;
rid = name_to_rid(PD("name","0"));
login_check_credentials();
if( !g.okHistory ){ login_needed(); return; }
if( g.zPath[0]=='i' ){
if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)"
" WHERE rid=%d AND tagname LIKE 'wiki-%%'", rid) ){
winfo_page();
return;
}
if( db_exists("SELECT 1 FROM plink WHERE cid=%d", rid) ){
vinfo_page();
return;
}
}
style_header("File Content");
@ <h2>Content Of:</h2>
@ <blockquote>
object_description(rid, 0);
@ </blockquote>
@ <hr>
@ <blockquote><pre>
content_get(rid, &content);
@ %h(blob_str(&content))
@ </pre></blockquote>
blob_reset(&content);
style_footer();
}
/*
** WEBPAGE: info
** URL: info/UUID
**
** The argument is a UUID which might be a baseline or a file or
** a ticket or something else. It might also be a wiki page name.
** Figure out what the UUID is an jump to it. If there is ambiguity,
** draw a page and let the user select the interpretation.
*/
void info_page(void){
const char *zName;
int rc, nName, cnt;
Stmt q;
zName = P("name");
if( zName==0 ) cgi_redirect("index");
nName = strlen(zName);
if( nName<4 || nName>UUID_SIZE || !validate16(zName, nName) ){
cgi_redirect("index");
}
db_multi_exec(
"CREATE TEMP TABLE refs(type,link);"
"INSERT INTO refs "
" SELECT 'f', rid FROM blob WHERE uuid GLOB '%s*'"
" UNION ALL"
" SELECT 'w', substr(tagname,6) FROM tag"
" WHERE tagname='wiki-%q'"
" UNION ALL"
" SELECT 't', tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*';",
zName, zName, zName
);
cnt = db_int(0, "SELECT count(*) FROM refs");
if( cnt==0 ){
style_header("Broken Link");
@ <p>No such object: %h(zName)</p>
style_footer();
return;
}
db_prepare(&q, "SELECT type, link FROM refs");
db_step(&q);
if( cnt==1 ){
int type = *db_column_text(&q, 0);
int rid = db_column_int(&q, 1);
db_finalize(&q);
if( type=='w' ){
wiki_page();
}else if( type=='t' ){
tktview_page();
}else{
cgi_replace_parameter("name", mprintf("%d", rid));
if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
vinfo_page();
}else{
finfo_page();
}
}
return;
}
/* Multiple objects */
style_header("Ambiguous Link");
@ <h2>Ambiguous Link: %h(zName)</h2>
@ <ul>
while( rc==SQLITE_ROW ){
int type = *db_column_text(&q, 0);
if( type=='f' ){
@ <li><p>
object_description(db_column_int(&q, 1), 1);
@ </p></li>
}else if( type=='w' ){
@ <li><p>
@ Wiki page <a href="%s(g.zBaseURL)/wiki?name=%s(zName)">%s(zName)</a>.
@ </li><p>
}else if( type=='t' ){
const char *zUuid = db_column_text(&q, 1);
@ <li><p>
@ Ticket <a href="%s(g.zBaseURL)/tktview?name=%s(zUuid)">%s(zUuid)</a>.
@ </li><p>
}
rc = db_step(&q);
}
@ </ul>
style_footer();
db_finalize(&q);
}
|
Changes to src/login.c.
| ︙ | ︙ | |||
42 43 44 45 46 47 48 49 50 51 52 53 54 |
** not really the point. The anonymous login keeps search-engine
** crawlers and site download tools like wget from walking change
** logs and downloading diffs of very version of the archive that
** has ever existed, and things like that.
*/
#include "config.h"
#include "login.h"
#include <time.h>
/*
** Return the name of the login cookie
*/
static char *login_cookie_name(void){
| > > > > > > > > > | > > > | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
** not really the point. The anonymous login keeps search-engine
** crawlers and site download tools like wget from walking change
** logs and downloading diffs of very version of the archive that
** has ever existed, and things like that.
*/
#include "config.h"
#include "login.h"
#ifdef __MINGW32__
# include <windows.h> /* for Sleep */
# define sleep Sleep /* windows does not have sleep, but Sleep */
#endif
#include <time.h>
/*
** Return the name of the login cookie
*/
static char *login_cookie_name(void){
static char *zCookieName = 0;
if( zCookieName==0 ){
int n = strlen(g.zTop);
zCookieName = malloc( n*2+16 );
/* 0123456789 12345 */
strcpy(zCookieName, "fossil_login_");
encode16((unsigned char*)g.zTop, (unsigned char*)&zCookieName[13], n);
}
return zCookieName;
}
/*
** WEBPAGE: /login
** WEBPAGE: /logout
**
** Generate the login page
|
| ︙ | ︙ | |||
222 223 224 225 226 227 228 |
/* If the HTTP connection is coming over 127.0.0.1 and if
** local login is disabled, then there is no need to check
** user credentials.
*/
zRemoteAddr = PD("REMOTE_ADDR","nil");
| | < | 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
/* If the HTTP connection is coming over 127.0.0.1 and if
** local login is disabled, then there is no need to check
** user credentials.
*/
zRemoteAddr = PD("REMOTE_ADDR","nil");
if( strcmp(zRemoteAddr, "127.0.0.1")==0 && db_get_int("localauth",0)==0 ){
uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
zCap = "s";
g.noPswd = 1;
}
/* Check the login cookie to see if it matches a known valid user.
|
| ︙ | ︙ | |||
282 283 284 285 286 287 288 |
/*
** Set the global capability flags based on a capability string.
*/
void login_set_capabilities(const char *zCap){
int i;
for(i=0; zCap[i]; i++){
switch( zCap[i] ){
| | | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
/*
** Set the global capability flags based on a capability string.
*/
void login_set_capabilities(const char *zCap){
int i;
for(i=0; zCap[i]; i++){
switch( zCap[i] ){
case 's': g.okSetup = 1;
case 'a': g.okAdmin = g.okRdTkt = g.okWrTkt = g.okQuery =
g.okRdWiki = g.okWrWiki = g.okNewWiki =
g.okApndWiki = g.okHistory = g.okClone =
g.okNewTkt = g.okPassword = g.okRdAddr = 1;
case 'i': g.okRead = g.okWrite = 1; break;
case 'o': g.okRead = 1; break;
case 'd': g.okDelete = 1; break;
case 'h': g.okHistory = 1; break;
case 'g': g.okClone = 1; break;
case 'p': g.okPassword = 1; break;
case 'q': g.okQuery = 1; break;
case 'j': g.okRdWiki = 1; break;
case 'k': g.okWrWiki = g.okRdWiki = g.okApndWiki =1; break;
case 'm': g.okApndWiki = 1; break;
case 'f': g.okNewWiki = 1; break;
case 'e': g.okRdAddr = 1; break;
case 'r': g.okRdTkt = 1; break;
case 'n': g.okNewTkt = 1; break;
case 'w': g.okWrTkt = g.okRdTkt = g.okNewTkt =
g.okApndTkt = 1; break;
case 'c': g.okApndTkt = 1; break;
}
}
}
/*
** If the current login lacks any of the capabilities listed in
** the input, then return 0. If all capabilities are present, then
** return 1.
*/
int login_has_capability(const char *zCap, int nCap){
int i;
int rc = 1;
if( nCap<0 ) nCap = strlen(zCap);
for(i=0; i<nCap && rc && zCap[i]; i++){
switch( zCap[i] ){
case 'a': rc = g.okAdmin; break;
case 'c': rc = g.okApndTkt; break;
case 'd': rc = g.okDelete; break;
case 'e': rc = g.okRdAddr; break;
case 'f': rc = g.okNewWiki; break;
case 'g': rc = g.okClone; break;
case 'h': rc = g.okHistory; break;
case 'i': rc = g.okWrite; break;
case 'j': rc = g.okRdWiki; break;
case 'k': rc = g.okWrWiki; break;
case 'm': rc = g.okApndWiki; break;
case 'n': rc = g.okNewTkt; break;
case 'o': rc = g.okRead; break;
case 'p': rc = g.okPassword; break;
case 'q': rc = g.okQuery; break;
case 'r': rc = g.okRdTkt; break;
case 's': rc = g.okSetup; break;
case 'w': rc = g.okWrTkt; break;
default: rc = 0; break;
}
}
return rc;
}
/*
** Call this routine when the credential check fails. It causes
** a redirect to the "login" page.
*/
void login_needed(void){
const char *zUrl = PD("REQUEST_URI", "index");
cgi_redirect(mprintf("login?g=%T", zUrl));
/* NOTREACHED */
assert(0);
}
|
Changes to src/main.c.
| ︙ | ︙ | |||
57 58 59 60 61 62 63 64 65 66 67 68 69 70 | int minPrefix; /* Number of digits needed for a distinct UUID */ int fSqlTrace; /* True if -sqltrace flag is present */ int fSqlPrint; /* True if -sqlprint flag is present */ int fHttpTrace; /* Trace outbound HTTP requests */ char *zPath; /* Name of webpage being served */ char *zExtra; /* Extra path information past the webpage name */ char *zBaseURL; /* Full text of the URL being served */ const char *zContentType; /* The content type of the input HTTP request */ int iErrPriority; /* Priority of current error message */ char *zErrMsg; /* Text of an error message */ Blob cgiIn; /* Input to an xfer www method */ int cgiPanic; /* Write error messages to CGI */ int *aCommitFile; | > | 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | int minPrefix; /* Number of digits needed for a distinct UUID */ int fSqlTrace; /* True if -sqltrace flag is present */ int fSqlPrint; /* True if -sqlprint flag is present */ int fHttpTrace; /* Trace outbound HTTP requests */ char *zPath; /* Name of webpage being served */ char *zExtra; /* Extra path information past the webpage name */ char *zBaseURL; /* Full text of the URL being served */ char *zTop; /* Parent directory of zPath */ const char *zContentType; /* The content type of the input HTTP request */ int iErrPriority; /* Priority of current error message */ char *zErrMsg; /* Text of an error message */ Blob cgiIn; /* Input to an xfer www method */ int cgiPanic; /* Write error messages to CGI */ int *aCommitFile; |
| ︙ | ︙ | |||
100 101 102 103 104 105 106 107 108 109 110 111 112 113 | int okNewWiki; /* f: create new wiki via web */ int okApndWiki; /* m: append to wiki via web */ int okWrWiki; /* k: edit wiki via web */ int okRdTkt; /* r: view tickets via web */ int okNewTkt; /* n: create new tickets */ int okApndTkt; /* c: append to tickets via the web */ int okWrTkt; /* w: make changes to tickets via web */ FILE *fDebug; /* Write debug information here, if the file exists */ }; /* ** Macro for debugging: */ | > | 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | int okNewWiki; /* f: create new wiki via web */ int okApndWiki; /* m: append to wiki via web */ int okWrWiki; /* k: edit wiki via web */ int okRdTkt; /* r: view tickets via web */ int okNewTkt; /* n: create new tickets */ int okApndTkt; /* c: append to tickets via the web */ int okWrTkt; /* w: make changes to tickets via web */ int okRdAddr; /* e: read email addresses on tickets */ FILE *fDebug; /* Write debug information here, if the file exists */ }; /* ** Macro for debugging: */ |
| ︙ | ︙ | |||
383 384 385 386 387 388 389 |
*/
void help_cmd(void){
int rc, idx;
const char *z;
if( g.argc!=3 ){
printf("Usage: %s help COMMAND.\nAvailable COMMANDs:\n", g.argv[0]);
cmd_cmd_list();
| | | 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 |
*/
void help_cmd(void){
int rc, idx;
const char *z;
if( g.argc!=3 ){
printf("Usage: %s help COMMAND.\nAvailable COMMANDs:\n", g.argv[0]);
cmd_cmd_list();
printf("This is fossil version " MANIFEST_VERSION " " MANIFEST_DATE "\n");
return;
}
rc = name_search(g.argv[2], aCommand, count(aCommand), &idx);
if( rc==1 ){
fossil_fatal("unknown command: %s", g.argv[2]);
}else if( rc==2 ){
fossil_fatal("ambiguous command prefix: %s", g.argv[2]);
|
| ︙ | ︙ | |||
410 411 412 413 414 415 416 |
z++;
}
}
putchar('\n');
}
/*
| | | | | | > | | > > | < < | < < > > | > > < | < | 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 |
z++;
}
}
putchar('\n');
}
/*
** Set the g.zBaseURL value to the full URL for the toplevel of
** the fossil tree. Set g.zHomeURL to g.zBaseURL without the
** leading "http://" and the host and port.
*/
void set_base_url(void){
int i;
const char *zHost = PD("HTTP_HOST","");
const char *zMode = PD("HTTPS","off");
const char *zCur = PD("REQUEST_URI","/");
for(i=0; zCur[i] && zCur[i]!='?' && zCur[i]!='#'; i++){}
if( g.zExtra ){
/* Skip to start of extra stuff, then pass over any /'s that might
** have separated the document root from the extra stuff. This
** ensures that the redirection actually redirects the root, not
** something deep down at the bottom of a URL.
*/
i -= strlen(g.zExtra);
while( i>0 && zCur[i-1]=='/' ){ i--; }
}
while( i>0 && zCur[i-1]!='/' ){ i--; }
while( i>0 && zCur[i-1]=='/' ){ i--; }
if( strcmp(zMode,"on")==0 ){
g.zBaseURL = mprintf("https://%s%.*s", zHost, i, zCur);
g.zTop = &g.zBaseURL[8+strlen(zHost)];
}else{
g.zBaseURL = mprintf("http://%s%.*s", zHost, i, zCur);
g.zTop = &g.zBaseURL[7+strlen(zHost)];
}
}
/*
** Preconditions:
**
** * Environment variables are set up according to the CGI standard.
** * The respository database has been located and opened.
**
** Process the webpage specified by the PATH_INFO or REQUEST_URI
** environment variable.
*/
static void process_one_web_page(void){
const char *zPathInfo;
char *zPath;
int idx;
int i, j;
/* Find the page that the user has requested, construct and deliver that
** page.
*/
zPathInfo = P("PATH_INFO");
if( zPathInfo==0 || zPathInfo[0]==0 ){
const char *zUri;
zUri = PD("REQUEST_URI","/");
for(i=0; zUri[i] && zUri[i]!='?' && zUri[i]!='#'; i++){}
for(j=i; j>0 && zUri[j-1]!='/'; j--){}
cgi_redirectf("%.*s/index", i, zUri);
}else{
zPath = mprintf("%s", zPathInfo);
}
/* Remove the leading "/" at the beginning of the path.
*/
g.zPath = &zPath[1];
for(i=1; zPath[i] && zPath[i]!='/'; i++){}
if( zPath[i]=='/' ){
zPath[i] = 0;
g.zExtra = &zPath[i+1];
}else{
g.zExtra = 0;
}
set_base_url();
if( g.zExtra ){
/* CGI parameters get this treatment elsewhere, but places like getfile
** will use g.zExtra directly.
*/
dehttpize(g.zExtra);
cgi_set_parameter_nocopy("name", g.zExtra);
}
/* Prevent robots from indexing this site.
*/
if( strcmp(g.zPath, "robots.txt")==0 ){
cgi_set_content_type("text/plain");
@ User-agent: *
@ Disallow: /
|
| ︙ | ︙ | |||
594 595 596 597 598 599 600 601 602 603 604 605 606 607 |
db_open_repository(g.argv[2]);
}else{
db_must_be_within_tree();
}
cgi_handle_http_request();
process_one_web_page();
}
/*
** COMMAND: server
**
** Usage: %fossil server ?-P|--port TCPPORT? ?REPOSITORY?
**
** Open a socket and begin listening and responding to HTTP requests on
| > > > > > > > > > | 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 |
db_open_repository(g.argv[2]);
}else{
db_must_be_within_tree();
}
cgi_handle_http_request();
process_one_web_page();
}
/*
** COMMAND: test-http
** Works like the http command but gives setup permission to all users.
*/
void cmd_test_http(void){
login_set_capabilities("s");
cmd_http();
}
/*
** COMMAND: server
**
** Usage: %fossil server ?-P|--port TCPPORT? ?REPOSITORY?
**
** Open a socket and begin listening and responding to HTTP requests on
|
| ︙ | ︙ |
Changes to src/main.mk.
| ︙ | ︙ | |||
12 13 14 15 16 17 18 19 20 21 22 23 24 25 | XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR) SRC = \ $(SRCDIR)/add.c \ $(SRCDIR)/bag.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/cgi.c \ $(SRCDIR)/checkin.c \ $(SRCDIR)/checkout.c \ $(SRCDIR)/clearsign.c \ $(SRCDIR)/clone.c \ $(SRCDIR)/comformat.c \ $(SRCDIR)/construct.c \ | > | 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR) SRC = \ $(SRCDIR)/add.c \ $(SRCDIR)/bag.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/branch.c \ $(SRCDIR)/cgi.c \ $(SRCDIR)/checkin.c \ $(SRCDIR)/checkout.c \ $(SRCDIR)/clearsign.c \ $(SRCDIR)/clone.c \ $(SRCDIR)/comformat.c \ $(SRCDIR)/construct.c \ |
| ︙ | ︙ | |||
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | $(SRCDIR)/merge.c \ $(SRCDIR)/merge3.c \ $(SRCDIR)/name.c \ $(SRCDIR)/pivot.c \ $(SRCDIR)/pqueue.c \ $(SRCDIR)/printf.c \ $(SRCDIR)/rebuild.c \ $(SRCDIR)/schema.c \ $(SRCDIR)/setup.c \ $(SRCDIR)/sha1.c \ $(SRCDIR)/style.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/timeline.c \ $(SRCDIR)/undo.c \ $(SRCDIR)/update.c \ $(SRCDIR)/url.c \ $(SRCDIR)/user.c \ $(SRCDIR)/verify.c \ $(SRCDIR)/vfile.c \ $(SRCDIR)/wiki.c \ $(SRCDIR)/wikiformat.c \ $(SRCDIR)/xfer.c \ $(SRCDIR)/zip.c TRANS_SRC = \ add_.c \ bag_.c \ blob_.c \ cgi_.c \ checkin_.c \ checkout_.c \ clearsign_.c \ clone_.c \ comformat_.c \ construct_.c \ | > > > > > > > | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | $(SRCDIR)/merge.c \ $(SRCDIR)/merge3.c \ $(SRCDIR)/name.c \ $(SRCDIR)/pivot.c \ $(SRCDIR)/pqueue.c \ $(SRCDIR)/printf.c \ $(SRCDIR)/rebuild.c \ $(SRCDIR)/rss.c \ $(SRCDIR)/schema.c \ $(SRCDIR)/setup.c \ $(SRCDIR)/sha1.c \ $(SRCDIR)/style.c \ $(SRCDIR)/subscript.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/tag.c \ $(SRCDIR)/timeline.c \ $(SRCDIR)/tkt.c \ $(SRCDIR)/tktconfig.c \ $(SRCDIR)/tktsetup.c \ $(SRCDIR)/undo.c \ $(SRCDIR)/update.c \ $(SRCDIR)/url.c \ $(SRCDIR)/user.c \ $(SRCDIR)/verify.c \ $(SRCDIR)/vfile.c \ $(SRCDIR)/wiki.c \ $(SRCDIR)/wikiformat.c \ $(SRCDIR)/xfer.c \ $(SRCDIR)/zip.c TRANS_SRC = \ add_.c \ bag_.c \ blob_.c \ branch_.c \ cgi_.c \ checkin_.c \ checkout_.c \ clearsign_.c \ clone_.c \ comformat_.c \ construct_.c \ |
| ︙ | ︙ | |||
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | merge_.c \ merge3_.c \ name_.c \ pivot_.c \ pqueue_.c \ printf_.c \ rebuild_.c \ schema_.c \ setup_.c \ sha1_.c \ style_.c \ sync_.c \ timeline_.c \ undo_.c \ update_.c \ url_.c \ user_.c \ verify_.c \ vfile_.c \ wiki_.c \ wikiformat_.c \ xfer_.c \ zip_.c OBJ = \ add.o \ bag.o \ blob.o \ cgi.o \ checkin.o \ checkout.o \ clearsign.o \ clone.o \ comformat.o \ construct.o \ | > > > > > > > | 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | merge_.c \ merge3_.c \ name_.c \ pivot_.c \ pqueue_.c \ printf_.c \ rebuild_.c \ rss_.c \ schema_.c \ setup_.c \ sha1_.c \ style_.c \ subscript_.c \ sync_.c \ tag_.c \ timeline_.c \ tkt_.c \ tktconfig_.c \ tktsetup_.c \ undo_.c \ update_.c \ url_.c \ user_.c \ verify_.c \ vfile_.c \ wiki_.c \ wikiformat_.c \ xfer_.c \ zip_.c OBJ = \ add.o \ bag.o \ blob.o \ branch.o \ cgi.o \ checkin.o \ checkout.o \ clearsign.o \ clone.o \ comformat.o \ construct.o \ |
| ︙ | ︙ | |||
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | merge.o \ merge3.o \ name.o \ pivot.o \ pqueue.o \ printf.o \ rebuild.o \ schema.o \ setup.o \ sha1.o \ style.o \ sync.o \ timeline.o \ undo.o \ update.o \ url.o \ user.o \ verify.o \ vfile.o \ wiki.o \ | > > > > > > | 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | merge.o \ merge3.o \ name.o \ pivot.o \ pqueue.o \ printf.o \ rebuild.o \ rss.o \ schema.o \ setup.o \ sha1.o \ style.o \ subscript.o \ sync.o \ tag.o \ timeline.o \ tkt.o \ tktconfig.o \ tktsetup.o \ undo.o \ update.o \ url.o \ user.o \ verify.o \ vfile.o \ wiki.o \ |
| ︙ | ︙ | |||
182 183 184 185 186 187 188 | # WARNING. DANGER. Running the testsuite modifies the repository the # build is done from, i.e. the checkout belongs to. Do not sync/push # the repository after running the tests. test: $(APPNAME) $(TCLSH) test/tester.tcl $(APPNAME) | | > > | | | | | > > > > > > > > > > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > > > > > > > > | | | | > > > > > > > > > > | > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 |
# WARNING. DANGER. Running the testsuite modifies the repository the
# build is done from, i.e. the checkout belongs to. Do not sync/push
# the repository after running the tests.
test: $(APPNAME)
$(TCLSH) test/tester.tcl $(APPNAME)
VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest
awk '{ printf "#define MANIFEST_UUID \"%s\"\n", $$1}' $(SRCDIR)/../manifest.uuid >VERSION.h
awk '{ printf "#define MANIFEST_VERSION \"[%.10s]\"\n", $$1}' $(SRCDIR)/../manifest.uuid >>VERSION.h
awk '$$1=="D"{printf "#define MANIFEST_DATE \"%s %s\"\n", substr($$2,1,10),substr($$2,12)}' $(SRCDIR)/../manifest >>VERSION.h
$(APPNAME): headers $(OBJ) sqlite3.o
$(TCC) -o $(APPNAME) $(OBJ) sqlite3.o $(LIB)
clean:
rm -f *.o *_.c $(APPNAME) VERSION.h
rm -f translate makeheaders mkindex page_index.h headers
rm -f add.h bag.h blob.h branch.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h construct.h content.h db.h delta.h deltacmd.h descendents.h diff.h diffcmd.h encode.h file.h http.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 rss.h schema.h setup.h sha1.h style.h subscript.h sync.h tag.h timeline.h tkt.h tktconfig.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h xfer.h zip.h
headers: makeheaders mkindex $(TRANS_SRC) ./VERSION.h
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
./mkindex $(TRANS_SRC) >page_index.h
touch headers
add_.c: $(SRCDIR)/add.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/add.c | sed -f $(SRCDIR)/VERSION >add_.c
add.o: add_.c add.h $(SRCDIR)/config.h
$(XTCC) -o add.o -c add_.c
add.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
bag_.c: $(SRCDIR)/bag.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/bag.c | sed -f $(SRCDIR)/VERSION >bag_.c
bag.o: bag_.c bag.h $(SRCDIR)/config.h
$(XTCC) -o bag.o -c bag_.c
bag.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
blob_.c: $(SRCDIR)/blob.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/blob.c | sed -f $(SRCDIR)/VERSION >blob_.c
blob.o: blob_.c blob.h $(SRCDIR)/config.h
$(XTCC) -o blob.o -c blob_.c
blob.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
branch_.c: $(SRCDIR)/branch.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/branch.c | sed -f $(SRCDIR)/VERSION >branch_.c
branch.o: branch_.c branch.h $(SRCDIR)/config.h
$(XTCC) -o branch.o -c branch_.c
branch.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
cgi_.c: $(SRCDIR)/cgi.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/cgi.c | sed -f $(SRCDIR)/VERSION >cgi_.c
cgi.o: cgi_.c cgi.h $(SRCDIR)/config.h
$(XTCC) -o cgi.o -c cgi_.c
cgi.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
checkin_.c: $(SRCDIR)/checkin.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/checkin.c | sed -f $(SRCDIR)/VERSION >checkin_.c
checkin.o: checkin_.c checkin.h $(SRCDIR)/config.h
$(XTCC) -o checkin.o -c checkin_.c
checkin.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
checkout_.c: $(SRCDIR)/checkout.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/checkout.c | sed -f $(SRCDIR)/VERSION >checkout_.c
checkout.o: checkout_.c checkout.h $(SRCDIR)/config.h
$(XTCC) -o checkout.o -c checkout_.c
checkout.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
clearsign_.c: $(SRCDIR)/clearsign.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/clearsign.c | sed -f $(SRCDIR)/VERSION >clearsign_.c
clearsign.o: clearsign_.c clearsign.h $(SRCDIR)/config.h
$(XTCC) -o clearsign.o -c clearsign_.c
clearsign.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
clone_.c: $(SRCDIR)/clone.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/clone.c | sed -f $(SRCDIR)/VERSION >clone_.c
clone.o: clone_.c clone.h $(SRCDIR)/config.h
$(XTCC) -o clone.o -c clone_.c
clone.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
comformat_.c: $(SRCDIR)/comformat.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/comformat.c | sed -f $(SRCDIR)/VERSION >comformat_.c
comformat.o: comformat_.c comformat.h $(SRCDIR)/config.h
$(XTCC) -o comformat.o -c comformat_.c
comformat.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
construct_.c: $(SRCDIR)/construct.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/construct.c | sed -f $(SRCDIR)/VERSION >construct_.c
construct.o: construct_.c construct.h $(SRCDIR)/config.h
$(XTCC) -o construct.o -c construct_.c
construct.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
content_.c: $(SRCDIR)/content.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/content.c | sed -f $(SRCDIR)/VERSION >content_.c
content.o: content_.c content.h $(SRCDIR)/config.h
$(XTCC) -o content.o -c content_.c
content.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
db_.c: $(SRCDIR)/db.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/db.c | sed -f $(SRCDIR)/VERSION >db_.c
db.o: db_.c db.h $(SRCDIR)/config.h
$(XTCC) -o db.o -c db_.c
db.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
delta_.c: $(SRCDIR)/delta.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/delta.c | sed -f $(SRCDIR)/VERSION >delta_.c
delta.o: delta_.c delta.h $(SRCDIR)/config.h
$(XTCC) -o delta.o -c delta_.c
delta.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
deltacmd_.c: $(SRCDIR)/deltacmd.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/deltacmd.c | sed -f $(SRCDIR)/VERSION >deltacmd_.c
deltacmd.o: deltacmd_.c deltacmd.h $(SRCDIR)/config.h
$(XTCC) -o deltacmd.o -c deltacmd_.c
deltacmd.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
descendents_.c: $(SRCDIR)/descendents.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/descendents.c | sed -f $(SRCDIR)/VERSION >descendents_.c
descendents.o: descendents_.c descendents.h $(SRCDIR)/config.h
$(XTCC) -o descendents.o -c descendents_.c
descendents.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
diff_.c: $(SRCDIR)/diff.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/diff.c | sed -f $(SRCDIR)/VERSION >diff_.c
diff.o: diff_.c diff.h $(SRCDIR)/config.h
$(XTCC) -o diff.o -c diff_.c
diff.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
diffcmd_.c: $(SRCDIR)/diffcmd.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/diffcmd.c | sed -f $(SRCDIR)/VERSION >diffcmd_.c
diffcmd.o: diffcmd_.c diffcmd.h $(SRCDIR)/config.h
$(XTCC) -o diffcmd.o -c diffcmd_.c
diffcmd.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
encode_.c: $(SRCDIR)/encode.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/encode.c | sed -f $(SRCDIR)/VERSION >encode_.c
encode.o: encode_.c encode.h $(SRCDIR)/config.h
$(XTCC) -o encode.o -c encode_.c
encode.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
file_.c: $(SRCDIR)/file.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/file.c | sed -f $(SRCDIR)/VERSION >file_.c
file.o: file_.c file.h $(SRCDIR)/config.h
$(XTCC) -o file.o -c file_.c
file.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
http_.c: $(SRCDIR)/http.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/http.c | sed -f $(SRCDIR)/VERSION >http_.c
http.o: http_.c http.h $(SRCDIR)/config.h
$(XTCC) -o http.o -c http_.c
http.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
info_.c: $(SRCDIR)/info.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/info.c | sed -f $(SRCDIR)/VERSION >info_.c
info.o: info_.c info.h $(SRCDIR)/config.h
$(XTCC) -o info.o -c info_.c
info.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
login_.c: $(SRCDIR)/login.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/login.c | sed -f $(SRCDIR)/VERSION >login_.c
login.o: login_.c login.h $(SRCDIR)/config.h
$(XTCC) -o login.o -c login_.c
login.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
main_.c: $(SRCDIR)/main.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/main.c | sed -f $(SRCDIR)/VERSION >main_.c
main.o: main_.c main.h page_index.h $(SRCDIR)/config.h
$(XTCC) -o main.o -c main_.c
main.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
manifest_.c: $(SRCDIR)/manifest.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/manifest.c | sed -f $(SRCDIR)/VERSION >manifest_.c
manifest.o: manifest_.c manifest.h $(SRCDIR)/config.h
$(XTCC) -o manifest.o -c manifest_.c
manifest.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
md5_.c: $(SRCDIR)/md5.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/md5.c | sed -f $(SRCDIR)/VERSION >md5_.c
md5.o: md5_.c md5.h $(SRCDIR)/config.h
$(XTCC) -o md5.o -c md5_.c
md5.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
merge_.c: $(SRCDIR)/merge.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/merge.c | sed -f $(SRCDIR)/VERSION >merge_.c
merge.o: merge_.c merge.h $(SRCDIR)/config.h
$(XTCC) -o merge.o -c merge_.c
merge.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
merge3_.c: $(SRCDIR)/merge3.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/merge3.c | sed -f $(SRCDIR)/VERSION >merge3_.c
merge3.o: merge3_.c merge3.h $(SRCDIR)/config.h
$(XTCC) -o merge3.o -c merge3_.c
merge3.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
name_.c: $(SRCDIR)/name.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/name.c | sed -f $(SRCDIR)/VERSION >name_.c
name.o: name_.c name.h $(SRCDIR)/config.h
$(XTCC) -o name.o -c name_.c
name.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
pivot_.c: $(SRCDIR)/pivot.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/pivot.c | sed -f $(SRCDIR)/VERSION >pivot_.c
pivot.o: pivot_.c pivot.h $(SRCDIR)/config.h
$(XTCC) -o pivot.o -c pivot_.c
pivot.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
pqueue_.c: $(SRCDIR)/pqueue.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/pqueue.c | sed -f $(SRCDIR)/VERSION >pqueue_.c
pqueue.o: pqueue_.c pqueue.h $(SRCDIR)/config.h
$(XTCC) -o pqueue.o -c pqueue_.c
pqueue.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
printf_.c: $(SRCDIR)/printf.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/printf.c | sed -f $(SRCDIR)/VERSION >printf_.c
printf.o: printf_.c printf.h $(SRCDIR)/config.h
$(XTCC) -o printf.o -c printf_.c
printf.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
rebuild_.c: $(SRCDIR)/rebuild.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/rebuild.c | sed -f $(SRCDIR)/VERSION >rebuild_.c
rebuild.o: rebuild_.c rebuild.h $(SRCDIR)/config.h
$(XTCC) -o rebuild.o -c rebuild_.c
rebuild.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
rss_.c: $(SRCDIR)/rss.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/rss.c | sed -f $(SRCDIR)/VERSION >rss_.c
rss.o: rss_.c rss.h $(SRCDIR)/config.h
$(XTCC) -o rss.o -c rss_.c
rss.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
schema_.c: $(SRCDIR)/schema.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/schema.c | sed -f $(SRCDIR)/VERSION >schema_.c
schema.o: schema_.c schema.h $(SRCDIR)/config.h
$(XTCC) -o schema.o -c schema_.c
schema.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
setup_.c: $(SRCDIR)/setup.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/setup.c | sed -f $(SRCDIR)/VERSION >setup_.c
setup.o: setup_.c setup.h $(SRCDIR)/config.h
$(XTCC) -o setup.o -c setup_.c
setup.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
sha1_.c: $(SRCDIR)/sha1.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/sha1.c | sed -f $(SRCDIR)/VERSION >sha1_.c
sha1.o: sha1_.c sha1.h $(SRCDIR)/config.h
$(XTCC) -o sha1.o -c sha1_.c
sha1.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
style_.c: $(SRCDIR)/style.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/style.c | sed -f $(SRCDIR)/VERSION >style_.c
style.o: style_.c style.h $(SRCDIR)/config.h
$(XTCC) -o style.o -c style_.c
style.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
subscript_.c: $(SRCDIR)/subscript.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/subscript.c | sed -f $(SRCDIR)/VERSION >subscript_.c
subscript.o: subscript_.c subscript.h $(SRCDIR)/config.h
$(XTCC) -o subscript.o -c subscript_.c
subscript.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
sync_.c: $(SRCDIR)/sync.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/sync.c | sed -f $(SRCDIR)/VERSION >sync_.c
sync.o: sync_.c sync.h $(SRCDIR)/config.h
$(XTCC) -o sync.o -c sync_.c
sync.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
tag_.c: $(SRCDIR)/tag.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/tag.c | sed -f $(SRCDIR)/VERSION >tag_.c
tag.o: tag_.c tag.h $(SRCDIR)/config.h
$(XTCC) -o tag.o -c tag_.c
tag.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
timeline_.c: $(SRCDIR)/timeline.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/timeline.c | sed -f $(SRCDIR)/VERSION >timeline_.c
timeline.o: timeline_.c timeline.h $(SRCDIR)/config.h
$(XTCC) -o timeline.o -c timeline_.c
timeline.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
tkt_.c: $(SRCDIR)/tkt.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/tkt.c | sed -f $(SRCDIR)/VERSION >tkt_.c
tkt.o: tkt_.c tkt.h $(SRCDIR)/config.h
$(XTCC) -o tkt.o -c tkt_.c
tkt.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
tktconfig_.c: $(SRCDIR)/tktconfig.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/tktconfig.c | sed -f $(SRCDIR)/VERSION >tktconfig_.c
tktconfig.o: tktconfig_.c tktconfig.h $(SRCDIR)/config.h
$(XTCC) -o tktconfig.o -c tktconfig_.c
tktconfig.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
tktsetup_.c: $(SRCDIR)/tktsetup.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/tktsetup.c | sed -f $(SRCDIR)/VERSION >tktsetup_.c
tktsetup.o: tktsetup_.c tktsetup.h $(SRCDIR)/config.h
$(XTCC) -o tktsetup.o -c tktsetup_.c
tktsetup.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
undo_.c: $(SRCDIR)/undo.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/undo.c | sed -f $(SRCDIR)/VERSION >undo_.c
undo.o: undo_.c undo.h $(SRCDIR)/config.h
$(XTCC) -o undo.o -c undo_.c
undo.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
update_.c: $(SRCDIR)/update.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/update.c | sed -f $(SRCDIR)/VERSION >update_.c
update.o: update_.c update.h $(SRCDIR)/config.h
$(XTCC) -o update.o -c update_.c
update.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
url_.c: $(SRCDIR)/url.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/url.c | sed -f $(SRCDIR)/VERSION >url_.c
url.o: url_.c url.h $(SRCDIR)/config.h
$(XTCC) -o url.o -c url_.c
url.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
user_.c: $(SRCDIR)/user.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/user.c | sed -f $(SRCDIR)/VERSION >user_.c
user.o: user_.c user.h $(SRCDIR)/config.h
$(XTCC) -o user.o -c user_.c
user.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
verify_.c: $(SRCDIR)/verify.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/verify.c | sed -f $(SRCDIR)/VERSION >verify_.c
verify.o: verify_.c verify.h $(SRCDIR)/config.h
$(XTCC) -o verify.o -c verify_.c
verify.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
vfile_.c: $(SRCDIR)/vfile.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/vfile.c | sed -f $(SRCDIR)/VERSION >vfile_.c
vfile.o: vfile_.c vfile.h $(SRCDIR)/config.h
$(XTCC) -o vfile.o -c vfile_.c
vfile.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
wiki_.c: $(SRCDIR)/wiki.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/wiki.c | sed -f $(SRCDIR)/VERSION >wiki_.c
wiki.o: wiki_.c wiki.h $(SRCDIR)/config.h
$(XTCC) -o wiki.o -c wiki_.c
wiki.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
wikiformat_.c: $(SRCDIR)/wikiformat.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/wikiformat.c | sed -f $(SRCDIR)/VERSION >wikiformat_.c
wikiformat.o: wikiformat_.c wikiformat.h $(SRCDIR)/config.h
$(XTCC) -o wikiformat.o -c wikiformat_.c
wikiformat.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
xfer_.c: $(SRCDIR)/xfer.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/xfer.c | sed -f $(SRCDIR)/VERSION >xfer_.c
xfer.o: xfer_.c xfer.h $(SRCDIR)/config.h
$(XTCC) -o xfer.o -c xfer_.c
xfer.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
zip_.c: $(SRCDIR)/zip.c $(SRCDIR)/VERSION translate
./translate $(SRCDIR)/zip.c | sed -f $(SRCDIR)/VERSION >zip_.c
zip.o: zip_.c zip.h $(SRCDIR)/config.h
$(XTCC) -o zip.o -c zip_.c
zip.h: makeheaders
./makeheaders add_.c:add.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.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 construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendents_.c:descendents.h diff_.c:diff.h diffcmd_.c:diffcmd.h encode_.c:encode.h file_.c:file.h http_.c:http.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 rss_.c:rss.h schema_.c:schema.h setup_.c:setup.h sha1_.c:sha1.h style_.c:style.h subscript_.c:subscript.h sync_.c:sync.h tag_.c:tag.h timeline_.c:timeline.h tkt_.c:tkt.h tktconfig_.c:tktconfig.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 xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h ./VERSION.h
touch headers
sqlite3.o: $(SRCDIR)/sqlite3.c
$(XTCC) -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_PRIVATE= -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_FTS3=1 -c $(SRCDIR)/sqlite3.c -o sqlite3.o
|
Changes to src/makemake.tcl.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/usr/bin/tclsh
#
# Run this TCL script to generate the "main.mk" makefile.
#
# Basenames of all source files:
#
set src {
add
bag
blob
cgi
checkin
checkout
clearsign
clone
comformat
construct
| > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#!/usr/bin/tclsh
#
# Run this TCL script to generate the "main.mk" makefile.
#
# Basenames of all source files:
#
set src {
add
bag
blob
branch
cgi
checkin
checkout
clearsign
clone
comformat
construct
|
| ︙ | ︙ | |||
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | merge merge3 name pivot pqueue printf rebuild schema setup sha1 style sync timeline undo update url user verify vfile wiki | > > > > > > | 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | merge merge3 name pivot pqueue printf rebuild rss schema setup sha1 style subscript sync tag timeline tkt tktconfig tktsetup undo update url user verify vfile wiki |
| ︙ | ︙ | |||
109 110 111 112 113 114 115 | # WARNING. DANGER. Running the testsuite modifies the repository the # build is done from, i.e. the checkout belongs to. Do not sync/push # the repository after running the tests. test: $(APPNAME) $(TCLSH) test/tester.tcl $(APPNAME) | | > > > > > | 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# WARNING. DANGER. Running the testsuite modifies the repository the
# build is done from, i.e. the checkout belongs to. Do not sync/push
# the repository after running the tests.
test: $(APPNAME)
$(TCLSH) test/tester.tcl $(APPNAME)
VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest
awk '{ printf "#define MANIFEST_UUID \"%s\"\n", $$1}' \
$(SRCDIR)/../manifest.uuid >VERSION.h
awk '{ printf "#define MANIFEST_VERSION \"[%.10s]\"\n", $$1}' \
$(SRCDIR)/../manifest.uuid >>VERSION.h
awk '$$1=="D"{printf "#define MANIFEST_DATE \"%s %s\"\n",\
substr($$2,1,10),substr($$2,12)}' \
$(SRCDIR)/../manifest >>VERSION.h
$(APPNAME): headers $(OBJ) sqlite3.o
$(TCC) -o $(APPNAME) $(OBJ) sqlite3.o $(LIB)
clean:
rm -f *.o *_.c $(APPNAME) VERSION.h
rm -f translate makeheaders mkindex page_index.h headers}
|
| ︙ | ︙ | |||
148 149 150 151 152 153 154 |
puts "$s.h:\tmakeheaders"
puts "\t./makeheaders $mhargs\n\ttouch headers\n"
}
puts "sqlite3.o:\t\$(SRCDIR)/sqlite3.c"
set opt {-DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_PRIVATE=}
| | > | 160 161 162 163 164 165 166 167 168 169 |
puts "$s.h:\tmakeheaders"
puts "\t./makeheaders $mhargs\n\ttouch headers\n"
}
puts "sqlite3.o:\t\$(SRCDIR)/sqlite3.c"
set opt {-DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_PRIVATE=}
append opt " -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4"
append opt " -DSQLITE_ENABLE_FTS3=1"
puts "\t\$(XTCC) $opt -c \$(SRCDIR)/sqlite3.c -o sqlite3.o\n"
|
Changes to src/manifest.c.
| ︙ | ︙ | |||
17 18 19 20 21 22 23 | ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | > > > > > > > > > > > > > > | > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | | | | | | | | | | | | | | > > | > > > > > > > > > | | | | | > > > > > > > > > > > > > > > > > > > > > > | > | | | > > > > > > > > > > > > > > > > > | | | | > > > > > > | > > > > > > | | | | | | | | | | | | | | | | | | | > | < < > | < > | | | < < < < < < > | | | | | | | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | | | > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 |
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to cross link control files and
** manifests. The file is named "manifest.c" because it was
** original only used to parse manifests. Then later clusters
** and control files and wiki pages and tickets were added.
*/
#include "config.h"
#include "manifest.h"
#include <assert.h>
#if INTERFACE
/*
** Types of control files
*/
#define CFTYPE_MANIFEST 1
#define CFTYPE_CLUSTER 2
#define CFTYPE_CONTROL 3
#define CFTYPE_WIKI 4
#define CFTYPE_TICKET 5
/*
** Mode parameter values
*/
#define CFMODE_READ 1
#define CFMODE_APPEND 2
#define CFMODE_WRITE 3
/*
** A parsed manifest or cluster.
*/
struct Manifest {
Blob content; /* The original content blob */
int type; /* Type of file */
int mode; /* Access mode */
char *zComment; /* Decoded comment */
char zUuid[UUID_SIZE+1]; /* Self UUID */
double rDate; /* Time in the "D" line */
char *zUser; /* Name of the user */
char *zRepoCksum; /* MD5 checksum of the baseline content */
char *zWiki; /* Text of the wiki page */
char *zWikiTitle; /* Name of the wiki page */
char *zTicketUuid; /* UUID for a ticket */
int nFile; /* Number of F lines */
int nFileAlloc; /* Slots allocated in aFile[] */
struct {
char *zName; /* Name of a file */
char *zUuid; /* UUID of the file */
} *aFile;
int nParent; /* Number of parents */
int nParentAlloc; /* Slots allocated in azParent[] */
char **azParent; /* UUIDs of parents */
int nCChild; /* Number of cluster children */
int nCChildAlloc; /* Number of closts allocated in azCChild[] */
char **azCChild; /* UUIDs of referenced objects in a cluster */
int nTag; /* Number of T lines */
int nTagAlloc; /* Slots allocated in aTag[] */
struct {
char *zName; /* Name of the tag */
char *zUuid; /* UUID that the tag is applied to */
char *zValue; /* Value if the tag is really a property */
} *aTag;
int nField; /* Number of J lines */
int nFieldAlloc; /* Slots allocated in aField[] */
struct {
char *zName; /* Key or field name */
char *zValue; /* Value of the field */
} *aField;
int nAttach; /* Number of A lines */
int nAttachAlloc; /* Slots allocated in aAttach[] */
struct {
char *zUuid; /* UUID of the attachment */
char *zName; /* Name of the attachment */
char *zDesc; /* Description of the attachment */
} *aAttach;
};
#endif
/*
** Clear the memory allocated in a manifest object
*/
void manifest_clear(Manifest *p){
blob_reset(&p->content);
free(p->aFile);
free(p->azParent);
free(p->azCChild);
free(p->aTag);
free(p->aField);
free(p->aAttach);
memset(p, 0, sizeof(*p));
}
/*
** Parse a blob into a Manifest object. The Manifest object
** takes over the input blob and will free it when the
** Manifest object is freed. Zeros are inserted into the blob
** as string terminators so that blob should not be used again.
**
** Return TRUE if the content really is a control file of some
** kind. Return FALSE if there are syntax errors.
**
** This routine is strict about the format of a control file.
** The format must match exactly or else it is rejected. This
** rule minimizes the risk that a content file will be mistaken
** for a control file simply because they look the same.
**
** The pContent is reset. If TRUE is returned, then pContent will
** be reset when the Manifest object is cleared. If FALSE is
** returned then the Manifest object is cleared automatically
** and pContent is reset before the return.
**
** The entire file can be PGP clear-signed. The signature is ignored.
** The file consists of zero or more cards, one card per line.
** (Except: the content of the W card can extend of multiple lines.)
** Each card is divided into tokens by a single space character.
** The first token is a single upper-case letter which is the card type.
** The card type determines the other parameters to the card.
** Cards must occur in lexicographical order.
*/
int manifest_parse(Manifest *p, Blob *pContent){
int seenHeader = 0;
int seenZ = 0;
int i, lineNo=0;
Blob line, token, a1, a2, a3;
Blob selfuuid;
char cPrevType = 0;
memset(p, 0, sizeof(*p));
memcpy(&p->content, pContent, sizeof(p->content));
sha1sum_blob(&p->content, &selfuuid);
memcpy(p->zUuid, blob_buffer(&selfuuid), UUID_SIZE);
p->zUuid[UUID_SIZE] = 0;
blob_zero(pContent);
pContent = &p->content;
blob_zero(&a1);
blob_zero(&a2);
md5sum_init();
while( blob_line(pContent, &line) ){
char *z = blob_buffer(&line);
lineNo++;
if( z[0]=='-' ){
if( strncmp(z, "-----BEGIN PGP ", 15)!=0 ){
goto manifest_syntax_error;
}
if( seenHeader ){
break;
}
while( blob_line(pContent, &line)>2 ){}
if( blob_line(pContent, &line)==0 ) break;
z = blob_buffer(&line);
}
if( z[0]<cPrevType ){
/* Lines of a manifest must occur in lexicographical order */
goto manifest_syntax_error;
}
cPrevType = z[0];
seenHeader = 1;
if( blob_token(&line, &token)!=1 ) goto manifest_syntax_error;
switch( z[0] ){
/*
** A <uuid> <filename> <description>
**
** Identifies an attachment to either a wiki page or a ticket.
** <uuid> is the artifact that is the attachment.
*/
case 'A': {
char *zName, *zUuid, *zDesc;
md5sum_step_text(blob_buffer(&line), blob_size(&line));
if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error;
if( blob_token(&line, &a3)==0 ) goto manifest_syntax_error;
zUuid = blob_terminate(&a1);
zName = blob_terminate(&a2);
zDesc = blob_terminate(&a3);
if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error;
if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
defossilize(zName);
if( !file_is_simple_pathname(zName) ){
goto manifest_syntax_error;
}
defossilize(zDesc);
if( p->nAttach>=p->nAttachAlloc ){
p->nAttachAlloc = p->nAttachAlloc*2 + 10;
p->aAttach = realloc(p->aAttach,
p->nAttachAlloc*sizeof(p->aAttach[0]) );
if( p->aAttach==0 ) fossil_panic("out of memory");
}
i = p->nAttach++;
p->aAttach[i].zUuid = zUuid;
p->aAttach[i].zName = zName;
p->aAttach[i].zDesc = zDesc;
if( i>0 && strcmp(p->aAttach[i-1].zUuid, zUuid)>=0 ){
goto manifest_syntax_error;
}
break;
}
/*
** C <comment>
**
** Comment text is fossil-encoded. There may be no more than
** one C line. C lines are required for manifests and are
** disallowed on all other control files.
*/
case 'C': {
md5sum_step_text(blob_buffer(&line), blob_size(&line));
if( p->zComment!=0 ) goto manifest_syntax_error;
if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
p->zComment = blob_terminate(&a1);
defossilize(p->zComment);
break;
}
/*
** D <timestamp>
**
** The timestamp should be ISO 8601. YYYY-MM-DDtHH:MM:SS
** There can be no more than 1 D line. D lines are required
** for all control files except for clusters.
*/
case 'D': {
char *zDate;
md5sum_step_text(blob_buffer(&line), blob_size(&line));
if( p->rDate!=0.0 ) goto manifest_syntax_error;
if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
zDate = blob_terminate(&a1);
p->rDate = db_double(0.0, "SELECT julianday(%Q)", zDate);
break;
}
/*
** E <mode>
**
** Access mode. <mode> can be one of "read", "append",
** or "write".
*/
case 'E': {
md5sum_step_text(blob_buffer(&line), blob_size(&line));
if( p->mode!=0 ) goto manifest_syntax_error;
if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
if( blob_eq(&a1, "write") ){
p->mode = CFMODE_WRITE;
}else if( blob_eq(&a1, "append") ){
p->mode = CFMODE_APPEND;
}else if( blob_eq(&a1, "read") ){
p->mode = CFMODE_READ;
}else{
goto manifest_syntax_error;
}
break;
}
/*
** F <filename> <uuid>
**
** Identifies a file in a manifest. Multiple F lines are
** allowed in a manifest. F lines are not allowed in any
** other control file. The filename is fossil-encoded.
*/
case 'F': {
char *zName, *zUuid;
md5sum_step_text(blob_buffer(&line), blob_size(&line));
if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error;
if( blob_token(&line, &a3)!=0 ) goto manifest_syntax_error;
zName = blob_terminate(&a1);
zUuid = blob_terminate(&a2);
if( blob_size(&a2)!=UUID_SIZE ) goto manifest_syntax_error;
if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
defossilize(zName);
if( !file_is_simple_pathname(zName) ){
goto manifest_syntax_error;
}
if( p->nFile>=p->nFileAlloc ){
p->nFileAlloc = p->nFileAlloc*2 + 10;
p->aFile = realloc(p->aFile, p->nFileAlloc*sizeof(p->aFile[0]) );
if( p->aFile==0 ) fossil_panic("out of memory");
}
i = p->nFile++;
p->aFile[i].zName = zName;
p->aFile[i].zUuid = zUuid;
if( i>0 && strcmp(p->aFile[i-1].zName, zName)>=0 ){
goto manifest_syntax_error;
}
break;
}
/*
** J '+'?<name> <value>
**
** Specifies a name value pair for ticket. If the first character
** of <name> is "+" then the value is appended to any preexisting
** value.
*/
case 'J': {
char *zName, *zValue;
md5sum_step_text(blob_buffer(&line), blob_size(&line));
if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error;
if( blob_token(&line, &a3)!=0 ) goto manifest_syntax_error;
zName = blob_terminate(&a1);
zValue = blob_terminate(&a2);
defossilize(zValue);
if( p->nField>=p->nFieldAlloc ){
p->nFieldAlloc = p->nFieldAlloc*2 + 10;
p->aField = realloc(p->aField,
p->nFieldAlloc*sizeof(p->aField[0]) );
if( p->aField==0 ) fossil_panic("out of memory");
}
i = p->nField++;
p->aField[i].zName = zName;
p->aField[i].zValue = zValue;
if( i>0 && strcmp(p->aField[i-1].zName, zName)>=0 ){
goto manifest_syntax_error;
}
break;
}
/*
** K <uuid>
**
** A K-line gives the UUID for the ticket which this control file
** is amending.
*/
case 'K': {
char *zUuid;
md5sum_step_text(blob_buffer(&line), blob_size(&line));
if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
zUuid = blob_terminate(&a1);
if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error;
if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
if( p->zTicketUuid!=0 ) goto manifest_syntax_error;
p->zTicketUuid = zUuid;
break;
}
/*
** L <wikititle>
**
** The wiki page title is fossil-encoded. There may be no more than
** one L line.
*/
case 'L': {
md5sum_step_text(blob_buffer(&line), blob_size(&line));
if( p->zWikiTitle!=0 ) goto manifest_syntax_error;
if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
p->zWikiTitle = blob_terminate(&a1);
defossilize(p->zWikiTitle);
if( !wiki_name_is_wellformed(p->zWikiTitle) ){
goto manifest_syntax_error;
}
break;
}
/*
** M <uuid>
**
** An M-line identifies another artifact by its UUID. M-lines
** occur in clusters only.
*/
case 'M': {
char *zUuid;
md5sum_step_text(blob_buffer(&line), blob_size(&line));
if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
zUuid = blob_terminate(&a1);
if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error;
if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
if( p->nCChild>=p->nCChildAlloc ){
p->nCChildAlloc = p->nCChildAlloc*2 + 10;
p->azCChild =
realloc(p->azCChild, p->nCChildAlloc*sizeof(p->azCChild[0]) );
if( p->azCChild==0 ) fossil_panic("out of memory");
}
i = p->nCChild++;
p->azCChild[i] = zUuid;
if( i>0 && strcmp(p->azCChild[i-1], zUuid)>=0 ){
goto manifest_syntax_error;
}
break;
}
/*
** P <uuid> ...
**
** Specify one or more other artifacts where are the parents of
** this artifact. The first parent is the primary parent. All
** others are parents by merge.
*/
case 'P': {
md5sum_step_text(blob_buffer(&line), blob_size(&line));
while( blob_token(&line, &a1) ){
char *zUuid;
if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error;
zUuid = blob_terminate(&a1);
if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
if( p->nParent>=p->nParentAlloc ){
p->nParentAlloc = p->nParentAlloc*2 + 5;
p->azParent = realloc(p->azParent, p->nParentAlloc*sizeof(char*));
if( p->azParent==0 ) fossil_panic("out of memory");
}
i = p->nParent++;
p->azParent[i] = zUuid;
}
break;
}
/*
** R <md5sum>
**
** Specify the MD5 checksum of the entire baseline in a
** manifest.
*/
case 'R': {
md5sum_step_text(blob_buffer(&line), blob_size(&line));
if( p->zRepoCksum!=0 ) goto manifest_syntax_error;
if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
if( blob_size(&a1)!=32 ) goto manifest_syntax_error;
p->zRepoCksum = blob_terminate(&a1);
if( !validate16(p->zRepoCksum, 32) ) goto manifest_syntax_error;
break;
}
/*
** T (+|*|-)<tagname> <uuid> ?<value>?
**
** Create or cancel a tag or property. The tagname is fossil-encoded.
** The first character of the name must be either "+" to create a
** singleton tag, "*" to create a propagating tag, or "-" to create
** anti-tag that undoes a prior "+" or blocks propagation of of
** a "*".
**
** The tag is applied to <uuid>. If <uuid> is "*" then the tag is
** applied to the current manifest. If <value> is provided then
** the tag is really a property with the given value.
**
** Tags are not allowed in clusters. Multiple T lines are allowed.
*/
case 'T': {
char *zName, *zUuid, *zValue;
md5sum_step_text(blob_buffer(&line), blob_size(&line));
if( blob_token(&line, &a1)==0 ){
goto manifest_syntax_error;
}
if( blob_token(&line, &a2)==0 ){
goto manifest_syntax_error;
}
zName = blob_terminate(&a1);
zUuid = blob_terminate(&a2);
if( blob_token(&line, &a3)==0 ){
zValue = 0;
}else{
zValue = blob_terminate(&a3);
defossilize(zValue);
}
if( blob_size(&a2)==UUID_SIZE && validate16(zUuid, UUID_SIZE) ){
/* A valid uuid */
}else if( blob_size(&a2)==1 && zUuid[0]=='*' ){
zUuid = p->zUuid;
}else{
goto manifest_syntax_error;
}
defossilize(zName);
if( zName[0]!='-' && zName[0]!='+' && zName[0]!='*' ){
goto manifest_syntax_error;
}
if( validate16(&zName[1], strlen(&zName[1])) ){
/* Do not allow tags whose names look like UUIDs */
goto manifest_syntax_error;
}
if( p->nTag>=p->nTagAlloc ){
p->nTagAlloc = p->nTagAlloc*2 + 10;
p->aTag = realloc(p->aTag, p->nTagAlloc*sizeof(p->aTag[0]) );
if( p->aTag==0 ) fossil_panic("out of memory");
}
i = p->nTag++;
p->aTag[i].zName = zName;
p->aTag[i].zUuid = zUuid;
p->aTag[i].zValue = zValue;
if( i>0 && strcmp(p->aTag[i-1].zName, zName)>=0 ){
goto manifest_syntax_error;
}
break;
}
/*
** U <login>
**
** Identify the user who created this control file by their
** login. Only one U line is allowed. Prohibited in clusters.
*/
case 'U': {
md5sum_step_text(blob_buffer(&line), blob_size(&line));
if( p->zUser!=0 ) goto manifest_syntax_error;
if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
p->zUser = blob_terminate(&a1);
defossilize(p->zUser);
break;
}
/*
** W <size>
**
** The next <size> bytes of the file contain the text of the wiki
** page. There is always an extra \n before the start of the next
** record.
*/
case 'W': {
int size;
Blob wiki;
md5sum_step_text(blob_buffer(&line), blob_size(&line));
if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
if( !blob_is_int(&a1, &size) ) goto manifest_syntax_error;
if( size<0 ) goto manifest_syntax_error;
if( p->zWiki!=0 ) goto manifest_syntax_error;
blob_zero(&wiki);
if( blob_extract(pContent, size+1, &wiki)!=size+1 ){
goto manifest_syntax_error;
}
p->zWiki = blob_buffer(&wiki);
md5sum_step_text(p->zWiki, size+1);
if( p->zWiki[size]!='\n' ) goto manifest_syntax_error;
p->zWiki[size] = 0;
break;
}
/*
** Z <md5sum>
**
** MD5 checksum on this control file. The checksum is over all
** lines (other than PGP-signature lines) prior to the current
** line. This must be the last record.
**
** This card is required for all control file types except for
** Manifest. It is not required for manifest only for historical
** compatibility reasons.
*/
case 'Z': {
int rc;
Blob hash;
if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
if( blob_size(&a1)!=32 ) goto manifest_syntax_error;
if( !validate16(blob_buffer(&a1), 32) ) goto manifest_syntax_error;
md5sum_finish(&hash);
rc = blob_compare(&hash, &a1);
blob_reset(&hash);
if( rc!=0 ) goto manifest_syntax_error;
seenZ = 1;
break;
}
default: {
goto manifest_syntax_error;
}
}
}
if( !seenHeader ) goto manifest_syntax_error;
if( p->nFile>0 ){
if( p->nCChild>0 ) goto manifest_syntax_error;
if( p->rDate==0.0 ) goto manifest_syntax_error;
if( p->nField>0 ) goto manifest_syntax_error;
if( p->zTicketUuid ) goto manifest_syntax_error;
if( p->nAttach>0 ) goto manifest_syntax_error;
if( p->zWiki ) goto manifest_syntax_error;
if( p->zWikiTitle ) goto manifest_syntax_error;
if( p->zTicketUuid ) goto manifest_syntax_error;
p->type = CFTYPE_MANIFEST;
}else if( p->nCChild>0 ){
if( p->rDate>0.0 ) goto manifest_syntax_error;
if( p->zComment!=0 ) goto manifest_syntax_error;
if( p->zUser!=0 ) goto manifest_syntax_error;
if( p->nTag>0 ) goto manifest_syntax_error;
if( p->nParent>0 ) goto manifest_syntax_error;
if( p->zRepoCksum!=0 ) goto manifest_syntax_error;
if( p->nField>0 ) goto manifest_syntax_error;
if( p->zTicketUuid ) goto manifest_syntax_error;
if( p->nAttach>0 ) goto manifest_syntax_error;
if( p->zWiki ) goto manifest_syntax_error;
if( p->zWikiTitle ) goto manifest_syntax_error;
if( !seenZ ) goto manifest_syntax_error;
p->type = CFTYPE_CLUSTER;
}else if( p->nField>0 ){
if( p->rDate==0.0 ) goto manifest_syntax_error;
if( p->zRepoCksum!=0 ) goto manifest_syntax_error;
if( p->zWiki ) goto manifest_syntax_error;
if( p->zWikiTitle ) goto manifest_syntax_error;
if( p->nCChild>0 ) goto manifest_syntax_error;
if( p->nTag>0 ) goto manifest_syntax_error;
if( p->zTicketUuid==0 ) goto manifest_syntax_error;
if( p->zUser==0 ) goto manifest_syntax_error;
if( !seenZ ) goto manifest_syntax_error;
p->type = CFTYPE_TICKET;
}else if( p->zWiki!=0 ){
if( p->rDate==0.0 ) goto manifest_syntax_error;
if( p->zRepoCksum!=0 ) goto manifest_syntax_error;
if( p->nCChild>0 ) goto manifest_syntax_error;
if( p->nTag>0 ) goto manifest_syntax_error;
if( p->zTicketUuid!=0 ) goto manifest_syntax_error;
if( p->zWikiTitle==0 ) goto manifest_syntax_error;
if( !seenZ ) goto manifest_syntax_error;
p->type = CFTYPE_WIKI;
}else if( p->nTag>0 ){
if( p->rDate<=0.0 ) goto manifest_syntax_error;
if( p->zRepoCksum!=0 ) goto manifest_syntax_error;
if( p->nParent>0 ) goto manifest_syntax_error;
if( p->nAttach>0 ) goto manifest_syntax_error;
if( p->nField>0 ) goto manifest_syntax_error;
if( p->zWiki ) goto manifest_syntax_error;
if( p->zWikiTitle ) goto manifest_syntax_error;
if( p->zTicketUuid ) goto manifest_syntax_error;
if( !seenZ ) goto manifest_syntax_error;
p->type = CFTYPE_CONTROL;
}else{
goto manifest_syntax_error;
}
md5sum_init();
return 1;
manifest_syntax_error:
/*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/
md5sum_init();
manifest_clear(p);
return 0;
}
/*
** Add a single entry to the mlink table. Also add the filename to
|
| ︙ | ︙ | |||
320 321 322 323 324 325 326 327 328 329 330 331 |
** is a cluster then remove all referenced elements from the
** unclustered table and create phantoms for any unknown elements.
*/
int manifest_crosslink(int rid, Blob *pContent){
int i;
Manifest m;
Stmt q;
if( manifest_parse(&m, pContent)==0 ){
return 0;
}
db_begin_transaction();
| > > | | | | | | | > | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | < < | < < < < | 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 |
** is a cluster then remove all referenced elements from the
** unclustered table and create phantoms for any unknown elements.
*/
int manifest_crosslink(int rid, Blob *pContent){
int i;
Manifest m;
Stmt q;
int parentid = 0;
if( manifest_parse(&m, pContent)==0 ){
return 0;
}
db_begin_transaction();
if( m.type==CFTYPE_MANIFEST ){
if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
for(i=0; i<m.nParent; i++){
int pid = uuid_to_rid(m.azParent[i], 1);
db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
"VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate);
if( i==0 ){
add_mlink(pid, 0, rid, &m);
parentid = pid;
}
}
db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid);
while( db_step(&q)==SQLITE_ROW ){
int cid = db_column_int(&q, 0);
add_mlink(rid, &m, cid, 0);
}
db_finalize(&q);
db_multi_exec(
"INSERT INTO event(type,mtime,objid,user,comment,"
" bgcolor,brbgcolor,euser,ecomment)"
"VALUES('ci',%.17g,%d,%Q,%Q,"
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype=1),"
"(SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype!=1),"
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
m.rDate, rid, m.zUser, m.zComment,
TAG_BGCOLOR, rid,
TAG_BGCOLOR, rid,
TAG_USER, rid,
TAG_COMMENT, rid
);
}
}
if( m.type==CFTYPE_CLUSTER ){
for(i=0; i<m.nCChild; i++){
int mid;
mid = uuid_to_rid(m.azCChild[i], 1);
if( mid>0 ){
db_multi_exec("DELETE FROM unclustered WHERE rid=%d", mid);
}
}
}
if( m.type==CFTYPE_CONTROL || m.type==CFTYPE_MANIFEST ){
for(i=0; i<m.nTag; i++){
int tid;
int type;
tid = uuid_to_rid(m.aTag[i].zUuid, 1);
switch( m.aTag[i].zName[0] ){
case '+': type = 1; break;
case '*': type = 2; break;
case '-': type = 0; break;
default:
fossil_fatal("unknown tag type in manifest: %s", m.aTag);
return 0;
}
tag_insert(&m.aTag[i].zName[1], type, m.aTag[i].zValue,
rid, m.rDate, tid);
}
if( parentid ){
tag_propagate_all(parentid);
}
}
if( m.type==CFTYPE_WIKI ){
char *zTag = mprintf("wiki-%s", m.zWikiTitle);
int tagid = tag_findid(zTag, 1);
int prior;
char *zComment;
tag_insert(zTag, 1, 0, rid, m.rDate, rid);
free(zTag);
prior = db_int(0,
"SELECT rid FROM tagxref"
" WHERE tagid=%d AND mtime<%.17g"
" ORDER BY mtime DESC",
tagid, m.rDate
);
if( prior ){
content_deltify(prior, rid, 0);
}
zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle);
db_multi_exec(
"INSERT INTO event(type,mtime,objid,user,comment,"
" bgcolor,brbgcolor,euser,ecomment)"
"VALUES('w',%.17g,%d,%Q,%Q,"
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype=1),"
"(SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype!=1),"
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
m.rDate, rid, m.zUser, zComment,
TAG_BGCOLOR, rid,
TAG_BGCOLOR, rid,
TAG_USER, rid,
TAG_COMMENT, rid
);
free(zComment);
}
if( m.type==CFTYPE_TICKET ){
char *zTag;
char *zComment;
ticket_insert(&m, 1, 1);
zTag = mprintf("tkt-%s", m.zTicketUuid);
tag_insert(zTag, 1, 0, rid, m.rDate, rid);
free(zTag);
zComment = mprintf("Changes to ticket [%.10s]", m.zTicketUuid);
db_multi_exec(
"INSERT INTO event(type,mtime,objid,user,comment)"
"VALUES('t',%.17g,%d,%Q,%Q)",
m.rDate, rid, m.zUser, zComment
);
free(zComment);
}
db_end_transaction(0);
manifest_clear(&m);
return 1;
}
|
Changes to src/merge.c.
| ︙ | ︙ | |||
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
** names might have been changed in the branch being merged in.
*/
void merge_cmd(void){
int vid; /* Current version */
int mid; /* Version we are merging against */
int pid; /* The pivot version - most recent common ancestor */
Stmt q;
if( g.argc!=3 ){
usage("VERSION");
}
db_must_be_within_tree();
vid = db_lget_int("checkout", 0);
if( vid==0 ){
fossil_panic("nothing is checked out");
| > > | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
** names might have been changed in the branch being merged in.
*/
void merge_cmd(void){
int vid; /* Current version */
int mid; /* Version we are merging against */
int pid; /* The pivot version - most recent common ancestor */
Stmt q;
int detailFlag;
detailFlag = find_option("detail",0,0)!=0;
if( g.argc!=3 ){
usage("VERSION");
}
db_must_be_within_tree();
vid = db_lget_int("checkout", 0);
if( vid==0 ){
fossil_panic("nothing is checked out");
|
| ︙ | ︙ | |||
213 214 215 216 217 218 219 | } db_finalize(&q); /* ** Do a three-way merge on files that have changes pid->mid and pid->vid */ db_prepare(&q, | | > > > > > | > < | > | > > > > > > > | 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
}
db_finalize(&q);
/*
** Do a three-way merge on files that have changes pid->mid and pid->vid
*/
db_prepare(&q,
"SELECT ridm, idv, ridp, ridv FROM fv"
" WHERE idp>0 AND idv>0 AND idm>0"
" AND ridm!=ridp AND (ridv!=ridp OR chnged)"
);
while( db_step(&q)==SQLITE_ROW ){
int ridm = db_column_int(&q, 0);
int idv = db_column_int(&q, 1);
int ridp = db_column_int(&q, 2);
int ridv = db_column_int(&q, 3);
int rc;
char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv);
char *zFullPath;
Blob m, p, v, r;
/* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */
if( detailFlag ){
printf("MERGE %s (pivot=%d v1=%d v2=%d)\n", zName, ridp, ridm, ridv);
}else{
printf("MERGE %s\n", zName);
}
undo_save(zName);
zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
content_get(ridp, &p);
content_get(ridm, &m);
blob_zero(&v);
blob_read_from_file(&v, zFullPath);
rc = blob_merge(&p, &m, &v, &r);
if( rc>=0 ){
blob_write_to_file(&r, zFullPath);
if( rc>0 ){
printf("***** %d merge conflicts in %s\n", rc, zName);
}
}else{
printf("***** Cannot merge binary file %s\n", zName);
}
free(zName);
blob_reset(&p);
blob_reset(&m);
blob_reset(&v);
blob_reset(&r);
db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(%d,%d)",
idv,ridm);
}
|
| ︙ | ︙ |
Changes to src/merge3.c.
| ︙ | ︙ | |||
23 24 25 26 27 28 29 | ** ** This module implements a 3-way merge */ #include "config.h" #include "merge3.h" #if 0 | < < < < < | < < < < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < | < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < | < < > > | < | < < < | < < | < < < < < < < | < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < < < < < < < | < < < < < < < < < < < < | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < < < < < < < < < < < | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < > > > > > > > > > | > > > | > | > > > | > > > > > > > > < | | | | < > | < < | > | < < < < | > | > > | > | > | | | | > > > > | > > > > > > > > > > > > > > > > | > | > > | > | < < > > > | < > > | | < < > > > > > | > | < < > > | < < | | > > > > > > > > > > > | > > | > | < < > | > | > > | > > > > > | > > > > > > > | > | > | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
**
** This module implements a 3-way merge
*/
#include "config.h"
#include "merge3.h"
#if 0
#define DEBUG(X) X
#else
#define DEBUG(X)
#endif
/*
** Opcodes
*/
#define CPY 0
#define DEL 1
#define INS 2
#define END 3
#define UNK 4
/*
** Compare a single line of text from pV1 and pV2. If the lines
** are the same, return true. Return false if they are different.
**
** The cursor on both pV1 and pV2 is unchanged.
*/
static int sameLine(Blob *pV1, Blob *pV2){
char *z1, *z2;
int i;
z1 = &blob_buffer(pV1)[blob_tell(pV1)];
z2 = &blob_buffer(pV2)[blob_tell(pV2)];
for(i=0; z1[i]!='\n' && z1[i]==z2[i]; i++){}
return z2[i]=='\n' || (z2[i]=='\r' && z2[i+1]=='\n')
|| (z1[i]=='\r' && z2[i]=='\n' && z1[i+1]=='\n');
}
/*
** Do a three-way merge. Initialize pOut to contain the result.
**
** The merge is an edit against pV2. Both pV1 and pV2 have a
** common origin at pPivot. Apply the changes of pPivot ==> pV1
** to pV2.
**
** The return is 0 upon complete success. If any input file is binary,
** -1 is returned and pOut is unmodified. If there are merge
** conflicts, the merge proceeds as best as it can and the number
** of conflicts is returns
*/
int blob_merge(Blob *pPivot, Blob *pV1, Blob *pV2, Blob *pOut){
int *aC1; /* Changes from pPivot to pV1 */
int *aC2; /* Changes from pPivot to pV2 */
int i1, i2;
int op1, op2;
int limit1, limit2;
int inConflict = 0;
int nConflict = 0;
static const char zBegin[] = ">>>>>>>> BEGIN MERGE CONFLICT <<<<<<<<\n";
static const char zEnd[] = ">>>>>>>>> END MERGE CONFLICT <<<<<<<<<\n";
aC1 = text_diff(pPivot, pV1, 0, 0);
aC2 = text_diff(pPivot, pV2, 0, 0);
if( aC2==0 || aC2==0 ){
free(aC1);
free(aC2);
return -1;
}
blob_zero(pOut);
blob_rewind(pV1);
blob_rewind(pV2);
blob_rewind(pPivot);
for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){}
limit1 = i1;
for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){}
limit2 = i2;
DEBUG(
for(i1=0; i1<limit1; i1+=3){
printf("c1: %4d %4d %4d\n", aC1[i1], aC1[i1+1], aC1[i1+2]);
}
for(i2=0; i2<limit2; i2+=3){
printf("c2: %4d %4d %4d\n", aC2[i2], aC2[i2+1], aC2[i2+2]);
}
)
op1 = op2 = UNK;
i1 = i2 = 0;
while( i1<limit1 && aC1[i1]==0 ){ i1++; }
while( i2<limit2 && aC2[i2]==0 ){ i2++; }
while(1){
if( op1==UNK ){
if( i1>=limit1 ){
op1 = END;
DEBUG( printf("get op1=END\n"); )
}else{
op1 = i1 % 3;
aC1[i1]--;
DEBUG( printf("get op1=%d from %d (%d->%d)\n",
op1,i1,aC1[i1]+1,aC1[i1]); )
while( i1<limit1 && aC1[i1]==0 ){ i1++; }
}
}
if( op2==UNK ){
if( i2>=limit2 ){
op2 = END;
DEBUG( printf("get op2=END\n"); )
}else{
op2 = i2 % 3;
aC2[i2]--;
DEBUG( printf("get op2=%d from %d (%d->%d)\n",
op2,i2,aC2[i2]+1,aC2[i2]); )
while( i2<limit2 && aC2[i2]==0 ){ i2++; }
}
}
DEBUG( printf("op1=%d op2=%d\n", op1, op2); )
if( op1==END ){
if( op2==INS ){
blob_copy_lines(pOut, pV2, 1);
op2 = UNK;
}else{
break;
}
}else if( op2==END ){
if( op1==INS ){
blob_copy_lines(pOut, pV1, 1);
op1 = UNK;
}else{
break;
}
}else if( op1==INS && op2==INS ){
if( !inConflict && sameLine(pV1, pV2) ){
blob_copy_lines(pOut, pV2, 1);
blob_copy_lines(0, pV1, 0);
op1 = UNK;
op2 = UNK;
}else{
if( !inConflict ){
inConflict = 1;
nConflict++;
blob_appendf(pOut, zBegin);
}
blob_copy_lines(pOut, pV1, 1);
op1 = UNK;
}
}else if( op1==INS ){
blob_copy_lines(pOut, pV1, 1);
op1 = UNK;
}else if( op2==INS ){
blob_copy_lines(pOut, pV2, 1);
op2 = UNK;
}else{
if( inConflict ){
inConflict = 0;
blob_appendf(pOut, zEnd);
}
if( op1==DEL || op2==DEL ){
blob_copy_lines(0, pPivot, 1);
if( op2==CPY ){
blob_copy_lines(0, pV2, 1);
}
if( op1==CPY ){
blob_copy_lines(0, pV1, 1);
}
}else{
assert( op1==CPY && op2==CPY );
blob_copy_lines(pOut, pPivot, 1);
blob_copy_lines(0, pV1, 1);
blob_copy_lines(0, pV2, 1);
}
op1 = UNK;
op2 = UNK;
}
}
if( inConflict ){
blob_appendf(pOut, zEnd);
}
free(aC1);
free(aC2);
return nConflict;
}
/*
** COMMAND: test-3-way-merge
**
** Combine change in going from PIVOT->VERSION1 with the change going
** from PIVOT->VERSION2 and write the combined changes into MERGED.
|
| ︙ | ︙ | |||
642 643 644 645 646 647 648 |
exit(1);
}
blob_reset(&pivot);
blob_reset(&v1);
blob_reset(&v2);
blob_reset(&merged);
}
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 236 237 238 239 240 241 242 |
exit(1);
}
blob_reset(&pivot);
blob_reset(&v1);
blob_reset(&v2);
blob_reset(&merged);
}
|
Changes to src/name.c.
| ︙ | ︙ | |||
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#include "name.h"
#include <assert.h>
/*
** This routine takes a user-entered UUID which might be in mixed
** case and might only be a prefix of the full UUID and converts it
** into the full-length UUID in canonical form.
**
** Return the number of errors.
*/
int name_to_uuid(Blob *pName, int iErrPriority){
int rc;
int sz;
sz = blob_size(pName);
if( sz>UUID_SIZE || sz<4 || !validate16(blob_buffer(pName), sz) ){
| > > > > > > > > > > > > > > > > > > > > > | > | > > > > > | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
#include "name.h"
#include <assert.h>
/*
** This routine takes a user-entered UUID which might be in mixed
** case and might only be a prefix of the full UUID and converts it
** into the full-length UUID in canonical form.
**
** If the input is not a UUID or a UUID prefix, then try to resolve
** the name as a tag.
**
** Return the number of errors.
*/
int name_to_uuid(Blob *pName, int iErrPriority){
int rc;
int sz;
sz = blob_size(pName);
if( sz>UUID_SIZE || sz<4 || !validate16(blob_buffer(pName), sz) ){
Stmt q;
Blob uuid;
db_prepare(&q,
"SELECT (SELECT uuid FROM blob WHERE rid=objid)"
" FROM tagxref JOIN event ON rid=objid"
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%B)"
" AND tagtype>0"
" AND value IS NULL"
" ORDER BY event.mtime DESC",
pName
);
blob_zero(&uuid);
if( db_step(&q)==SQLITE_ROW ){
db_column_blob(&q, 0, &uuid);
}
db_finalize(&q);
if( blob_size(&uuid)==0 ){
fossil_error(iErrPriority, "not a valid object name: %b", pName);
blob_reset(&uuid);
return 1;
}else{
blob_reset(pName);
*pName = uuid;
return 0;
}
}
blob_materialize(pName);
canonical16(blob_buffer(pName), sz);
if( sz==UUID_SIZE ){
rc = db_int(1, "SELECT 0 FROM blob WHERE uuid=%B", pName);
if( rc ){
fossil_error(iErrPriority, "unknown object: %b", pName);
|
| ︙ | ︙ | |||
70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
zOrig[sz-1]--;
fossil_error(iErrPriority, "non-unique name prefix: %s", zOrig);
rc = 1;
}else{
rc = 0;
}
}
}
return rc;
}
/*
** COMMAND: test-name-to-uuid
**
| > > | 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
zOrig[sz-1]--;
fossil_error(iErrPriority, "non-unique name prefix: %s", zOrig);
rc = 1;
}else{
rc = 0;
}
}
}else{
rc = 0;
}
return rc;
}
/*
** COMMAND: test-name-to-uuid
**
|
| ︙ | ︙ | |||
118 119 120 121 122 123 124 |
rid = atoi(zName);
if( db_exists("SELECT 1 FROM blob WHERE rid=%d", rid) ){
return rid;
}
}
blob_init(&name, zName, -1);
if( name_to_uuid(&name, 1) ){
| | | 147 148 149 150 151 152 153 154 155 156 157 158 159 |
rid = atoi(zName);
if( db_exists("SELECT 1 FROM blob WHERE rid=%d", rid) ){
return rid;
}
}
blob_init(&name, zName, -1);
if( name_to_uuid(&name, 1) ){
fossil_fatal("%s", g.zErrMsg);
}
rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &name);
blob_reset(&name);
return rid;
}
|
Changes to src/pivot.c.
| ︙ | ︙ | |||
80 81 82 83 84 85 86 |
/*
** Find the most recent common ancestor of the primary and one of
** the secondaries. Return its rid. Return 0 if no common ancestor
** can be found.
*/
int pivot_find(void){
Stmt q1, q2, u1, i1;
| | | 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
/*
** Find the most recent common ancestor of the primary and one of
** the secondaries. Return its rid. Return 0 if no common ancestor
** can be found.
*/
int pivot_find(void){
Stmt q1, q2, u1, i1;
int rid = 0;
/* aqueue must contain at least one primary and one other. Otherwise
** we abort early
*/
if( db_int(0, "SELECT count(distinct src) FROM aqueue")<2 ){
fossil_panic("lack both primary and secondary files");
}
|
| ︙ | ︙ |
Changes to src/printf.c.
| ︙ | ︙ | |||
47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
#define etSQLESCAPE2 14 /* Strings with '\'' doubled and enclosed in '',
NULL pointers replaced by SQL NULL. %Q */
#define etPOINTER 15 /* The %p conversion */
#define etHTMLIZE 16 /* Make text safe for HTML */
#define etHTTPIZE 17 /* Make text safe for HTTP. "/" encoded as %2f */
#define etURLIZE 18 /* Make text safe for HTTP. "/" not encoded */
#define etFOSSILIZE 19 /* The fossil header encoding format. */
/*
** An "etByte" is an 8-bit unsigned value.
*/
typedef unsigned char etByte;
| > > > | 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
#define etSQLESCAPE2 14 /* Strings with '\'' doubled and enclosed in '',
NULL pointers replaced by SQL NULL. %Q */
#define etPOINTER 15 /* The %p conversion */
#define etHTMLIZE 16 /* Make text safe for HTML */
#define etHTTPIZE 17 /* Make text safe for HTTP. "/" encoded as %2f */
#define etURLIZE 18 /* Make text safe for HTTP. "/" not encoded */
#define etFOSSILIZE 19 /* The fossil header encoding format. */
#define etPATH 20 /* Path type */
#define etWIKISTR 21 /* Wiki text rendered from a char* */
#define etWIKIBLOB 22 /* Wiki text rendered from a Blob* */
/*
** An "etByte" is an 8-bit unsigned value.
*/
typedef unsigned char etByte;
|
| ︙ | ︙ | |||
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
{ 's', 0, 4, etSTRING, 0, 0 },
{ 'g', 0, 1, etGENERIC, 30, 0 },
{ 'z', 0, 6, etDYNSTRING, 0, 0 },
{ 'q', 0, 4, etSQLESCAPE, 0, 0 },
{ 'Q', 0, 4, etSQLESCAPE2, 0, 0 },
{ 'b', 0, 2, etBLOB, 0, 0 },
{ 'B', 0, 2, etBLOBSQL, 0, 0 },
{ 'h', 0, 4, etHTMLIZE, 0, 0 },
{ 't', 0, 4, etHTTPIZE, 0, 0 }, /* "/" -> "%2F" */
{ 'T', 0, 4, etURLIZE, 0, 0 }, /* "/" unchanged */
{ 'F', 0, 4, etFOSSILIZE, 0, 0 },
{ 'c', 0, 0, etCHARX, 0, 0 },
{ 'o', 8, 0, etRADIX, 0, 2 },
{ 'u', 10, 0, etRADIX, 0, 0 },
{ 'x', 16, 0, etRADIX, 16, 1 },
{ 'X', 16, 0, etRADIX, 0, 4 },
{ 'f', 0, 1, etFLOAT, 0, 0 },
{ 'e', 0, 1, etEXP, 30, 0 },
{ 'E', 0, 1, etEXP, 14, 0 },
{ 'G', 0, 1, etGENERIC, 14, 0 },
{ 'i', 10, 1, etRADIX, 0, 0 },
{ 'n', 0, 0, etSIZE, 0, 0 },
{ '%', 0, 0, etPERCENT, 0, 0 },
{ 'p', 16, 0, etPOINTER, 0, 1 },
};
#define etNINFO (sizeof(fmtinfo)/sizeof(fmtinfo[0]))
/*
** "*val" is a double such that 0.1 <= *val < 10.0
** Return the ascii code for the leading digit of *val, then
** multiply "*val" by 10.0 to renormalize.
| > > > | 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
{ 's', 0, 4, etSTRING, 0, 0 },
{ 'g', 0, 1, etGENERIC, 30, 0 },
{ 'z', 0, 6, etDYNSTRING, 0, 0 },
{ 'q', 0, 4, etSQLESCAPE, 0, 0 },
{ 'Q', 0, 4, etSQLESCAPE2, 0, 0 },
{ 'b', 0, 2, etBLOB, 0, 0 },
{ 'B', 0, 2, etBLOBSQL, 0, 0 },
{ 'w', 0, 2, etWIKISTR, 0, 0 },
{ 'W', 0, 2, etWIKIBLOB, 0, 0 },
{ 'h', 0, 4, etHTMLIZE, 0, 0 },
{ 't', 0, 4, etHTTPIZE, 0, 0 }, /* "/" -> "%2F" */
{ 'T', 0, 4, etURLIZE, 0, 0 }, /* "/" unchanged */
{ 'F', 0, 4, etFOSSILIZE, 0, 0 },
{ 'c', 0, 0, etCHARX, 0, 0 },
{ 'o', 8, 0, etRADIX, 0, 2 },
{ 'u', 10, 0, etRADIX, 0, 0 },
{ 'x', 16, 0, etRADIX, 16, 1 },
{ 'X', 16, 0, etRADIX, 0, 4 },
{ 'f', 0, 1, etFLOAT, 0, 0 },
{ 'e', 0, 1, etEXP, 30, 0 },
{ 'E', 0, 1, etEXP, 14, 0 },
{ 'G', 0, 1, etGENERIC, 14, 0 },
{ 'i', 10, 1, etRADIX, 0, 0 },
{ 'n', 0, 0, etSIZE, 0, 0 },
{ '%', 0, 0, etPERCENT, 0, 0 },
{ 'p', 16, 0, etPOINTER, 0, 1 },
{ '/', 0, 0, etPATH, 0, 0 },
};
#define etNINFO (sizeof(fmtinfo)/sizeof(fmtinfo[0]))
/*
** "*val" is a double such that 0.1 <= *val < 10.0
** Return the ascii code for the leading digit of *val, then
** multiply "*val" by 10.0 to renormalize.
|
| ︙ | ︙ | |||
167 168 169 170 171 172 173 | ** the function "func". Returns -1 on a error. ** ** Note that the order in which automatic variables are declared below ** seems to make a big difference in determining how fast this beast ** will run. */ int vxprintf( | < | | 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
** the function "func". Returns -1 on a error.
**
** Note that the order in which automatic variables are declared below
** seems to make a big difference in determining how fast this beast
** will run.
*/
int vxprintf(
Blob *pBlob, /* Append output to this blob */
const char *fmt, /* Format string */
va_list ap /* arguments */
){
int c; /* Next character in the format string */
char *bufpt; /* Pointer to the conversion buffer */
int precision; /* Precision of the current field */
int length; /* Length of the field */
|
| ︙ | ︙ | |||
206 207 208 209 210 211 212 | int exp, e2; /* exponent of real numbers */ double rounder; /* Used for rounding floating point values */ etByte flag_dp; /* True if decimal point should be shown */ etByte flag_rtz; /* True if trailing zeros should be removed */ etByte flag_exp; /* True to force display of the exponent */ int nsd; /* Number of significant digits returned */ | < | | | 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
int exp, e2; /* exponent of real numbers */
double rounder; /* Used for rounding floating point values */
etByte flag_dp; /* True if decimal point should be shown */
etByte flag_rtz; /* True if trailing zeros should be removed */
etByte flag_exp; /* True to force display of the exponent */
int nsd; /* Number of significant digits returned */
count = length = 0;
bufpt = 0;
for(; (c=(*fmt))!=0; ++fmt){
if( c!='%' ){
int amt;
bufpt = (char *)fmt;
amt = 1;
while( (c=(*++fmt))!='%' && c!=0 ) amt++;
blob_append(pBlob,bufpt,amt);
count += amt;
if( c==0 ) break;
}
if( (c=(*++fmt))==0 ){
errorflag = 1;
blob_append(pBlob,"%",1);
count++;
break;
}
/* Find out what flags are present */
flag_leftjustify = flag_plussign = flag_blanksign =
flag_alternateform = flag_altform2 = flag_zeropad = 0;
done = 0;
|
| ︙ | ︙ | |||
539 540 541 542 543 544 545 546 547 548 549 550 551 552 |
for(idx=1; idx<precision; idx++) buf[idx] = c;
length = precision;
}else{
length =1;
}
bufpt = buf;
break;
case etSTRING:
case etDYNSTRING:
bufpt = va_arg(ap,char*);
if( bufpt==0 ){
bufpt = "";
}else if( xtype==etDYNSTRING ){
zExtra = bufpt;
| > > > > > > > > > > > > > > > > | 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 |
for(idx=1; idx<precision; idx++) buf[idx] = c;
length = precision;
}else{
length =1;
}
bufpt = buf;
break;
case etPATH: {
int i;
char *e = va_arg(ap,char*);
if( e==0 ){e="";}
length = strlen(e);
zExtra = bufpt = malloc(length+1);
for( i=0; i<length; i++ ){
if( e[i]=='\\' ){
bufpt[i]='/';
}else{
bufpt[i]=e[i];
}
}
bufpt[length]='\0';
break;
}
case etSTRING:
case etDYNSTRING:
bufpt = va_arg(ap,char*);
if( bufpt==0 ){
bufpt = "";
}else if( xtype==etDYNSTRING ){
zExtra = bufpt;
|
| ︙ | ︙ | |||
638 639 640 641 642 643 644 645 646 647 648 649 650 |
case etFOSSILIZE: {
char *zMem = va_arg(ap,char*);
if( zMem==0 ) zMem = "";
zExtra = bufpt = fossilize(zMem, -1);
length = strlen(bufpt);
if( precision>=0 && precision<length ) length = precision;
break;
}
case etERROR:
buf[0] = '%';
buf[1] = c;
errorflag = 0;
idx = 1+(c!=0);
| > > > > > > > > > > > > > > > | | | | | | | 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 |
case etFOSSILIZE: {
char *zMem = va_arg(ap,char*);
if( zMem==0 ) zMem = "";
zExtra = bufpt = fossilize(zMem, -1);
length = strlen(bufpt);
if( precision>=0 && precision<length ) length = precision;
break;
}
case etWIKISTR: {
char *zWiki = va_arg(ap, char*);
Blob wiki;
blob_init(&wiki, zWiki, -1);
wiki_convert(&wiki, pBlob, WIKI_INLINE);
blob_reset(&wiki);
length = width = 0;
break;
}
case etWIKIBLOB: {
Blob *pWiki = va_arg(ap, Blob*);
wiki_convert(pWiki, pBlob, WIKI_INLINE);
length = width = 0;
break;
}
case etERROR:
buf[0] = '%';
buf[1] = c;
errorflag = 0;
idx = 1+(c!=0);
blob_append(pBlob,"%",idx);
count += idx;
if( c==0 ) fmt--;
break;
}/* End switch over the format type */
/*
** The text of the conversion is pointed to by "bufpt" and is
** "length" characters long. The field width is "width". Do
** the output.
*/
if( !flag_leftjustify ){
register int nspace;
nspace = width-length;
if( nspace>0 ){
count += nspace;
while( nspace>=etSPACESIZE ){
blob_append(pBlob,spaces,etSPACESIZE);
nspace -= etSPACESIZE;
}
if( nspace>0 ) blob_append(pBlob,spaces,nspace);
}
}
if( length>0 ){
blob_append(pBlob,bufpt,length);
count += length;
}
if( flag_leftjustify ){
register int nspace;
nspace = width-length;
if( nspace>0 ){
count += nspace;
while( nspace>=etSPACESIZE ){
blob_append(pBlob,spaces,etSPACESIZE);
nspace -= etSPACESIZE;
}
if( nspace>0 ) blob_append(pBlob,spaces,nspace);
}
}
if( zExtra ){
free(zExtra);
}
}/* End for loop over the format string */
return errorflag ? -1 : count;
|
| ︙ | ︙ |
Changes to src/rebuild.c.
| ︙ | ︙ | |||
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
*******************************************************************************
**
** This file contains code used to rebuild the database.
*/
#include "config.h"
#include "rebuild.h"
#include <assert.h>
/*
** Core function to rebuild the infomration in the derived tables of a
** fossil repository from the blobs. This function is shared between
** 'rebuild_database' ('rebuild') and 'reconstruct_cmd'
** ('reconstruct'), both of which have to regenerate this information
** from scratch.
*/
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < | > | < < | > > > > > | > > > > > > > > > > > > > > > | | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
*******************************************************************************
**
** This file contains code used to rebuild the database.
*/
#include "config.h"
#include "rebuild.h"
#include <assert.h>
/*
** Schema changes
*/
static const char zSchemaUpdates[] =
@ -- Index on the delta table
@ --
@ CREATE INDEX IF NOT EXISTS delta_i1 ON delta(srcid);
@
@ -- Artifacts that should not be processed are identified in the
@ -- "shun" table. Artifacts that are control-file forgeries or
@ -- spam can be shunned in order to prevent them from contaminating
@ -- the repository.
@ --
@ CREATE TABLE IF NOT EXISTS shun(uuid UNIQUE);
@
@ -- An entry in this table describes a database query that generates a
@ -- table of tickets.
@ --
@ CREATE TABLE IF NOT EXISTS reportfmt(
@ rn integer primary key, -- Report number
@ owner text, -- Owner of this report format (not used)
@ title text, -- Title of this report
@ cols text, -- A color-key specification
@ sqlcode text -- An SQL SELECT statement for this report
@ );
;
/*
** Core function to rebuild the infomration in the derived tables of a
** fossil repository from the blobs. This function is shared between
** 'rebuild_database' ('rebuild') and 'reconstruct_cmd'
** ('reconstruct'), both of which have to regenerate this information
** from scratch.
**
** If the randomize parameter is true, then the BLOBs are deliberately
** extracted in a random order. This feature is used to test the
** ability of fossil to accept records in any order and still
** construct a sane repository.
*/
int rebuild_db(int randomize, int ttyOutput){
Stmt s;
int errCnt = 0;
char *zTable;
int cnt = 0;
db_multi_exec(zSchemaUpdates);
for(;;){
zTable = db_text(0,
"SELECT name FROM sqlite_master"
" WHERE type='table'"
" AND name NOT IN ('blob','delta','rcvfrom','user','config','shun')");
if( zTable==0 ) break;
db_multi_exec("DROP TABLE %Q", zTable);
free(zTable);
}
db_multi_exec(zRepositorySchema2);
ticket_create_table(0);
db_multi_exec("INSERT INTO unclustered SELECT rid FROM blob");
db_multi_exec(
"DELETE FROM unclustered"
" WHERE rid IN (SELECT rid FROM shun JOIN blob USING(uuid))"
);
db_multi_exec(
"DELETE FROM config WHERE name IN ('remote-code', 'remote-maxid')"
);
db_prepare(&s,
"SELECT rid, size FROM blob %s"
" WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)",
randomize ? "ORDER BY random()" : ""
);
while( db_step(&s)==SQLITE_ROW ){
int rid = db_column_int(&s, 0);
int size = db_column_int(&s, 1);
if( size>=0 ){
Blob content;
if( ttyOutput ){
cnt++;
printf("%d...\r", cnt);
fflush(stdout);
}
content_get(rid, &content);
manifest_crosslink(rid, &content);
blob_reset(&content);
}else{
db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid);
}
}
db_finalize(&s);
if( ttyOutput ){
printf("\n");
}
return errCnt;
}
/*
** COMMAND: rebuild
**
** Usage: %fossil rebuild REPOSITORY
**
** Reconstruct the named repository database from the core
** records. Run this command after updating the fossil
** executable in a way that changes the database schema.
*/
void rebuild_database(void){
int forceFlag;
int randomizeFlag;
int errCnt;
forceFlag = find_option("force","f",0)!=0;
randomizeFlag = find_option("randomize", 0, 0)!=0;
if( g.argc!=3 ){
usage("REPOSITORY-FILENAME");
}
db_open_repository(g.argv[2]);
db_begin_transaction();
errCnt = rebuild_db(randomizeFlag, 1);
if( errCnt && !forceFlag ){
printf("%d errors. Rolling back changes. Use --force to force a commit.\n",
errCnt);
db_end_transaction(1);
}else{
db_end_transaction(0);
}
}
|
Added src/report.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 |
/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** Code to generate the bug report listings
*/
#include "config.h"
#include "report.h"
#include <assert.h>
/* Forward references to static routines */
static void report_format_hints(void);
/*
** WEBPAGE: /reportlist
*/
void view_list(void){
Stmt q;
login_check_credentials();
if( !g.okRdTkt ){ login_needed(); return; }
style_header("Available Report Formats");
db_prepare(&q, "SELECT rn, title, owner FROM reportfmt ORDER BY title");
@ <p>Choose a report format from the following list:</p>
@ <ol>
while( db_step(&q)==SQLITE_ROW ){
int rn = db_column_int(&q, 0);
const char *zTitle = db_column_text(&q, 1);
const char *zOwner = db_column_text(&q, 2);
@ <li><a href="rptview?rn=%d(rn)"
@ rel="nofollow">%h(zTitle)</a>
if( g.okWrite && zOwner && zOwner[0] ){
@ (by <i>%h(zOwner)</i>)
}
if( g.okWrTkt ){
@ [<a href="rptedit?rn=%d(rn)&copy=1" rel="nofollow">copy</a>]
}
if( g.okAdmin || (g.okWrTkt && zOwner && strcmp(g.zLogin,zOwner)==0) ){
@ [<a href="rptedit?rn=%d(rn)" rel="nofollow">edit</a>]
}
@ [<a href="rptsql?rn=%d(rn)" rel="nofollow">sql</a>]
@ </li>
}
if( g.okWrTkt ){
@ <li><a href="rptnew">Create a new report format</a></li>
}
@ </ol>
common_footer();
}
/*
** Remove whitespace from both ends of a string.
*/
char *trim_string(const char *zOrig){
int i;
while( isspace(*zOrig) ){ zOrig++; }
i = strlen(zOrig);
while( i>0 && isspace(zOrig[i-1]) ){ i--; }
return mprintf("%.*s", i, zOrig);
}
/*
** Extract a numeric (integer) value from a string.
*/
char *extract_integer(const char *zOrig){
if( zOrig == NULL || zOrig[0] == 0 ) return "";
while( *zOrig && !isdigit(*zOrig) ){ zOrig++; }
if( *zOrig ){
/* we have a digit. atoi() will get as much of the number as it
** can. We'll run it through mprintf() to get a string. Not
** an efficient way to do it, but effective.
*/
return mprintf("%d", atoi(zOrig));
}
return "";
}
/*
** Remove blank lines from the beginning of a string and
** all whitespace from the end. Removes whitespace preceeding a NL,
** which also converts any CRNL sequence into a single NL.
*/
char *remove_blank_lines(const char *zOrig){
int i, j, n;
char *z;
for(i=j=0; isspace(zOrig[i]); i++){ if( zOrig[i]=='\n' ) j = i+1; }
n = strlen(&zOrig[j]);
while( n>0 && isspace(zOrig[j+n-1]) ){ n--; }
z = mprintf("%.*s", n, &zOrig[j]);
for(i=j=0; z[i]; i++){
if( z[i+1]=='\n' && z[i]!='\n' && isspace(z[i]) ){
z[j] = z[i];
while(isspace(z[j]) && z[j] != '\n' ){ j--; }
j++;
continue;
}
z[j++] = z[i];
}
z[j] = 0;
return z;
}
/*********************************************************************/
/*
** This is the SQLite authorizer callback used to make sure that the
** SQL statements entered by users do not try to do anything untoward.
** If anything suspicious is tried, set *(char**)pError to an error
** message obtained from malloc.
*/
static int report_query_authorizer(
void *pError,
int code,
const char *zArg1,
const char *zArg2,
const char *zArg3,
const char *zArg4
){
char *zError = *(char**)pError;
if( zError ){
/* We've already seen an error. No need to continue. */
return SQLITE_OK;
}
switch( code ){
case SQLITE_SELECT:
case SQLITE_FUNCTION: {
break;
}
case SQLITE_READ: {
static const char *azAllowed[] = {
"ticket",
"blob",
"filename",
"mlink",
"plink",
"event",
"tag",
"tagxref",
}
int i;
for(i=0; i<sizeof(azAllowed)/sizeof(azAllowed[0]); i++){
if( strcasecmp(zArg1, azAllowed[i])==0 ) break;
}
if( i>=sizeof(azAllowed)/sizeof(azAllowed[0]) ){
zError = mprintf("cannot access table %s", zArg1);
}
break;
}
default: {
zError = mprintf("only SELECT statements are allowed");
break;
}
}
return SQLITE_OK;
}
/*
** Check the given SQL to see if is a valid query that does not
** attempt to do anything dangerous. Return 0 on success and a
** pointer to an error message string (obtained from malloc) if
** there is a problem.
*/
char *verify_sql_statement(char *zSql){
int i;
char *zErr1 = 0;
char *zErr2 = 0;
char *zTail;
/* First make sure the SQL is a single query command by verifying that
** the first token is "SELECT" and that there are no unquoted semicolons.
*/
for(i=0; isspace(zSql[i]); i++){}
if( strncasecmp(&zSql[i],"select",6)!=0 ){
return mprintf("The SQL must be a SELECT statement");
}
for(i=0; zSql[i]; i++){
if( zSql[i]==';' ){
int bad;
int c = zSql[i+1];
zSql[i+1] = 0;
bad = sqlite3_complete(zSql);
zSql[i+1] = c;
if( bad ){
/* A complete statement basically means that an unquoted semi-colon
** was found. We don't actually check what's after that.
*/
return mprintf("Semi-colon detected! "
"Only a single SQL statement is allowed");
}
}
}
/* Compile the statement and check for illegal accesses or syntax errors. */
sqlite3_set_authorizer(g.db, report_query_authorizer, (void*)&zErr);
rc = sqlite3_prepare(g.db, zSql, -1, &pStmt, &zTail);
if( rc!=SQLITE_OK ){
free(zErr);
zErr = mprintf("Syntax error: %s", sqlite3_errmsg(g.db));
}
if( pStmt ){
sqlite3_finalize(pStmt);
}
sqlite3_set_authorizer(g.db, 0, 0);
return zErr;
}
/*
** WEBPAGE: /rptsql
*/
void view_see_sql(void){
int rn, rc;
char *zTitle;
char *zSQL;
char *zOwner;
char *zClrKey;
Stmt q;
login_check_credentials();
if( !g.okQuery ){
login_needed();
return;
}
rn = atoi(PD("rn","0"));
db_prepare(&q, "SELECT title, sqlcode, owner, cols "
"FROM reportfmt WHERE rn=%d",rn);
style_header("SQL For Report Format Number %d", rn);
if( db_step(&q)!=SQLITE_ROW ){
@ <p>Unknown report number: %d(rn)</p>
style_footer();
return;
}
zTitle = db_column_text(&q, 0);
zSQL = db_column_text(&q, 1);
zOwner = db_column_text(&q, 2);
zClrKey = db_column_text(&q, 3);
@ <table cellpadding=0 cellspacing=0 border=0>
@ <tr><td valign="top" align="right">Title:</td><td width=15></td>
@ <td colspan=3>%h(zTitle)</td></tr>
@ <tr><td valign="top" align="right">Owner:</td><td></td>
@ <td colspan=3>%h(zOwner)</td></tr>
@ <tr><td valign="top" align="right">SQL:</td><td></td>
@ <td valign="top"><pre>
@ %h(zSQL)
@ </pre></td>
@ <td width=15></td><td valign="top">
output_color_key(zClrKey, 0, "border=0 cellspacing=0 cellpadding=3");
@ </td>
@ </tr></table>
report_format_hints();
style_footer();
}
/*
** WEBPAGE: /rptnew
** WEBPAGE: /rptedit
*/
void view_edit(void){
int rn;
const char *zTitle;
const char *z;
const char *zOwner;
char *zClrKey;
char *zSQL;
char *zErr = 0;
login_check_credentials();
if( !g.okQuery ){
login_needed();
return;
}
view_add_functions(0);
rn = atoi(PD("rn","0"));
zTitle = P("t");
zOwner = PD("w",g.zLogin);
z = P("s");
zSQL = z ? trim_string(z) : 0;
zClrKey = trim_string(PD("k",""));
if( rn>0 && P("del2") ){
db_multi_exec("DELETE FROM reportfmt WHERE rn=%d", rn);
cgi_redirect("reportlist");
return;
}else if( rn>0 && P("del1") ){
zTitle = db_text(0, "SELECT title FROM reportfmt "
"WHERE rn=%d", rn);
if( zTitle==0 ) cgi_redirect("reportlist");
style_header("Are You Sure?");
@ <form action="rptedit" method="POST">
@ <p>You are about to delete all traces of the report
@ <strong>%h(zTitle)</strong> from
@ the database. This is an irreversible operation. All records
@ related to this report will be removed and cannot be recovered.</p>
@
@ <input type="hidden" name="rn" value="%d(rn)">
@ <input type="submit" name="del2" value="Delete The Report">
@ <input type="submit" name="can" value="Cancel">
@ </form>
style_footer();
return;
}else if( P("can") ){
/* user cancelled */
cgi_redirect("reportlist");
return;
}
if( zTitle && zSQL ){
if( zSQL[0]==0 ){
zErr = "Please supply an SQL query statement";
}else if( (zTitle = trim_string(zTitle))[0]==0 ){
zErr = "Please supply a title";
}else{
zErr = verify_sql_statement(zSQL);
}
if( zErr==0 ){
if( rn>0 ){
db_multi_exec("UPDATE reportfmt SET title=%Q, sqlcode=%Q,"
" owner=%Q, cols=%Q WHERE rn=%d",
zTitle, zSQL, zOwner, zClrKey, rn);
}else{
db_multi_exec("INSERT INTO reportfmt(title,sqlcode,owner,cols) "
"VALUES(%Q,%Q,%Q,%Q)",
zTitle, zSQL, zOwner, zClrKey);
rn = db_last_insert_rowid();
}
cgi_redirect(mprintf("rptview?rn=%d", rn));
return;
}
}else if( rn==0 ){
zTitle = "";
zSQL =
@ SELECT
@ CASE WHEN status IN ('new','active') THEN '#f2dcdc'
@ WHEN status='review' THEN '#e8e8bd'
@ WHEN status='fixed' THEN '#cfe8bd'
@ WHEN status='tested' THEN '#bde5d6'
@ WHEN status='defer' THEN '#cacae5'
@ ELSE '#c8c8c8' END AS 'bgcolor',
@ tn AS '#',
@ type AS 'Type',
@ status AS 'Status',
@ sdate(origtime) AS 'Created',
@ owner AS 'By',
@ subsystem AS 'Subsys',
@ sdate(changetime) AS 'Changed',
@ assignedto AS 'Assigned',
@ severity AS 'Svr',
@ priority AS 'Pri',
@ title AS 'Title'
@ FROM ticket
;
zClrKey =
@ #ffffff Key:
@ #f2dcdc Active
@ #e8e8e8 Review
@ #cfe8bd Fixed
@ #bde5d6 Tested
@ #cacae5 Deferred
@ #c8c8c8 Closed
;
}else{
db_prepare(&q, "SELECT title, sqlcode, owner, cols "
"FROM reportfmt WHERE rn=%d",rn);
if( db_step(&q)==SQLITE_ROW ){
zTitle = db_column_malloc(&q, 0);
zSQL = db_column_malloc(&q, 1);
zOwner = db_column_malloc(&q, 2);
zClrKey = db_column_malloc(&q, 3);
}
if( P("copy") ){
rn = 0;
zTitle = mprintf("Copy Of %s", zTitle);
zOwner = g.zLogin;
}
}
if( zOwner==0 ) zOwner = g.zLogin;
style_submenu_element("Cancel", "Cancel", "reportlist");
if( rn>0 ){
style_submenu_element("Delete", "Delete", "rptedit?rn=%d&del1=1", rn);
}
style_header(rn>0 ? "Edit Report Format":"Create New Report Format");
if( zErr ){
@ <blockquote><font color="#ff0000"><b>%h(zErr)</b></font></blockquote>
}
@ <form action="rptedit" method="POST">
@ <input type="hidden" name="rn" value="%d(rn)">
@ <p>Report Title:<br>
@ <input type="text" name="t" value="%h(zTitle)" size="60"></p>
@ <p>Enter a complete SQL query statement against the "TICKET" table:<br>
@ <textarea name="s" rows="20" cols="80">%h(zSQL)</textarea>
@ </p>
if( g.okAdmin ){
@ <p>Report owner:
@ <input type="text" name="w" size="20" value="%h(zOwner)">
@ </p>
} else {
@ <input type="hidden" name="w" value="%h(zOwner)">
}
@ <p>Enter an optional color key in the following box. (If blank, no
@ color key is displayed.) Each line contains the text for a single
@ entry in the key. The first token of each line is the background
@ color for that line.<br>
@ <textarea name="k" rows="8" cols="50">%h(zClrKey)</textarea>
@ </p>
if( !g.okAdmin && strcmp(zOwner,g.zLogin)!=0 ){
@ <p>This report format is owned by %h(zOwner). You are not allowed
@ to change it.</p>
@ </form>
report_format_hints();
style_footer();
return;
}
@ <input type="submit" value="Apply Changes">
if( rn>0 ){
@ <input type="submit" value="Delete This Report" name="del1">
}
@ </form>
report_format_hints();
style_footer();
}
/*
** Output a bunch of text that provides information about report
** formats
*/
static void report_format_hints(void){
char *zSchema;
zSchema = db_text(0,"SELECT sql FROM sqlite_master WHERE name='ticket'");
@ <hr><h3>TICKET Schema</h3>
@ <blockquote><pre>
@ %h(zSchema)
@ </pre></blockquote>
@ <h3>Notes</h3>
@ <ul>
@ <li><p>The SQL must consist of a single SELECT statement</p></li>
@
@ <li><p>If a column of the result set is named "#" then that column
@ is assumed to hold a ticket number. A hyperlink will be created from
@ that column to a detailed view of the ticket.</p></li>
@
@ <li><p>If a column of the result set is named "bgcolor" then the content
@ of that column determines the background color of the row.</p></li>
@
@ <li><p>The <b>user()</b> SQL function returns a string
@ which is the login of the current user.</p></li>
@
@ <li><p>The first column whose name begins with underscore ("_") and all
@ subsequent columns are shown on their own rows in the table. This might
@ be useful for displaying the description of tickets.
@ </p></li>
@
@ <li><p>The <b>aux()</b> SQL function takes a parameter name as an argument
@ and returns the value that the user enters in the resulting HTML form. A
@ second optional parameter provides a default value for the field.</p></li>
@
@ <li><p>The <b>option()</b> SQL function takes a parameter name
@ and a quoted SELECT statement as parameters. The query results are
@ presented as an HTML dropdown menu and the function returns
@ the currently selected value. Results may be a single value column or
@ two <b>value,description</b> columns. The first row is the default.</p></li>
@
@ <li><p>The <b>cgi()</b> SQL function takes a parameter name as an argument
@ and returns the value of a corresponding CGI query value. If the CGI
@ parameter doesn't exist, an optional second argument will be returned
@ instead.</p></li>
@
@ <li><p>If a column is wrapped by the <b>wiki()</b> SQL function, it will
@ be rendered as wiki formatted content.</p></li>
@
@ <li><p>If a column is wrapped by the <b>tkt()</b> SQL function, it will
@ be shown as a ticket id with a link to the appropriate page</p></li>
@
@ <li><p>If a column is wrapped by the <b>chng()</b> SQL function, it will
@ be shown as a baseline id with a link to the appropriate page.</p></li>
@
@ <li><p>The <b>search()</b> SQL function takes a keyword pattern and
@ a search text. The function returns an integer score which is
@ higher depending on how well the search went.</p></li>
@
@ <li><p>The query can join other tables in the database besides TICKET.
@ </p></li>
@ </ul>
@
@ <h3>Examples</h3>
@ <p>In this example, the first column in the result set is named
@ "bgcolor". The value of this column is not displayed. Instead, it
@ selects the background color of each row based on the TICKET.STATUS
@ field of the database. The color key at the right shows the various
@ color codes.</p>
@ <table align="right" style="margin: 0 5px;" border=1 cellspacing=0 width=125>
@ <tr bgcolor="#f2dcdc"><td align="center">new or active</td></tr>
@ <tr bgcolor="#e8e8bd"><td align="center">review</td></tr>
@ <tr bgcolor="#cfe8bd"><td align="center">fixed</td></tr>
@ <tr bgcolor="#bde5d6"><td align="center">tested</td></tr>
@ <tr bgcolor="#cacae5"><td align="center">defer</td></tr>
@ <tr bgcolor="#c8c8c8"><td align="center">closed</td></tr>
@ </table>
@ <blockquote><pre>
@ SELECT
@ CASE WHEN status IN ('new','active') THEN '#f2dcdc'
@ WHEN status='review' THEN '#e8e8bd'
@ WHEN status='fixed' THEN '#cfe8bd'
@ WHEN status='tested' THEN '#bde5d6'
@ WHEN status='defer' THEN '#cacae5'
@ ELSE '#c8c8c8' END as 'bgcolor',
@ tn AS '#',
@ type AS 'Type',
@ status AS 'Status',
@ sdate(origtime) AS 'Created',
@ owner AS 'By',
@ subsystem AS 'Subsys',
@ sdate(changetime) AS 'Changed',
@ assignedto AS 'Assigned',
@ severity AS 'Svr',
@ priority AS 'Pri',
@ title AS 'Title'
@ FROM ticket
@ </pre></blockquote>
@ <p>To base the background color on the TICKET.PRIORITY or
@ TICKET.SEVERITY fields, substitute the following code for the
@ first column of the query:</p>
@ <table align="right" style="margin: 0 5px;" border=1 cellspacing=0 width=125>
@ <tr bgcolor="#f2dcdc"><td align="center">1</td></tr>
@ <tr bgcolor="#e8e8bd"><td align="center">2</td></tr>
@ <tr bgcolor="#cfe8bd"><td align="center">3</td></tr>
@ <tr bgcolor="#cacae5"><td align="center">4</td></tr>
@ <tr bgcolor="#c8c8c8"><td align="center">5</td></tr>
@ </table>
@ <blockquote><pre>
@ SELECT
@ CASE priority WHEN 1 THEN '#f2dcdc'
@ WHEN 2 THEN '#e8e8bd'
@ WHEN 3 THEN '#cfe8bd'
@ WHEN 4 THEN '#cacae5'
@ ELSE '#c8c8c8' END as 'bgcolor',
@ ...
@ FROM ticket
@ </pre></blockquote>
#if 0
@ <p>You can, of course, substitute different colors if you choose.
@ Here is a palette of suggested background colors:</p>
@ <blockquote>
@ <table border=1 cellspacing=0 width=300>
@ <tr><td align="center" bgcolor="#ffbdbd">#ffbdbd</td>
@ <td align="center" bgcolor="#f2dcdc">#f2dcdc</td></tr>
@ <tr><td align="center" bgcolor="#ffffbd">#ffffbd</td>
@ <td align="center" bgcolor="#e8e8bd">#e8e8bd</td></tr>
@ <tr><td align="center" bgcolor="#c0ebc0">#c0ebc0</td>
@ <td align="center" bgcolor="#cfe8bd">#cfe8bd</td></tr>
@ <tr><td align="center" bgcolor="#c0c0f4">#c0c0f4</td>
@ <td align="center" bgcolor="#d6d6e8">#d6d6e8</td></tr>
@ <tr><td align="center" bgcolor="#d0b1ff">#d0b1ff</td>
@ <td align="center" bgcolor="#d2c0db">#d2c0db</td></tr>
@ <tr><td align="center" bgcolor="#bbbbbb">#bbbbbb</td>
@ <td align="center" bgcolor="#d0d0d0">#d0d0d0</td></tr>
@ </table>
@ </blockquote>
#endif
@ <p>To see the TICKET.DESCRIPTION and TICKET.REMARKS fields, include
@ them as the last two columns of the result set and given them names
@ that begin with an underscore. Like this:</p>
@ <blockquote><pre>
@ SELECT
@ tn AS '#',
@ type AS 'Type',
@ status AS 'Status',
@ sdate(origtime) AS 'Created',
@ owner AS 'By',
@ subsystem AS 'Subsys',
@ sdate(changetime) AS 'Changed',
@ assignedto AS 'Assigned',
@ severity AS 'Svr',
@ priority AS 'Pri',
@ title AS 'Title',
@ description AS '_Description', -- When the column name begins with '_'
@ remarks AS '_Remarks' -- the data is shown on a separate row.
@ FROM ticket
@ </pre></blockquote>
@
@ <p>Or, to see part of the description on the same row, use the
@ <b>wiki()</b> function with some string manipulation. Using the
@ <b>tkt()</b> function on the ticket number will also generate a linked
@ field, but without the extra <i>edit</i> column:
@ </p>
@ <blockquote><pre>
@ SELECT
@ tkt(tn) AS '',
@ title AS 'Title',
@ wiki(substr(description,0,80)) AS 'Description'
@ FROM ticket
@ </pre></blockquote>
@
}
/*********************************************************************/
static void output_report_field(const char *zData,int rn){
const char *zWkey = wiki_key();
const char *zTkey = tkt_key();
const char *zCkey = chng_key();
if( !strncmp(zData,zWkey,strlen(zWkey)) ){
output_formatted(&zData[strlen(zWkey)],0);
}else if( !strncmp(zData,zTkey,strlen(zTkey)) ){
output_ticket(atoi(&zData[strlen(zTkey)]),rn);
}else if( !strncmp(zData,zCkey,strlen(zCkey)) ){
output_chng(atoi(&zData[strlen(zCkey)]));
}else{
@ %h(zData)
}
}
static void column_header(int rn,const char *zCol, int nCol, int nSorted,
const char *zDirection, const char *zExtra
){
int set = (nCol==nSorted);
int desc = !strcmp(zDirection,"DESC");
/*
** Clicking same column header 3 times in a row resets any sorting.
** Note that we link to rptview, which means embedded reports will get
** sent to the actual report view page as soon as a user tries to do
** any sorting. I don't see that as a Bad Thing.
*/
if(set && desc){
@ <th bgcolor="%s(BG1)" class="bkgnd1">
@ <a href="rptview?rn=%d(rn)%s(zExtra)">%h(zCol)</a></th>
}else{
if(set){
@ <th bgcolor="%s(BG1)" class="bkgnd1"><a
}else{
@ <th><a
}
@ href="rptview?rn=%d(rn)&order_by=%d(nCol)&\
@ order_dir=%s(desc?"ASC":"DESC")\
@ %s(zExtra)">%h(zCol)</a></th>
}
}
/*********************************************************************/
struct GenerateHTML {
int rn;
int nCount;
};
/*
** The callback function for db_query
*/
static int generate_html(
void* pUser, /* Pointer to output state */
int nArg, /* Number of columns in this result row */
char **azArg, /* Text of data in all columns */
char **azName /* Names of the columns */
){
struct GenerateHTML* pState = (struct GenerateHTML*)pUser;
int i;
int tn; /* Ticket number. (value of column named '#') */
int rn; /* Report number */
int ncol; /* Number of columns in the table */
int multirow; /* True if multiple table rows per line of data */
int newrowidx; /* Index of first column that goes on a separate row */
int iBg = -1; /* Index of column that determines background color */
char *zBg = 0; /* Use this background color */
char zPage[30]; /* Text version of the ticket number */
/* Get the report number
*/
rn = pState->rn;
/* Figure out the number of columns, the column that determines background
** color, and whether or not this row of data is represented by multiple
** rows in the table.
*/
ncol = 0;
multirow = 0;
newrowidx = -1;
for(i=0; i<nArg; i++){
if( azName[i][0]=='b' && strcmp(azName[i],"bgcolor")==0 ){
zBg = azArg ? azArg[i] : 0;
iBg = i;
continue;
}
if( g.okWrite && azName[i][0]=='#' ){
ncol++;
}
if( !multirow ){
if( azName[i][0]=='_' ){
multirow = 1;
newrowidx = i;
}else{
ncol++;
}
}
}
/* The first time this routine is called, output a table header
*/
if( pState->nCount==0 ){
char zExtra[2000];
int nField = atoi(PD("order_by","0"));
const char* zDir = PD("order_dir","");
zDir = !strcmp("ASC",zDir) ? "ASC" : "DESC";
zExtra[0] = 0;
if( g.nAux ){
@ <tr>
@ <td colspan=%d(ncol)><form action="rptview" method="GET">
@ <input type="hidden" name="rn" value="%d(rn)">
for(i=0; i<g.nAux; i++){
const char *zN = g.azAuxName[i];
const char *zP = g.azAuxParam[i];
if( g.azAuxVal[i] && g.azAuxVal[i][0] ){
appendf(zExtra,0,sizeof(zExtra),
"&%t=%t",g.azAuxParam[i],g.azAuxVal[i]);
}
if( g.azAuxOpt[i] ){
@ %h(zN):
if( g.anAuxCols[i]==1 ) {
cgi_v_optionmenu( 0, zP, g.azAuxVal[i], g.azAuxOpt[i] );
}else if( g.anAuxCols[i]==2 ){
cgi_v_optionmenu2( 0, zP, g.azAuxVal[i], g.azAuxOpt[i] );
}
}else{
@ %h(zN): <input type="text" name="%h(zP)" value="%h(g.azAuxVal[i])">
}
}
@ <input type="submit" value="Go">
@ </form></td></tr>
}
@ <tr>
tn = -1;
for(i=0; i<nArg; i++){
char *zName = azName[i];
if( i==iBg ) continue;
if( newrowidx>=0 && i>=newrowidx ){
if( g.okWrite && tn>=0 ){
@ <th> </th>
tn = -1;
}
if( zName[0]=='_' ) zName++;
@ </tr><tr><th colspan=%d(ncol)>%h(zName)</th>
}else{
if( zName[0]=='#' ){
tn = i;
}
/*
** This handles any sorting related stuff. Note that we don't
** bother trying to sort on the "wiki format" columns. I don't
** think it makes much sense, visually.
*/
column_header(rn,azName[i],i+1,nField,zDir,zExtra);
}
}
if( g.okWrite && tn>=0 ){
@ <th> </th>
}
@ </tr>
}
if( azArg==0 ){
@ <tr><td colspan="%d(ncol)">
@ <i>No records match the report criteria</i>
@ </td></tr>
return 0;
}
++pState->nCount;
/* Output the separator above each entry in a table which has multiple lines
** per database entry.
*/
if( newrowidx>=0 ){
@ <tr><td colspan=%d(ncol)><font size=1> </font></td></tr>
}
/* Output the data for this entry from the database
*/
if( zBg==0 ) zBg = "white";
@ <tr bgcolor="%h(zBg)">
tn = 0;
zPage[0] = 0;
for(i=0; i<nArg; i++){
char *zData;
if( i==iBg ) continue;
zData = azArg[i];
if( zData==0 ) zData = "";
if( newrowidx>=0 && i>=newrowidx ){
if( tn>0 && g.okWrite ){
@ <td valign="top"><a href="tktedit?tn=%d(tn),%d(rn)">edit</a></td>
tn = 0;
}
if( zData[0] ){
@ </tr><tr bgcolor="%h(zBg)"><td colspan=%d(ncol)>
output_formatted(zData, zPage[0] ? zPage : 0);
}
}else if( azName[i][0]=='#' ){
tn = atoi(zData);
if( tn>0 ) bprintf(zPage, sizeof(zPage), "%d", tn);
@ <td valign="top"><a href="tktview?tn=%d(tn),%d(rn)">%h(zData)</a></td>
}else if( zData[0]==0 ){
@ <td valign="top"> </td>
}else{
@ <td valign="top">
output_report_field(zData,rn);
@ </td>
}
}
if( tn>0 && g.okWrite ){
@ <td valign="top"><a href="tktedit?tn=%d(tn),%d(rn)">edit</a></td>
}
@ </tr>
return 0;
}
/*
** Output the text given in the argument. Convert tabs and newlines into
** spaces.
*/
static void output_no_tabs(const char *z){
while( z && z[0] ){
int i, j;
for(i=0; z[i] && (!isspace(z[i]) || z[i]==' '); i++){}
if( i>0 ){
cgi_printf("%.*s", i, z);
}
for(j=i; isspace(z[j]); j++){}
if( j>i ){
cgi_printf("%*s", j-i, "");
}
z += j;
}
}
/*
** Output a row as a tab-separated line of text.
*/
static int output_tab_separated(
void *pUser, /* Pointer to row-count integer */
int nArg, /* Number of columns in this result row */
char **azArg, /* Text of data in all columns */
char **azName /* Names of the columns */
){
int *pCount = (int*)pUser;
int i;
if( *pCount==0 ){
for(i=0; i<nArg; i++){
output_no_tabs(azName[i]);
cgi_printf("%c", i<nArg-1 ? '\t' : '\n');
}
}
++*pCount;
for(i=0; i<nArg; i++){
output_no_tabs(azArg[i]);
cgi_printf("%c", i<nArg-1 ? '\t' : '\n');
}
return 0;
}
/*
** Generate HTML that describes a color key.
*/
void output_color_key(const char *zClrKey, int horiz, char *zTabArgs){
int i, j, k;
char *zSafeKey, *zToFree;
while( isspace(*zClrKey) ) zClrKey++;
if( zClrKey[0]==0 ) return;
@ <table %s(zTabArgs)>
if( horiz ){
@ <tr>
}
zToFree = zSafeKey = mprintf("%h", zClrKey);
while( zSafeKey[0] ){
while( isspace(*zSafeKey) ) zSafeKey++;
for(i=0; zSafeKey[i] && !isspace(zSafeKey[i]); i++){}
for(j=i; isspace(zSafeKey[j]); j++){}
for(k=j; zSafeKey[k] && zSafeKey[k]!='\n' && zSafeKey[k]!='\r'; k++){}
if( !horiz ){
cgi_printf("<tr bgcolor=\"%.*s\"><td>%.*s</td></tr>\n",
i, zSafeKey, k-j, &zSafeKey[j]);
}else{
cgi_printf("<td bgcolor=\"%.*s\">%.*s</td>\n",
i, zSafeKey, k-j, &zSafeKey[j]);
}
zSafeKey += k;
}
free(zToFree);
if( horiz ){
@ </tr>
}
@ </table>
}
/*
** WEBPAGE: /rptview
**
** Generate a report. The rn query parameter is the report number
** corresponding to REPORTFMT.RN. If the tablist query parameter exists,
** then the output consists of lines of tab-separated fields instead of
** an HTML table.
*/
void rptview_page(void){
int count = 0;
int rn;
char *zSql;
char *zTitle;
char *zOwner;
char *zClrKey;
int tabs;
Stmt q;
login_check_credentials();
if( !g.okRead ){ login_needed(); return; }
rn = atoi(PD("rn","0"));
if( rn==0 ){
cgi_redirect("reportlist");
return;
}
tabs = P("tablist")!=0;
view_add_functions(tabs);
db_prepare(&q,
"SELECT title, sqlcode, owner, cols FROM reportfmt WHERE rn=%d", rn);
if( db_step(&q)!=SQLITE_ROW ){
cgi_redirect("reportlist");
return;
}
zTitle = db_column_malloc(&q, 0);
zSql = db_column_malloc(&q, 1);
zOwner = db_column_malloc(&q, 2);
zClrKey = db_column_malloc(&q, 3);
db_finalize(&q);
if( P("order_by") ){
/*
** If the user wants to do a column sort, wrap the query into a sub
** query and then sort the results. This is a whole lot easier than
** trying to insert an ORDER BY into the query itself, especially
** if the query is already ordered.
*/
int nField = atoi(P("order_by"));
if( nField > 0 ){
const char* zDir = PD("order_dir","");
zDir = !strcmp("ASC",zDir) ? "ASC" : "DESC";
zSql = mprintf("SELECT * FROM (%s) ORDER BY %d %s", zSql, nField, zDir);
}
}
count = 0;
if( !tabs ){
struct GenerateHTML sState;
db_execute("PRAGMA empty_result_callbacks=ON");
style_submenu_element("Raw", "Raw",
"rptview?tablist=1&%s", P("QUERY_STRING",""));
if( g.okAdmin
|| (g.okQuery && g.zLogin && zOwner && strcmp(g.zLogin,zOwner)==0) ){
style_submentu_element("Edit", "Edit", "rptedit?rn=%d", rn);
}
style_submenu_element("SQL", "SQL", "rptsql?rn=%d",rn);
style_header(zTitle);
output_color_key(zClrKey, 1,
"border=0 cellpadding=3 cellspacing=0 class=\"report\"");
@ <table border=1 cellpadding=2 cellspacing=0 class="report">
sState.rn = rn;
sState.nCount = 0;
sqlite3_exec(g.db, zSql, generate_html, &sState, 0);
@ </table>
style_footer();
}else{
sqlite3_exec(g.db, zSql, output_tab_separated, &count, 0);
cgi_set_content_type("text/plain");
}
}
|
Added src/rss.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License version 2 as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to create a RSS feed for the CGI interface.
*/
#include "config.h"
#include "rss.h"
#include <assert.h>
#include <time.h>
time_t rss_datetime_to_time_t(const char *dt){
struct tm the_tm;
the_tm.tm_year = atoi(dt)-1900;
the_tm.tm_mon = atoi(&dt[5])-1;
the_tm.tm_mday = atoi(&dt[8]);
the_tm.tm_hour = atoi(&dt[11]);
the_tm.tm_min = atoi(&dt[14]);
the_tm.tm_sec = atoi(&dt[17]);
return mktime(&the_tm);
}
/*
** WEBPAGE: timeline.rss
*/
void page_timeline_rss(void){
Stmt q;
int nLine=0;
char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0;
const char zSQL[] =
@ SELECT
@ blob.rid,
@ uuid,
@ datetime(event.mtime),
@ coalesce(ecomment,comment),
@ coalesce(euser,user),
@ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim),
@ (SELECT count(*) FROM plink WHERE cid=blob.rid)
@ FROM event, blob
@ WHERE blob.rid=event.objid
@ ORDER BY event.mtime DESC
;
cgi_set_content_type("application/rss+xml");
zProjectName = db_get("project-name", 0);
if( zProjectName==0 ){
zFreeProjectName = zProjectName = mprintf("Fossil source repository for: %s",
g.zBaseURL);
}
zProjectDescr = db_get("project-description", 0);
if( zProjectDescr==0 ){
zProjectDescr = zProjectName;
}
zPubDate = cgi_rfc822_datestamp(time(NULL));
@ <?xml version="1.0"?>
@ <rss version="2.0">
@ <channel>
@ <title>%s(zProjectName)</title>
@ <link>%s(g.zBaseURL)</link>
@ <description>%s(zProjectDescr)</description>
@ <pubDate>%s(zPubDate)</pubDate>
@ <generator>Fossil version %s(MANIFEST_VERSION) %s(MANIFEST_DATE)</generator>
db_prepare(&q, zSQL);
while( db_step(&q)==SQLITE_ROW && nLine<=20 ){
const char *zId = db_column_text(&q, 1);
const char *zDate = db_column_text(&q, 2);
const char *zCom = db_column_text(&q, 3);
const char *zAuthor = db_column_text(&q, 4);
char *zPrefix = "";
int nChild = db_column_int(&q, 5);
int nParent = db_column_int(&q, 6);
zDate = cgi_rfc822_datestamp(rss_datetime_to_time_t(zDate));
if( nParent>1 && nChild>1 ){
zPrefix = "*MERGE/FORK* ";
}else if( nParent>1 ){
zPrefix = "*MERGE* ";
}else if( nChild>1 ){
zPrefix = "*FORK* ";
}
@ <item>
@ <title>%s(zPrefix)%s(zCom)</title>
@ <link>%s(g.zBaseURL)/vinfo/%s(zId)</link>
@ <description>%s(zPrefix)%s(zCom)</description>
@ <pubDate>%s(zDate)</pubDate>
@ <author>%s(zAuthor)</author>
@ <guid>%s(g.zBaseURL)/vinfo/%s(zId)</guid>
@ </item>
nLine++;
}
db_finalize(&q);
@ </channel>
@ </rss>
if( zFreeProjectName != 0 ){
free( zFreeProjectName );
}
}
|
Changes to src/schema.c.
| ︙ | ︙ | |||
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | @ -- in the form of name-value pairs. @ -- @ CREATE TABLE config( @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry @ value CLOB, -- Content of the named parameter @ CHECK( typeof(name)='text' AND length(name)>=1 ) @ ); ; const char zRepositorySchema2[] = @ -- Filenames @ -- @ CREATE TABLE filename( @ fnid INTEGER PRIMARY KEY, -- Filename ID @ name TEXT UNIQUE -- Name of file page @ ); @ @ -- Linkages between manifests, files created by that manifest, and @ -- the names of those files. @ -- @ CREATE TABLE mlink( @ mid INTEGER REFERENCES blob, -- Manifest ID where change occurs @ pid INTEGER REFERENCES blob, -- File ID in parent manifest @ fid INTEGER REFERENCES blob, -- Changed file ID in this manifest @ fnid INTEGER REFERENCES filename -- Name of the file @ ); @ CREATE INDEX mlink_i1 ON mlink(mid); | > > > > > > > > > > > > > > > > > > > > > > | 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | @ -- in the form of name-value pairs. @ -- @ CREATE TABLE config( @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry @ value CLOB, -- Content of the named parameter @ CHECK( typeof(name)='text' AND length(name)>=1 ) @ ); @ @ -- Artifacts that should not be processed are identified in the @ -- "shun" table. Artifacts that are control-file forgeries or @ -- spam can be shunned in order to prevent them from contaminating @ -- the repository. @ -- @ CREATE TABLE shun(uuid UNIQUE); @ @ -- An entry in this table describes a database query that generates a @ -- table of tickets. @ -- @ CREATE TABLE reportfmt( @ rn integer primary key, -- Report number @ owner text, -- Owner of this report format (not used) @ title text, -- Title of this report @ cols text, -- A color-key specification @ sqlcode text -- An SQL SELECT statement for this report @ ); ; const char zRepositorySchema2[] = @ -- Filenames @ -- @ CREATE TABLE filename( @ fnid INTEGER PRIMARY KEY, -- Filename ID @ name TEXT UNIQUE -- Name of file page @ ); @ @ -- Linkages between manifests, files created by that manifest, and @ -- the names of those files. @ -- @ -- pid==0 if the file is added by check-in mid. @ -- fid==0 if the file is removed by check-in mid. @ -- @ CREATE TABLE mlink( @ mid INTEGER REFERENCES blob, -- Manifest ID where change occurs @ pid INTEGER REFERENCES blob, -- File ID in parent manifest @ fid INTEGER REFERENCES blob, -- Changed file ID in this manifest @ fnid INTEGER REFERENCES filename -- Name of the file @ ); @ CREATE INDEX mlink_i1 ON mlink(mid); |
| ︙ | ︙ | |||
150 151 152 153 154 155 156 | @ UNIQUE(pid, cid) @ ); @ CREATE INDEX plink_i2 ON plink(cid); @ @ -- Events used to generate a timeline @ -- @ CREATE TABLE event( | | | | | > > > | > | < | 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | @ UNIQUE(pid, cid) @ ); @ CREATE INDEX plink_i2 ON plink(cid); @ @ -- Events used to generate a timeline @ -- @ CREATE TABLE event( @ type TEXT, -- Type of event @ mtime DATETIME, -- Date and time when the event occurs @ objid INTEGER PRIMARY KEY, -- Associated record ID @ uid INTEGER REFERENCES user, -- User who caused the event @ bgcolor TEXT, -- Color set by 'bgcolor' property @ brbgcolor TEXT, -- Color set by 'br-bgcolor' property @ euser TEXT, -- User set by 'user' property @ user TEXT, -- Name of the user @ ecomment TEXT, -- Comment set by 'comment' property @ comment TEXT -- Comment describing the event @ ); @ CREATE INDEX event_i1 ON event(mtime); @ @ -- A record of phantoms. A phantom is a record for which we know the @ -- UUID but we do not (yet) know the file content. @ -- @ CREATE TABLE phantom( @ rid INTEGER PRIMARY KEY -- Record ID of the phantom @ ); |
| ︙ | ︙ | |||
188 189 190 191 192 193 194 | @ -- used to reduce push operations to a single HTTP request in the @ -- common case when one repository only talks to a single server. @ -- @ CREATE TABLE unsent( @ rid INTEGER PRIMARY KEY -- Record ID of the phantom @ ); @ | > | | < | | < | < | | | | > > > > > > > > > > | | > | > > > | | > > > > > > > > > > | 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | @ -- used to reduce push operations to a single HTTP request in the @ -- common case when one repository only talks to a single server. @ -- @ CREATE TABLE unsent( @ rid INTEGER PRIMARY KEY -- Record ID of the phantom @ ); @ @ -- Each baseline or manifest can have one or more tags. A tag @ -- is defined by a row in the next table. @ -- @ -- Wiki pages are tagged with "wiki-NAME" where NAME is the name of @ -- the wiki page. Tickets changes are tagged with "ticket-UUID" where @ -- UUID is the indentifier of the ticket. @ -- @ CREATE TABLE tag( @ tagid INTEGER PRIMARY KEY, -- Numeric tag ID @ tagname TEXT UNIQUE -- Tag name. @ ); @ INSERT INTO tag VALUES(1, 'bgcolor'); -- TAG_BGCOLOR @ INSERT INTO tag VALUES(2, 'comment'); -- TAG_COMMENT @ INSERT INTO tag VALUES(3, 'user'); -- TAG_USER @ INSERT INTO tag VALUES(4, 'hidden'); -- TAG_HIDDEN @ @ -- Assignments of tags to baselines. Note that we allow tags to @ -- have values assigned to them. So we are not really dealing with @ -- tags here. These are really properties. But we are going to @ -- keep calling them tags because in many cases the value is ignored. @ -- @ CREATE TABLE tagxref( @ tagid INTEGER REFERENCES tag, -- The tag that added or removed @ tagtype INTEGER, -- 0:cancel 1:single 2:branch @ srcid INTEGER REFERENCES blob, -- Origin of the tag. 0 for propagated tags @ value TEXT, -- Value of the tag. Might be NULL. @ mtime TIMESTAMP, -- Time of addition or removal @ rid INTEGER REFERENCE blob, -- Baseline that tag added/removed from @ UNIQUE(rid, tagid) @ ); @ CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime); ; /* ** Predefined tagid values */ #if INTERFACE # define TAG_BGCOLOR 1 # define TAG_COMMENT 2 # define TAG_USER 3 # define TAG_HIDDEN 4 #endif /* ** The schema for the locate FOSSIL database file found at the root ** of very check-out. This database contains the complete state of ** the checkout. */ const char zLocalSchema[] = |
| ︙ | ︙ |
Changes to src/setup.c.
1 2 3 4 5 6 7 8 9 10 11 12 | /* ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public ** License as published by the Free Software Foundation; either ** version 2 of the License, or (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* ** Copyright (c) 2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public ** License as published by the Free Software Foundation; either ** version 2 of the License, or (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. ** ** You should have received a copy of the GNU General Public ** License along with this library; if not, write to the ** Free Software Foundation, Inc., 59 Temple Place - Suite 330, ** Boston, MA 02111-1307, USA. ** ** Author contact information: ** drh@hwaci.com |
| ︙ | ︙ | |||
31 32 33 34 35 36 37 | /* ** Output a single entry for a menu generated using an HTML table. ** If zLink is not NULL or an empty string, then it is the page that ** the menu entry will hyperlink to. If zLink is NULL or "", then ** the menu entry has no hyperlink - it is disabled. */ | | | < | | | | | > > > > > > > > > > | | < | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
/*
** Output a single entry for a menu generated using an HTML table.
** If zLink is not NULL or an empty string, then it is the page that
** the menu entry will hyperlink to. If zLink is NULL or "", then
** the menu entry has no hyperlink - it is disabled.
*/
void setup_menu_entry(
const char *zTitle,
const char *zLink,
const char *zDesc
){
@ <tr><td valign="top" align="right">
if( zLink && zLink[0] ){
@ <a href="%s(zLink)">%h(zTitle)</a>
}else{
@ %h(zTitle)
}
@ </td><td valign="top">%h(zDesc)</td></tr>
}
/*
** WEBPAGE: /setup
*/
void setup_page(void){
login_check_credentials();
if( !g.okSetup ){
login_needed();
}
style_header("Setup");
@ <table border="0" cellspacing="20">
setup_menu_entry("Users", "setup_ulist",
"Grant privileges to individual users.");
setup_menu_entry("Access", "setup_access",
"Control access settings.");
setup_menu_entry("Configuration", "setup_config",
"Configure the WWW components of the repository");
setup_menu_entry("Timeline", "setup_timeline",
"Timeline display preferences");
setup_menu_entry("Tickets", "setup_ticket",
"Configure the trouble-ticketing system for this repository");
setup_menu_entry("CSS", "setup_editcss",
"Edit the Cascading Style Sheet used by all pages of this repository");
setup_menu_entry("Header", "setup_header",
"Edit HTML text inserted at the top of every page");
setup_menu_entry("Footer", "setup_footer",
"Edit HTML text inserted at the bottom of every page");
@ </table>
style_footer();
}
/*
** WEBPAGE: setup_ulist
**
** Show a list of users. Clicking on any user jumps to the edit
** screen for that user.
*/
void setup_ulist(void){
Stmt s;
login_check_credentials();
if( !g.okSetup ){
login_needed();
return;
}
style_submenu_element("Add", "Add User", "setup_uedit");
|
| ︙ | ︙ | |||
121 122 123 124 125 126 127 128 129 130 131 132 133 134 | @ <b>Notes:</b> @ <ol> @ <li><p>The permission flags are as follows:</p> @ <ol type="a"> @ <li value="1"><b>Admin</b>: Create and delete users</li> @ <li value="3"><b>Append-Tkt</b>: Append to tickets</li> @ <li value="4"><b>Delete</b>: Delete wiki and tickets</li> @ <li value="6"><b>New-Wiki</b>: Create new wiki pages</li> @ <li value="7"><b>Clone</b>: Clone the repository</li> @ <li value="8"><b>History</b>: View detail repository history</li> @ <li value="9"><b>Check-In</b>: Commit new versions in the repository</li> @ <li value="10"><b>Read-Wiki</b>: View wiki pages</li> @ <li value="11"><b>Write-Wiki</b>: Edit wiki pages</li> @ <li value="13"><b>Append-Wiki</b>: Append to wiki pages</li> | > | 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | @ <b>Notes:</b> @ <ol> @ <li><p>The permission flags are as follows:</p> @ <ol type="a"> @ <li value="1"><b>Admin</b>: Create and delete users</li> @ <li value="3"><b>Append-Tkt</b>: Append to tickets</li> @ <li value="4"><b>Delete</b>: Delete wiki and tickets</li> @ <li value="5"><b>Email</b>: View EMail addresses on tickets</li> @ <li value="6"><b>New-Wiki</b>: Create new wiki pages</li> @ <li value="7"><b>Clone</b>: Clone the repository</li> @ <li value="8"><b>History</b>: View detail repository history</li> @ <li value="9"><b>Check-In</b>: Commit new versions in the repository</li> @ <li value="10"><b>Read-Wiki</b>: View wiki pages</li> @ <li value="11"><b>Write-Wiki</b>: Edit wiki pages</li> @ <li value="13"><b>Append-Wiki</b>: Append to wiki pages</li> |
| ︙ | ︙ | |||
155 156 157 158 159 160 161 |
}
/*
** WEBPAGE: /setup_uedit
*/
void user_edit(void){
const char *zId, *zLogin, *zInfo, *zCap;
| | | | 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
}
/*
** WEBPAGE: /setup_uedit
*/
void user_edit(void){
const char *zId, *zLogin, *zInfo, *zCap;
char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
char *oak, *oad, *oaq, *oac, *oaf, *oam, *oah, *oag, *oae;
int doWrite;
int uid;
int higherUser = 0; /* True if user being edited is SETUP and the */
/* user doing the editing is ADMIN. Disallow editing */
/* Must have ADMIN privleges to access this page
*/
|
| ︙ | ︙ | |||
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
if( doWrite ){
const char *zPw;
const char *zLogin;
char zCap[30];
int i = 0;
int aa = P("aa")!=0;
int ad = P("ad")!=0;
int ai = P("ai")!=0;
int aj = P("aj")!=0;
int ak = P("ak")!=0;
int an = P("an")!=0;
int ao = P("ao")!=0;
int ap = P("ap")!=0;
int aq = P("aq")!=0;
int ar = P("ar")!=0;
int as = g.okSetup && P("as")!=0;
int aw = P("aw")!=0;
int ac = P("ac")!=0;
int af = P("af")!=0;
int am = P("am")!=0;
int ah = P("ah")!=0;
int ag = P("ag")!=0;
if( aa ){ zCap[i++] = 'a'; }
if( ac ){ zCap[i++] = 'c'; }
if( ad ){ zCap[i++] = 'd'; }
if( af ){ zCap[i++] = 'f'; }
if( ah ){ zCap[i++] = 'h'; }
if( ag ){ zCap[i++] = 'g'; }
if( ai ){ zCap[i++] = 'i'; }
if( aj ){ zCap[i++] = 'j'; }
if( ak ){ zCap[i++] = 'k'; }
if( am ){ zCap[i++] = 'm'; }
| > > | 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
if( doWrite ){
const char *zPw;
const char *zLogin;
char zCap[30];
int i = 0;
int aa = P("aa")!=0;
int ad = P("ad")!=0;
int ae = P("ae")!=0;
int ai = P("ai")!=0;
int aj = P("aj")!=0;
int ak = P("ak")!=0;
int an = P("an")!=0;
int ao = P("ao")!=0;
int ap = P("ap")!=0;
int aq = P("aq")!=0;
int ar = P("ar")!=0;
int as = g.okSetup && P("as")!=0;
int aw = P("aw")!=0;
int ac = P("ac")!=0;
int af = P("af")!=0;
int am = P("am")!=0;
int ah = P("ah")!=0;
int ag = P("ag")!=0;
if( aa ){ zCap[i++] = 'a'; }
if( ac ){ zCap[i++] = 'c'; }
if( ad ){ zCap[i++] = 'd'; }
if( ae ){ zCap[i++] = 'e'; }
if( af ){ zCap[i++] = 'f'; }
if( ah ){ zCap[i++] = 'h'; }
if( ag ){ zCap[i++] = 'g'; }
if( ai ){ zCap[i++] = 'i'; }
if( aj ){ zCap[i++] = 'j'; }
if( ak ){ zCap[i++] = 'k'; }
if( am ){ zCap[i++] = 'm'; }
|
| ︙ | ︙ | |||
234 235 236 237 238 239 240 |
zCap[i] = 0;
zPw = P("pw");
if( zPw==0 || zPw[0]==0 ){
zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid);
}
zLogin = P("login");
| | | 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
zCap[i] = 0;
zPw = P("pw");
if( zPw==0 || zPw[0]==0 ){
zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid);
}
zLogin = P("login");
if( uid>0 &&
db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid)
){
style_header("User Creation Error");
@ <font color="red">Login "%h(zLogin)" is already used by a different
@ user.</font>
@
@ <p><a href="setup_uedit?id=%d(uid))>[Bummer]</a></p>
|
| ︙ | ︙ | |||
259 260 261 262 263 264 265 | } /* Load the existing information about the user, if any */ zLogin = ""; zInfo = ""; zCap = ""; | | > | 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
}
/* Load the existing information about the user, if any
*/
zLogin = "";
zInfo = "";
zCap = "";
oaa = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam =
oan = oao = oap = oaq = oar = oas = oaw = "";
if( uid ){
zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
if( strchr(zCap, 'a') ) oaa = " checked";
if( strchr(zCap, 'c') ) oac = " checked";
if( strchr(zCap, 'd') ) oad = " checked";
if( strchr(zCap, 'e') ) oae = " checked";
if( strchr(zCap, 'f') ) oaf = " checked";
if( strchr(zCap, 'g') ) oag = " checked";
if( strchr(zCap, 'h') ) oah = " checked";
if( strchr(zCap, 'i') ) oai = " checked";
if( strchr(zCap, 'j') ) oaj = " checked";
if( strchr(zCap, 'k') ) oak = " checked";
if( strchr(zCap, 'm') ) oam = " checked";
|
| ︙ | ︙ | |||
319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
@ <td align="right" valign="top">Capabilities:</td>
@ <td>
if( g.okSetup ){
@ <input type="checkbox" name="as"%s(oas)>Setup</input><br>
}
@ <input type="checkbox" name="aa"%s(oaa)>Admin</input><br>
@ <input type="checkbox" name="ad"%s(oad)>Delete</input><br>
@ <input type="checkbox" name="ap"%s(oap)>Password</input><br>
@ <input type="checkbox" name="aq"%s(oaq)>Query</input><br>
@ <input type="checkbox" name="ai"%s(oai)>Check-In</input><br>
@ <input type="checkbox" name="ao"%s(oao)>Check-Out</input><br>
@ <input type="checkbox" name="ah"%s(oah)>History</input><br>
@ <input type="checkbox" name="ag"%s(oag)>Clone</input><br>
@ <input type="checkbox" name="aj"%s(oaj)>Read Wiki</input><br>
| > | 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
@ <td align="right" valign="top">Capabilities:</td>
@ <td>
if( g.okSetup ){
@ <input type="checkbox" name="as"%s(oas)>Setup</input><br>
}
@ <input type="checkbox" name="aa"%s(oaa)>Admin</input><br>
@ <input type="checkbox" name="ad"%s(oad)>Delete</input><br>
@ <input type="checkbox" name="ae"%s(oad)>Email</input><br>
@ <input type="checkbox" name="ap"%s(oap)>Password</input><br>
@ <input type="checkbox" name="aq"%s(oaq)>Query</input><br>
@ <input type="checkbox" name="ai"%s(oai)>Check-In</input><br>
@ <input type="checkbox" name="ao"%s(oao)>Check-Out</input><br>
@ <input type="checkbox" name="ah"%s(oah)>History</input><br>
@ <input type="checkbox" name="ag"%s(oag)>Clone</input><br>
@ <input type="checkbox" name="aj"%s(oaj)>Read Wiki</input><br>
|
| ︙ | ︙ | |||
359 360 361 362 363 364 365 |
@ </p></li>
@
}
@
@ <li><p>
@ The <b>Delete</b> privilege give the user the ability to erase
@ wiki, tickets, and atttachments that have been added by anonymous
| | > > | | 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 |
@ </p></li>
@
}
@
@ <li><p>
@ The <b>Delete</b> privilege give the user the ability to erase
@ wiki, tickets, and atttachments that have been added by anonymous
@ users. This capability is intended for deletion of spam. The
@ delete capability is only in effect for 24 hours after the item
@ is first posted. The Setup user can delete anything at any time.
@ </p></li>
@
@ <li><p>
@ The <b>Query</b> privilege allows the user to create or edit
@ report formats by specifying appropriate SQL. Users can run
@ existing reports without the Query privilege.
@ </p></li>
@
@ <li><p>
@ An <b>Admin</b> user can add other users, create new ticket report
@ formats, and change system defaults. But only the <b>Setup</b> user
@ is able to change the repository to
|
| ︙ | ︙ | |||
428 429 430 431 432 433 434 |
}
if( zQ==0 && P("submit") ){
zQ = "off";
}
if( zQ ){
int iQ = strcmp(zQ,"on")==0 || atoi(zQ);
if( iQ!=iVal ){
| | | | > > > > > > > > > > > > > > > > > > > > > > | | > > > > > | > > > > > > > > | > > > | > > > > > > > > > > > > < | | < > | < < | | < < < < | < < < < < | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 |
}
if( zQ==0 && P("submit") ){
zQ = "off";
}
if( zQ ){
int iQ = strcmp(zQ,"on")==0 || atoi(zQ);
if( iQ!=iVal ){
db_set(zVar, iQ ? "1" : "0", 0);
iVal = iQ;
}
}
if( iVal ){
@ <input type="checkbox" name="%s(zQParm)" checked>%s(zLabel)</input>
}else{
@ <input type="checkbox" name="%s(zQParm)">%s(zLabel)</input>
}
}
/*
** Generate an entry box for an attribute.
*/
static void entry_attribute(
const char *zLabel, /* The text label on the entry box */
int width, /* Width of the entry box */
const char *zVar, /* The corresponding row in the VAR table */
const char *zQParm, /* The query parameter */
char *zDflt /* Default value if VAR table entry does not exist */
){
const char *zVal = db_get(zVar, zDflt);
const char *zQ = P(zQParm);
if( zQ && strcmp(zQ,zVal)!=0 ){
db_set(zVar, zQ, 0);
zVal = zQ;
}
@ <input type="text" name="%s(zQParm)" value="%h(zVal)" size="%d(width)">
@ %s(zLabel)
}
/*
** Generate a text box for an attribute.
*/
static void textarea_attribute(
const char *zLabel, /* The text label on the textarea */
int rows, /* Rows in the textarea */
int cols, /* Columns in the textarea */
const char *zVar, /* The corresponding row in the VAR table */
const char *zQP, /* The query parameter */
const char *zDflt /* Default value if VAR table entry does not exist */
){
const char *z = db_get(zVar, (char*)zDflt);
const char *zQ = P(zQP);
if( zQ && strcmp(zQ,z)!=0 ){
db_set(zVar, zQ, 0);
z = zQ;
}
if( rows>0 && cols>0 ){
@ <textarea name="%s(zQP)" rows="%d(rows)" cols="%d(cols)">%h(z)</textarea>
@ %s(zLabel)
}
}
/*
** WEBPAGE: setup_access
*/
void setup_access(void){
login_check_credentials();
if( !g.okSetup ){
login_needed();
}
style_header("Access Control Settings");
db_begin_transaction();
@ <form action="%s(g.zBaseURL)/setup_access" method="POST">
@ <hr>
onoff_attribute("Require password for local access",
"localauth", "localauth", 1);
@ <p>When enabled, the password sign-in is required for
@ web access coming from 127.0.0.1. When disabled, web access
@ from 127.0.0.1 is allows without any login - the user id is selected
@ from the ~/.fossil database. Password login is always required
@ for incoming web connections on internet addresses other than
@ 127.0.0.1.</p></li>
@ <hr>
entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766");
@ <p>The number of hours for which a login is valid. This must be a
@ positive number. The default is 8760 hours which is approximately equal
@ to a year.</p>
@ <hr>
@ <p><input type="submit" name="submit" value="Apply Changes"></p>
@ </form>
db_end_transaction(0);
style_footer();
}
/*
** WEBPAGE: setup_timeline
*/
void setup_timeline(void){
login_check_credentials();
if( !g.okSetup ){
login_needed();
}
style_header("Timeline Display Preferences");
db_begin_transaction();
@ <form action="%s(g.zBaseURL)/setup_timeline" method="POST">
@ <hr>
onoff_attribute("Block markup in timeline",
"timeline-block-markup", "tbm", 0);
@ <p>In timeline displays, check-in comments can be displayed with or
@ without block markup (paragraphs, tables, etc.)</p>
@ <hr>
entry_attribute("Max timeline comment length", 6,
"timeline-max-comment", "tmc", "0");
@ <p>The maximum length of a comment to be displayed in a timeline.
@ "0" there is no length limit.</p>
@ <hr>
@ <p><input type="submit" name="submit" value="Apply Changes"></p>
@ </form>
db_end_transaction(0);
style_footer();
}
/*
** WEBPAGE: setup_config
*/
void setup_config(void){
login_check_credentials();
if( !g.okSetup ){
login_needed();
}
style_header("WWW Configuration");
db_begin_transaction();
@ <form action="%s(g.zBaseURL)/setup_config" method="POST">
@ <hr />
entry_attribute("Project Name", 60, "project-name", "pn", "");
@ <p>Give your project a name so visitors know what this site is about.
@ The project name will also be used as the RSS feed title.</p>
@ <hr />
textarea_attribute("Project Description", 5, 60,
"project-description", "pd", "");
@ <p>Describe your project. This will be used in page headers for search
@ engines as well as a short RSS description.</p>
@ <hr />
@ <p><input type="submit" name="submit" value="Apply Changes"></p>
@ </form>
db_end_transaction(0);
style_footer();
}
/*
** WEBPAGE: setup_editcss
*/
void setup_editcss(void){
login_check_credentials();
if( !g.okSetup ){
login_needed();
}
style_header("Edit CSS");
@ <form action="%s(g.zBaseURL)/setup_editcss" method="POST">
@ Edit the CSS:<br />
textarea_attribute("", 40, 80, "css", "css", zDefaultCSS);
@ <br />
@ <input type="submit" name="submit" value="Apply Changes">
@ </form>
@ <hr>
@ Here is the default CSS:
@ <blockquote><pre>
@ %h(zDefaultCSS)
@ </pre></blockquote>
style_footer();
}
/*
** WEBPAGE: setup_header
*/
void setup_header(void){
login_check_credentials();
if( !g.okSetup ){
login_needed();
}
db_begin_transaction();
if( P("clear")!=0 ){
db_multi_exec("DELETE FROM config WHERE name='header'");
cgi_replace_parameter("header", zDefaultHeader);
}else{
textarea_attribute(0, 0, 0, "header", "header", zDefaultHeader);
}
style_header("Edit Page Header");
@ <form action="%s(g.zBaseURL)/setup_header" method="POST">
@ <p>Edit HTML text with embedded subscript that will be used to
@ generate the beginning of every page through start of the main
@ menu.</p>
textarea_attribute("", 40, 80, "header", "header", zDefaultHeader);
@ <br />
@ <input type="submit" name="submit" value="Apply Changes">
@ <input type="submit" name="clear" value="Revert To Default">
@ </form>
@ <hr>
@ Here is the default page header:
@ <blockquote><pre>
@ %h(zDefaultHeader)
@ </pre></blockquote>
db_end_transaction(0);
}
/*
** WEBPAGE: setup_footer
*/
void setup_footer(void){
login_check_credentials();
if( !g.okSetup ){
login_needed();
}
db_begin_transaction();
if( P("clear")!=0 ){
db_multi_exec("DELETE FROM config WHERE name='footer'");
cgi_replace_parameter("footer", zDefaultFooter);
}else{
textarea_attribute(0, 0, 0, "footer", "footer", zDefaultFooter);
}
style_header("Edit Page Footer");
@ <form action="%s(g.zBaseURL)/setup_footer" method="POST">
@ <p>Edit HTML text with embedded subscript that will be used to
@ generate the end of every page.</p>
textarea_attribute("", 20, 80, "footer", "footer", zDefaultFooter);
@ <br />
@ <input type="submit" name="submit" value="Apply Changes">
@ <input type="submit" name="clear" value="Revert To Default">
@ </form>
@ <hr>
@ Here is the default page footer:
@ <blockquote><pre>
@ %h(zDefaultFooter)
@ </pre></blockquote>
db_end_transaction(0);
}
/*
** WEBPAGE: setup_ticket
*/
void setup_ticket(void){
const char *zConfig;
int isSubmit;
login_check_credentials();
if( !g.okSetup ){
login_needed();
}
isSubmit = P("submit")!=0;
db_begin_transaction();
zConfig = P("cfg");
if( zConfig==0 ){
zConfig = db_text((char*)zDefaultTicketConfig,
"SELECT value FROM config WHERE name='ticket-configuration'");
}
style_header("Edit Ticket Configuration");
if( P("clear")!=0 ){
db_multi_exec("DELETE FROM config WHERE name='ticket-configuration'");
zConfig = zDefaultTicketConfig;
}else if( isSubmit ){
char *zErr = ticket_config_check(zConfig);
if( zErr==0 ){
db_multi_exec(
"REPLACE INTO config(name,value) VALUES('ticket-configuration',"
"%Q)", zConfig
);
}else{
@ <p><font color="red"><b>
@ SCRIPT ERROR: %h(zErr)
@ </b></font></p>
}
}
@ <form action="%s(g.zBaseURL)/setup_ticket" method="POST">
@ <p>Edit the "subscript" script that defines the ticketing
@ system setup for this server.</p>
@ <textarea name="cfg" rows="40" cols="80">%h(zConfig)</textarea>
@ <br />
@ <input type="submit" name="submit" value="Apply Changes">
@ <input type="submit" name="clear" value="Revert To Default">
@ </form>
@ <hr>
@ Here is the default page header:
@ <blockquote><pre>
@ %h(zDefaultTicketConfig)
@ </pre></blockquote>
db_end_transaction(0);
}
|
Changes to src/sha1.c.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* ** This implementation of SHA1 is adapted from the example implementation ** contained in RFC-3174. */ #include <stdint.h> #include "config.h" #include "sha1.h" /* * If you do not have the ISO standard stdint.h header file, then you * must typdef the following: * name meaning * uint32_t unsigned 32 bit integer * uint8_t unsigned 8 bit integer (i.e., unsigned char) | > < | < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
/*
** This implementation of SHA1 is adapted from the example implementation
** contained in RFC-3174.
*/
#include <stdint.h>
#include <sys/types.h>
#include "config.h"
#include "sha1.h"
/*
* If you do not have the ISO standard stdint.h header file, then you
* must typdef the following:
* name meaning
* uint32_t unsigned 32 bit integer
* uint8_t unsigned 8 bit integer (i.e., unsigned char)
*
*/
#define SHA1HashSize 20
#define shaSuccess 0
#define shaInputTooLong 1
#define shaStateError 2
/*
* This structure will hold context information for the SHA-1
* hashing operation
*/
typedef struct SHA1Context SHA1Context;
struct SHA1Context {
uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */
uint32_t Length_Low; /* Message length in bits */
uint32_t Length_High; /* Message length in bits */
int Message_Block_Index; /* Index into message block array */
uint8_t Message_Block[64]; /* 512-bit message blocks */
int Computed; /* Is the digest computed? */
int Corrupted; /* Is the message digest corrupted? */
};
/*
|
| ︙ | ︙ |
Changes to src/sqlite3.c.
more than 10,000 changes
Changes to src/style.c.
1 | /* | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /* ** Copyright (c) 2006,2007 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public ** License version 2 as published by the Free Software Foundation. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. ** ** You should have received a copy of the GNU General Public ** License along with this library; if not, write to the ** Free Software Foundation, Inc., 59 Temple Place - Suite 330, ** Boston, MA 02111-1307, USA. ** ** Author contact information: ** drh@hwaci.com |
| ︙ | ︙ | |||
44 45 46 47 48 49 50 | /* ** Add a new element to the submenu */ void style_submenu_element( const char *zLabel, const char *zTitle, | | > > > | > | > > > > > | > | > | < < < > > > > | > > > | < > | < < > | > > | | | | | | | | | > | < | | > < | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < < | | < < < < < < < < | > > > > > | 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
/*
** Add a new element to the submenu
*/
void style_submenu_element(
const char *zLabel,
const char *zTitle,
const char *zLink,
...
){
va_list ap;
assert( nSubmenu < sizeof(aSubmenu)/sizeof(aSubmenu[0]) );
aSubmenu[nSubmenu].zLabel = zLabel;
aSubmenu[nSubmenu].zTitle = zTitle;
va_start(ap, zLink);
aSubmenu[nSubmenu].zLink = vmprintf(zLink, ap);
va_end(ap);
nSubmenu++;
}
/*
** Compare two submenu items for sorting purposes
*/
static int submenuCompare(const void *a, const void *b){
const struct Submenu *A = (const struct Submenu*)a;
const struct Submenu *B = (const struct Submenu*)b;
return strcmp(A->zLabel, B->zLabel);
}
/*
** The Subscript interpreter used to render header and footer.
*/
static struct Subscript *pInterp;
/*
** Draw the header.
*/
void style_header(const char *zTitle){
const char *zLogInOut = "Login";
const char *zHeader = db_get("header", (char*)zDefaultHeader);
login_check_credentials();
if( pInterp ) return;
/* Generate the header up through the main menu */
pInterp = SbS_Create();
SbS_Store(pInterp, "project_name",
db_get("project-name","Unnamed Fossil Project"), 0);
SbS_Store(pInterp, "title", zTitle, 0);
SbS_Store(pInterp, "baseurl", g.zBaseURL, 0);
SbS_Store(pInterp, "manifest_version", MANIFEST_VERSION, 0);
SbS_Store(pInterp, "manifest_date", MANIFEST_DATE, 0);
if( g.zLogin ){
SbS_Store(pInterp, "login", g.zLogin, 0);
zLogInOut = "Logout";
}
SbS_Render(pInterp, zHeader);
/* Generate the main menu and the submenu (if any) */
@ <div class="mainmenu">
@ <a href="%s(g.zBaseURL)/home">Home</a>
if( g.okRead ){
@ <a href="%s(g.zBaseURL)/leaves">Leaves</a>
@ <a href="%s(g.zBaseURL)/timeline">Timeline</a>
}
if( g.okRdWiki ){
@ <a href="%s(g.zBaseURL)/wiki">Wiki</a>
}
#if 0
@ <font color="#888888">Search</font>
@ <font color="#888888">Ticket</font>
@ <font color="#888888">Reports</font>
#endif
if( g.okSetup ){
@ <a href="%s(g.zBaseURL)/setup">Setup</a>
}
if( !g.noPswd ){
@ <a href="%s(g.zBaseURL)/login">%s(zLogInOut)</a>
}
@ </div>
if( nSubmenu>0 ){
int i;
@ <div class="submenu">
qsort(aSubmenu, nSubmenu, sizeof(aSubmenu[0]), submenuCompare);
for(i=0; i<nSubmenu; i++){
struct Submenu *p = &aSubmenu[i];
if( p->zLink==0 ){
@ <span class="label">%h(p->zLabel)</span>
}else{
@ <a class="label" href="%s(p->zLink)">%h(p->zLabel)</a>
}
}
@ </div>
}
@ <div class="content">
g.cgiPanic = 1;
}
/*
** Draw the footer at the bottom of the page.
*/
void style_footer(void){
const char *zFooter;
if( pInterp==0 ) return;
zFooter = db_get("footer", (char*)zDefaultFooter);
@ </div>
SbS_Render(pInterp, zFooter);
SbS_Destroy(pInterp);
pInterp = 0;
}
/* @-comment: // */
/*
** The default page header.
*/
const char zDefaultHeader[] =
@ <html>
@ <head>
@ <title>[project_name html]: [title html]</title>
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
@ href="[baseurl puts]/timeline.rss">
@ <link rel="stylesheet" href="[baseurl puts]/style.css" type="text/css"
@ media="screen">
@ </head>
@ <body>
@ <div class="header">
@ <div class="logo">
@ <!-- <img src="logo.gif" alt="logo"><br></br> -->
@ <nobr>[project_name html]</nobr>
@ </div>
@ <div class="title">[title html]</div>
@ <div class="status"><nobr>
@ [/login exists enable_output] Logged in as [0 /login get html]
@ [/login exists not enable_output] Not logged in
@ [1 enable_output]
@ </nobr></div>
@ </div>
;
/*
** The default page footer
*/
const char zDefaultFooter[] =
@ <div class="footer">
@ Fossil version [manifest_version puts] [manifest_date puts]
@ </div>
@ </body></html>
;
/*
** The default Cascading Style Sheet.
*/
const char zDefaultCSS[] =
@ /* General settings for the entire page */
@ body {
@ margin: 0ex 1ex;
@ padding: 0px;
@ background-color: white;
@ font-family: "sans serif";
@ }
@
@ /* The project logo in the upper left-hand corner of each page */
@ div.logo {
@ display: table-cell;
@ text-align: center;
@ vertical-align: bottom;
@ font-weight: bold;
@ color: #558195;
@ }
@
@ /* The page title centered at the top of each page */
@ div.title {
@ display: table-cell;
@ font-size: 2em;
@ font-weight: bold;
@ text-align: center;
@ color: #558195;
@ vertical-align: bottom;
@ width: 100%;
@ }
@
@ /* The login status message in the top right-hand corner */
@ div.status {
@ display: table-cell;
@ text-align: right;
@ vertical-align: bottom;
@ color: #558195;
@ font-size: 0.8em;
@ font-weight: bold;
@ }
@
@ /* The header across the top of the page */
@ div.header {
@ display: table;
@ width: 100%;
@ }
@
@ /* The main menu bar that appears at the top of the page beneath
@ ** the header */
@ div.mainmenu {
@ padding: 5px 10px 5px 10px;
@ font-size: 0.9em;
@ font-weight: bold;
@ text-align: center;
@ letter-spacing: 1px;
@ background-color: #558195;
@ color: white;
@ }
@
@ /* The submenu bar that *sometimes* appears below the main menu */
@ div.submenu {
@ padding: 3px 10px 3px 0px;
@ font-size: 0.9em;
@ text-align: center;
@ background-color: #456878;
@ color: white;
@ }
@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited {
@ padding: 3px 10px 3px 10px;
@ color: white;
@ text-decoration: none;
@ }
@ div.mainmenu a:hover, div.submenu a:hover {
@ color: #558195;
@ background-color: white;
@ }
@
@ /* All page content from the bottom of the menu or submenu down to
@ ** the footer */
@ div.content {
@ padding: 0ex 1ex 0ex 2ex;
@ }
@
@ /* Some pages have section dividers */
@ div.section {
@ margin-bottom: 0px;
@ margin-top: 1em;
@ padding: 1px 1px 1px 1px;
@ font-size: 1.2em;
@ font-weight: bold;
@ background-color: #558195;
@ color: white;
@ }
@
@ /* The "Date" that occurs on the left hand side of timelines */
@ div.divider {
@ background: #a1c4d4;
@ border: 2px #558195 solid;
@ font-size: 1em; font-weight: normal;
@ padding: .25em;
@ margin: .2em 0 .2em 0;
@ float: left;
@ clear: left;
@ }
@
@ /* The footer at the very bottom of the page */
@ div.footer {
@ font-size: 0.8em;
@ margin-top: 12px;
@ padding: 5px 10px 5px 10px;
@ text-align: right;
@ background-color: #558195;
@ color: white;
@ }
@
@ /* The label/value pairs on (for example) the vinfo page */
@ table.label-value th {
@ vertical-align: top;
@ text-align: right;
@ padding: 0.2ex 2ex;
@ }
;
/*
** WEBPAGE: style.css
*/
void page_style_css(void){
char *zCSS = 0;
cgi_set_content_type("text/css");
zCSS = db_get("css",(char*)zDefaultCSS);
cgi_append_content(zCSS, -1);
}
/*
** WEBPAGE: test_env
*/
void page_test_env(void){
style_header("Environment Test");
@ g.zBaseURL = %h(g.zBaseURL)<br>
@ g.zTop = %h(g.zTop)<br>
cgi_print_all();
style_footer();
}
|
Added src/subscript.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 |
/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License version 2 as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains an implementation of the "subscript" interpreter.
**
** Subscript attempts to be an extremely light-weight scripting
** language. It contains the barest of bare essentials. It is
** stack-based and forth-like. Everything is in a single global
** namespace. There is only a single datatype of zero-terminated
** string. The stack is of fixed, limited depth. The symbal table
** is of a limited and fixed size.
**
** TOKENS:
**
** * All tokens are separated from each other by whitespace.
** * Leading and trailing whitespace is ignored.
** * Text within nested {...} is a single string token. The outermost
** curly braces are not part of the token.
** * An identifier with a leading "/" is a string token.
** * A token that looks like a number is a string token.
** * An identifier token is called a "verb".
**
** PROCESSING:
**
** * The input is divided into tokens. Whitespace is discarded.
** String and verb tokens are passed into the engine.
** * String tokens are pushed onto the stack.
** * If a verb token corresponds to a procedure, that procedure is
** run. The procedure might use, pop, or pull elements from
** the stack.
** * If a verb token corresponds to a variable, the value of that
** variable is pushed onto the stack.
**
** This module attempts to be completely self-contained so that it can
** be portable to other projects.
*/
#include "config.h"
#include "subscript.h"
#include <assert.h>
#if INTERFACE
typedef struct Subscript Subscript;
#define SBS_OK 0
#define SBS_ERROR 1
#define SBS_RETURN 2
#define SBS_BREAK 3
#endif
/*
** Configuration constants
*/
#define SBSCONFIG_NHASH 41 /* Size of the hash table */
#define SBSCONFIG_NSTACK 20 /* Maximum stack depth */
#define SBSCONFIG_ERRSIZE 100 /* Maximum size of an error message */
/*
** Available token types:
*/
#define SBSTT_WHITESPACE 1 /* ex: \040 */
#define SBSTT_NAME 2 /* ex: /abcde */
#define SBSTT_VERB 3 /* ex: abcde */
#define SBSTT_STRING 4 /* ex: {...} */
#define SBSTT_QUOTED 5 /* ex: "...\n..." */
#define SBSTT_INTEGER 6 /* Integer including option sign */
#define SBSTT_INCOMPLETE 7 /* Unterminated string token */
#define SBSTT_UNKNOWN 8 /* Unknown token */
#define SBSTT_EOF 9 /* End of input */
/*
** Values are stored in the hash table as instances of the following
** structure.
*/
typedef struct SbSValue SbSValue;
struct SbSValue {
int flags; /* Bitmask of SBSVAL_* values */
union {
struct {
int size; /* Number of bytes in string, not counting final zero */
char *z; /* Pointer to string content */
} str; /* Value if SBSVAL_STR */
struct {
int (*xVerb)(Subscript*, void*); /* Function to do the work */
void *pArg; /* 2nd parameter to xVerb */
} verb; /* Value if SBSVAL_VERB */
} u;
};
#define SBSVAL_VERB 0x0001 /* Value stored in u.verb */
#define SBSVAL_STR 0x0002 /* Value stored in u.str */
#define SBSVAL_DYN 0x0004 /* u.str.z is dynamically allocated */
#define SBSVAL_EXEC 0x0008 /* u.str.z is a script */
/*
** An entry in the hash table is an instance of this structure.
*/
typedef struct SbsHashEntry SbsHashEntry;
struct SbsHashEntry {
SbsHashEntry *pNext; /* Next entry with the same hash on zKey */
SbSValue val; /* The payload */
int nKey; /* Length of the key */
char zKey[1]; /* The key */
};
/*
** A hash table is an instance of the following structure.
*/
typedef struct SbsHashTab SbsHashTab;
struct SbsHashTab {
SbsHashEntry *aHash[SBSCONFIG_NHASH]; /* The hash table */
};
/*
** An instance of the Subscript interpreter
*/
struct Subscript {
int nStack; /* Number of entries on stack */
SbsHashTab symTab; /* The symbol table */
char zErrMsg[SBSCONFIG_ERRSIZE]; /* Space to write an error message */
SbSValue aStack[SBSCONFIG_NSTACK]; /* The stack */
};
/*
** Given an input string z of length n, identify the token that
** starts at z[0]. Write the token type into *pTokenType and
** return the length of the token.
*/
static int sbs_next_token(const char *z, int n, int *pTokenType){
int c;
if( n<=0 || z[0]==0 ){
*pTokenType = SBSTT_EOF;
return 0;
}
c = z[0];
if( isspace(c) ){
int i;
*pTokenType = SBSTT_WHITESPACE;
for(i=1; i<n && isspace(z[i]); i++){}
return i;
}
if( c=='#' ){
int i;
for(i=1; i<n && z[i] && z[i-1]!='\n'; i++){}
*pTokenType = SBSTT_WHITESPACE;
return i;
}
if( c=='"' ){
int i;
for(i=1; i<n && z[i] && z[i]!='"'; i++){
if( z[i]=='\\' && i<n-1 ){ i++; }
}
if( z[i]=='"' ){
*pTokenType = SBSTT_QUOTED;
return i+1;
}else{
*pTokenType = SBSTT_INCOMPLETE;
return i;
}
}
if( c=='{' ){
int depth = 1;
int i;
for(i=1; i<n && z[i]; i++){
if( z[i]=='{' ){
depth++;
}else if( z[i]=='}' ){
depth--;
if( depth==0 ){
i++;
break;
}
}
}
if( depth ){
*pTokenType = SBSTT_INCOMPLETE;
}else{
*pTokenType = SBSTT_STRING;
}
return i;
}
if( c=='/' && n>=2 && isalpha(z[1]) ){
int i;
for(i=2; i<n && (isalnum(z[i]) || z[i]=='_'); i++){}
*pTokenType = SBSTT_NAME;
return i;
}
if( isalpha(c) ){
int i;
for(i=1; i<n && (isalnum(z[i]) || z[i]=='_'); i++){}
*pTokenType = SBSTT_VERB;
return i;
}
if( isdigit(c) || ((c=='-' || c=='+') && n>=2 && isdigit(z[1])) ){
int i;
for(i=1; i<n && isdigit(z[i]); i++){}
*pTokenType = SBSTT_INTEGER;
return i;
}
*pTokenType = SBSTT_UNKNOWN;
return 1;
}
/*
** Release any memory allocated by a value.
*/
static void sbs_value_reset(SbSValue *p){
if( p->flags & SBSVAL_DYN ){
free(p->u.str.z);
p->flags = SBSVAL_STR;
p->u.str.z = "";
p->u.str.size = 0;
}
}
/*
** Compute a hash on a string.
*/
static int sbs_hash(const char *z, int n){
int h = 0;
int i;
for(i=0; i<n; i++){
h ^= (h<<1) | z[i];
}
h &= 0x7ffffff;
return h % SBSCONFIG_NHASH;
}
/*
** Look up a value in the hash table. Return a pointer to the value.
** Return NULL if not found.
*/
static const SbSValue *sbs_fetch(
SbsHashTab *pHash,
const char *zKey,
int nKey
){
int h;
SbsHashEntry *p;
if( nKey<0 ) nKey = strlen(zKey);
h = sbs_hash(zKey, nKey);
for(p = pHash->aHash[h]; p; p=p->pNext){
if( p->nKey==nKey && memcmp(p->zKey,zKey,nKey)==0 ){
return &p->val;
}
}
return 0;
}
/*
** Store a value in the hash table. Overwrite any prior value stored
** under the same name.
**
** If the value in the 4th argument needs to be reset or freed,
** the hash table will take over responsibiliity for doing so.
*/
static int sbs_store(
SbsHashTab *pHash, /* Insert into this hash table */
const char *zKey, /* The key */
int nKey, /* Size of the key */
const SbSValue *pValue /* The value to be stored */
){
int h;
SbsHashEntry *p, *pNew;
if( nKey<0 ) nKey = strlen(zKey);
h = sbs_hash(zKey, nKey);
for(p = pHash->aHash[h]; p; p=p->pNext){
if( p->nKey==nKey && memcmp(p->zKey,zKey,nKey)==0 ){
sbs_value_reset(&p->val);
memcpy(&p->val, pValue, sizeof(p->val));
return SBS_OK;
}
}
pNew = malloc( sizeof(*pNew) + nKey );
if( pNew ){
pNew->nKey = nKey;
memcpy(pNew->zKey, zKey, nKey+1);
memcpy(&pNew->val, pValue, sizeof(pNew->val));
pNew->pNext = pHash->aHash[h];
pHash->aHash[h] = pNew;
return SBS_OK;
}
return SBS_ERROR;
}
/*
** Reset a hash table.
*/
static void sbs_hash_reset(SbsHashTab *pHash){
int i;
SbsHashEntry *p, *pNext;
for(i=0; i<SBSCONFIG_NHASH; i++){
for(p=pHash->aHash[i]; p; p=pNext){
pNext = p->pNext;
sbs_value_reset(&p->val);
free(p);
}
}
memset(pHash, 0, sizeof(*pHash));
}
/*
** Push a value onto the stack of an interpreter
*/
static int sbs_push(Subscript *p, SbSValue *pVal){
if( p->nStack>=SBSCONFIG_NSTACK ){
sqlite3_snprintf(SBSCONFIG_ERRSIZE, p->zErrMsg, "stack overflow");
return SBS_ERROR;
}
p->aStack[p->nStack++] = *pVal;
return SBS_OK;
}
/*
** Create a new subscript interpreter. Return a pointer to the
** new interpreter, or return NULL if malloc fails.
*/
struct Subscript *SbS_Create(void){
Subscript *p;
p = malloc( sizeof(*p) );
if( p ){
memset(p, 0, sizeof(*p));
}
return p;
}
/*
** Destroy an subscript interpreter
*/
void SbS_Destroy(struct Subscript *p){
int i;
sbs_hash_reset(&p->symTab);
for(i=0; i<p->nStack; i++){
sbs_value_reset(&p->aStack[i]);
}
free(p);
}
/*
** Set the error message for an interpreter. Verb implementations
** use this routine when they encounter an error.
*/
void SbS_SetErrorMessage(struct Subscript *p, const char *zErr, ...){
int nErr;
char *zMsg;
va_list ap;
va_start(ap, zErr);
zMsg = vmprintf(zErr, ap);
va_end(ap);
nErr = strlen(zMsg);
if( nErr>sizeof(p->zErrMsg)-1 ){
nErr = sizeof(p->zErrMsg)-1;
}
memcpy(p->zErrMsg, zMsg, nErr);
p->zErrMsg[nErr] = 0;
free(zMsg);
}
/*
** Return a pointer to the current error message for the
** interpreter.
*/
const char *SbS_GetErrorMessage(struct Subscript *p){
return p->zErrMsg;
}
/*
** Add a new verb the given interpreter
*/
int SbS_AddVerb(
struct Subscript *p,
const char *zVerb,
int (*xVerb)(struct Subscript*,void*),
void *pArg
){
SbSValue v;
v.flags = SBSVAL_VERB;
v.u.verb.xVerb = xVerb;
v.u.verb.pArg = pArg;
return sbs_store(&p->symTab, zVerb, -1, &v);
}
/*
** Store a value in an interpreter variable.
*/
int SbS_Store(
struct Subscript *p, /* Store into this interpreter */
const char *zName, /* Name of the variable */
const char *zValue, /* Value of the variable */
int persistence /* 0: static. 1: ephemeral. 2: dynamic */
){
SbSValue v;
v.flags = SBSVAL_STR;
v.u.str.size = strlen(zValue);
if( persistence==1 ){
v.u.str.z = mprintf("%s", zValue);
}else{
v.u.str.z = (char*)zValue;
}
if( persistence>0 ){
v.flags |= SBSVAL_DYN;
}
return sbs_store(&p->symTab, zName, -1, &v);
}
/*
** Push a string value onto the stack.
**
** If the 4th parameter is 0, then the string is static.
** If the 4th parameter is non-zero then the string was obtained
** from malloc and Subscript will take responsibility for freeing
** it.
**
** Return 0 on success and non-zero if there is an error.
*/
int SbS_Push(
struct Subscript *p, /* Push onto this interpreter */
char *z, /* String value to push */
int n, /* Length of the string, or -1 */
int dyn /* If true, z was obtained from malloc */
){
SbSValue v;
v.flags = SBSVAL_STR;
if( dyn ){
v.flags |= SBSVAL_DYN;
}
if( n<0 ) n = strlen(z);
v.u.str.size = n;
v.u.str.z = z;
return sbs_push(p, &v);
}
/*
** Push an integer value onto the stack.
**
** This routine really just converts the integer into a string
** then calls SbS_Push.
*/
int SbS_PushInt(struct Subscript *p, int iVal){
if( iVal==0 ){
return SbS_Push(p, "0", 1, 0);
}else if( iVal==1 ){
return SbS_Push(p, "1", 1, 0);
}else{
char *z;
int n;
char zVal[50];
sprintf(zVal, "%d", iVal);
n = strlen(zVal);
z = malloc( n+1 );
if( z ){
strcpy(z, zVal);
return SbS_Push(p, z, n, 1);
}else{
return SBS_ERROR;
}
}
}
/*
** Pop and destroy zero or more values from the stack.
** Return the number of values remaining on the stack after
** the pops occur.
*/
int SbS_Pop(struct Subscript *p, int N){
while( N>0 && p->nStack>0 ){
p->nStack--;
sbs_value_reset(&p->aStack[p->nStack]);
N--;
}
return p->nStack;
}
/*
** Return the N-th element of the stack. 0 is the top of the stack.
** 1 is the first element down. 2 is the second element. And so forth.
** Return NULL if there is no N-th element.
**
** The pointer returned is only valid until the value is popped
** from the stack.
*/
const char *SbS_StackValue(struct Subscript *p, int N, int *pSize){
SbSValue *pVal;
if( N<0 || N>=p->nStack ){
return 0;
}
pVal = &p->aStack[p->nStack-N-1];
if( (pVal->flags & SBSVAL_STR)==0 ){
return 0;
}
*pSize = pVal->u.str.size;
return pVal->u.str.z;
}
/*
** A convenience routine for extracting an integer value from the
** stack.
*/
int SbS_StackValueInt(struct Subscript *p, int N){
int n, v;
int isNeg = 0;
const char *z = SbS_StackValue(p, N, &n);
v = 0;
if( n==0 ) return 0;
if( z[0]=='-' ){
isNeg = 1;
z++;
n--;
}else if( z[0]=='+' ){
z++;
n--;
}
while( n>0 && isdigit(z[0]) ){
v = v*10 + z[0] - '0';
z++;
n--;
}
if( isNeg ){
v = -v;
}
return v;
}
/*
** Retrieve the value of a variable from the interpreter. Return
** NULL if no such variable is defined.
**
** The returned string is not necessarily (probably not) zero-terminated.
** The string may be deallocated the next time anything is done to
** the interpreter. Make a copy if you need it to persist.
*/
const char *SbS_Fetch(
struct Subscript *p, /* The interpreter we are interrogating */
const char *zKey, /* Name of the variable. Case sensitive */
int nKey, /* Length of the key */
int *pLength /* Write the length here */
){
const SbSValue *pVal;
pVal = sbs_fetch(&p->symTab, zKey, nKey);
if( pVal==0 || (pVal->flags & SBSVAL_STR)==0 ){
*pLength = 0;
return 0;
}else{
*pLength = pVal->u.str.size;
return pVal->u.str.z;
}
}
/*
** Generate an error and return non-zero if the stack has
** fewer than N elements. This is utility routine used in
** the implementation of verbs.
*/
int SbS_RequireStack(struct Subscript *p, int N, const char *zCmd){
if( p->nStack>=N ) return 0;
sqlite3_snprintf(sizeof(p->zErrMsg), p->zErrMsg,
"\"%s\" requires at least %d stack elements - only found %d",
zCmd, N, p->nStack);
return 1;
}
/*
** Subscript command: STRING NAME set
**
** Write the value of STRING into variable called NAME.
*/
static int setCmd(Subscript *p, void *pNotUsed){
SbSValue *pTos;
SbSValue *pNos;
if( SbS_RequireStack(p, 2, "set") ) return SBS_ERROR;
pTos = &p->aStack[--p->nStack];
pNos = &p->aStack[--p->nStack];
sbs_store(&p->symTab, pTos->u.str.z, pTos->u.str.size, pNos);
sbs_value_reset(pTos);
return 0;
}
/*
** Subscript command: INTEGER not INTEGER
*/
static int notCmd(struct Subscript *p, void *pNotUsed){
int n;
if( SbS_RequireStack(p, 1, "not") ) return 1;
n = SbS_StackValueInt(p, 0);
SbS_Pop(p, 1);
SbS_PushInt(p, !n);
return 0;
}
#define SBSOP_ADD 1
#define SBSOP_SUB 2
#define SBSOP_MUL 3
#define SBSOP_DIV 4
#define SBSOP_AND 5
#define SBSOP_OR 6
#define SBSOP_MIN 7
#define SBSOP_MAX 8
#define SBSOP_EQ 9
#define SBSOP_NE 10
#define SBSOP_LT 11
#define SBSOP_GT 12
#define SBSOP_LE 13
#define SBSOP_GE 14
/*
** Subscript command: INTEGER INTEGER <binary-op> INTEGER
*/
static int bopCmd(struct Subscript *p, void *pOp){
int a, b, c;
if( SbS_RequireStack(p, 2, "BINARY-OP") ) return 1;
a = SbS_StackValueInt(p, 1);
b = SbS_StackValueInt(p, 0);
switch( (int)pOp ){
case SBSOP_EQ: c = a==b; break;
case SBSOP_NE: c = a!=b; break;
case SBSOP_LT: c = a<b; break;
case SBSOP_LE: c = a<=b; break;
case SBSOP_GT: c = a>b; break;
case SBSOP_GE: c = a>=b; break;
case SBSOP_ADD: c = a+b; break;
case SBSOP_SUB: c = a-b; break;
case SBSOP_MUL: c = a*b; break;
case SBSOP_DIV: c = b!=0 ? a/b : 0; break;
case SBSOP_AND: c = a && b; break;
case SBSOP_OR: c = a || b; break;
case SBSOP_MIN: c = a<b ? a : b; break;
case SBSOP_MAX: c = a<b ? b : a; break;
}
SbS_Pop(p, 2);
SbS_PushInt(p, c);
return 0;
}
/*
** Subscript command: STRING STRING streq INTEGER
*/
static int streqCmd(struct Subscript *p, void *pOp){
int c, nA, nB;
const char *A, *B;
if( SbS_RequireStack(p, 2, "BINARY-OP") ) return 1;
A = SbS_StackValue(p, 1, &nA);
B = SbS_StackValue(p, 0, &nB);
c = nA==nB && memcmp(A,B,nA)==0;
SbS_Pop(p, 2);
SbS_PushInt(p, c);
return 0;
}
/*
** Subscript command: STRING hascap INTEGER
**
** Return true if the user has all of the capabilities listed.
*/
static int hascapCmd(struct Subscript *p, void *pNotUsed){
const char *z;
int n, a;
if( SbS_RequireStack(p, 1, "hascap") ) return 1;
z = SbS_StackValue(p, 0, &n);
a = login_has_capability(z, n);
SbS_Pop(p, 1);
SbS_PushInt(p, a);
return 0;
}
/*
** Subscript command: STRING linecount INTEGER
**
** Return one more than the number of \n characters in STRING.
*/
static int linecntCmd(struct Subscript *p, void *pNotUsed){
const char *z;
int size, n, i;
if( SbS_RequireStack(p, 1, "linecount") ) return 1;
z = SbS_StackValue(p, 0, &size);
for(n=1, i=0; i<size; i++){
if( z[i]=='\n' ) n++;
}
SbS_Pop(p, 1);
SbS_PushInt(p, n);
return 0;
}
/*
** Subscript command: STRING length INTEGER
**
** Return one more than the number characters in STRING.
*/
static int lengthCmd(struct Subscript *p, void *pNotUsed){
int size;
if( SbS_RequireStack(p, 1, "length") ) return 1;
SbS_StackValue(p, 0, &size);
SbS_Pop(p, 1);
SbS_PushInt(p, size);
return 0;
}
/*
** Subscript command: NAME exists INTEGER
**
** Return TRUE if the variable NAME exists.
*/
static int existsCmd(struct Subscript *p, void *pNotUsed){
const char *z;
int size, x;
if( SbS_RequireStack(p, 1, "exists") ) return 1;
z = SbS_StackValue(p, 0, &size);
x = sbs_fetch(&p->symTab, (char*)z, size)!=0;
SbS_Pop(p, 1);
SbS_PushInt(p, x);
return 0;
}
/*
** Subscript command: VALUE NAME get VALUE
**
** Push the value of varible NAME onto the stack if it exists.
** If NAME does not exist, puts VALUE onto the stack instead.
*/
static int getCmd(Subscript *p, void *pNotUsed){
const char *zName;
int nName;
const SbSValue *pVal;
int rc = 0;
if( SbS_RequireStack(p, 2, "get") ) return SBS_ERROR;
zName = SbS_StackValue(p, 0, &nName);
pVal = sbs_fetch(&p->symTab, (char*)zName, nName);
if( pVal!=0 && (pVal->flags & SBSVAL_STR)!=0 ){
SbS_Pop(p, 2);
rc = SbS_Push(p, pVal->u.str.z, pVal->u.str.size, 0);
}else{
SbS_Pop(p, 1);
}
return rc;
}
/*
** True if output is enabled. False if disabled.
*/
static int enableOutput = 1;
/*
** Subscript command: BOOLEAN enable_output
**
** Enable or disable the puts and hputs commands.
*/
static int enableOutputCmd(struct Subscript *p, void *pNotUsed){
if( SbS_RequireStack(p, 1, "enable_output") ) return 1;
enableOutput = SbS_StackValueInt(p, 0)!=0;
SbS_Pop(p, 1);
return 0;
}
/*
** Send text to the appropriate output: Either to the console
** or to the CGI reply buffer.
*/
static void sendText(const char *z, int n){
if( enableOutput && n ){
if( n<0 ) n = strlen(z);
if( g.cgiPanic ){
cgi_append_content(z, n);
}else{
fwrite(z, 1, n, stdout);
}
}
}
/*
** Subscript command: STRING puts
** Subscript command: STRING html
**
** Output STRING as HTML (html) or unchanged (puts).
** Pop it from the stack.
*/
static int putsCmd(struct Subscript *p, void *pConvert){
int size;
const char *z;
char *zOut;
if( SbS_RequireStack(p, 1, "puts") ) return 1;
if( enableOutput ){
z = SbS_StackValue(p, 0, &size);
if( pConvert ){
zOut = htmlize(z, size);
size = strlen(zOut);
}else{
zOut = (char*)z;
}
sendText(zOut, size);
if( pConvert ){
free(zOut);
}
}
SbS_Pop(p, 1);
return 0;
}
/*
** Subscript command: STRING wiki
**
** Render the input string as wiki.
*/
static int wikiCmd(struct Subscript *p, void *pConvert){
int size;
const char *z;
if( SbS_RequireStack(p, 1, "wiki") ) return 1;
if( enableOutput ){
Blob src;
z = SbS_StackValue(p, 0, &size);
blob_init(&src, z, size);
wiki_convert(&src, 0, WIKI_INLINE);
blob_reset(&src);
}
SbS_Pop(p, 1);
return 0;
}
/*
** Subscript command: NAME TEXT-LIST NUMLINES combobox
**
** Generate an HTML combobox. NAME is both the name of the
** CGI parameter and the name of a variable that contains the
** currently selected value. TEXT-LIST is a list of possible
** values for the combobox. NUMLINES is 1 for a true combobox.
** If NUMLINES is greater than one then the display is a listbox
** with the number of lines given.
*/
static int comboboxCmd(struct Subscript *p, void *NotUsed){
if( SbS_RequireStack(p, 3, "combobox") ) return 1;
if( enableOutput ){
int height;
Blob list, elem;
char *zName, *zList;
int nName, nList, nValue;
const char *zValue;
char *z, *zH;
height = SbS_StackValueInt(p, 0);
zList = (char*)SbS_StackValue(p, 1, &nList);
blob_init(&list, zList, nList);
zName = (char*)SbS_StackValue(p, 2, &nName);
zValue = SbS_Fetch(p, zName, nName, &nValue);
z = mprintf("<select name=\"%z\" size=\"%d\">",
htmlize(zName, nName), height);
sendText(z, -1);
free(z);
while( blob_token(&list, &elem) ){
zH = htmlize(blob_buffer(&elem), blob_size(&elem));
if( zValue && blob_size(&elem)==nValue
&& memcmp(zValue, blob_buffer(&elem), nValue)==0 ){
z = mprintf("<option value=\"%s\" selected>%s</option>", zH, zH);
}else{
z = mprintf("<option value=\"%s\">%s</option>", zH, zH);
}
free(zH);
sendText(z, -1);
free(z);
}
sendText("</select>", -1);
blob_reset(&list);
}
SbS_Pop(p, 3);
return 0;
}
/*
** Subscript command: STRING BOOLEAN if
**
** Evaluate STRING as a script if BOOLEAN is true.
*/
static int ifCmd(struct Subscript *p, void *pNotUsed){
int cond;
int rc = SBS_OK;
if( SbS_RequireStack(p, 2, "if") ) return 1;
cond = SbS_StackValueInt(p, 0);
if( cond ){
SbSValue script = p->aStack[p->nStack-2];
p->aStack[p->nStack-2].flags = 0;
SbS_Pop(p, 2);
rc = SbS_Eval(p, script.u.str.z, script.u.str.size);
sbs_value_reset(&script);
}else{
SbS_Pop(p, 2);
}
return rc;
}
/*
** Subscript command: ... STRING COUNT concat STRING
**
** Concatenate COUNT strings into a single string and leave
** the concatenation on the stack.
*/
static int concatCmd(struct Subscript *p, void *pNotUsed){
int count;
int nByte;
char *z;
int i, j;
if( SbS_RequireStack(p, 1, "concat") ) return SBS_ERROR;
count = SbS_StackValueInt(p, 0);
if( SbS_RequireStack(p, count+1, "concat") ) return SBS_ERROR;
SbS_Pop(p, 1);
nByte = 1;
for(i=p->nStack-count; i<p->nStack; i++){
nByte += p->aStack[i].u.str.size;
}
z = malloc(nByte);
if( z==0 ){ fossil_panic("out of memory"); }
for(j=0, i=p->nStack-count; i<p->nStack; i++){
nByte = p->aStack[i].u.str.size;
memcpy(&z[j], p->aStack[i].u.str.z, nByte);
j += nByte;
}
z[j] = 0;
SbS_Pop(p, count);
SbS_Push(p, z, j, 1);
return SBS_OK;
}
/*
** A table of built-in commands
*/
static const struct {
const char *zCmd;
int (*xCmd)(Subscript*,void*);
void *pArg;
} aBuiltin[] = {
{ "add", bopCmd, (void*)SBSOP_AND },
{ "and", bopCmd, (void*)SBSOP_AND },
{ "combobox", comboboxCmd, 0, },
{ "concat", concatCmd, 0, },
{ "div", bopCmd, (void*)SBSOP_DIV },
{ "enable_output", enableOutputCmd, 0 },
{ "eq", bopCmd, (void*)SBSOP_EQ },
{ "exists", existsCmd, 0, },
{ "get", getCmd, 0, },
{ "hascap", hascapCmd, 0 },
{ "html", putsCmd, (void*)1 },
{ "if", ifCmd, 0, },
{ "le", bopCmd, (void*)SBSOP_LE },
{ "length", lengthCmd, 0 },
{ "linecount", linecntCmd, 0 },
{ "lt", bopCmd, (void*)SBSOP_LT },
{ "max", bopCmd, (void*)SBSOP_MAX },
{ "min", bopCmd, (void*)SBSOP_MIN },
{ "mul", bopCmd, (void*)SBSOP_MUL },
{ "not", notCmd, 0 },
{ "or", bopCmd, (void*)SBSOP_OR },
{ "puts", putsCmd, 0 },
{ "set", setCmd, 0 },
{ "streq", streqCmd, 0 },
{ "sub", bopCmd, (void*)SBSOP_SUB },
{ "wiki", wikiCmd, 0, },
};
/*
** Compare a zero-terminated string zPattern against
** an unterminated string zStr of length nStr.
**
** Return less than, equal to, or greater than zero if
** zPattern is less than, equal to, or greater than zStr.
*/
static int compare_cmd(const char *zPattern, const char *zStr, int nStr){
int c = strncmp(zPattern, zStr, nStr);
if( c==0 && zPattern[nStr]!=0 ){
c = 1;
}
return c;
}
/*
** Evaluate the script given by the first nScript bytes of zScript[].
** Return 0 on success and non-zero for an error.
*/
int SbS_Eval(struct Subscript *p, const char *zScript, int nScript){
int rc = SBS_OK;
if( nScript<0 ) nScript = strlen(zScript);
while( nScript>0 && rc==SBS_OK ){
int n;
int ttype;
n = sbs_next_token(zScript, nScript, &ttype);
#if 0
{
int i, nElem;
const char *zElem;
if( p->nStack>0 ){
printf("STACK:");
for(i=0; i<p->nStack; i++){
zElem = SbS_StackValue(p, i, &nElem);
printf(" [%.*s]", nElem, zElem);
}
printf("\n");
}
printf("TOKEN(%d): [%.*s]\n", ttype, n, zScript);
}
#endif
switch( ttype ){
case SBSTT_WHITESPACE: {
break;
}
case SBSTT_EOF: {
nScript = 0;
break;
}
case SBSTT_INCOMPLETE:
case SBSTT_UNKNOWN: {
rc = SBS_ERROR;
nScript = n;
break;
}
case SBSTT_INTEGER: {
rc = SbS_Push(p, (char*)zScript, n, 0);
break;
}
case SBSTT_NAME: {
rc = SbS_Push(p, (char*)&zScript[1], n-1, 0);
break;
}
case SBSTT_STRING: {
rc = SbS_Push(p, (char*)&zScript[1], n-2, 0);
break;
}
case SBSTT_QUOTED: {
char *z = mprintf("%.*s", n-2, &zScript[1]);
int i, j;
for(i=j=0; z[i]; i++, j++){
int c = z[i];
if( c=='\\' && z[i+1] ){
c = z[++i];
if( c=='n' ){
c = '\n';
}else if( c>='0' && c<='7' ){
int k;
c -= '0';
for(k=1; k<3 && z[i+k]>='0' && z[i+k]<='7'; k++){
c = c*8 + z[i+k] - '0';
}
i += k-1;
}
}
z[j] = c;
}
z[j] = 0;
rc = SbS_Push(p, z, j, 1);
break;
}
case SBSTT_VERB: {
/* First look up the verb in the hash table */
const SbSValue *pVal = sbs_fetch(&p->symTab, (char*)zScript, n);
if( pVal==0 ){
/* If the verb is not in the hash table, look for a
** built-in command */
int upr = sizeof(aBuiltin)/sizeof(aBuiltin[0]) - 1;
int lwr = 0;
rc = SBS_ERROR;
while( upr>=lwr ){
int i = (upr+lwr)/2;
int c = compare_cmd(aBuiltin[i].zCmd, zScript, n);
if( c==0 ){
rc = aBuiltin[i].xCmd(p, aBuiltin[i].pArg);
break;
}else if( c>0 ){
upr = i-1;
}else{
lwr = i+1;
}
}
if( upr<lwr ){
SbS_SetErrorMessage(p, "unknown verb: %.*s", n, zScript);
}
}else if( pVal->flags & SBSVAL_VERB ){
rc = pVal->u.verb.xVerb(p, pVal->u.verb.pArg);
}else if( pVal->flags & SBSVAL_EXEC ){
rc = SbS_Eval(p, pVal->u.str.z, pVal->u.str.size);
}else{
rc = SbS_Push(p, pVal->u.str.z, pVal->u.str.size, 0);
}
break;
}
}
zScript += n;
nScript -= n;
}
return rc;
}
/*
** The z[] input contains text mixed with subscript scripts.
** The subscript scripts are contained within [...]. This routine
** processes the template and writes the results on either
** stdout or into CGI.
*/
int SbS_Render(struct Subscript *p, const char *z){
int i = 0;
int rc = SBS_OK;
while( z[i] ){
if( z[i]=='[' ){
sendText(z, i);
z += i+1;
for(i=0; z[i] && z[i]!=']'; i++){}
rc = SbS_Eval(p, z, i);
if( rc!=SBS_OK ) break;
if( z[i] ) i++;
z += i;
i = 0;
}else{
i++;
}
}
if( rc==SBS_ERROR ){
sendText("<hr><p><font color=\"red\"><b>ERROR: ", -1);
sendText(SbS_GetErrorMessage(p), -1);
sendText("</b></font></p>", -1);
}else{
sendText(z, i);
}
return rc;
}
/*
** COMMAND: test-subscript
*/
void test_subscript(void){
Subscript *p;
Blob in;
if( g.argc<3 ){
usage("FILE");
}
blob_zero(&in);
blob_read_from_file(&in, g.argv[2]);
p = SbS_Create();
SbS_Render(p, blob_str(&in));
}
|
Changes to src/sync.c.
| ︙ | ︙ | |||
22 23 24 25 26 27 28 29 30 31 32 33 34 35 | ******************************************************************************* ** ** This file contains code used to push, pull, and sync a repository */ #include "config.h" #include "sync.h" #include <assert.h> /* ** This routine processes the command-line argument for push, pull, ** and sync. If a command-line argument is given, that is the URL ** of a server to sync against. If no argument is given, use the ** most recently synced URL. Remember the current URL for next time. */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
*******************************************************************************
**
** This file contains code used to push, pull, and sync a repository
*/
#include "config.h"
#include "sync.h"
#include <assert.h>
/*
** If the respository is configured for autosyncing, then do an
** autosync. This will be a pull if the argument is true or a push
** if the argument is false. Return true if the autosync is done
** and false if autosync is not requested for the current repository.
*/
int autosync(int pullFlag){
const char *zUrl;
if( db_get_boolean("autosync", 0)==0 ){
return 0;
}
zUrl = db_get("last-sync-url", 0);
if( zUrl==0 ){
return 0; /* No default server */
}
url_parse(zUrl);
if( g.urlIsFile ){
return 0; /* Network sync only */
}
if( g.urlPort!=80 ){
printf("Autosync: http://%s:%d%s\n", g.urlName, g.urlPort, g.urlPath);
}else{
printf("Autosync: http://%s%s\n", g.urlName, g.urlPath);
}
client_sync(!pullFlag, pullFlag, 0);
return 1;
}
/*
** This routine processes the command-line argument for push, pull,
** and sync. If a command-line argument is given, that is the URL
** of a server to sync against. If no argument is given, use the
** most recently synced URL. Remember the current URL for next time.
*/
|
| ︙ | ︙ | |||
44 45 46 47 48 49 50 |
if( zUrl==0 ){
usage("URL");
}
url_parse(zUrl);
if( g.urlIsFile ){
fossil_fatal("network sync only");
}
| | | 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
if( zUrl==0 ){
usage("URL");
}
url_parse(zUrl);
if( g.urlIsFile ){
fossil_fatal("network sync only");
}
db_set("last-sync-url", zUrl, 0);
user_select();
if( g.argc==2 ){
if( g.urlPort!=80 ){
printf("Server: http://%s:%d%s\n", g.urlName, g.urlPort, g.urlPath);
}else{
printf("Server: http://%s%s\n", g.urlName, g.urlPath);
}
|
| ︙ | ︙ |
Added src/tag.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 |
/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License version 2 as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to manage tags
*/
#include "config.h"
#include "tag.h"
#include <assert.h>
/*
** Propagate the tag given by tagid to the children of pid.
**
** This routine assumes that tagid is a tag that should be
** propagated and that the tag is already present in pid.
**
** If tagtype is 2 then the tag is being propagated from an
** ancestor node. If tagtype is 0 it means a branch tag is
** being cancelled.
*/
void tag_propagate(
int pid, /* Propagate the tag to children of this node */
int tagid, /* Tag to propagate */
int tagType, /* 2 for a propagating tag. 0 for an antitag */
const char *zValue, /* Value of the tag. Might be NULL */
double mtime /* Timestamp on the tag */
){
PQueue queue;
Stmt s, ins, eventupdate;
assert( tagType==0 || tagType==2 );
pqueue_init(&queue);
pqueue_insert(&queue, pid, 0.0);
db_prepare(&s,
"SELECT cid, plink.mtime,"
" coalesce(srcid=0 AND tagxref.mtime<:mtime, %d) AS doit"
" FROM plink LEFT JOIN tagxref ON cid=rid AND tagid=%d"
" WHERE pid=:pid AND isprim",
tagType!=0, tagid
);
db_bind_double(&s, ":mtime", mtime);
if( tagType==2 ){
db_prepare(&ins,
"REPLACE INTO tagxref(tagid, tagtype, srcid, value, mtime, rid)"
"VALUES(%d,2,0,%Q,:mtime,:rid)",
tagid, zValue
);
db_bind_double(&ins, ":mtime", mtime);
}else{
zValue = 0;
db_prepare(&ins,
"DELETE FROM tagxref WHERE tagid=%d AND rid=:rid", tagid
);
}
if( tagid==TAG_BGCOLOR ){
db_prepare(&eventupdate,
"UPDATE event SET brbgcolor=%Q WHERE objid=:rid", zValue
);
}
while( (pid = pqueue_extract(&queue))!=0 ){
db_bind_int(&s, ":pid", pid);
while( db_step(&s)==SQLITE_ROW ){
int doit = db_column_int(&s, 2);
if( doit ){
int cid = db_column_int(&s, 0);
double mtime = db_column_double(&s, 1);
pqueue_insert(&queue, cid, mtime);
db_bind_int(&ins, ":rid", cid);
db_step(&ins);
db_reset(&ins);
if( tagid==TAG_BGCOLOR ){
db_bind_int(&eventupdate, ":rid", cid);
db_step(&eventupdate);
db_reset(&eventupdate);
}
}
}
db_reset(&s);
}
pqueue_clear(&queue);
db_finalize(&ins);
db_finalize(&s);
if( tagid==TAG_BGCOLOR ){
db_finalize(&eventupdate);
}
}
/*
** Propagate all propagatable tags in pid to its children.
*/
void tag_propagate_all(int pid){
Stmt q;
db_prepare(&q,
"SELECT tagid, tagtype, mtime, value FROM tagxref"
" WHERE rid=%d"
" AND (tagtype=0 OR tagtype=2)",
pid
);
while( db_step(&q)==SQLITE_ROW ){
int tagid = db_column_int(&q, 0);
int tagtype = db_column_int(&q, 1);
double mtime = db_column_double(&q, 2);
const char *zValue = db_column_text(&q, 3);
tag_propagate(pid, tagid, tagtype, zValue, mtime);
}
db_finalize(&q);
}
/*
** Get a tagid for the given TAG. Create a new tag if necessary
** if createFlag is 1.
*/
int tag_findid(const char *zTag, int createFlag){
int id;
id = db_int(0, "SELECT tagid FROM tag WHERE tagname=%Q", zTag);
if( id==0 && createFlag ){
db_multi_exec("INSERT INTO tag(tagname) VALUES(%Q)", zTag);
id = db_last_insert_rowid();
}
return id;
}
/*
** Insert a tag into the database.
*/
void tag_insert(
const char *zTag, /* Name of the tag (w/o the "+" or "-" prefix */
int tagtype, /* 0:cancel 1:singleton 2:propagated */
const char *zValue, /* Value if the tag is really a property */
int srcId, /* Artifact that contains this tag */
double mtime, /* Timestamp. Use default if <=0.0 */
int rid /* Artifact to which the tag is to attached */
){
Stmt s;
const char *zCol;
int tagid = tag_findid(zTag, 1);
int rc;
if( mtime<=0.0 ){
mtime = db_double(0.0, "SELECT julianday('now')");
}
db_prepare(&s,
"SELECT 1 FROM tagxref"
" WHERE tagid=%d"
" AND rid=%d"
" AND mtime>=:mtime",
tagid, rid
);
db_bind_double(&s, ":mtime", mtime);
rc = db_step(&s);
db_finalize(&s);
if( rc==SQLITE_ROW ){
/* Another entry this is more recent already exists. Do nothing */
return;
}
db_prepare(&s,
"REPLACE INTO tagxref(tagid,tagtype,srcId,value,mtime,rid)"
" VALUES(%d,%d,%d,%Q,:mtime,%d)",
tagid, tagtype, srcId, zValue, rid
);
db_bind_double(&s, ":mtime", mtime);
db_step(&s);
db_finalize(&s);
if( tagtype==0 ){
zValue = 0;
}
zCol = 0;
switch( tagid ){
case TAG_BGCOLOR: {
if( tagtype==1 ){
zCol = "bgcolor";
}else{
zCol = "brbgcolor";
}
break;
}
case TAG_COMMENT: {
zCol = "ecomment";
break;
}
case TAG_USER: {
zCol = "euser";
break;
}
}
if( zCol ){
db_multi_exec("UPDATE event SET %s=%Q WHERE objid=%d", zCol, zValue, rid);
}
if( tagtype==0 || tagtype==2 ){
tag_propagate(rid, tagid, tagtype, zValue, mtime);
}
}
/*
** COMMAND: test-tag
** %fossil test-tag (+|*|-)TAGNAME UUID ?VALUE?
**
** Add a tag or anti-tag to the rebuildable tables of the local repository.
** No tag artifact is created so the new tag is erased the next
** time the repository is rebuilt. This routine is for testing
** use only.
*/
void testtag_cmd(void){
const char *zTag;
const char *zValue;
int rid;
int tagtype;
db_must_be_within_tree();
if( g.argc!=4 && g.argc!=5 ){
usage("TAGNAME UUID ?VALUE?");
}
zTag = g.argv[2];
switch( zTag[0] ){
case '+': tagtype = 1; break;
case '*': tagtype = 2; break;
case '-': tagtype = 0; break;
default:
fossil_fatal("tag should begin with '+', '*', or '-'");
return;
}
rid = name_to_rid(g.argv[3]);
if( rid==0 ){
fossil_fatal("no such object: %s", g.argv[3]);
}
zValue = g.argc==5 ? g.argv[4] : 0;
db_begin_transaction();
tag_insert(zTag, tagtype, zValue, -1, 0.0, rid);
db_end_transaction(0);
}
/*
** Add a control record to the repository that either creates
** or cancels a tag.
*/
static void tag_add_artifact(
const char *zTagname, /* The tag to add or cancel */
const char *zObjName, /* Name of object attached to */
const char *zValue, /* Value for the tag. Might be NULL */
int tagtype /* 0:cancel 1:singleton 2:propagated */
){
int rid;
int nrid;
char *zDate;
Blob uuid;
Blob ctrl;
Blob cksum;
static const char zTagtype[] = { '-', '+', '*' };
assert( tagtype>=0 && tagtype<=2 );
user_select();
rid = name_to_rid(zObjName);
blob_zero(&uuid);
db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d", rid);
blob_zero(&ctrl);
if( validate16(zTagname, strlen(zTagname)) ){
fossil_fatal("invalid tag name \"%s\" - might be confused with a UUID",
zTagname);
}
zDate = db_text(0, "SELECT datetime('now')");
zDate[10] = 'T';
blob_appendf(&ctrl, "D %s\n", zDate);
blob_appendf(&ctrl, "T %c%F %b", zTagtype[tagtype], zTagname, &uuid);
if( tagtype && zValue && zValue[0] ){
blob_appendf(&ctrl, " %F\n", zValue);
}else{
blob_appendf(&ctrl, "\n");
}
blob_appendf(&ctrl, "U %F\n", g.zLogin);
md5sum_blob(&ctrl, &cksum);
blob_appendf(&ctrl, "Z %b\n", &cksum);
db_begin_transaction();
nrid = content_put(&ctrl, 0, 0);
manifest_crosslink(nrid, &ctrl);
db_end_transaction(0);
/* Do an autosync push if requested */
autosync(0);
}
/*
** COMMAND: tag
** Usage: %fossil tag SUBCOMMAND ...
**
** Run various subcommands to control tags and properties
**
** %fossil tag add TAGNAME UUID ?VALUE?
**
** Add a new tag or property to UUID.
**
** %fossil tag branch TAGNAME UUID ?VALUE?
**
** Add a new tag or property to UUID and make that
** tag propagate to all direct children.
**
** %fossil tag delete TAGNAME UUID
**
** Delete the tag TAGNAME from UUID
**
** %fossil tag find TAGNAME
**
** List all baselines that use TAGNAME
**
** %fossil tag list ?UUID?
**
** List all tags, or if UUID is supplied, list
** all tags and their values for UUID.
*/
void tag_cmd(void){
int n;
db_find_and_open_repository();
if( g.argc<3 ){
goto tag_cmd_usage;
}
n = strlen(g.argv[2]);
if( n==0 ){
goto tag_cmd_usage;
}
if( strncmp(g.argv[2],"add",n)==0 ){
char *zValue;
if( g.argc!=5 && g.argc!=6 ){
usage("add TAGNAME UUID ?VALUE?");
}
zValue = g.argc==6 ? g.argv[5] : 0;
tag_add_artifact(g.argv[3], g.argv[4], zValue, 1);
}else
if( strncmp(g.argv[2],"branch",n)==0 ){
char *zValue;
if( g.argc!=5 && g.argc!=6 ){
usage("branch TAGNAME UUID ?VALUE?");
}
zValue = g.argc==6 ? g.argv[5] : 0;
tag_add_artifact(g.argv[3], g.argv[4], zValue, 2);
}else
if( strncmp(g.argv[2],"delete",n)==0 ){
if( g.argc!=5 ){
usage("delete TAGNAME UUID");
}
tag_add_artifact(g.argv[3], g.argv[4], 0, 0);
}else
if( strncmp(g.argv[2],"find",n)==0 ){
Stmt q;
if( g.argc!=4 ){
usage("find TAGNAME");
}
db_prepare(&q,
"SELECT blob.uuid FROM tagxref, blob"
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
" AND blob.rid=tagxref.rid", g.argv[3]
);
while( db_step(&q)==SQLITE_ROW ){
printf("%s\n", db_column_text(&q, 0));
}
db_finalize(&q);
}else
if( strncmp(g.argv[2],"list",n)==0 ){
Stmt q;
if( g.argc==3 ){
db_prepare(&q,
"SELECT tagname"
" FROM tag"
" WHERE EXISTS(SELECT 1 FROM tagxref"
" WHERE tagid=tag.tagid"
" AND tagtype>0)"
" ORDER BY tagname"
);
while( db_step(&q)==SQLITE_ROW ){
printf("%s\n", db_column_text(&q, 0));
}
db_finalize(&q);
}else if( g.argc==4 ){
int rid = name_to_rid(g.argv[3]);
db_prepare(&q,
"SELECT tagname, value"
" FROM tagxref, tag"
" WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
" AND tagtype>0"
" ORDER BY tagname",
rid
);
while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
const char *zValue = db_column_text(&q, 1);
if( zValue ){
printf("%s=%s\n", zName, zValue);
}else{
printf("%s\n", zName);
}
}
db_finalize(&q);
}else{
usage("tag list ?UUID?");
}
}else
{
goto tag_cmd_usage;
}
return;
tag_cmd_usage:
usage("add|branch|delete|find|list ...");
}
|
Changes to src/timeline.c.
| ︙ | ︙ | |||
31 32 33 34 35 36 37 |
/*
** Generate a hyperlink to a version.
*/
void hyperlink_to_uuid(const char *zUuid){
char zShortUuid[UUID_SIZE+1];
sprintf(zShortUuid, "%.10s", zUuid);
if( g.okHistory ){
| | | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/*
** Generate a hyperlink to a version.
*/
void hyperlink_to_uuid(const char *zUuid){
char zShortUuid[UUID_SIZE+1];
sprintf(zShortUuid, "%.10s", zUuid);
if( g.okHistory ){
@ <a href="%s(g.zBaseURL)/info/%s(zUuid)">[%s(zShortUuid)]</a>
}else{
@ <b>[%s(zShortUuid)]</b>
}
}
/*
** Generate a hyperlink that invokes javascript to highlight
|
| ︙ | ︙ | |||
73 74 75 76 77 78 79 |
@ <a href="%s(g.zBaseURL)/diff?v1=%s(zV1)&v2=%s(zV2)">[diff]</a>
}
}
}
/*
** Output a timeline in the web format given a query. The query
| | > > < > > > > > > > > > > > > > > > > > < < < | < < > > > | > > | | | | | | | | | | > > > | > > > > > > > > > > > | | 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
@ <a href="%s(g.zBaseURL)/diff?v1=%s(zV1)&v2=%s(zV2)">[diff]</a>
}
}
}
/*
** Output a timeline in the web format given a query. The query
** should return these columns:
**
** 0. rid
** 1. UUID
** 2. Date/Time
** 3. Comment string
** 4. User
** 5. Number of non-merge children
** 6. Number of parents
** 7. True if is a leaf
** 8. background color
** 9. type ("ci", "w")
*/
void www_print_timeline(
Stmt *pQuery,
int *pFirstEvent,
int *pLastEvent,
int (*xCallback)(int, Blob*),
Blob *pArg
){
int cnt = 0;
int wikiFlags;
int mxWikiLen;
Blob comment;
char zPrevDate[20];
zPrevDate[0] = 0;
mxWikiLen = db_get_int("timeline-max-comment", 0);
if( db_get_boolean("timeline-block-markup", 0) ){
wikiFlags = WIKI_INLINE;
}else{
wikiFlags = WIKI_INLINE | WIKI_NOBLOCK;
}
db_multi_exec(
"CREATE TEMP TABLE IF NOT EXISTS seen(rid INTEGER PRIMARY KEY);"
"DELETE FROM seen;"
);
@ <table cellspacing=0 border=0 cellpadding=0>
blob_zero(&comment);
while( db_step(pQuery)==SQLITE_ROW ){
int rid = db_column_int(pQuery, 0);
const char *zUuid = db_column_text(pQuery, 1);
int nPChild = db_column_int(pQuery, 5);
int nParent = db_column_int(pQuery, 6);
int isLeaf = db_column_int(pQuery, 7);
const char *zBgClr = db_column_text(pQuery, 8);
const char *zDate = db_column_text(pQuery, 2);
const char *zType = db_column_text(pQuery, 9);
const char *zUser = db_column_text(pQuery, 4);
if( cnt==0 && pFirstEvent ){
*pFirstEvent = rid;
}
cnt++;
if( pLastEvent ){
*pLastEvent = rid;
}
db_multi_exec("INSERT OR IGNORE INTO seen VALUES(%d)", rid);
if( xCallback ){
xCallback(rid, pArg);
}
if( memcmp(zDate, zPrevDate, 10) ){
sprintf(zPrevDate, "%.10s", zDate);
@ <tr><td colspan=3>
@ <div class="divider">%s(zPrevDate)</div>
@ </td></tr>
}
@ <tr>
@ <td valign="top">%s(&zDate[11])</td>
@ <td width="20" align="center" valign="top">
@ <font id="m%d(rid)" size="+1" color="white">*</font></td>
if( zBgClr && zBgClr[0] ){
@ <td valign="top" align="left" bgcolor="%h(zBgClr)">
}else{
@ <td valign="top" align="left">
}
if( zType[0]=='c' ){
hyperlink_to_uuid_with_mouseover(zUuid, "xin", "xout", rid);
if( nParent>1 ){
@ <b>Merge</b>
}
if( nPChild>1 ){
@ <b>Fork</b>
}
if( isLeaf ){
@ <b>Leaf</b>
}
}else{
hyperlink_to_uuid(zUuid);
}
db_column_blob(pQuery, 3, &comment);
if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
Blob truncated;
blob_zero(&truncated);
blob_append(&truncated, blob_buffer(&comment), mxWikiLen);
blob_append(&truncated, "...", 3);
wiki_convert(&truncated, 0, wikiFlags);
blob_reset(&truncated);
}else{
wiki_convert(&comment, 0, wikiFlags);
}
blob_reset(&comment);
@ (by %h(zUser))</td></tr>
}
@ </table>
}
/*
** Generate javascript code that records the parents and children
** of the version rid.
|
| ︙ | ︙ | |||
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
zSep = ",";
}
db_finalize(&q);
blob_appendf(pOut, "];\n");
return 0;
}
/*
** WEBPAGE: timeline
**
** Query parameters:
**
** d=STARTDATE date in iso8601 notation. dflt: newest event
** n=INTEGER number of events to show. dflt: 25
** e=INTEGER starting event id. dflt: nil
** u=NAME show only events from user. dflt: nil
** a show events after and including. dflt: false
** r show only related events. dflt: false
*/
void page_timeline(void){
Stmt q;
char *zSQL;
Blob scriptInit;
char zDate[100];
const char *zStart = P("d");
int nEntry = atoi(PD("n","20"));
const char *zUser = P("u");
int objid = atoi(PD("e","0"));
int relatedEvents = P("r")!=0;
int afterFlag = P("a")!=0;
int firstEvent;
int lastEvent;
/* To view the timeline, must have permission to read project data.
*/
login_check_credentials();
if( !g.okRead ){ login_needed(); return; }
style_header("Timeline");
if( !g.okHistory &&
db_exists("SELECT 1 FROM user"
" WHERE login='anonymous'"
" AND cap LIKE '%%h%%'") ){
@ <p><b>Note:</b> You will be able to access <u>much</u> more
| > > > > > > > > > > > > > > > > > > > > > > > > > > > | < < < | < | > | < > | | > | | | > > > > | > > > > > > > > > > > | > > | 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 |
blob_appendf(pOut, "%s\"m%d\"", zSep, pid);
zSep = ",";
}
db_finalize(&q);
blob_appendf(pOut, "];\n");
return 0;
}
/*
** Return a pointer to a constant string that forms the basis
** for a timeline query for the WWW interface.
*/
const char *timeline_query_for_www(void){
static const char zBaseSql[] =
@ SELECT
@ blob.rid,
@ uuid,
@ datetime(event.mtime,'localtime') AS timestamp,
@ coalesce(ecomment, comment),
@ coalesce(euser, user),
@ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim=1),
@ (SELECT count(*) FROM plink WHERE cid=blob.rid),
@ NOT EXISTS (SELECT 1 FROM plink WHERE pid=blob.rid),
@ coalesce(bgcolor, brbgcolor),
@ event.type
@ FROM event JOIN blob
@ WHERE blob.rid=event.objid
;
return zBaseSql;
}
/*
** WEBPAGE: timeline
**
** Query parameters:
**
** d=STARTDATE date in iso8601 notation. dflt: newest event
** n=INTEGER number of events to show. dflt: 25
** e=INTEGER starting event id. dflt: nil
** u=NAME show only events from user. dflt: nil
** a show events after and including. dflt: false
** r show only related events. dflt: false
** y=TYPE show only TYPE ('ci' or 'w') dflt: nil
** s show the SQL dflt: nil
*/
void page_timeline(void){
Stmt q;
Blob sql;
char *zSQL;
Blob scriptInit;
char zDate[100];
const char *zStart = P("d");
int nEntry = atoi(PD("n","20"));
const char *zUser = P("u");
int objid = atoi(PD("e","0"));
int relatedEvents = P("r")!=0;
int afterFlag = P("a")!=0;
const char *zType = P("y");
int firstEvent;
int lastEvent;
/* To view the timeline, must have permission to read project data.
*/
login_check_credentials();
if( !g.okRead ){ login_needed(); return; }
style_header("Timeline");
if( !g.okHistory &&
db_exists("SELECT 1 FROM user"
" WHERE login='anonymous'"
" AND cap LIKE '%%h%%'") ){
@ <p><b>Note:</b> You will be able to access <u>much</u> more
@ historical information if <a href="%s(g.zTop)/login">login</a>.</p>
}
blob_zero(&sql);
blob_append(&sql, timeline_query_for_www(), -1);
if( zType ){
blob_appendf(&sql, " AND event.type=%Q", zType);
}
if( zUser ){
blob_appendf(&sql, " AND event.user=%Q", zUser);
}
if( objid ){
char *z = db_text(0, "SELECT datetime(event.mtime, 'localtime') FROM event"
" WHERE objid=%d", objid);
if( z ){
zStart = z;
}
}
if( zStart ){
while( isspace(zStart[0]) ){ zStart++; }
if( zStart[0] ){
blob_appendf(&sql,
" AND event.mtime %s (SELECT julianday(%Q, 'utc'))",
afterFlag ? ">=" : "<=", zStart);
}
}
if( relatedEvents && objid ){
db_multi_exec(
"CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
);
if( afterFlag ){
compute_descendents(objid, nEntry);
}else{
compute_ancestors(objid, nEntry);
}
blob_append(&sql, " AND event.objid IN ok", -1);
}
if( afterFlag ){
blob_appendf(&sql, " ORDER BY event.mtime ASC LIMIT %d",
nEntry);
}else{
blob_appendf(&sql, " ORDER BY event.mtime DESC LIMIT %d",
nEntry);
}
zSQL = blob_str(&sql);
if( afterFlag ){
zSQL = mprintf("SELECT * FROM (%s) ORDER BY timestamp DESC", zSQL);
}
db_prepare(&q, zSQL);
if( P("s")!=0 ){
@ <hr><p>%h(zSQL)</p><hr>
}
blob_zero(&sql);
if( afterFlag ){
free(zSQL);
}
zDate[0] = 0;
blob_zero(&scriptInit);
zDate[0] = 0;
www_print_timeline(&q, &firstEvent, &lastEvent,
save_parentage_javascript, &scriptInit);
db_finalize(&q);
@ <p>firstEvent=%d(firstEvent) lastEvent=%d(lastEvent)</p>
if( zStart==0 ){
zStart = zDate;
}
@ <script>
@ var parentof = new Object();
@ var childof = new Object();
cgi_append_content(blob_buffer(&scriptInit), blob_size(&scriptInit));
|
| ︙ | ︙ | |||
339 340 341 342 343 344 345 346 | @ <form method="GET" action="%s(g.zBaseURL)/timeline"> @ Start Date: @ <input type="text" size="30" value="%h(zStart)" name="d"> @ Number Of Entries: @ <input type="text" size="4" value="%d(nEntry)" name="n"> @ <br><input type="submit" value="Submit"> @ </form> @ <form method="GET" action="%s(g.zBaseURL)/timeline"> | > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < < | | | | | | | | | | < | > > > > > > > > > > > > > > > > > > > > > | 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 |
@ <form method="GET" action="%s(g.zBaseURL)/timeline">
@ Start Date:
@ <input type="text" size="30" value="%h(zStart)" name="d">
@ Number Of Entries:
@ <input type="text" size="4" value="%d(nEntry)" name="n">
@ <br><input type="submit" value="Submit">
@ </form>
@ <table><tr><td>
@ <form method="GET" action="%s(g.zBaseURL)/timeline">
@ <input type="hidden" value="%d(lastEvent)" name="e">
@ <input type="hidden" value="%d(nEntry)" name="n">
@ <input type="submit" value="Next %d(nEntry) Rows">
@ </form></td><td>
@ <form method="GET" action="%s(g.zBaseURL)/timeline">
@ <input type="hidden" value="%d(firstEvent)" name="e">
@ <input type="hidden" value="%d(nEntry)" name="n">
@ <input type="hidden" value="1" name="a">
@ <input type="submit" value="Previous %d(nEntry) Rows">
@ </form></td></tr></table>
style_footer();
}
/*
** The input query q selects various records. Print a human-readable
** summary of those records.
**
** Limit the number of entries printed to nLine.
**
** The query should return these columns:
**
** 0. rid
** 1. uuid
** 2. Date/Time
** 3. Comment string and user
** 4. Number of non-merge children
** 5. Number of parents
*/
void print_timeline(Stmt *q, int mxLine){
int nLine = 0;
char zPrevDate[20];
const char *zCurrentUuid=0;
Stmt currentQ;
int rid = db_lget_int("checkout", 0);
zPrevDate[0] = 0;
db_prepare(¤tQ,
"SELECT uuid"
" FROM blob WHERE rid=%d", rid
);
if( db_step(¤tQ)==SQLITE_ROW ){
zCurrentUuid = db_column_text(¤tQ, 0);
}
while( db_step(q)==SQLITE_ROW && nLine<=mxLine ){
const char *zId = db_column_text(q, 1);
const char *zDate = db_column_text(q, 2);
const char *zCom = db_column_text(q, 3);
int nChild = db_column_int(q, 4);
int nParent = db_column_int(q, 5);
char *zFree = 0;
int n = 0;
char zPrefix[80];
char zUuid[UUID_SIZE+1];
sprintf(zUuid, "%.10s", zId);
if( memcmp(zDate, zPrevDate, 10) ){
printf("=== %.10s ===\n", zDate);
memcpy(zPrevDate, zDate, 10);
nLine++;
}
if( zCom==0 ) zCom = "";
printf("%.8s ", &zDate[11]);
zPrefix[0] = 0;
if( nParent>1 ){
sqlite3_snprintf(sizeof(zPrefix), zPrefix, "*MERGE* ");
n = strlen(zPrefix);
}
if( nChild>1 ){
sqlite3_snprintf(sizeof(zPrefix)-n, &zPrefix[n], "*FORK* ");
n = strlen(zPrefix);
}
if( strcmp(zCurrentUuid,zId)==0 ){
sqlite3_snprintf(sizeof(zPrefix)-n, &zPrefix[n], "*CURRENT* ");
n += strlen(zPrefix);
}
zFree = sqlite3_mprintf("[%.10s] %s%s", zUuid, zPrefix, zCom);
nLine += comment_print(zFree, 9, 79);
sqlite3_free(zFree);
}
db_finalize(¤tQ);
}
/*
** Return a pointer to a static string that forms the basis for
** a timeline query for display on a TTY.
*/
const char *timeline_query_for_tty(void){
static const char zBaseSql[] =
@ SELECT
@ blob.rid,
@ uuid,
@ datetime(event.mtime,'localtime'),
@ coalesce(ecomment,comment) || ' (by ' || coalesce(euser,user,'?') ||')',
@ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim),
@ (SELECT count(*) FROM plink WHERE cid=blob.rid)
@ FROM event, blob
@ WHERE blob.rid=event.objid
;
return zBaseSql;
}
/*
** COMMAND: timeline
**
** Usage: %fossil timeline ?WHEN? ?UUID|DATETIME? ?-n|--count N?
**
** Print a summary of activity going backwards in date and time
|
| ︙ | ︙ | |||
464 465 466 467 468 469 470 |
k = strlen(zOrigin);
blob_zero(&uuid);
blob_append(&uuid, zOrigin, -1);
if( strcmp(zOrigin, "now")==0 ){
if( mode==3 || mode==4 ){
fossil_fatal("cannot compute descendents or ancestors of a date");
}
| | | < < < < < < | | > | 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 |
k = strlen(zOrigin);
blob_zero(&uuid);
blob_append(&uuid, zOrigin, -1);
if( strcmp(zOrigin, "now")==0 ){
if( mode==3 || mode==4 ){
fossil_fatal("cannot compute descendents or ancestors of a date");
}
zDate = mprintf("(SELECT datetime('now'))");
}else if( strncmp(zOrigin, "current", k)==0 ){
objid = db_lget_int("checkout",0);
zDate = mprintf("(SELECT mtime FROM plink WHERE cid=%d)", objid);
}else if( name_to_uuid(&uuid, 0)==0 ){
objid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &uuid);
zDate = mprintf("(SELECT mtime FROM plink WHERE cid=%d)", objid);
}else{
if( mode==3 || mode==4 ){
fossil_fatal("cannot compute descendents or ancestors of a date");
}
zDate = mprintf("(SELECT julianday(%Q, 'utc'))", zOrigin);
}
zSQL = mprintf("%s AND event.mtime %s %s",
timeline_query_for_tty(),
(mode==1 || mode==4) ? "<=" : ">=",
zDate
);
if( mode==3 || mode==4 ){
db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)");
if( mode==3 ){
compute_descendents(objid, n);
}else{
compute_ancestors(objid, n);
|
| ︙ | ︙ |
Added src/tkt.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 |
/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License version 2 as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used render and control ticket entry
** and display pages.
*/
#include "config.h"
#include "tkt.h"
#include <assert.h>
/*
** The list of database user-defined fields in the TICKET table.
** The real table also contains some addition fields for internal
** used. The internal-use fields begin with "tkt_".
*/
static int nField = 0;
static char **azField = 0; /* Names of database fields */
static char **azValue = 0; /* Original values */
static char **azAppend = 0; /* Value to be appended */
/*
** A subscript interpreter used for processing Tickets.
*/
static struct Subscript *pInterp = 0;
/*
** Compare two entries in azField for sorting purposes
*/
static int nameCmpr(const void *a, const void *b){
return strcmp(*(char**)a, *(char**)b);
}
/*
** Obtain a list of all fields of the TICKET table. Put them
** in sorted order in azField[].
**
** Also allocate space for azValue[] and azAppend[] and initialize
** all the values there to zero.
*/
static void getAllTicketFields(void){
Stmt q;
int i;
if( nField>0 ) return;
db_prepare(&q, "PRAGMA table_info(ticket)");
while( db_step(&q)==SQLITE_ROW ){
const char *zField = db_column_text(&q, 1);
if( strncmp(zField,"tkt_",4)==0 ) continue;
if( nField%10==0 ){
azField = realloc(azField, sizeof(azField)*3*(nField+10) );
if( azField==0 ){
fossil_fatal("out of memory");
}
}
azField[nField] = mprintf("%s", zField);
nField++;
}
db_finalize(&q);
qsort(azField, nField, sizeof(azField[0]), nameCmpr);
azAppend = &azField[nField];
memset(azAppend, 0, sizeof(azAppend[0])*nField);
azValue = &azAppend[nField];
for(i=0; i<nField; i++){
azValue[i] = "";
}
}
/*
** Return the index into azField[] of the given field name.
** Return -1 if zField is not in azField[].
*/
static int fieldId(const char *zField){
int i;
for(i=0; i<nField; i++){
if( strcmp(azField[i], zField)==0 ) return i;
}
return -1;
}
/*
** Query the database for all TICKET fields for the specific
** ticket whose name is given by the "name" CGI parameter.
** Load the values for all fields into the interpreter.
**
** Only load those fields which do not already exist as
** variables.
*/
static void initializeVariablesFromDb(void){
const char *zName;
Stmt q;
int i, n, size, j;
zName = PD("name","");
db_prepare(&q, "SELECT * FROM ticket WHERE tkt_uuid GLOB '%q*'", zName);
if( db_step(&q)==SQLITE_ROW ){
n = db_column_count(&q);
for(i=0; i<n; i++){
const char *zVal = db_column_text(&q, i);
const char *zName = db_column_name(&q, i);
if( zVal==0 ) zVal = "";
for(j=0; j<nField; j++){
if( strcmp(azField[j],zName)==0 ){
azValue[j] = mprintf("%s", zVal);
break;
}
}
if( SbS_Fetch(pInterp, zName, -1, &size)==0 ){
SbS_Store(pInterp, db_column_name(&q,i), zVal, 1);
}
}
}else{
db_finalize(&q);
db_prepare(&q, "PRAGMA table_info(ticket)");
while( db_step(&q)==SQLITE_ROW ){
const char *zField = db_column_text(&q, 1);
if( SbS_Fetch(pInterp, zField, -1, &size)==0 ){
SbS_Store(pInterp, zField, "", 0);
}
}
}
db_finalize(&q);
}
/*
** Transfer all CGI parameters to variables in the interpreter.
*/
static void initializeVariablesFromCGI(void){
int i;
const char *z;
for(i=0; (z = cgi_parameter_name(i))!=0; i++){
SbS_Store(pInterp, z, P(z), 0);
}
}
/*
** Rebuild all tickets named in the _pending_ticket table.
**
** This routine is called just prior to commit after new
** out-of-sequence ticket changes have been added.
*/
static int ticket_rebuild_at_commit(void){
Stmt q;
db_multi_exec(
"DELETE FROM ticket WHERE tkt_uuid IN _pending_ticket"
);
db_prepare(&q, "SELECT uuid FROM _pending_ticket");
while( db_step(&q)==SQLITE_ROW ){
const char *zUuid = db_column_text(&q, 0);
ticket_rebuild_entry(zUuid);
}
db_multi_exec(
"DELETE FROM _pending_ticket"
);
return 0;
}
/*
** Update an entry of the TICKET table according to the information
** in the control file given in p. Attempt to create the appropriate
** TICKET table entry if createFlag is true. If createFlag is false,
** that means we already know the entry exists and so we can save the
** work of trying to create it.
*/
void ticket_insert(Manifest *p, int createFlag, int checkTime){
Blob sql;
Stmt q;
int i;
const char *zSep;
getAllTicketFields();
if( createFlag ){
db_multi_exec("INSERT OR IGNORE INTO ticket(tkt_uuid, tkt_mtime) "
"VALUES(%Q, 0)", p->zTicketUuid);
}
blob_zero(&sql);
blob_appendf(&sql, "UPDATE ticket SET tkt_mtime=:mtime");
zSep = "SET";
for(i=0; i<p->nField; i++){
const char *zName = p->aField[i].zName;
if( zName[0]=='+' ){
zName++;
if( fieldId(zName)<0 ) continue;
blob_appendf(&sql,", %s=%s || %Q", zName, zName, p->aField[i].zValue);
}else{
if( fieldId(zName)<0 ) continue;
blob_appendf(&sql,", %s=%Q", zName, p->aField[i].zValue);
}
}
blob_appendf(&sql, " WHERE tkt_uuid='%s' AND tkt_mtime<:mtime",
p->zTicketUuid);
db_prepare(&q, "%s", blob_str(&sql));
db_bind_double(&q, ":mtime", p->rDate);
db_step(&q);
db_finalize(&q);
if( checkTime && db_changes()==0 ){
static int isInit = 0;
if( !isInit ){
db_multi_exec("CREATE TEMP TABLE _pending_ticket(uuid TEXT UNIQUE)");
db_commit_hook(ticket_rebuild_at_commit, 1);
isInit = 1;
}
db_multi_exec("INSERT OR IGNORE INTO _pending_ticket "
"VALUES(%Q)", p->zTicketUuid);
}
blob_reset(&sql);
}
/*
** Rebuild an entire entry in the TICKET table
*/
void ticket_rebuild_entry(const char *zTktUuid){
char *zTag = mprintf("tkt-%s", zTktUuid);
int tagid = tag_findid(zTag, 1);
Stmt q;
Manifest manifest;
Blob content;
int createFlag = 1;
db_multi_exec(
"DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid
);
db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid);
while( db_step(&q)==SQLITE_ROW ){
int rid = db_column_int(&q, 0);
content_get(rid, &content);
manifest_parse(&manifest, &content);
ticket_insert(&manifest, createFlag, 0);
manifest_clear(&manifest);
createFlag = 0;
}
db_finalize(&q);
}
/*
** Create the subscript interpreter and load the ticket configuration.
*/
void ticket_init(void){
char *zConfig;
if( pInterp ) return;
pInterp = SbS_Create();
zConfig = db_text((char*)zDefaultTicketConfig,
"SELECT value FROM config WHERE name='ticket-configuration'");
SbS_Eval(pInterp, zConfig, -1);
}
/*
** Recreate the ticket table.
*/
void ticket_create_table(int separateConnection){
char *zSql;
int nSql;
db_multi_exec("DROP TABLE IF EXISTS ticket;");
ticket_init();
zSql = (char*)SbS_Fetch(pInterp, "ticket_sql", -1, &nSql);
if( zSql==0 ){
fossil_panic("no ticket_sql defined by ticket configuration");
}
if( separateConnection ){
zSql = mprintf("%.*s", nSql, zSql);
db_init_database(g.zRepositoryName, zSql, 0);
free(zSql);
}else{
db_multi_exec("%.*s", nSql, zSql);
}
}
/*
** Repopulate the ticket table
*/
void ticket_rebuild(void){
Stmt q;
db_begin_transaction();
db_prepare(&q,"SELECT tagname FROM tag WHERE tagname GLOB 'tkt-*'");
while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
int len;
zName += 4;
len = strlen(zName);
if( len<20 || !validate16(zName, len) ) continue;
ticket_rebuild_entry(zName);
}
db_finalize(&q);
db_end_transaction(0);
}
/*
** WEBPAGE: tktview
**
** View a ticket.
*/
void tktview_page(void){
char *zScript;
int nScript;
login_check_credentials();
if( !g.okRdTkt ){ login_needed(); return; }
if( g.okWrTkt ){
style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T",
g.zTop, PD("name",""));
}
style_header("View Ticket");
ticket_init();
initializeVariablesFromDb();
zScript = (char*)SbS_Fetch(pInterp, "tktview_template", -1, &nScript);
zScript = mprintf("%.*s", nScript, zScript);
SbS_Render(pInterp, zScript);
style_footer();
}
/*
** Subscript command: STRING FIELD append_field
**
** FIELD is the name of a database column to which we might want
** to append text. STRING is the text to be appended to that
** column. The append does not actually occur until the
** submit_ticket_change verb is run.
*/
static int appendRemarkCmd(struct Subscript *p, void *notUsed){
int idx;
const char *zField, *zValue;
int nField, nValue;
if( SbS_RequireStack(p, 2, "append_field") ) return 1;
zField = SbS_StackValue(p, 0, &nField);
for(idx=0; idx<nField; idx++){
if( strncmp(azField[idx], zField, nField)==0 && azField[idx][nField]==0 ){
break;
}
}
if( idx>=nField ){
SbS_SetErrorMessage(p, "no such TICKET column: %.*s", nField, zField);
return SBS_ERROR;
}
zValue = SbS_StackValue(p, 1, &nValue);
azAppend[idx] = mprintf("%.*s", nValue, zValue);
SbS_Pop(p, 2);
return SBS_OK;
}
/*
** Subscript command: submit_ticket
**
** Construct and submit a new ticket artifact.
*/
static int submitTicketCmd(struct Subscript *p, void *pUuid){
char *zDate;
const char *zUuid;
int i;
int rid;
Blob tktchng, cksum;
zUuid = (const char *)pUuid;
blob_zero(&tktchng);
zDate = db_text(0, "SELECT datetime('now')");
zDate[10] = 'T';
blob_appendf(&tktchng, "D %s\n", zDate);
free(zDate);
for(i=0; i<nField; i++){
const char *zValue;
int nValue;
if( azAppend[i] ){
blob_appendf(&tktchng, "J +%s %z\n", azField[i],
fossilize(azAppend[i], -1));
}else{
zValue = SbS_Fetch(p, azField[i], -1, &nValue);
if( zValue ){
while( nValue>0 && isspace(zValue[nValue-1]) ){ nValue--; }
if( strncmp(zValue, azValue[i], nValue)
|| strlen(azValue[i])!=nValue ){
blob_appendf(&tktchng, "J %s %z\n",
azField[i], fossilize(zValue,nValue));
}
}
}
}
if( *(char**)pUuid ){
zUuid = db_text(0,
"SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", P("name")
);
}else{
zUuid = db_text(0, "SELECT lower(hex(randomblob(20)))");
}
*(const char**)pUuid = zUuid;
blob_appendf(&tktchng, "K %s\n", zUuid);
blob_appendf(&tktchng, "U %F\n", g.zLogin ? g.zLogin : "");
md5sum_blob(&tktchng, &cksum);
blob_appendf(&tktchng, "Z %b\n", &cksum);
if( strncmp(g.zPath,"debug_",6)==0 ){
@ <hr><pre>
@ %h(blob_str(&tktchng))
@ </pre><hr>
blob_zero(&tktchng);
SbS_Pop(p, 1);
return SBS_OK;
}
rid = content_put(&tktchng, 0, 0);
if( rid==0 ){
fossil_panic("trouble committing ticket: %s", g.zErrMsg);
}
manifest_crosslink(rid, &tktchng);
return SBS_RETURN;
}
/*
** WEBPAGE: tktnew
** WEBPAGE: debug_tktnew
**
** Enter a new ticket. the tktnew_template script in the ticket
** configuration is used. The /tktnew page is the official ticket
** entry page. The /debug_tktnew page is used for debugging the
** tktnew_template in the ticket configuration. /debug_tktnew works
** just like /tktnew except that it does not really save the new ticket
** when you press submit - it just prints the ticket artifact at the
** top of the screen.
*/
void tktnew_page(void){
char *zScript;
int nScript;
char *zNewUuid = 0;
login_check_credentials();
if( !g.okNewTkt ){ login_needed(); return; }
style_header("New Ticket");
ticket_init();
getAllTicketFields();
initializeVariablesFromCGI();
@ <form method="POST" action="%s(g.zBaseURL)/%s(g.zPath)">
zScript = (char*)SbS_Fetch(pInterp, "tktnew_template", -1, &nScript);
zScript = mprintf("%.*s", nScript, zScript);
SbS_Store(pInterp, "login", g.zLogin, 0);
SbS_Store(pInterp, "date", db_text(0, "SELECT datetime('now')"), 2);
SbS_AddVerb(pInterp, "submit_ticket", submitTicketCmd, (void*)&zNewUuid);
if( SbS_Render(pInterp, zScript)==SBS_RETURN && zNewUuid ){
cgi_redirect(mprintf("%s/tktview/%s", g.zBaseURL, zNewUuid));
return;
}
@ </form>
style_footer();
}
/*
** WEBPAGE: tktedit
** WEBPAGE: debug_tktedit
**
** Edit a ticket. The ticket is identified by the name CGI parameter.
** /tktedit is the official page. The /debug_tktedit page does the same
** thing except that it does not save the ticket change record when you
** press submit - it instead prints the ticket change record at the top
** of the page. The /debug_tktedit page is intended to be used when
** debugging ticket configurations.
*/
void tktedit_page(void){
char *zScript;
int nScript;
int nName;
const char *zName;
int nRec;
login_check_credentials();
if( !g.okApndTkt && !g.okWrTkt ){ login_needed(); return; }
style_header("Edit Ticket");
zName = P("name");
if( zName==0 || (nName = strlen(zName))<4 || nName>UUID_SIZE
|| !validate16(zName,nName) ){
@ <font color="red"><b>Not a valid ticket id: \"%h(zName)\"</b></font>
style_footer();
return;
}
nRec = db_int(0, "SELECT count(*) FROM ticket WHERE tkt_uuid GLOB '%q*'",
zName);
if( nRec==0 ){
@ <font color="red"><b>No such ticket: \"%h(zName)\"</b></font>
style_footer();
return;
}
if( nRec>1 ){
@ <font color="red"><b>%d(nRec) tickets begin with: \"%h(zName)\"</b></font>
style_footer();
return;
}
ticket_init();
getAllTicketFields();
initializeVariablesFromCGI();
initializeVariablesFromDb();
@ <form method="POST" action="%s(g.zBaseURL)/%s(g.zPath)">
@ <input type="hidden" name="name" value="%s(zName)">
zScript = (char*)SbS_Fetch(pInterp, "tktedit_template", -1, &nScript);
zScript = mprintf("%.*s", nScript, zScript);
SbS_Store(pInterp, "login", g.zLogin, 0);
SbS_Store(pInterp, "date", db_text(0, "SELECT datetime('now')"), 2);
SbS_AddVerb(pInterp, "append_field", appendRemarkCmd, 0);
SbS_AddVerb(pInterp, "submit_ticket", submitTicketCmd, (void*)&zName);
if( SbS_Render(pInterp, zScript)==SBS_RETURN && zName ){
cgi_redirect(mprintf("%s/tktview/%s", g.zBaseURL, zName));
return;
}
@ </form>
style_footer();
}
/*
** Check the ticket configuration in zConfig to see if it appears to
** be well-formed. If everything is OK, return NULL. If something is
** amiss, then return a pointer to a string (obtained from malloc) that
** describes the problem.
*/
char *ticket_config_check(const char *zConfig){
struct Subscript *p;
char *zErr = 0;
const char *z;
int n;
int i;
int rc;
sqlite3 *db;
static const char *azRequired[] = {
"tktnew_template",
"tktview_template",
"tktedit_template",
};
p = SbS_Create();
rc = SbS_Eval(p, zConfig, strlen(zConfig));
if( rc!=SBS_OK ){
zErr = mprintf("%s", SbS_GetErrorMessage(p));
SbS_Destroy(p);
return zErr;
}
for(i=0; i<sizeof(azRequired)/sizeof(azRequired[0]); i++){
z = SbS_Fetch(p, azRequired[i], -1, &n);
if( z==0 ){
zErr = mprintf("missing definition: %s", azRequired[i]);
SbS_Destroy(p);
return zErr;
}
}
z = SbS_Fetch(p, "ticket_sql", -1, &n);
if( z==0 ){
zErr = mprintf("missing definition: ticket_sql");
SbS_Destroy(p);
return zErr;
}
rc = sqlite3_open(":memory:", &db);
if( rc==SQLITE_OK ){
char *zSql = mprintf("%.*s", n, z);
rc = sqlite3_exec(db, zSql, 0, 0, &zErr);
if( rc!=SQLITE_OK ){
sqlite3_close(db);
SbS_Destroy(p);
return zErr;
}
/* TODO: verify that the TICKET table exists and has required fields */
sqlite3_close(db);
}
SbS_Destroy(p);
return 0;
}
|
Added src/tktconfig.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 |
/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License version 2 as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains a string constant that is the default ticket
** configuration.
*/
#include "config.h"
#include "tktconfig.h"
/*
** This is a sample "ticket configuration" file for fossil.
**
** There is considerable flexibility in how tickets are defined
** in fossil. Each repository can define its own ticket setup
** differently. Each repository has an instance of a file, like
** this one that defines how that repository deals with tickets.
**
** This file is in the form of a script in an exceedingly
** minimalist scripting language called "subscript". Here are
** the rules:
**
** SYNTAX
**
** * The script consists of a sequence of whitespace
** separated tokens. Whitespace is ignored, except
** as its role as a token separator.
**
** * Lines that begin with '#' are considered to be whitespace.
**
** * Text within matching {...} is consider to be a single "string"
** token, even if the text spans multiple lines and includes
** embedded whitespace. The outermost {...} are not part of
** the text of the token.
**
** * A token that begins with "/" is a string token.
**
** * A token that looks like a number is a string token.
**
** * Tokens that do not fall under the previous three rules
** are "verb" tokens.
**
** PROCESSING:
**
** * When a script is processed, the engine reads tokens
** one by one.
**
** * String tokens are pushed onto the stack.
**
** * Verb tokens which correspond to the names of variables
** cause the corresponding variable to be pushed onto the
** stack.
**
** * Verb tokens which correspond to procedures cause the
** procedures to run. The procedures might push or pull
** values from the stack.
**
** There are just a handful of verbs. For the purposes of this
** configuration script, there is only a single verb: "set". The
** "set" verb pops two arguments from the stack. The topmost is
** the name of a variable. The second element is the value. The
** "set" verb sets the value of the named variable.
**
** VALUE NAME set
**
** This configuration file just sets the values of various variables.
** Some of the variables have special meanings. The content of some
** of the variables are additional subscript scripts.
*/
/* @-comment: ** */
const char zDefaultTicketConfig[] =
@ ############################################################################
@ # Every ticket configuration *must* define an SQL statement that creates
@ # the TICKET table. This table must have three columns named
@ # tkt_id, tkt_uuid, and tkt_mtime. tkt_id must be the integer primary
@ # key and tkt_uuid and tkt_mtime must be unique. A configuration should
@ # define addition columns as necessary. All columns should be in all
@ # lower-case letters and should not begin with "tkt".
@ #
@ {
@ CREATE TABLE ticket(
@ -- Do not change any column that begins with tkt_
@ tkt_id INTEGER PRIMARY KEY,
@ tkt_uuid TEXT,
@ tkt_mtime DATE,
@ -- Add as many field as required below this line
@ type TEXT,
@ status TEXT,
@ subsystem TEXT,
@ priority TEXT,
@ severity TEXT,
@ foundin TEXT,
@ contact TEXT,
@ title TEXT,
@ comment TEXT,
@ -- Do not alter this UNIQUE clause:
@ UNIQUE(tkt_uuid, tkt_mtime)
@ );
@ -- Add indices as desired
@ } /ticket_sql set
@
@ ############################################################################
@ # You can define additional variables here. These variables will be
@ # accessible to the page templates when they run.
@ #
@ {
@ Code_Defect
@ Build_Problem
@ Documentation
@ Feature_Request
@ Incident
@ } /type_choices set
@ {Immediate High Medium Low Zero} /priority_choices set
@ {Critical Severe Important Minor Cosmetic} /severity_choices set
@ {
@ Open
@ Fixed
@ Rejected
@ Unable_To_Reproduce
@ Works_As_Designed
@ External_Bug
@ Not_A_Bug
@ Duplicate
@ Overcome_By_Events
@ Drive_By_Patch
@ } /resolution_choices set
@ {
@ Open
@ Verified
@ In_Process
@ Deferred
@ Fixed
@ Tested
@ Closed
@ } /status_choices set
@ {one two three} /subsystem_choices set
@
@ ##########################################################################
@ # The "tktnew_template" variable is set to text which is a template for
@ # the HTML of the "New Ticket" page. Within this template, text contained
@ # within [...] is subscript. That subscript runs when the page is
@ # rendered.
@ #
@ {
@ <!-- load database field names not found in CGI with an empty string -->
@ <!-- start a form -->
@ [{
@ {Open} /status set
@ submit_ticket
@ } /submit exists if]
@ <table cellpadding="5">
@ <tr>
@ <td colspan="2">
@ Enter a one-line summary of the problem:<br>
@ <input type="text" name="title" size="60" value="[{} /title get html]">
@ </td>
@ </tr>
@
@ <tr>
@ <td align="right">Type:
@ [/type type_choices 1 combobox]
@ </td>
@ <td>What type of ticket is this?</td>
@ </tr>
@
@ <tr>
@ <td align="right">Version:
@ <input type="text" name="foundin" size="20" value="[{} /foundin get html]">
@ </td>
@ <td>In what version or build number do you observer the problem?</td>
@ </tr>
@
@ <tr>
@ <td align="right">Severity:
@ [/severity severity_choices 1 combobox]
@ </td>
@ <td>How debilitating is the problem? How badly does the problem
@ effect the operation of the product?</td>
@ </tr>
@
@ <tr>
@ <td align="right">EMail:
@ <input type="text" name="contact" value="[{} /contact get html]" size="30">
@ </td>
@ <td>Not publically visible. Used by developers to contact you with
@ questions.</td>
@ </tr>
@
@ <tr>
@ <td colspan="2">
@ Enter a detailed description of the problem.
@ For code defects, be sure to provide details on exactly how
@ the problem can be reproduced. Provide as much detail as
@ possible.
@ <br>
@ <textarea name="comment" cols="80"
@ rows="[{} /comment get linecount 50 max 10 min html]"
@ wrap="virtual" class="wikiedit">[{} /comment get html]</textarea><br>
@ <input type="submit" name="preview" value="Preview">
@ </tr>
@
@ [/preview exists enable_output]
@ <tr><td colspan="2">
@ Description Preview:<br><hr>
@ [{} /comment get wiki]
@ <hr>
@ </td></tr>
@ [1 enable_output]
@
@ <tr>
@ <td align="right">
@ <input type="submit" name="submit" value="Submit">
@ </td>
@ <td>After filling in the information above, press this button to create
@ the new ticket</td>
@ </tr>
@ </table>
@ <!-- end of form -->
@ } /tktnew_template set
@
@ ##########################################################################
@ # The template for the "edit ticket" page
@ #
@ # Then generated text is inserted inside a form which feeds back to itself.
@ # All CGI parameters are loaded into variables. All database files are
@ # loaded into variables if they have not previously been loaded by
@ # CGI parameters.
@ {
@ [
@ login /username get /username set
@ {
@ {
@ username login eq /samename set
@ {
@ "\n\n<hr><i>" login " added on " date ":</i><br>\n"
@ cmappnd 6 concat /comment append_field
@ } samename if
@ {
@ "\n\n<hr><i>" login " claiming to be " username " added on " date
@ "</i><br>\n" cmappnd 8 concat /comment append_field
@ } samename not if
@ } 0 {} /cmappnd get length lt if
@ submit_ticket
@ } /submit exists if
@ ]
@ <table cellpadding="5">
@ <tr><td align="right">Title:</td><td>
@ <input type="text" name="title" value="[title html]" size="60">
@ </td></tr>
@ <tr><td align="right">Status:</td><td>
@ [/status status_choices 1 combobox]
@ </td></tr>
@ <tr><td align="right">Type:</td><td>
@ [/type type_choices 1 combobox]
@ </td></tr>
@ <tr><td align="right">Severity:</td><td>
@ [/severity severity_choices 1 combobox]
@ </td></tr>
@ <tr><td align="right">Priority:</td><td>
@ [/priority priority_choices 1 combobox]
@ </td></tr>
@ <tr><td align="right">Resolution:</td><td>
@ [/resolution resolution_choices 1 combobox]
@ </td></tr>
@ <tr><td align="right">Subsystem:</td><td>
@ [/subsystem subsystem_choices 1 combobox]
@ </td></tr>
@ [/e hascap enable_output]
@ <tr><td align="right">Contact:</td><td>
@ <input type="text" name="contact" size="40" value="[contact html]">
@ </td></tr>
@ [1 enable_output]
@ <tr><td align="right">Version Found In:</td><td>
@ <input type="text" name="foundin" size="50" value="[foundin html]">
@ </td></tr>
@ <tr><td colspan="2">
@
@ [
@ 0 /eall get /eall set # eall means "edit all". default==no
@ /aonlybtn exists not /eall set # Edit all if no aonlybtn CGI param
@ /eallbtn exists /eall set # Edit all if eallbtn CGI param
@ /w hascap eall and /eall set # WrTkt permission needed to edit all
@ ]
@
@ [eall enable_output]
@ Description And Comments:<br>
@ <textarea name="comment" cols="80"
@ rows="[{} /comment get linecount 15 max 10 min html]"
@ wrap="virtual" class="wikiedit">[comment html]</textarea><br>
@ <input type="hidden" name="eall" value="1">
@ <input type="submit" name="aonlybtn" value="Append Remark">
@
@ [eall not enable_output]
@ Append Remark from
@ <input type="text" name="username" value="[username html]" size="30">:<br>
@ <textarea name="cmappnd" cols="80" rows="15"
@ wrap="virtual" class="wikiedit">[{} /cmappnd get html]</textarea><br>
@ [/w hascap eall not and enable_output]
@ <input type="submit" name="eallbtn" value="Edit All">
@
@ [1 enable_output]
@ </td></tr>
@ <tr><td align="right"></td><td>
@ <input type="submit" name="submit" value="Submit Changes">
@ </td></tr>
@ </table>
@ } /tktedit_template set
@
@ ##########################################################################
@ # The template for the "view ticket" page
@ {
@ <!-- load database fields automatically loaded into variables -->
@ <table cellpadding="5">
@ <tr><td align="right">Title:</td><td>
@ [title html]
@ </td></tr>
@ <tr><td align="right">Status:</td><td>
@ [status html]
@ </td></tr>
@ <tr><td align="right">Type:</td><td>
@ [type html]
@ </td></tr>
@ <tr><td align="right">Severity:</td><td>
@ [severity html]
@ </td></tr>
@ <tr><td align="right">Priority:</td><td>
@ [priority html]
@ </td></tr>
@ <tr><td align="right">Resolution:</td><td>
@ [priority html]
@ </td></tr>
@ <tr><td align="right">Subsystem:</td><td>
@ [subsystem html]
@ </td></tr>
@ [{e} hascap enable_output]
@ <tr><td align="right">Contact:</td><td>
@ [contact html]
@ </td></tr>
@ [1 enable_output]
@ <tr><td align="right">Version Found In:</td><td>
@ [foundin html]
@ </td></tr>
@ <tr><td colspan="2">
@ Description And Comments:<br>
@ [comment wiki]
@ </td></tr>
@ </table>
@ } /tktview_template set
;
|
Added src/tktsetup.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
/*
** Copyright (c) 2007 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License version 2 as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code to implement the ticket configuration
** setup screens.
*/
#include "config.h"
#include "tktsetup.h"
#include <assert.h>
/*
** Main sub-menu for configuring the ticketing system.
** WEBPAGE: /tktsetup
*/
void tktsetup_page(void){
login_check_credentials();
if( !g.okSetup ){
login_needed();
}
style_header("Ticket Setup");
@ <i>TBD...</i>
style_footer();
}
|
Changes to src/translate.c.
| ︙ | ︙ | |||
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
}
/*
** Translate the input stream into the output stream
*/
static void trans(FILE *in, FILE *out){
int i, j, k; /* Loop counters */
int lastWasEq = 0; /* True if last non-whitespace character was "=" */
char zLine[2000]; /* A single line of input */
char zOut[4000]; /* The input line translated into appropriate output */
while( fgets(zLine, sizeof(zLine), in) ){
for(i=0; zLine[i] && isspace(zLine[i]); i++){}
if( zLine[i]!='@' ){
if( inPrint || inStr ) end_block(out);
fprintf(out,"%s",zLine);
i += strlen(&zLine[i]);
while( i>0 && isspace(zLine[i-1]) ){ i--; }
lastWasEq = i>0 && zLine[i-1]=='=';
}else if( lastWasEq ){
/* If the last non-whitespace character before the first @ was
| > > > > > > > | | > > | > | 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
}
/*
** Translate the input stream into the output stream
*/
static void trans(FILE *in, FILE *out){
int i, j, k; /* Loop counters */
char c1, c2; /* Characters used to start a comment */
int lastWasEq = 0; /* True if last non-whitespace character was "=" */
char zLine[2000]; /* A single line of input */
char zOut[4000]; /* The input line translated into appropriate output */
c1 = c2 = '-';
while( fgets(zLine, sizeof(zLine), in) ){
for(i=0; zLine[i] && isspace(zLine[i]); i++){}
if( zLine[i]!='@' ){
if( inPrint || inStr ) end_block(out);
fprintf(out,"%s",zLine);
/* 0123456789 12345 */
if( strncmp(zLine, "/* @-comment: ", 14)==0 ){
c1 = zLine[14];
c2 = zLine[15];
}
i += strlen(&zLine[i]);
while( i>0 && isspace(zLine[i-1]) ){ i--; }
lastWasEq = i>0 && zLine[i-1]=='=';
}else if( lastWasEq ){
/* If the last non-whitespace character before the first @ was
** an "=" then generate a string literal. But skip comments
** consisting of all text between c1 and c2 (default "--")
** and end of line.
*/
int indent, omitline;
i++;
if( isspace(zLine[i]) ){ i++; }
indent = i - 2;
if( indent<0 ) indent = 0;
omitline = 0;
for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
if( zLine[i]==c1 && (c2==' ' || zLine[i+1]==c2) ){
omitline = 1; break;
}
if( zLine[i]=='"' || zLine[i]=='\\' ){ zOut[j++] = '\\'; }
zOut[j++] = zLine[i];
}
while( j>0 && isspace(zOut[j-1]) ){ j--; }
zOut[j] = 0;
if( j<=0 && omitline ){
fprintf(out,"\n");
|
| ︙ | ︙ |
Changes to src/update.c.
| ︙ | ︙ | |||
23 24 25 26 27 28 29 30 31 32 33 | ** ** This file contains code used to merge the changes in the current ** checkout into a different version and switch to that version. */ #include "config.h" #include "update.h" #include <assert.h> /* ** COMMAND: update ** | > > > > > > > | | > > > > | > > > > | > > > > | > | > > > > > > > > > > > | < < < < > | > | > > | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
**
** This file contains code used to merge the changes in the current
** checkout into a different version and switch to that version.
*/
#include "config.h"
#include "update.h"
#include <assert.h>
/*
** Return true if artifact rid is a version
*/
int is_a_version(int rid){
return db_exists("SELECT 1 FROM plink WHERE cid=%d", rid);
}
/*
** COMMAND: update
**
** Usage: %fossil update ?VERSION? ?--force? ?--latest?
**
** The optional argument is a version that should become the current
** version. If the argument is omitted, then use the leaf of the
** tree that begins with the current version, if there is only a
** single leaf. If there are a multiple leaves, the latest is used
** if the --latest flag is present.
**
** This command is different from the "checkout" in that edits are
** not overwritten. Edits are merged into the new version.
**
** If there are uncommitted edits and the safemerge option is
** enabled then no update will occur unless you provide the
** --force flag.
*/
void update_cmd(void){
int vid; /* Current version */
int tid=0; /* Target version - version we are changing to */
Stmt q;
int latestFlag; /* Pick the latest version if true */
int forceFlag; /* True force the update */
latestFlag = find_option("latest",0, 0)!=0;
forceFlag = find_option("force","f",0)!=0;
if( g.argc!=3 && g.argc!=2 ){
usage("?VERSION?");
}
db_must_be_within_tree();
vid = db_lget_int("checkout", 0);
if( vid==0 ){
fossil_fatal("cannot find current version");
}
if( db_exists("SELECT 1 FROM vmerge") ){
fossil_fatal("cannot update an uncommitted merge");
}
if( !forceFlag && db_get_int("safemerge", 0) && unsaved_changes() ){
fossil_fatal("there are uncommitted changes and safemerge is enabled");
}
if( g.argc==3 ){
tid = name_to_rid(g.argv[2]);
if( tid==0 ){
fossil_fatal("not a version: %s", g.argv[2]);
}
if( !is_a_version(tid) ){
fossil_fatal("not a version: %s", g.argv[2]);
}
}
if( tid==0 ){
/*
** Do an autosync pull prior to the update, if autosync is on and they
** did not want a specific version (i.e. another branch, a past revision).
** By not giving a specific version, they are asking for the latest, thus
** pull to get the latest, then update.
*/
autosync(1);
}
if( tid==0 ){
compute_leaves(vid);
if( !latestFlag && db_int(0, "SELECT count(*) FROM leaves")>1 ){
db_prepare(&q,
"%s "
" AND event.objid IN leaves"
" ORDER BY event.mtime DESC",
timeline_query_for_tty()
);
print_timeline(&q, 100);
db_finalize(&q);
fossil_fatal("Multiple descendents");
}
tid = db_int(0, "SELECT rid FROM leaves, event"
" WHERE event.objid=leaves.rid"
" ORDER BY event.mtime DESC");
}
db_begin_transaction();
vfile_check_signature(vid);
undo_begin();
load_vfile_from_rid(tid);
|
| ︙ | ︙ | |||
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
unlink(zFullPath);
free(zFullPath);
}
}else if( idt>0 && idv>0 && ridt!=ridv && chnged ){
/* Merge the changes in the current tree into the target version */
Blob e, r, t, v;
char *zFullPath;
printf("MERGE %s\n", zName);
undo_save(zName);
zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
content_get(ridt, &t);
content_get(ridv, &v);
blob_zero(&e);
blob_read_from_file(&e, zFullPath);
| > | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
unlink(zFullPath);
free(zFullPath);
}
}else if( idt>0 && idv>0 && ridt!=ridv && chnged ){
/* Merge the changes in the current tree into the target version */
Blob e, r, t, v;
int rc;
char *zFullPath;
printf("MERGE %s\n", zName);
undo_save(zName);
zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
content_get(ridt, &t);
content_get(ridv, &v);
blob_zero(&e);
blob_read_from_file(&e, zFullPath);
rc = blob_merge(&v, &e, &t, &r);
if( rc>=0 ){
blob_write_to_file(&r, zFullPath);
if( rc>0 ){
printf("***** %d merge conflicts in %s\n", rc, zName);
}
}else{
printf("***** Cannot merge binary file %s\n", zName);
}
free(zFullPath);
blob_reset(&v);
blob_reset(&e);
blob_reset(&t);
blob_reset(&r);
}
}
db_finalize(&q);
/*
** Clean up the mid and pid VFILE entries. Then commit the changes.
*/
db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid);
manifest_to_disk(tid);
db_lset_int("checkout", tid);
db_end_transaction(0);
}
/*
** Get the contents of a file within a given revision.
*/
int historical_version_of_file(
const char *revision, /* The baseline name containing the file */
const char *file, /* Full treename of the file */
Blob *content /* Put the content here */
){
Blob mfile;
Manifest m;
int i, rid=0;
rid = name_to_rid(revision);
content_get(rid, &mfile);
if( manifest_parse(&m, &mfile) ){
for(i=0; i<m.nFile; i++){
if( strcmp(m.aFile[i].zName, file)==0 ){
rid = uuid_to_rid(m.aFile[i].zUuid, 0);
return content_get(rid, content);
}
}
fossil_fatal("file %s does not exist in baseline: %s", file, revision);
}else{
fossil_panic("could not parse manifest for baseline: %s", revision);
}
return 0;
}
/*
** COMMAND: revert
**
** Usage: %fossil revert ?--yes? ?-r REVISION? FILE
**
** Revert to the current repository version of FILE, or to
** the version associated with baseline REVISION if the -r flag
** appears. This command will confirm your operation unless the
** file is missing or the --yes option is used.
**/
void revert_cmd(void){
const char *zFile;
const char *zRevision;
Blob fname;
Blob record;
Blob ans;
int rid = 0, yesRevert;
yesRevert = find_option("yes", "y", 0)!=0;
zRevision = find_option("revision", "r", 1);
verify_all_options();
if( g.argc<3 ){
usage("?OPTIONS FILE");
}
db_must_be_within_tree();
zFile = mprintf("%/", g.argv[g.argc-1]);
if( !file_tree_name(zFile, &fname) ){
fossil_panic("unknown file: %s", zFile);
}
if( access(zFile, 0) ) yesRevert = 1;
if( yesRevert==0 ){
char *prompt = mprintf("revert file %B? this will"
" destroy local changes [y/N]? ",
&fname);
blob_zero(&ans);
prompt_user(prompt, &ans);
if( blob_str(&ans)[0]=='y' ){
yesRevert = 1;
}
}
if( yesRevert==1 && zRevision!=0 ){
historical_version_of_file(zRevision, zFile, &record);
}else if( yesRevert==1 ){
rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B", &fname);
if( rid==0 ){
fossil_panic("no history for file: %b", &fname);
}
content_get(rid, &record);
}
if( yesRevert==1 ){
blob_write_to_file(&record, zFile);
printf("%s reverted\n", zFile);
blob_reset(&record);
blob_reset(&fname);
}else{
printf("revert canceled\n");
}
}
|
Changes to src/user.c.
1 2 3 4 5 6 7 8 9 10 11 | /* ** Copyright (c) 2006 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public ** License version 2 as published by the Free Software Foundation. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /* ** Copyright (c) 2006 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public ** License version 2 as published by the Free Software Foundation. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. ** ** You should have received a copy of the GNU General Public ** License along with this library; if not, write to the ** Free Software Foundation, Inc., 59 Temple Place - Suite 330, ** Boston, MA 02111-1307, USA. ** ** Author contact information: ** drh@hwaci.com |
| ︙ | ︙ | |||
42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
z[i] = 0;
break;
}
if( z[i]<' ' ) z[i] = ' ';
}
blob_append(pBlob, z, -1);
}
/*
** Do a single prompt for a passphrase. Store the results in the blob.
*/
static void prompt_for_passphrase(const char *zPrompt, Blob *pPassphrase){
char *z = getpass(zPrompt);
strip_string(pPassphrase, z);
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
z[i] = 0;
break;
}
if( z[i]<' ' ) z[i] = ' ';
}
blob_append(pBlob, z, -1);
}
#ifdef __MINGW32__
/*
** getpass for Windows
*/
static char *getpass(const char *prompt){
static char pwd[64];
size_t i;
fputs(prompt,stderr);
fflush(stderr);
for(i=0; i<sizeof(pwd)-1; ++i){
pwd[i] = _getch();
if(pwd[i]=='\r' || pwd[i]=='\n'){
break;
}
/* BS or DEL */
else if(i>0 && (pwd[i]==8 || pwd[i]==127)){
i -= 2;
continue;
}
/* CTRL-C */
else if(pwd[i]==3) {
i=0;
break;
}
/* ESC */
else if(pwd[i]==27){
i=0;
break;
}
else{
fputc('*',stderr);
}
}
pwd[i]='\0';
fputs("\n", stderr);
return pwd;
}
#endif
/*
** Do a single prompt for a passphrase. Store the results in the blob.
*/
static void prompt_for_passphrase(const char *zPrompt, Blob *pPassphrase){
char *z = getpass(zPrompt);
strip_string(pPassphrase, z);
|
| ︙ | ︙ | |||
123 124 125 126 127 128 129 | ** Query or set the default user. The default user is the ** user for command-line interaction. ** ** %fossil user list ** ** List all users known to the repository ** | | > > > > | > > > > | | | 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
** Query or set the default user. The default user is the
** user for command-line interaction.
**
** %fossil user list
**
** List all users known to the repository
**
** %fossil user new ?USERNAME?
**
** Create a new user in the repository. Users can never be
** deleted. They can be denied all access but they must continue
** to exist in the database.
**
** %fossil user password USERNAME
**
** Change the web access password for a user.
*/
void user_cmd(void){
int n;
db_find_and_open_repository();
if( g.argc<3 ){
usage("capabilities|default|list|new|password ...");
}
n = strlen(g.argv[2]);
if( n>=2 && strncmp(g.argv[2],"new",n)==0 ){
Blob passwd, login, contact;
if( g.argc>=4 ){
blob_zero(&login);
blob_append(&login, g.argv[3], -1);
}else{
prompt_user("login: ", &login);
}
if( db_exists("SELECT 1 FROM user WHERE login=%B", &login) ){
fossil_fatal("user %b already exists", &login);
}
prompt_user("contact-info: ", &contact);
prompt_for_password("password: ", &passwd, 1);
db_multi_exec(
"INSERT INTO user(login,pw,cap,info)"
"VALUES(%B,%B,'jnor',%B)",
&login, &passwd, &contact
);
}else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
user_select();
if( g.argc==3 ){
printf("%s\n", g.zLogin);
}else if( g.localOpen ){
db_lset("default-user", g.zLogin);
}else{
db_set("default-user", g.zLogin, 0);
}
}else if( n>=2 && strncmp(g.argv[2],"list",n)==0 ){
Stmt q;
db_prepare(&q, "SELECT login, info FROM user ORDER BY login");
while( db_step(&q)==SQLITE_ROW ){
printf("%-12s %s\n", db_column_text(&q, 0), db_column_text(&q, 1));
}
db_finalize(&q);
}else if( n>=2 && strncmp(g.argv[2],"password",2)==0 ){
char *zPrompt;
int uid;
Blob pw;
if( g.argc!=4 ) usage("password USERNAME");
uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
if( uid==0 ){
fossil_fatal("no such user: %s", g.argv[3]);
}
zPrompt = mprintf("new passwd for %s: ", g.argv[3]);
prompt_for_password(zPrompt, &pw, 1);
if( blob_size(&pw)==0 ){
|
| ︙ | ︙ | |||
250 251 252 253 254 255 256 |
if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return;
if( attempt_user(db_get("default-user", 0)) ) return;
if( attempt_user(getenv("USER")) ) return;
| | > | | 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return;
if( attempt_user(db_get("default-user", 0)) ) return;
if( attempt_user(getenv("USER")) ) return;
db_prepare(&s, "SELECT uid, login FROM user"
" WHERE login NOT IN ('anonymous','nobody')");
if( db_step(&s)==SQLITE_ROW ){
g.userUid = db_column_int(&s, 0);
g.zLogin = mprintf("%s", db_column_text(&s, 1));
}
db_finalize(&s);
if( g.userUid==0 ){
db_prepare(&s, "SELECT uid, login FROM user");
if( db_step(&s)==SQLITE_ROW ){
g.userUid = db_column_int(&s, 0);
g.zLogin = mprintf("%s", db_column_text(&s, 1));
}
db_finalize(&s);
}
if( g.userUid==0 ){
db_multi_exec(
"INSERT INTO user(login, pw, cap, info)"
"VALUES('anonymous', '', 'cfghjkmnoqw', '')"
);
g.userUid = db_last_insert_rowid();
g.zLogin = "anonymous";
}
}
|
Changes to src/verify.c.
| ︙ | ︙ | |||
18 19 20 21 22 23 24 | ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file contains code used to help verify the integrity of the | | > > > > > > | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file contains code used to help verify the integrity of the ** the repository. ** ** This file primarily implements the verify_before_commit() interface. ** Any function can call verify_before_commit() with a record id (RID) ** as an argument. Then before the next change to the database commits, ** this routine will reach in and check that the record can be extracted ** correctly from the BLOB table. */ #include "config.h" #include "verify.h" #include <assert.h> /* ** Load the record identify by rid. Make sure we can reproduce it |
| ︙ | ︙ | |||
54 55 56 57 58 59 60 |
}
blob_reset(&hash);
}
blob_reset(&uuid);
}
/*
| > > | > > > > > | | | > | < > | | < < < | > < < | > | > > > > | 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
}
blob_reset(&hash);
}
blob_reset(&uuid);
}
/*
** The following bag holds the rid for every record that needs
** to be verified.
*/
static Bag toVerify;
static int inFinalVerify = 0;
/*
** This routine is called just prior to each commit operation.
*/
static int verify_at_commit(void){
int rid;
inFinalVerify = 1;
rid = bag_first(&toVerify);
while( rid>0 ){
verify_rid(rid);
rid = bag_next(&toVerify, rid);
}
bag_clear(&toVerify);
inFinalVerify = 0;
return 0;
}
/*
** Arrange to verify a particular record prior to committing.
**
** If the record rid is less than 1, then just initialize the
** verification system but do not record anything as needing
** verification.
*/
void verify_before_commit(int rid){
static int isInit = 0;
if( !isInit ){
db_commit_hook(verify_at_commit, 1000);
isInit = 1;
}
assert( !inFinalVerify );
if( rid>0 ){
bag_insert(&toVerify, rid);
}
}
/*
** COMMAND: test-verify-all
**
** Verify all records in the repository.
*/
void verify_all_cmd(void){
Stmt q;
int cnt = 0;
db_must_be_within_tree();
db_prepare(&q, "SELECT rid FROM blob");
while( db_step(&q)==SQLITE_ROW ){
int rid = db_column_int(&q, 0);
verify_before_commit(rid);
cnt++;
assert( bag_count(&toVerify)==cnt );
}
db_finalize(&q);
verify_at_commit();
assert( bag_count(&toVerify)==0 );
}
|
Changes to src/vfile.c.
| ︙ | ︙ | |||
96 97 98 99 100 101 102 |
"INSERT INTO vfile(vid,rid,mrid,pathname) "
" VALUES(:vid,:id,:id,:name)");
db_bind_int(&ins, ":vid", vid);
while( blob_line(p, &line) ){
char *z = blob_buffer(&line);
if( z[0]=='-' ){
if( seenHeader ) break;
| | | 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
"INSERT INTO vfile(vid,rid,mrid,pathname) "
" VALUES(:vid,:id,:id,:name)");
db_bind_int(&ins, ":vid", vid);
while( blob_line(p, &line) ){
char *z = blob_buffer(&line);
if( z[0]=='-' ){
if( seenHeader ) break;
while( blob_line(p, &line)>2 ){}
if( blob_line(p, &line)==0 ) break;
}
seenHeader = 1;
if( z[0]!='F' || z[1]!=' ' ) continue;
blob_token(&line, &token); /* Skip the "F" token */
if( blob_token(&line, &name)==0 ) break;
if( blob_token(&line, &uuid)==0 ) break;
|
| ︙ | ︙ |
Changes to src/wiki.c.
| ︙ | ︙ | |||
23 24 25 26 27 28 29 | ** ** This file contains code to do formatting of wiki text. */ #include <assert.h> #include "config.h" #include "wiki.h" | < > | | > > > < | < < | | | < > | | | > | < | | > | | < | | | | < > | | | > > > | < < | < < < | > | < < < < < < < < < < < < | | < < | > > | > | < | > > > | < > | > > > | | | | | | | | | < | | | | > | < < < < | | < < | | | > > > > > > > > | > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > | > | > | | > > | < < < > > | < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < | < < | < > | | > | < > > > > > > > > > > > > > > > > > > > | | > | > | < < < < > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | < > | < > > > > | < > | < > > | | | > | < > > > > > > | < > | > | > > > > > > > > > | > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > | > > > > > > > > > > > | > > < > | > > | < < | > | > | < | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 |
**
** This file contains code to do formatting of wiki text.
*/
#include <assert.h>
#include "config.h"
#include "wiki.h"
/*
** Return true if the input string is a well-formed wiki page name.
**
** Well-formed wiki page names do not begin or end with whitespace,
** and do not contain tabs or other control characters and do not
** contain more than a single space character in a row. Well-formed
** names must be between 3 and 100 chracters in length, inclusive.
*/
int wiki_name_is_wellformed(const char *z){
int i;
if( z[0]<=0x20 ){
return 0;
}
for(i=1; z[i]; i++){
if( z[i]<0x20 ) return 0;
if( z[i]==0x20 && z[i-1]==0x20 ) return 0;
}
if( z[i-1]==' ' ) return 0;
if( i<3 || i>100 ) return 0;
return 1;
}
/*
** Check a wiki name. If it is not well-formed, then issue an error
** and return true. If it is well-formed, return false.
*/
static int check_name(const char *z){
if( !wiki_name_is_wellformed(z) ){
style_header("Wiki Page Name Error");
@ The wiki name "<b>%h(z)</b>" is not well-formed. Rules for
@ wiki page names:
@ <ul>
@ <li> Must not begin or end with a space.
@ <li> Must not contain any control characters, including tab or
@ newline.
@ <li> Must not have two or more spaces in a row internally.
@ <li> Must be between 3 and 100 characters in length.
@ </ul>
style_footer();
return 1;
}
return 0;
}
/*
** WEBPAGE: home
** WEBPAGE: index
** WEBPAGE: not_found
*/
void home_page(void){
char *zPageName = db_get("project-name",0);
if( zPageName ){
login_check_credentials();
g.zExtra = zPageName;
cgi_set_parameter_nocopy("name", g.zExtra);
g.okRdWiki = 1;
g.okApndWiki = 0;
g.okWrWiki = 0;
g.okHistory = 0;
wiki_page();
return;
}
style_header("Home");
@ <p>This is a stub home-page for the project.
@ To fill in this page, first go to
@ <a href="%s(g.zBaseURL)/setup_config">setup/config</a>
@ and establish a "Project Name". Then create a
@ wiki page with that name. The content of that wiki page
@ will be displayed in place of this message.
style_footer();
}
/*
** Return true if the given pagename is the name of the sandbox
*/
static int is_sandbox(const char *zPagename){
return strcasecmp(zPagename,"sandbox")==0 ||
strcasecmp(zPagename,"sand box")==0;
}
/*
** WEBPAGE: wiki
** URL: /wiki?name=PAGENAME
*/
void wiki_page(void){
char *zTag;
int rid;
int isSandbox;
Blob wiki;
Manifest m;
const char *zPageName;
char *zHtmlPageName;
char *zBody = mprintf("%s","<i>Empty Page</i>");
login_check_credentials();
if( !g.okRdWiki ){ login_needed(); return; }
zPageName = P("name");
if( zPageName==0 ){
style_header("Wiki");
@ <ul>
@ <li> <a href="%s(g.zBaseURL)/timeline?y=w">Recent changes</a> to wiki
@ pages. </li>
@ <li> <a href="%s(g.zBaseURL)/wiki_rules">Formatting rules</a> for
@ wiki.</li>
@ <li> Use the <a href="%s(g.zBaseURL)/wiki?name=Sandbox">Sandbox</a>
@ to experiment.</li>
@ <li> <a href="%s(g.zBaseURL)/wcontent">List of All Wiki Pages</a>
@ available on this server.</li>
@ </ul>
style_footer();
return;
}
if( check_name(zPageName) ) return;
isSandbox = is_sandbox(zPageName);
if( isSandbox ){
zBody = db_get("sandbox",zBody);
}else{
zTag = mprintf("wiki-%s", zPageName);
rid = db_int(0,
"SELECT rid FROM tagxref"
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
" ORDER BY mtime DESC", zTag
);
free(zTag);
memset(&m, 0, sizeof(m));
blob_zero(&m.content);
if( rid ){
Blob content;
content_get(rid, &content);
manifest_parse(&m, &content);
if( m.type==CFTYPE_WIKI ){
zBody = m.zWiki;
}
}
}
if( isSandbox || (rid && g.okWrWiki) || (!rid && g.okNewWiki) ){
style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T",
g.zTop, zPageName);
}
if( isSandbox || (rid && g.okApndWiki) ){
style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T",
g.zTop, zPageName);
}
if( !isSandbox && g.okHistory ){
style_submenu_element("History", "History", "%s/whistory?name=%T",
g.zTop, zPageName);
}
zHtmlPageName = mprintf("%h", zPageName);
style_header(zHtmlPageName);
blob_init(&wiki, zBody, -1);
wiki_convert(&wiki, 0, 0);
blob_reset(&wiki);
if( !isSandbox ){
manifest_clear(&m);
}
style_footer();
}
/*
** WEBPAGE: wikiedit
** URL: /wikiedit?name=PAGENAME
*/
void wikiedit_page(void){
char *zTag;
int rid;
int isSandbox;
Blob wiki;
Manifest m;
const char *zPageName;
char *zHtmlPageName;
int n;
const char *z;
char *zBody = (char*)P("w");
if( zBody ){
zBody = mprintf("%s", zBody);
}
login_check_credentials();
zPageName = PD("name","");
if( check_name(zPageName) ) return;
isSandbox = is_sandbox(zPageName);
if( isSandbox ){
if( zBody==0 ){
zBody = db_get("sandbox","");
}
}else{
zTag = mprintf("wiki-%s", zPageName);
rid = db_int(0,
"SELECT rid FROM tagxref"
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
" ORDER BY mtime DESC", zTag
);
free(zTag);
if( (rid && !g.okWrWiki) || (!rid && !g.okNewWiki) ){
login_needed();
return;
}
memset(&m, 0, sizeof(m));
blob_zero(&m.content);
if( rid && zBody==0 ){
Blob content;
content_get(rid, &content);
manifest_parse(&m, &content);
if( m.type==CFTYPE_WIKI ){
zBody = m.zWiki;
}
}
}
if( P("submit")!=0 && zBody!=0 ){
char *zDate;
Blob cksum;
int nrid;
blob_zero(&wiki);
db_begin_transaction();
if( isSandbox ){
db_set("sandbox",zBody,0);
}else{
zDate = db_text(0, "SELECT datetime('now')");
zDate[10] = 'T';
blob_appendf(&wiki, "D %s\n", zDate);
free(zDate);
blob_appendf(&wiki, "L %F\n", zPageName);
if( rid ){
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
blob_appendf(&wiki, "P %s\n", zUuid);
free(zUuid);
}
if( g.zLogin ){
blob_appendf(&wiki, "U %F\n", g.zLogin);
}
blob_appendf(&wiki, "W %d\n%s\n", strlen(zBody), zBody);
md5sum_blob(&wiki, &cksum);
blob_appendf(&wiki, "Z %b\n", &cksum);
blob_reset(&cksum);
nrid = content_put(&wiki, 0, 0);
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
manifest_crosslink(nrid, &wiki);
blob_reset(&wiki);
content_deltify(rid, nrid, 0);
}
db_end_transaction(0);
cgi_redirectf("wiki?name=%T", zPageName);
}
if( P("cancel")!=0 ){
cgi_redirectf("wiki?name=%T", zPageName);
return;
}
if( zBody==0 ){
zBody = mprintf("<i>Empty Page</i>");
}
zHtmlPageName = mprintf("Edit: %h", zPageName);
style_header(zHtmlPageName);
if( P("preview")!=0 ){
blob_zero(&wiki);
blob_append(&wiki, zBody, -1);
@ Preview:<hr>
wiki_convert(&wiki, 0, 0);
@ <hr>
blob_reset(&wiki);
}
for(n=2, z=zBody; z[0]; z++){
if( z[0]=='\n' ) n++;
}
if( n<20 ) n = 20;
if( n>200 ) n = 200;
@ <form method="POST" action="%s(g.zBaseURL)/wikiedit">
@ <input type="hidden" name="name" value="%h(zPageName)">
@ <textarea name="w" class="wikiedit" cols="80"
@ rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
@ <br>
@ <input type="submit" name="preview" value="Preview Your Changes">
@ <input type="submit" name="submit" value="Apply These Changes">
@ <input type="submit" name="cancel" value="Cancel">
@ </form>
if( !isSandbox ){
manifest_clear(&m);
}
style_footer();
}
/*
** Append the wiki text for an remark to the end of the given BLOB.
*/
static void appendRemark(Blob *p){
char *zDate;
const char *zUser;
const char *zRemark;
zDate = db_text(0, "SELECT datetime('now')");
blob_appendf(p, "\n\n<hr><i>On %s UTC %h", zDate, g.zLogin);
free(zDate);
zUser = PD("u",g.zLogin);
if( zUser[0] && strcmp(zUser,g.zLogin) ){
blob_appendf(p, " (claiming to be %h)", zUser);
}
zRemark = PD("r","");
blob_appendf(p, " added:</i><br />\n%s", zRemark);
}
/*
** WEBPAGE: wikiappend
** URL: /wikiappend?name=PAGENAME
*/
void wikiappend_page(void){
char *zTag;
int rid;
int isSandbox;
const char *zPageName;
char *zHtmlPageName;
const char *zUser;
login_check_credentials();
zPageName = PD("name","");
if( check_name(zPageName) ) return;
isSandbox = is_sandbox(zPageName);
if( !isSandbox ){
zTag = mprintf("wiki-%s", zPageName);
rid = db_int(0,
"SELECT rid FROM tagxref"
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
" ORDER BY mtime DESC", zTag
);
free(zTag);
if( !rid ){
cgi_redirect("index");
return;
}
}
if( !g.okApndWiki ){
login_needed();
return;
}
if( P("submit")!=0 && P("r")!=0 && P("u")!=0 ){
char *zDate;
Blob cksum;
int nrid;
Blob body;
Blob content;
Blob wiki;
Manifest m;
blob_zero(&body);
if( isSandbox ){
blob_appendf(&body, db_get("sandbox",""));
appendRemark(&body);
db_set("sandbox", blob_str(&body), 0);
}else{
content_get(rid, &content);
manifest_parse(&m, &content);
if( m.type==CFTYPE_WIKI ){
blob_appendf(&body, m.zWiki, -1);
}
manifest_clear(&m);
blob_zero(&wiki);
db_begin_transaction();
zDate = db_text(0, "SELECT datetime('now')");
zDate[10] = 'T';
blob_appendf(&wiki, "D %s\n", zDate);
blob_appendf(&wiki, "L %F\n", zPageName);
if( rid ){
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
blob_appendf(&wiki, "P %s\n", zUuid);
free(zUuid);
}
if( g.zLogin ){
blob_appendf(&wiki, "U %F\n", g.zLogin);
}
blob_appendf(&body, "\n<hr>\n");
appendRemark(&body);
blob_appendf(&wiki, "W %d\n%s\n", blob_size(&body), blob_str(&body));
md5sum_blob(&wiki, &cksum);
blob_appendf(&wiki, "Z %b\n", &cksum);
blob_reset(&cksum);
nrid = content_put(&wiki, 0, 0);
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
manifest_crosslink(nrid, &wiki);
blob_reset(&wiki);
content_deltify(rid, nrid, 0);
db_end_transaction(0);
}
cgi_redirectf("wiki?name=%T", zPageName);
}
if( P("cancel")!=0 ){
cgi_redirectf("wiki?name=%T", zPageName);
return;
}
zHtmlPageName = mprintf("Append Comment To: %h", zPageName);
style_header(zHtmlPageName);
if( P("preview")!=0 ){
Blob preview;
blob_zero(&preview);
appendRemark(&preview);
@ Preview:<hr>
wiki_convert(&preview, 0, 0);
@ <hr>
blob_reset(&preview);
}
zUser = PD("u", g.zLogin);
@ <form method="POST" action="%s(g.zBaseURL)/wikiappend">
@ <input type="hidden" name="name" value="%h(zPageName)">
@ Your Name:
@ <input type="text" name="u" size="20" value="%h(zUser)"><br>
@ Comment to append:<br>
@ <textarea name="r" class="wikiedit" cols="80"
@ rows="10" wrap="virtual">%h(PD("r",""))</textarea>
@ <br>
@ <input type="submit" name="preview" value="Preview Your Comment">
@ <input type="submit" name="submit" value="Append Your Changes">
@ <input type="submit" name="cancel" value="Cancel">
@ </form>
style_footer();
}
/*
** WEBPAGE: whistory
** URL: /whistory?name=PAGENAME
**
** Show the complete change history for a single wiki page.
*/
void whistory_page(void){
Stmt q;
char *zTitle;
char *zSQL;
const char *zPageName;
login_check_credentials();
if( !g.okHistory ){ login_needed(); return; }
zPageName = PD("name","");
zTitle = mprintf("History Of %h", zPageName);
style_header(zTitle);
free(zTitle);
zSQL = mprintf("%s AND event.objid IN "
" (SELECT rid FROM tagxref WHERE tagid="
"(SELECT tagid FROM tag WHERE tagname='wiki-%q'))"
"ORDER BY mtime DESC",
timeline_query_for_www(), zPageName);
db_prepare(&q, zSQL);
free(zSQL);
www_print_timeline(&q, 0, 0, 0, 0);
db_finalize(&q);
style_footer();
}
/*
** WEBPAGE: wcontent
**
** List all available wiki pages with date created and last modified.
*/
void wcontent_page(void){
Stmt q;
login_check_credentials();
if( !g.okRdWiki ){ login_needed(); return; }
style_header("Available Wiki Pages");
@ <ul>
db_prepare(&q,
"SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname GLOB 'wiki-*'"
" ORDER BY lower(tagname)"
);
while( db_step(&q)==SQLITE_ROW ){
const char *zName = db_column_text(&q, 0);
@ <li><a href="%s(g.zBaseURL)/wiki?name=%T(zName)">%h(zName)</a></li>
}
db_finalize(&q);
@ </ul>
style_footer();
}
/*
** WEBPAGE: wiki_rules
*/
void wikirules_page(void){
style_header("Wiki Formatting Rules");
@ <h2>Formatting Rule Summary</h2>
@ <ol>
@ <li> Blank lines are paragraph breaks
@ <li> Bullet list items are a "*" at the beginning of the line.
@ <li> Enumeration list items are a number at the beginning of a line.
@ <li> Indented pargraphs begin with a tab or two spaces.
@ <li> Hyperlinks are contained with square brackets: "[target]"
@ <li> Most ordinary HTML works.
@ <li> <verbatim> and <nowiki>.
@ </ol>
@ <p>We call the first five rules above "wiki" formatting rules. The
@ last two rules are the HTML formatting rule.</p>
@ <h2>Formatting Rule Details</h2>
@ <ol>
@ <li> <p><b>Paragraphs</b>. Any sequence of one or more blank lines forms
@ a paragraph break. Centered or right-justified paragraphs are not
@ supported by wiki markup, but you can do these things if you need them
@ using HTML.</p>
@ <li> <p><b>Bullet Lists</b>.
@ A bullet list item begins with a single "*" character surrounded on
@ both sides by two or more spaces or by a tab. Only a single level
@ of bullet list is supported by wiki. For tested lists, use HTML.</p>
@ <li> <p><b>Enumeration Lists</b>.
@ An enumeration list item begins with one or more digits optionally
@ followed by a "." surrounded on both sides by two or more spaces or
@ by a tab. The number is significant and becomes the number shown
@ in the rendered enumeration item. Only a single level of enumeration
@ list is supported by wiki. For nested enumerations or for
@ enumerations that count using letters or roman numerials, use HTML.</p>
@ <li> <p><b>Indented Paragraphs</b>.
@ Any paragraph that begins with two or more spaces or a tab and
@ which is not a bullet or enumeration list item is rendered
@ indented. Only a single level of indentation is supported by</p>
@ <li> <p><b>Hyperlinks</b>.
@ Text within square brackets ("[...]") becomes a hyperlink. The
@ target can be a wiki page name, the UUID of a check-in or ticket,
@ the name of an image, or a URL. By default, the target is displayed
@ as the text of the hyperlink. But you can specify alternative text
@ after the target name separated by a "|" character.</p>
@ <li> <p><b>HTML</b>.
@ The following standard HTML elements may be used:
@ <a>
@ <address>
@ <b>
@ <big>
@ <blockquote>
@ <br>
@ <center>
@ <cite>
@ <code>
@ <dd>
@ <dfn>
@ <dl>
@ <dt>
@ <em>
@ <font>
@ <h1>
@ <h2>
@ <h3>
@ <h4>
@ <h5>
@ <h6>
@ <hr>
@ <img>
@ <i>
@ <kbd>
@ <li>
@ <nobr>
@ <ol>
@ <p>
@ <pre>
@ <s>
@ <samp>
@ <small>
@ <strike>
@ <strong>
@ <sub>
@ <sup>
@ <table>
@ <td>
@ <th>
@ <tr>
@ <tt>
@ <u>
@ <ul>
@ <var>.
@ In addition, there are two non-standard elements available:
@ <verbatim> and <nowiki>.
@ No other elements are allowed. All attributes are checked and
@ only a few benign attributes are allowed on each element.
@ In particular, any attributes that specify javascript or CSS
@ are elided.</p>
@ <p>The <verbatim> tag disables all wiki and HTML markup
@ up through the next </verbatim>. The <nowiki> tag
@ disables all wiki formatting rules through the matching
@ </nowiki> element.
@ </ol>
style_footer();
}
|
Changes to src/wikiformat.c.
| ︙ | ︙ | |||
29 30 31 32 33 34 35 36 37 38 39 40 41 | #if INTERFACE /* ** Allowed wiki transformation operations */ #define WIKI_NOFOLLOW 0x001 #define WIKI_HTML 0x002 #endif /* ** These are the only markup attributes allowed. */ | > > | | | | | | | | | | | | | | | > | | | | | | | | | > > | | | | | > | | < | | | | | | | | | | | | | | | | | | | | | | | | | | > | | < | | | | | | | | | < > | | < > > > > > > > > > | > > < | 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
#if INTERFACE
/*
** Allowed wiki transformation operations
*/
#define WIKI_NOFOLLOW 0x001
#define WIKI_HTML 0x002
#define WIKI_INLINE 0x004 /* Do not surround with <p>..</p> */
#define WIKI_NOBLOCK 0x008 /* No block markup of any kind */
#endif
/*
** These are the only markup attributes allowed.
*/
#define ATTR_ALIGN 0x0000001
#define ATTR_ALT 0x0000002
#define ATTR_BGCOLOR 0x0000004
#define ATTR_BORDER 0x0000008
#define ATTR_CELLPADDING 0x0000010
#define ATTR_CELLSPACING 0x0000020
#define ATTR_CLEAR 0x0000040
#define ATTR_COLOR 0x0000080
#define ATTR_COLSPAN 0x0000100
#define ATTR_COMPACT 0x0000200
#define ATTR_FACE 0x0000400
#define ATTR_HEIGHT 0x0000800
#define ATTR_HREF 0x0001000
#define ATTR_HSPACE 0x0002000
#define ATTR_ID 0x0004000
#define ATTR_NAME 0x0008000
#define ATTR_ROWSPAN 0x0010000
#define ATTR_SIZE 0x0020000
#define ATTR_SRC 0x0040000
#define ATTR_START 0x0080000
#define ATTR_TYPE 0x0100000
#define ATTR_VALIGN 0x0200000
#define ATTR_VALUE 0x0400000
#define ATTR_VSPACE 0x0800000
#define ATTR_WIDTH 0x1000000
static const struct AllowedAttribute {
const char *zName;
unsigned int iMask;
} aAttribute[] = {
{ 0, 0 },
{ "align", ATTR_ALIGN, },
{ "alt", ATTR_ALT, },
{ "bgcolor", ATTR_BGCOLOR, },
{ "border", ATTR_BORDER, },
{ "cellpadding", ATTR_CELLPADDING, },
{ "cellspacing", ATTR_CELLSPACING, },
{ "clear", ATTR_CLEAR, },
{ "color", ATTR_COLOR, },
{ "colspan", ATTR_COLSPAN, },
{ "compact", ATTR_COMPACT, },
{ "face", ATTR_FACE, },
{ "height", ATTR_HEIGHT, },
{ "href", ATTR_HREF, },
{ "hspace", ATTR_HSPACE, },
{ "id", ATTR_ID, },
{ "name", ATTR_NAME, },
{ "rowspan", ATTR_ROWSPAN, },
{ "size", ATTR_SIZE, },
{ "src", ATTR_SRC, },
{ "start", ATTR_START, },
{ "type", ATTR_TYPE, },
{ "valign", ATTR_VALIGN, },
{ "value", ATTR_VALUE, },
{ "vspace", ATTR_VSPACE, },
{ "width", ATTR_WIDTH, },
};
/*
** Use binary search to locate a tag in the aAttribute[] table.
*/
static int findAttr(const char *z){
int i, c, first, last;
first = 1;
last = sizeof(aAttribute)/sizeof(aAttribute[0]) - 1;
while( first<=last ){
i = (first+last)/2;
c = strcmp(aAttribute[i].zName, z);
if( c==0 ){
return i;
}else if( c<0 ){
first = i+1;
}else{
last = i-1;
}
}
return 0;
}
/*
** Allowed markup.
**
** Except for MARKUP_INVALID, this must all be in alphabetical order
** and in numerical sequence. The first markup type must be zero.
** The value for MARKUP_XYZ must correspond to the <xyz> entry
** in aAllowedMarkup[].
*/
#define MARKUP_INVALID 0
#define MARKUP_A 1
#define MARKUP_ADDRESS 2
#define MARKUP_B 3
#define MARKUP_BIG 4
#define MARKUP_BLOCKQUOTE 5
#define MARKUP_BR 6
#define MARKUP_CENTER 7
#define MARKUP_CITE 8
#define MARKUP_CODE 9
#define MARKUP_DD 10
#define MARKUP_DFN 11
#define MARKUP_DL 12
#define MARKUP_DT 13
#define MARKUP_EM 14
#define MARKUP_FONT 15
#define MARKUP_H1 16
#define MARKUP_H2 17
#define MARKUP_H3 18
#define MARKUP_H4 19
#define MARKUP_H5 20
#define MARKUP_H6 21
#define MARKUP_HR 22
#define MARKUP_IMG 23
#define MARKUP_I 24
#define MARKUP_KBD 25
#define MARKUP_LI 26
#define MARKUP_NOBR 27
#define MARKUP_NOWIKI 28
#define MARKUP_OL 29
#define MARKUP_P 30
#define MARKUP_PRE 31
#define MARKUP_S 32
#define MARKUP_SAMP 33
#define MARKUP_SMALL 34
#define MARKUP_STRIKE 35
#define MARKUP_STRONG 36
#define MARKUP_SUB 37
#define MARKUP_SUP 38
#define MARKUP_TABLE 39
#define MARKUP_TD 40
#define MARKUP_TH 41
#define MARKUP_TR 42
#define MARKUP_TT 43
#define MARKUP_U 44
#define MARKUP_UL 45
#define MARKUP_VAR 46
#define MARKUP_VERBATIM 47
/*
** The various markup is divided into the following types:
*/
#define MUTYPE_SINGLE 0x0001 /* <img>, <br>, or <hr> */
#define MUTYPE_BLOCK 0x0002 /* Forms a new paragraph. ex: <p>, <h2> */
#define MUTYPE_FONT 0x0004 /* Font changes. ex: <b>, <font>, <sub> */
#define MUTYPE_LIST 0x0010 /* Lists. <ol>, <ul>, or <dl> */
#define MUTYPE_LI 0x0020 /* List items. <li>, <dd>, <dt> */
#define MUTYPE_TABLE 0x0040 /* <table> */
#define MUTYPE_TR 0x0080 /* <tr> */
#define MUTYPE_TD 0x0100 /* <td> or <th> */
#define MUTYPE_SPECIAL 0x0200 /* <nowiki> or <verbatim> */
#define MUTYPE_HYPERLINK 0x0400 /* <a> */
/*
** These markup types must have an end tag.
*/
#define MUTYPE_STACK (MUTYPE_BLOCK | MUTYPE_FONT | MUTYPE_LIST | MUTYPE_TABLE)
/*
** This markup types are allowed for "inline" text.
*/
#define MUTYPE_INLINE (MUTYPE_FONT | MUTYPE_HYPERLINK)
static const struct AllowedMarkup {
const char *zName; /* Name of the markup */
char iCode; /* The MARKUP_* code */
short int iType; /* The MUTYPE_* code */
int allowedAttr; /* Allowed attributes on this markup */
} aMarkup[] = {
{ 0, MARKUP_INVALID, 0, 0 },
{ "a", MARKUP_A, MUTYPE_HYPERLINK,
ATTR_HREF|ATTR_NAME },
{ "address", MARKUP_ADDRESS, MUTYPE_BLOCK, 0 },
{ "b", MARKUP_B, MUTYPE_FONT, 0 },
{ "big", MARKUP_BIG, MUTYPE_FONT, 0 },
{ "blockquote", MARKUP_BLOCKQUOTE, MUTYPE_BLOCK, 0 },
{ "br", MARKUP_BR, MUTYPE_SINGLE, ATTR_CLEAR },
{ "center", MARKUP_CENTER, MUTYPE_BLOCK, 0 },
{ "cite", MARKUP_CITE, MUTYPE_FONT, 0 },
{ "code", MARKUP_CODE, MUTYPE_FONT, 0 },
{ "dd", MARKUP_DD, MUTYPE_LI, 0 },
{ "dfn", MARKUP_DFN, MUTYPE_FONT, 0 },
{ "dl", MARKUP_DL, MUTYPE_LIST, ATTR_COMPACT },
|
| ︙ | ︙ | |||
230 231 232 233 234 235 236 237 238 |
ATTR_TYPE|ATTR_VALUE },
{ "nobr", MARKUP_NOBR, MUTYPE_FONT, 0 },
{ "nowiki", MARKUP_NOWIKI, MUTYPE_SPECIAL, 0 },
{ "ol", MARKUP_OL, MUTYPE_LIST,
ATTR_START|ATTR_TYPE|ATTR_COMPACT },
{ "p", MARKUP_P, MUTYPE_BLOCK, ATTR_ALIGN },
{ "pre", MARKUP_PRE, MUTYPE_BLOCK, 0 },
{ "samp", MARKUP_SAMP, MUTYPE_FONT, 0 },
{ "small", MARKUP_SMALL, MUTYPE_FONT, 0 },
| > < > < | | 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
ATTR_TYPE|ATTR_VALUE },
{ "nobr", MARKUP_NOBR, MUTYPE_FONT, 0 },
{ "nowiki", MARKUP_NOWIKI, MUTYPE_SPECIAL, 0 },
{ "ol", MARKUP_OL, MUTYPE_LIST,
ATTR_START|ATTR_TYPE|ATTR_COMPACT },
{ "p", MARKUP_P, MUTYPE_BLOCK, ATTR_ALIGN },
{ "pre", MARKUP_PRE, MUTYPE_BLOCK, 0 },
{ "s", MARKUP_S, MUTYPE_FONT, 0 },
{ "samp", MARKUP_SAMP, MUTYPE_FONT, 0 },
{ "small", MARKUP_SMALL, MUTYPE_FONT, 0 },
{ "strike", MARKUP_STRIKE, MUTYPE_FONT, 0 },
{ "strong", MARKUP_STRONG, MUTYPE_FONT, 0 },
{ "sub", MARKUP_SUB, MUTYPE_FONT, 0 },
{ "sup", MARKUP_SUP, MUTYPE_FONT, 0 },
{ "table", MARKUP_TABLE, MUTYPE_TABLE,
ATTR_ALIGN|ATTR_BGCOLOR|ATTR_BORDER|ATTR_CELLPADDING|
ATTR_CELLSPACING|ATTR_HSPACE|ATTR_VSPACE },
{ "td", MARKUP_TD, MUTYPE_TD,
ATTR_ALIGN|ATTR_BGCOLOR|ATTR_COLSPAN|
ATTR_ROWSPAN|ATTR_VALIGN },
{ "th", MARKUP_TH, MUTYPE_TD,
ATTR_ALIGN|ATTR_BGCOLOR|ATTR_COLSPAN|
ATTR_ROWSPAN|ATTR_VALIGN },
{ "tr", MARKUP_TR, MUTYPE_TR,
ATTR_ALIGN|ATTR_BGCOLOR||ATTR_VALIGN },
{ "tt", MARKUP_TT, MUTYPE_FONT, 0 },
{ "u", MARKUP_U, MUTYPE_FONT, 0 },
{ "ul", MARKUP_UL, MUTYPE_LIST,
ATTR_TYPE|ATTR_COMPACT },
{ "var", MARKUP_VAR, MUTYPE_FONT, 0 },
{ "verbatim", MARKUP_VERBATIM, MUTYPE_SPECIAL, ATTR_ID },
};
/*
** Use binary search to locate a tag in the aMarkup[] table.
*/
static int findTag(const char *z){
int i, c, first, last;
first = 1;
last = sizeof(aMarkup)/sizeof(aMarkup[0]) - 1;
while( first<=last ){
i = (first+last)/2;
c = strcmp(aMarkup[i].zName, z);
if( c==0 ){
assert( aMarkup[i].iCode==i );
return i;
|
| ︙ | ︙ | |||
294 295 296 297 298 299 300 | #define TOKEN_ENUM 7 /* " \(?\d+[.)]? " */ #define TOKEN_INDENT 8 /* " " */ #define TOKEN_TEXT 9 /* None of the above */ /* ** State flags */ | | | | | > | > > > > > > > > > > > > > > > > > > > | | 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
#define TOKEN_ENUM 7 /* " \(?\d+[.)]? " */
#define TOKEN_INDENT 8 /* " " */
#define TOKEN_TEXT 9 /* None of the above */
/*
** State flags
*/
#define AT_NEWLINE 0x001 /* At start of a line */
#define AT_PARAGRAPH 0x002 /* At start of a paragraph */
#define ALLOW_WIKI 0x004 /* Allow wiki markup */
#define FONT_MARKUP_ONLY 0x008 /* Only allow MUTYPE_FONT markup */
#define INLINE_MARKUP_ONLY 0x010 /* Allow only "inline" markup */
#define IN_LIST 0x020 /* Within wiki <ul> or <ol> */
/*
** Current state of the rendering engine
*/
typedef struct Renderer Renderer;
struct Renderer {
Blob *pOut; /* Output appended to this blob */
int state; /* Flag that govern rendering */
int wikiList; /* Current wiki list type */
int inVerbatim; /* True in <verbatim> mode */
int preVerbState; /* Value of state prior to verbatim */
int wantAutoParagraph; /* True if a <p> is desired */
int inAutoParagraph; /* True if within an automatic paragraph */
const char *zVerbatimId; /* The id= attribute of <verbatim> */
int nStack; /* Number of elements on the stack */
int nAlloc; /* Space allocated for aStack */
unsigned char *aStack; /* Open markup stack */
};
/*
** z points to a "<" character. Check to see if this is the start of
** a valid markup. If it is, return the total number of characters in
** the markup including the initial "<" and the terminating ">". If
** it is not well-formed markup, return 0.
*/
static int markupLength(const char *z){
int n = 1;
int inparen = 0;
if( z[n]=='/' ){ n++; }
if( !isalpha(z[n]) ) return 0;
while( isalnum(z[n]) ){ n++; }
if( z[n]!='>' && !isspace(z[n]) ) return 0;
while( z[n] && (z[n]!='>' || inparen) ){
if( z[n]=='"' ){
inparen = !inparen;
}
n++;
}
|
| ︙ | ︙ | |||
408 409 410 411 412 413 414 415 416 417 418 419 420 421 |
if( z[n]=='\t' ) i++;
i++;
n++;
}
if( i<2 || isspace(z[n]) ) return 0;
return n;
}
/*
** Check to see if the z[] string is the beginning of an indented
** paragraph. If it is, return the length of the indent. Otherwise
** return 0.
*/
static int indentLength(const char *z){
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 |
if( z[n]=='\t' ) i++;
i++;
n++;
}
if( i<2 || isspace(z[n]) ) return 0;
return n;
}
/*
** Check to see if the z[] string is the beginning of a enumeration value.
** If it is, return the length of the bullet text. Otherwise return 0.
**
** Syntax:
** * a tab or two or more spaces
** * one or more digits
** * optional "."
** * another tab or two ore more spaces.
**
*/
static int enumLength(const char *z){
int i, n;
n = 0;
i = 0;
while( z[n]==' ' || z[n]=='\t' ){
if( z[n]=='\t' ) i++;
i++;
n++;
}
if( i<2 ) return 0;
for(i=0; isdigit(z[n]); i++, n++){}
if( i==0 ) return 0;
if( z[n]=='.' ){
n++;
}
i = 0;
while( z[n]==' ' || z[n]=='\t' ){
if( z[n]=='\t' ) i++;
i++;
n++;
}
if( i<2 || isspace(z[n]) ) return 0;
return n;
}
/*
** Check to see if the z[] string is the beginning of an indented
** paragraph. If it is, return the length of the indent. Otherwise
** return 0.
*/
static int indentLength(const char *z){
|
| ︙ | ︙ | |||
447 448 449 450 451 452 453 | } /* ** z points to the start of a token. Return the number of ** characters in that token. Write the token type into *pTokenType. */ | | | | < | < < | | | 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 |
}
/*
** z points to the start of a token. Return the number of
** characters in that token. Write the token type into *pTokenType.
*/
static int nextToken(const char *z, Renderer *p, int *pTokenType){
int n;
if( z[0]=='<' ){
n = markupLength(z);
if( n>0 ){
*pTokenType = TOKEN_MARKUP;
return n;
}else{
*pTokenType = TOKEN_CHARACTER;
return 1;
}
}
if( z[0]=='&' && (p->inVerbatim || !isElement(z)) ){
*pTokenType = TOKEN_CHARACTER;
return 1;
}
if( (p->state & ALLOW_WIKI)!=0 ){
if( z[0]=='\n' ){
n = paragraphBreakLength(z);
if( n>0 ){
*pTokenType = TOKEN_PARAGRAPH;
return n;
}else if( isspace(z[1]) ){
*pTokenType = TOKEN_NEWLINE;
return 1;
}
}
if( (p->state & AT_NEWLINE)!=0 && isspace(z[0]) ){
n = bulletLength(z);
if( n>0 ){
*pTokenType = TOKEN_BULLET;
return n;
}
n = enumLength(z);
if( n>0 ){
*pTokenType = TOKEN_ENUM;
return n;
}
}
if( (p->state & AT_PARAGRAPH)!=0 && isspace(z[0]) ){
n = indentLength(z);
if( n>0 ){
*pTokenType = TOKEN_INDENT;
return n;
}
}
if( z[0]=='[' && (n = linkLength(z))>0 ){
*pTokenType = TOKEN_LINK;
return n;
}
}
*pTokenType = TOKEN_TEXT;
return 1 + textLength(z+1, p->state & ALLOW_WIKI);
}
/*
** A single markup is parsed into an instance of the following
** structure.
*/
typedef struct ParsedMarkup ParsedMarkup;
|
| ︙ | ︙ | |||
602 603 604 605 606 607 608 |
static void renderMarkup(Blob *pOut, ParsedMarkup *p){
int i;
if( p->endTag ){
blob_appendf(pOut, "</%s>", aMarkup[p->iCode].zName);
}else{
blob_appendf(pOut, "<%s", aMarkup[p->iCode].zName);
for(i=0; i<p->nAttr; i++){
| | | 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 |
static void renderMarkup(Blob *pOut, ParsedMarkup *p){
int i;
if( p->endTag ){
blob_appendf(pOut, "</%s>", aMarkup[p->iCode].zName);
}else{
blob_appendf(pOut, "<%s", aMarkup[p->iCode].zName);
for(i=0; i<p->nAttr; i++){
blob_appendf(pOut, " %s", aAttribute[p->aAttr[i].iCode].zName);
if( p->aAttr[i].zValue ){
blob_appendf(pOut, "=\"%s\"", p->aAttr[i].zValue);
}
}
blob_append(pOut, ">", 1);
}
}
|
| ︙ | ︙ | |||
626 627 628 629 630 631 632 |
char *z = p->aAttr[i].zValue;
if( z==0 ) continue;
n = strlen(z);
z[n] = p->aAttr[i].cTerm;
}
}
| < < < < < < < < < < < < < < < | 693 694 695 696 697 698 699 700 701 702 703 704 705 706 |
char *z = p->aAttr[i].zValue;
if( z==0 ) continue;
n = strlen(z);
z[n] = p->aAttr[i].cTerm;
}
}
/*
** Pop a single element off of the stack. As the element is popped,
** output its end tag.
*/
static void popStack(Renderer *p){
if( p->nStack ){
p->nStack--;
|
| ︙ | ︙ | |||
684 685 686 687 688 689 690 |
popStack(p);
}
}
/*
** Pop the stack until the top-most element of the stack
** is an element that matches the type in iMask. Return
| > | | | | | > > > > > > > | > | | > > > > > > | > > > > > > > > > > > > > > > > > > > > > | > | 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 |
popStack(p);
}
}
/*
** Pop the stack until the top-most element of the stack
** is an element that matches the type in iMask. Return
** code of the markup element that is on left on top of the stack.
** If the stack does not have an element
** that matches iMask, then leave the stack unchanged and
** return false (MARKUP_INVALID).
*/
static int backupToType(Renderer *p, int iMask){
int i;
for(i=p->nStack-1; i>=0 && (aMarkup[p->aStack[i]].iType&iMask)==0; i--){}
if( i<0 ) return 0;
i++;
while( p->nStack>i ){
popStack(p);
}
return p->aStack[i-1];
}
/*
** Begin a new paragraph if that something that is needed.
*/
static void startAutoParagraph(Renderer *p){
if( p->wantAutoParagraph==0 ) return;
blob_appendf(p->pOut, "<p>", -1);
pushStack(p, MARKUP_P);
p->wantAutoParagraph = 0;
p->inAutoParagraph = 1;
}
/*
** End a paragraph if we are in one.
*/
static void endAutoParagraph(Renderer *p){
if( p->inAutoParagraph ){
popStackToTag(p, MARKUP_P);
p->inAutoParagraph = 0;
}
}
/*
** If the input string corresponds to an existing baseline,
** return true.
*/
static int is_valid_uuid(const char *z){
int n = strlen(z);
if( n<4 || n>UUID_SIZE ) return 0;
if( !validate16(z, n) ) return 0;
return 1;
}
/*
** Resolve a hyperlink. The argument is the content of the [...]
** in the wiki. Append the URL to the output of the Renderer.
*/
static void resolveHyperlink(const char *zTarget, Renderer *p){
if( strncmp(zTarget, "http:", 5)==0
|| strncmp(zTarget, "https:", 6)==0
|| strncmp(zTarget, "ftp:", 4)==0
|| strncmp(zTarget, "mailto:", 7)==0
){
blob_appendf(p->pOut, zTarget);
}else if( zTarget[0]=='/' ){
blob_appendf(p->pOut, "%s%h", g.zBaseURL, zTarget);
}else if( is_valid_uuid(zTarget) ){
blob_appendf(p->pOut, "%s/info/%s", g.zBaseURL, zTarget);
}else if( wiki_name_is_wellformed(zTarget) ){
blob_appendf(p->pOut, "%s/wiki?name=%T", g.zBaseURL, zTarget);
}else{
blob_appendf(p->pOut, "error");
}
}
/*
** Check to see if the given parsed markup is the correct
** </verbatim> tag.
*/
static int endVerbatim(Renderer *p, ParsedMarkup *pMarkup){
|
| ︙ | ︙ | |||
750 751 752 753 754 755 756 757 758 |
**
** This routine will probably modify the content of z[].
*/
static void wiki_render(Renderer *p, char *z){
int tokenType;
ParsedMarkup markup;
int n;
while( z[0] ){
| > | > > | > > > > > > > > > < | > > > > > > | | > | > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | > | > > > > | > | 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 |
**
** This routine will probably modify the content of z[].
*/
static void wiki_render(Renderer *p, char *z){
int tokenType;
ParsedMarkup markup;
int n;
int inlineOnly = (p->state & INLINE_MARKUP_ONLY)!=0;
while( z[0] ){
n = nextToken(z, p, &tokenType);
p->state &= ~(AT_NEWLINE|AT_PARAGRAPH);
switch( tokenType ){
case TOKEN_PARAGRAPH: {
if( inlineOnly ){
/* blob_append(p->pOut, " ¶ ", -1); */
blob_append(p->pOut, " ", -1);
}else{
if( p->wikiList ){
popStackToTag(p, p->wikiList);
p->wikiList = 0;
}
endAutoParagraph(p);
blob_appendf(p->pOut, "\n\n", 1);
p->wantAutoParagraph = 1;
}
p->state |= AT_PARAGRAPH|AT_NEWLINE;
break;
}
case TOKEN_NEWLINE: {
blob_append(p->pOut, "\n", 1);
p->state |= AT_NEWLINE;
break;
}
case TOKEN_BULLET: {
if( inlineOnly ){
blob_append(p->pOut, " • ", -1);
}else{
if( p->wikiList!=MARKUP_UL ){
if( p->wikiList ){
popStackToTag(p, p->wikiList);
}
pushStack(p, MARKUP_UL);
blob_append(p->pOut, "<ul>", 4);
p->wikiList = MARKUP_UL;
}
popStackToTag(p, MARKUP_LI);
startAutoParagraph(p);
pushStack(p, MARKUP_LI);
blob_append(p->pOut, "<li>", 4);
}
break;
}
case TOKEN_ENUM: {
if( inlineOnly ){
blob_appendf(p->pOut, " (%d) ", atoi(z));
}else{
if( p->wikiList!=MARKUP_OL ){
if( p->wikiList ){
popStackToTag(p, p->wikiList);
}
pushStack(p, MARKUP_OL);
blob_append(p->pOut, "<ol>", 4);
p->wikiList = MARKUP_OL;
}
popStackToTag(p, MARKUP_LI);
startAutoParagraph(p);
pushStack(p, MARKUP_LI);
blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z));
}
break;
}
case TOKEN_INDENT: {
if( inlineOnly ){
assert( p->wikiList==0 );
pushStack(p, MARKUP_BLOCKQUOTE);
blob_append(p->pOut, "<blockquote>", -1);
p->wantAutoParagraph = 0;
p->wikiList = MARKUP_BLOCKQUOTE;
}
break;
}
case TOKEN_CHARACTER: {
startAutoParagraph(p);
if( z[0]=='<' ){
blob_append(p->pOut, "<", 4);
}else if( z[0]=='&' ){
blob_append(p->pOut, "&", 5);
}
break;
}
case TOKEN_LINK: {
char *zTarget;
char *zDisplay = 0;
int i, j;
int savedState;
startAutoParagraph(p);
zTarget = &z[1];
for(i=1; z[i] && z[i]!=']'; i++){
if( z[i]=='|' && zDisplay==0 ){
zDisplay = &z[i+1];
z[i] = 0;
for(j=i-1; j>0 && isspace(z[j]); j--){ z[j] = 0; }
}
}
z[i] = 0;
if( zDisplay==0 ){
zDisplay = zTarget;
}else{
while( isspace(*zDisplay) ) zDisplay++;
}
blob_append(p->pOut, "<a href=\"", -1);
resolveHyperlink(zTarget, p);
blob_append(p->pOut, "\">", -1);
savedState = p->state;
p->state &= ~ALLOW_WIKI;
p->state |= FONT_MARKUP_ONLY;
wiki_render(p, zDisplay);
p->state = savedState;
blob_append(p->pOut, "</a>", 4);
break;
}
case TOKEN_TEXT: {
startAutoParagraph(p);
blob_append(p->pOut, z, n);
break;
}
case TOKEN_MARKUP: {
parseMarkup(&markup, z);
if( p->inVerbatim ){
if( endVerbatim(p, &markup) ){
p->inVerbatim = 0;
p->state = p->preVerbState;
blob_append(p->pOut, "</pre>", 6);
}else{
unparseMarkup(&markup);
blob_append(p->pOut, "<", 4);
n = 1;
}
}else if( markup.iCode==MARKUP_INVALID ){
unparseMarkup(&markup);
startAutoParagraph(p);
blob_append(p->pOut, "<", 4);
n = 1;
}else if( (markup.iType&MUTYPE_FONT)==0
&& (p->state & FONT_MARKUP_ONLY)!=0 ){
/* Do nothing */
}else if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){
/* Do nothing */
}else if( markup.iCode==MARKUP_NOWIKI ){
if( markup.endTag ){
p->state |= ALLOW_WIKI;
}else{
p->state &= ~ALLOW_WIKI;
}
}else if( markup.endTag ){
popStackToTag(p, markup.iCode);
}else if( markup.iCode==MARKUP_VERBATIM ){
if( markup.nAttr==1 ){
p->zVerbatimId = markup.aAttr[0].zValue;
}else{
p->zVerbatimId = 0;
}
p->inVerbatim = 1;
p->preVerbState = p->state;
p->state &= ~ALLOW_WIKI;
blob_append(p->pOut, "<pre>", 5);
p->wantAutoParagraph = 0;
}else if( markup.iType==MUTYPE_LI ){
if( backupToType(p, MUTYPE_LIST)==0 ){
pushStack(p, MARKUP_UL);
blob_append(p->pOut, "<ul>", 4);
}
pushStack(p, MARKUP_LI);
renderMarkup(p->pOut, &markup);
|
| ︙ | ︙ | |||
875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 |
if( stackTopType(p)==MUTYPE_TABLE ){
pushStack(p, MARKUP_TR);
blob_append(p->pOut, "<tr>", 4);
}
pushStack(p, markup.iCode);
renderMarkup(p->pOut, &markup);
}
}else{
if( (markup.iType & MUTYPE_STACK )!=0 ){
pushStack(p, markup.iCode);
}
renderMarkup(p->pOut, &markup);
}
break;
}
}
z += n;
}
}
/*
** Transform the text in the pIn blob. Write the results
** into the pOut blob. The pOut blob should already be
** initialized. The output is merely appended to pOut.
| > > > > > > > > > > < | | < < < < < < < < < < < < | > > > > > > > > > | > > > > | < | | 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 |
if( stackTopType(p)==MUTYPE_TABLE ){
pushStack(p, MARKUP_TR);
blob_append(p->pOut, "<tr>", 4);
}
pushStack(p, markup.iCode);
renderMarkup(p->pOut, &markup);
}
}else if( markup.iType==MUTYPE_HYPERLINK ){
popStackToTag(p, markup.iCode);
startAutoParagraph(p);
renderMarkup(p->pOut, &markup);
pushStack(p, markup.iCode);
}else{
if( markup.iType==MUTYPE_FONT ){
startAutoParagraph(p);
}else if( markup.iType==MUTYPE_BLOCK ){
p->wantAutoParagraph = 0;
}
if( (markup.iType & MUTYPE_STACK )!=0 ){
pushStack(p, markup.iCode);
}
renderMarkup(p->pOut, &markup);
}
break;
}
}
z += n;
}
}
/*
** Transform the text in the pIn blob. Write the results
** into the pOut blob. The pOut blob should already be
** initialized. The output is merely appended to pOut.
** If pOut is NULL, then the output is appended to the CGI
** reply.
*/
void wiki_convert(Blob *pIn, Blob *pOut, int flags){
char *z;
Renderer renderer;
memset(&renderer, 0, sizeof(renderer));
renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH;
if( flags & WIKI_NOBLOCK ){
renderer.state |= INLINE_MARKUP_ONLY;
}
if( flags & WIKI_INLINE ){
renderer.wantAutoParagraph = 0;
}else{
renderer.wantAutoParagraph = 1;
}
if( pOut ){
renderer.pOut = pOut;
}else{
renderer.pOut = cgi_output_blob();
}
z = blob_str(pIn);
wiki_render(&renderer, z);
endAutoParagraph(&renderer);
while( renderer.nStack ){
popStack(&renderer);
}
blob_append(renderer.pOut, "\n", 1);
free(renderer.aStack);
}
/*
** COMMAND: test-wiki-render
*/
void test_wiki_render(void){
Blob in, out;
if( g.argc!=3 ) usage("FILE");
blob_zero(&out);
blob_read_from_file(&in, g.argv[2]);
wiki_convert(&in, &out, 0);
blob_write_to_file(&out, "-");
}
|
Changes to src/xfer.c.
| ︙ | ︙ | |||
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
**
** If any error occurs, write a message into pErr which has already
** be initialized to an empty string.
*/
static void xfer_accept_file(Xfer *pXfer){
int n;
int rid;
Blob content, hash;
if( pXfer->nToken<3
|| pXfer->nToken>4
|| !blob_is_uuid(&pXfer->aToken[1])
|| !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &n)
|| n<0
|| (pXfer->nToken==4 && !blob_is_uuid(&pXfer->aToken[2]))
){
blob_appendf(&pXfer->err, "malformed file line");
return;
}
blob_zero(&content);
blob_zero(&hash);
blob_extract(pXfer->pIn, n, &content);
if( pXfer->nToken==4 ){
Blob src;
| > > > > > | | 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
**
** If any error occurs, write a message into pErr which has already
** be initialized to an empty string.
*/
static void xfer_accept_file(Xfer *pXfer){
int n;
int rid;
int srcid = 0;
Blob content, hash;
if( pXfer->nToken<3
|| pXfer->nToken>4
|| !blob_is_uuid(&pXfer->aToken[1])
|| !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &n)
|| n<0
|| (pXfer->nToken==4 && !blob_is_uuid(&pXfer->aToken[2]))
){
blob_appendf(&pXfer->err, "malformed file line");
return;
}
blob_zero(&content);
blob_zero(&hash);
blob_extract(pXfer->pIn, n, &content);
if( db_exists("SELECT 1 FROM shun WHERE uuid=%B", &pXfer->aToken[1]) ){
/* Ignore files that have been shunned */
return;
}
if( pXfer->nToken==4 ){
Blob src;
srcid = rid_from_uuid(&pXfer->aToken[2], 1);
if( content_get(srcid, &src)==0 ){
content_put(&content, blob_str(&pXfer->aToken[1]), srcid);
blob_appendf(pXfer->pOut, "gimme %b\n", &pXfer->aToken[2]);
pXfer->nGimmeSent++;
pXfer->nDanglingFile++;
return;
}
|
| ︙ | ︙ | |||
135 136 137 138 139 140 141 |
}else{
manifest_crosslink(rid, &content);
}
remote_has(rid);
}
/*
| | | < | | | | < | | > > | | > > > | 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
}else{
manifest_crosslink(rid, &content);
}
remote_has(rid);
}
/*
** Try to send a file as a delta against its parent.
** If successful, return the number of bytes in the delta.
** If we cannot generate an appropriate delta, then send
** nothing and return zero.
*/
static int send_delta_parent(
Xfer *pXfer, /* The transfer context */
int rid, /* record id of the file to send */
Blob *pContent, /* The content of the file to send */
Blob *pUuid /* The UUID of the file to send */
){
static const char *azQuery[] = {
"SELECT pid FROM plink x"
" WHERE cid=%d"
" AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
" AND NOT EXISTS(SELECT 1 FROM plink y"
" WHERE y.pid=x.cid AND y.cid=x.pid)",
"SELECT pid FROM mlink x"
" WHERE fid=%d"
" AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
" AND NOT EXISTS(SELECT 1 FROM mlink y"
" WHERE y.pid=x.fid AND y.fid=x.pid)"
};
int i;
Blob src, delta;
int size = 0;
int srcId = 0;
for(i=0; srcId==0 && i<count(azQuery); i++){
srcId = db_int(0, azQuery[i], rid);
}
if( srcId>0 && content_get(srcId, &src) ){
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", srcId);
blob_delta_create(&src, pContent, &delta);
|
| ︙ | ︙ | |||
181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
}
blob_reset(&delta);
free(zUuid);
blob_reset(&src);
}
return size;
}
/*
** Send the file identified by rid.
**
** The pUuid can be NULL in which case the correct UUID is computed
** from the rid.
**
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < < | | | > > > > > > > | | | | | | | | | | | > > | > > | 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
}
blob_reset(&delta);
free(zUuid);
blob_reset(&src);
}
return size;
}
/*
** Try to send a file as a native delta.
** If successful, return the number of bytes in the delta.
** If we cannot generate an appropriate delta, then send
** nothing and return zero.
*/
static int send_delta_native(
Xfer *pXfer, /* The transfer context */
int rid, /* record id of the file to send */
Blob *pUuid /* The UUID of the file to send */
){
Blob src, delta;
int size = 0;
int srcId;
srcId = db_int(0, "SELECT srcid FROM delta WHERE rid=%d", rid);
if( srcId>0 ){
blob_zero(&delta);
db_blob(&delta, "SELECT content FROM blob WHERE rid=%d", rid);
blob_uncompress(&delta, &delta);
blob_zero(&src);
db_blob(&src, "SELECT uuid FROM blob WHERE rid=%d", srcId);
blob_appendf(pXfer->pOut, "file %b %b %d\n",
pUuid, &src, blob_size(&delta));
blob_append(pXfer->pOut, blob_buffer(&delta), blob_size(&delta));
size = blob_size(&delta);
blob_reset(&delta);
blob_reset(&src);
}else{
size = 0;
}
return size;
}
/*
** Send the file identified by rid.
**
** The pUuid can be NULL in which case the correct UUID is computed
** from the rid.
**
** Try to send the file as a native delta if nativeDelta is true, or
** as a parent delta if nativeDelta is false.
*/
static void send_file(Xfer *pXfer, int rid, Blob *pUuid, int nativeDelta){
Blob content, uuid;
int size = 0;
if( db_exists("SELECT 1 FROM onremote WHERE rid=%d", rid) ){
return;
}
blob_zero(&uuid);
if( pUuid==0 ){
db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d AND size>=0", rid);
if( blob_size(&uuid)==0 ){
return;
}
pUuid = &uuid;
}
if( pXfer->mxSend<=blob_size(pXfer->pOut) ){
blob_appendf(pXfer->pOut, "igot %b\n", pUuid);
pXfer->nIGotSent++;
blob_reset(&uuid);
return;
}
if( nativeDelta ){
size = send_delta_native(pXfer, rid, pUuid);
if( size ){
pXfer->nDeltaSent++;
}
}
if( size==0 ){
content_get(rid, &content);
if( !nativeDelta && blob_size(&content)>100 ){
size = send_delta_parent(pXfer, rid, &content, pUuid);
}
if( size==0 ){
int size = blob_size(&content);
blob_appendf(pXfer->pOut, "file %b %d\n", pUuid, size);
blob_append(pXfer->pOut, blob_buffer(&content), size);
pXfer->nFileSent++;
}else{
pXfer->nDeltaSent++;
}
}
remote_has(rid);
blob_reset(&uuid);
}
/*
** Send a gimme message for every phantom.
*/
static void request_phantoms(Xfer *pXfer){
Stmt q;
db_prepare(&q,
"SELECT uuid FROM phantom JOIN blob USING(rid)"
" WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
);
while( db_step(&q)==SQLITE_ROW ){
const char *zUuid = db_column_text(&q, 0);
blob_appendf(pXfer->pOut, "gimme %s\n", zUuid);
pXfer->nGimmeSent++;
}
db_finalize(&q);
}
|
| ︙ | ︙ | |||
373 374 375 376 377 378 379 |
/*
** Send an igot message for every entry in unclustered table.
** Return the number of messages sent.
*/
static int send_unclustered(Xfer *pXfer){
Stmt q;
int cnt = 0;
| > | > > | 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 |
/*
** Send an igot message for every entry in unclustered table.
** Return the number of messages sent.
*/
static int send_unclustered(Xfer *pXfer){
Stmt q;
int cnt = 0;
db_prepare(&q,
"SELECT uuid FROM unclustered JOIN blob USING(rid)"
" WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
);
while( db_step(&q)==SQLITE_ROW ){
blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
cnt++;
}
db_finalize(&q);
return cnt;
}
|
| ︙ | ︙ | |||
400 401 402 403 404 405 406 407 408 409 410 411 412 413 |
** Process this message and form an appropriate reply.
*/
void page_xfer(void){
int isPull = 0;
int isPush = 0;
int nErr = 0;
Xfer xfer;
memset(&xfer, 0, sizeof(xfer));
blobarray_zero(xfer.aToken, count(xfer.aToken));
cgi_set_content_type(g.zContentType);
blob_zero(&xfer.err);
xfer.pIn = &g.cgiIn;
xfer.pOut = cgi_output_blob();
| > | 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 |
** Process this message and form an appropriate reply.
*/
void page_xfer(void){
int isPull = 0;
int isPush = 0;
int nErr = 0;
Xfer xfer;
int deltaFlag = 0;
memset(&xfer, 0, sizeof(xfer));
blobarray_zero(xfer.aToken, count(xfer.aToken));
cgi_set_content_type(g.zContentType);
blob_zero(&xfer.err);
xfer.pIn = &g.cgiIn;
xfer.pOut = cgi_output_blob();
|
| ︙ | ︙ | |||
448 449 450 451 452 453 454 |
if( blob_eq(&xfer.aToken[0], "gimme")
&& xfer.nToken==2
&& blob_is_uuid(&xfer.aToken[1])
){
if( isPull ){
int rid = rid_from_uuid(&xfer.aToken[1], 0);
if( rid ){
| | | 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 |
if( blob_eq(&xfer.aToken[0], "gimme")
&& xfer.nToken==2
&& blob_is_uuid(&xfer.aToken[1])
){
if( isPull ){
int rid = rid_from_uuid(&xfer.aToken[1], 0);
if( rid ){
send_file(&xfer, rid, &xfer.aToken[1], deltaFlag);
}
}
}else
/* igot UUID
**
** Client announces that it has a particular file.
|
| ︙ | ︙ | |||
513 514 515 516 517 518 519 |
@ error not\sauthorized\sto\sread
nErr++;
break;
}
isPull = 1;
}else{
if( !g.okWrite ){
| > | | | | > | > | > > | 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 |
@ error not\sauthorized\sto\sread
nErr++;
break;
}
isPull = 1;
}else{
if( !g.okWrite ){
if( !isPull ){
cgi_reset_content();
@ error not\sauthorized\sto\swrite
nErr++;
}else{
@ message pull\sonly\s-\snot\sauthorized\sto\spush
}
}else{
isPush = 1;
}
}
}else
/* clone
**
** The client knows nothing. Tell all.
*/
if( blob_eq(&xfer.aToken[0], "clone") ){
login_check_credentials();
if( !g.okClone ){
cgi_reset_content();
@ error not\sauthorized\sto\sclone
nErr++;
break;
}
isPull = 1;
deltaFlag = 1;
@ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
}else
/* login USER NONCE SIGNATURE
**
** Check for a valid login. This has to happen before anything else.
** The client can send multiple logins. Permissions are cumulative.
|
| ︙ | ︙ | |||
696 697 698 699 700 701 702 |
if( zCookie ){
blob_appendf(&send, "cookie %s\n", zCookie);
}
/* Generate gimme messages for phantoms and leaf messages
** for all leaves.
*/
| | | 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 |
if( zCookie ){
blob_appendf(&send, "cookie %s\n", zCookie);
}
/* Generate gimme messages for phantoms and leaf messages
** for all leaves.
*/
if( pullFlag || cloneFlag ){
request_phantoms(&xfer);
}
if( pushFlag ){
send_unsent(&xfer);
nMsg += send_unclustered(&xfer);
}
|
| ︙ | ︙ | |||
775 776 777 778 779 780 781 |
*/
if( xfer.nToken==2
&& blob_eq(&xfer.aToken[0], "igot")
&& blob_is_uuid(&xfer.aToken[1])
){
int rid = 0;
nMsg++;
| | | 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 |
*/
if( xfer.nToken==2
&& blob_eq(&xfer.aToken[0], "igot")
&& blob_is_uuid(&xfer.aToken[1])
){
int rid = 0;
nMsg++;
if( pullFlag || cloneFlag ){
if( !db_exists("SELECT 1 FROM blob WHERE uuid='%b' AND size>=0",
&xfer.aToken[1]) ){
rid = content_put(0, blob_str(&xfer.aToken[1]), 0);
newPhantom = 1;
}
}
if( rid==0 ){
|
| ︙ | ︙ | |||
806 807 808 809 810 811 812 |
){
if( blob_eq_str(&xfer.aToken[1], zSCode, -1) ){
fossil_fatal("server loop");
}
nMsg++;
if( zPCode==0 ){
zPCode = mprintf("%b", &xfer.aToken[2]);
| | < < | | > > > > > > > > > > | | 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 |
){
if( blob_eq_str(&xfer.aToken[1], zSCode, -1) ){
fossil_fatal("server loop");
}
nMsg++;
if( zPCode==0 ){
zPCode = mprintf("%b", &xfer.aToken[2]);
db_set("project-code", zPCode, 0);
}
blob_appendf(&send, "clone\n");
nMsg++;
}else
/* cookie TEXT
**
** The server might include a cookie in its reply. The client
** should remember this cookie and send it back to the server
** in its next query.
**
** Each cookie received overwrites the prior cookie from the
** same server.
*/
if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
db_set("cookie", blob_str(&xfer.aToken[1]), 0);
}else
/* message MESSAGE
**
** Print a message. Similar to "error" but does not stop processing
*/
if( blob_eq(&xfer.aToken[0],"message") && xfer.nToken==2 ){
char *zMsg = blob_terminate(&xfer.aToken[1]);
defossilize(zMsg);
printf("Server says: %s\n", zMsg);
}else
/* error MESSAGE
**
** Report an error and abandon the sync session
*/
if( blob_eq(&xfer.aToken[0],"error") && xfer.nToken==2 ){
char *zMsg = blob_terminate(&xfer.aToken[1]);
defossilize(zMsg);
blob_appendf(&xfer.err, "server says: %s", zMsg);
}else
|
| ︙ | ︙ | |||
878 879 880 881 882 883 884 885 886 |
** another round
*/
if( xfer.nFileSent+xfer.nDeltaSent>0 ){
go = 1;
}
};
http_close();
db_end_transaction(0);
}
| > | 946 947 948 949 950 951 952 953 954 955 |
** another round
*/
if( xfer.nFileSent+xfer.nDeltaSent>0 ){
go = 1;
}
};
http_close();
db_multi_exec("DROP TABLE onremote");
db_end_transaction(0);
}
|
Changes to src/zip.c.
| ︙ | ︙ | |||
320 321 322 323 324 325 326 327 | rid = name_to_rid(g.argv[2]); zip_of_baseline(rid, &zip); blob_write_to_file(&zip, g.argv[3]); } /* ** WEBPAGE: zip ** | > | | | | 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 |
rid = name_to_rid(g.argv[2]);
zip_of_baseline(rid, &zip);
blob_write_to_file(&zip, g.argv[3]);
}
/*
** WEBPAGE: zip
** URL: /zip/RID.zip
**
** Generate a ZIP archive for the baseline.
** Return that ZIP archive as the HTTP reply content.
*/
void baseline_zip_page(void){
int rid;
char *zName;
int i;
Blob zip;
login_check_credentials();
if( !g.okRead || !g.okHistory ){ login_needed(); return; }
zName = mprintf("%s", PD("name",""));
i = strlen(zName);
for(i=strlen(zName)-1; i>5; i--){
if( zName[i]=='.' ){
zName[i] = 0;
break;
}
}
|
| ︙ | ︙ |
Changes to test/merge1.test.
| ︙ | ︙ | |||
75 76 77 78 79 80 81 |
111 - This is line one OF the demo program - 1111
222 - The second line program line in code - 2222
333 - This is a test of the merging algohm - 3333
444 - If all goes well, we will be pleased - 4444
555 - we think it well and other stuff too - 5555
}
write_file_indented t23 {
| > > | > > > > > > > > > > > | | 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
111 - This is line one OF the demo program - 1111
222 - The second line program line in code - 2222
333 - This is a test of the merging algohm - 3333
444 - If all goes well, we will be pleased - 4444
555 - we think it well and other stuff too - 5555
}
write_file_indented t23 {
>>>>>>>> BEGIN MERGE CONFLICT <<<<<<<<
111 - This is line ONE of the demo program - 1111
111 - This is line one OF the demo program - 1111
>>>>>>>>> END MERGE CONFLICT <<<<<<<<<
222 - The second line program line in code - 2222
333 - This is a test of the merging algohm - 3333
444 - If all goes well, we will be pleased - 4444
555 - we think it well and other stuff too - 5555
}
write_file_indented t32 {
>>>>>>>> BEGIN MERGE CONFLICT <<<<<<<<
111 - This is line one OF the demo program - 1111
111 - This is line ONE of the demo program - 1111
>>>>>>>>> END MERGE CONFLICT <<<<<<<<<
222 - The second line program line in code - 2222
333 - This is a test of the merging algohm - 3333
444 - If all goes well, we will be pleased - 4444
555 - we think it well and other stuff too - 5555
}
fossil test-3 t1 t3 t2 a32
test merge1-2.1 {[same_file t32 a32]}
fossil test-3 t1 t2 t3 a23
test merge1-2.2 {[same_file t23 a23]}
write_file_indented t1 {
111 - This is line one of the demo program - 1111
222 - The second line program line in code - 2222
333 - This is a test of the merging algohm - 3333
|
| ︙ | ︙ | |||
220 221 222 223 224 225 226 |
333 - This is a test of the merging algohm - 3333
555 - we think it well and other stuff too - 5555
}
fossil test-3 t1 t3 t2 a32
test merge1-6.1 {[same_file t32 a32]}
fossil test-3 t1 t2 t3 a23
test merge1-6.2 {[same_file t32 a23]}
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | 233 234 235 236 237 238 239 |
333 - This is a test of the merging algohm - 3333
555 - we think it well and other stuff too - 5555
}
fossil test-3 t1 t3 t2 a32
test merge1-6.1 {[same_file t32 a32]}
fossil test-3 t1 t2 t3 a23
test merge1-6.2 {[same_file t32 a23]}
|
Changes to test/tester.tcl.
| ︙ | ︙ | |||
85 86 87 88 89 90 91 |
proc write_file_indented {filename txt} {
write_file $filename [string trim [string map [list "\n " \n] $txt]]\n
}
# Return true if two files are the same
#
proc same_file {a b} {
| > > > > | | 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
proc write_file_indented {filename txt} {
write_file $filename [string trim [string map [list "\n " \n] $txt]]\n
}
# Return true if two files are the same
#
proc same_file {a b} {
set x [read_file $a]
regsub -all { +\n} $x \n x
set y [read_file $b]
regsub -all { +\n} $y \n y
return [expr {$x==$y}]
}
# Perform a test
#
proc test {name expr} {
global bad_test
set r [uplevel 1 [list expr $expr]]
|
| ︙ | ︙ |
Changes to todo.txt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Things to work on:
* Use the wiki_convert() routine to render comments and other text
on web pages.
* If the server does not have write permission on the database
file, or on the directory containing the database file (and
it is thus unable to update the database because it cannot create
a rollback journal) then it currently fails silently on a push.
It needs to return a helpful error.
* If the server returns an error (for example if the outbound /xfer
message exceeds the maximum POST size of the server) the client
does not report this error back to the user as it should.
| > > > > > > > > > > > > > > > > > > > > > > > > > > > < < < < < < < < < < | < < < < < < < | < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
Bugs:
* When doing an update, if a file is not found that should be there
then the update aborts, only after it has written several files. It
does not update your checked out version, thus when doing a fossil
changes, you see everything that has come over from your update
command. Steps to reproduce:
$ rm src/main.c
$ fossil update 9b30
UPDATE ... etc ...
fossil: no such file: src/main.c
$ fossil info
(shows version you were on prior to update)
$ fossil changes
EDITED ... many ...
* Bug: If the server closes the socket unexpectedly, the
fwrite() in http.c:103 throws a signal and kills the child
process. fwrite() is not suppose to do this. Need to figure
out what is going wrong.
* Bug: Make sure merge and other commands (check-out) do not try
to use a phantom.
* Bug: When clone use incorrect http URL, local repo file is still created.
Things to work on:
* Use the wiki_convert() routine to render comments and other text
on web pages.
* If the server does not have write permission on the database
file, or on the directory containing the database file (and
it is thus unable to update the database because it cannot create
a rollback journal) then it currently fails silently on a push.
It needs to return a helpful error.
* If the server returns an error (for example if the outbound /xfer
message exceeds the maximum POST size of the server) the client
does not report this error back to the user as it should.
* The ipaddr field of the rcvfrom table is not being set. This
field should be the IP address from which information is received
for the local repository. So when somebody does a push of new
files we record the ipaddr. Or when we do a pull, we record
the ipaddr. (I think this has been fixed. Need to test.)
* Additional information displayed for the "vinfo" page:
+ All leaves of this version that are not included in the
descendant list. With date, user, comment, and hyperlink.
Leaves in the descendant table should be marked as such.
See the compute_leaves() function to see how to find all
leaves.
+ Add file diff links to the file change list.
* Enhancements to the diff and tkdiff commands in the cli.
Allow the entire tree or a subtree to be diffed, not just a
single file. Allow diffs against any two arbitrary versions,
not just diffs against the current check-out.
* Ticketing interface (expand this bullet)
+ Create new tickets as files in the file hierarchy
+ Append remarks to a ticket
+ Add attachments to a ticket
+ Change attributes of a ticket
|
| ︙ | ︙ | |||
113 114 115 116 117 118 119 |
* Add the ability to override check-in comments. It is not possible
to change the comment in the check-in itself due to the cryptographic
hash. But you can add a new record to the repository that overloads
a check-in comment with a new comment. Comment changes should be
GPG clearsigned at the very least. Comment changes only apply if
the user who made the change has the right permissions.
| < < < > > > > | 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
* Add the ability to override check-in comments. It is not possible
to change the comment in the check-in itself due to the cryptographic
hash. But you can add a new record to the repository that overloads
a check-in comment with a new comment. Comment changes should be
GPG clearsigned at the very least. Comment changes only apply if
the user who made the change has the right permissions.
* Make the interface to fossil look pretty and be customizable so
that other people will be attracted to it, will take over maintenance
of it, and we can eventually move on to other things.
This has begun, but I wonder if we need to use a templating system for
full customization. If the CSS is done correctly, you can change 99.9%
of everything. See: http://www.csszengarden.com/
|
Added tools/fossil_chat.tcl.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
#!/home/drh/bin/tobe
#
# Simple chat client for Tcl/Tk.
#
package require Tk
set SERVERHOST fossil-scm.hwaci.com
# set SERVERHOST 127.0.0.1
#set SERVERHOST 64.5.53.192
set SERVERPORT 8615
# set to correct values if you have to use a proxy
set PROXYHOST {}
set PROXYPORT {}
# Setup the user interface
wm title . Fossil-Chat
wm iconname . [wm title .]
menu .mb -type menubar
if {$tcl_platform(platform)=="unix" && $tcl_platform(os)!="Darwin"} {
pack .mb -side top -fill x
} else {
. config -menu .mb
}
.mb add cascade -label File -underline 0 -menu .mb.file
menu .mb.file -tearoff 0
.mb.file add command -label Send -command send_message
.mb.file add command -label {Remove older messages} -command cleanup_record
.mb.file add separator
.mb.file add command -label {Exit} -command exit
frame .who
pack .who -side right -anchor n -fill y
label .who.title -text {Users: }
pack .who.title -side top -anchor nw
label .who.list -anchor w -justify left -text {}
pack .who.list -side top -anchor nw -expand 1 -padx 5
label .who.time -text {} -justify right
proc update_time {} {
after 1000 update_time
set now [clock seconds]
set time [clock format $now -format %H:%M -gmt 1]
.who.time config -text "UTC: $time"
}
update_time
pack .who.time -side bottom -anchor sw
frame .input
pack .input -side bottom -fill x
text .input.t -bd 1 -relief sunken -bg white -fg black -width 60 -height 3 \
-wrap word -yscrollcommand [list .input.sb set] -takefocus 1
bind .input.t <Key-Return> {send_message; break}
pack .input.t -side left -fill both -expand 1
scrollbar .input.sb -orient vertical -command [list .input.t yview]
pack .input.sb -side left -fill y
frame .msg
pack .msg -side top -fill both -expand 1
text .msg.t -bd 1 -relief sunken -bg white -fg black -width 60 -height 20 \
-wrap word -yscrollcommand [list .msg.sb set] -takefocus 0
bindtags .msg.t [list .msg.t . all]
.msg.t tag config error -foreground red
.msg.t tag config meta -foreground forestgreen
.msg.t tag config norm -foreground black
pack .msg.t -side left -fill both -expand 1
scrollbar .msg.sb -orient vertical -command [list .msg.t yview]
pack .msg.sb -side left -fill y
update
# Send periodic messages to keep the TCP/IP link up
#
proc keep_alive {} {
global TIMER SOCKET
catch {after cancel $TIMER}
set TIMER [after 300000 keep_alive]
catch {puts $SOCKET noop; flush $SOCKET}
}
# Connect to the server
proc connect {} {
global SOCKET tcl_platform
catch {close $SOCKET}
if {[catch {
if {$::PROXYHOST ne {}} {
set SOCKET [socket $::PROXYHOST $::PROXYPORT]
puts $SOCKET "CONNECT $::SERVERHOST:$::SERVERPORT HTTP/1.1"
puts $SOCKET "Host: $::SERVERHOST:$::SERVERPORT"
puts $SOCKET ""
} else {
set SOCKET [socket $::SERVERHOST $::SERVERPORT]
}
fconfigure $SOCKET -translation binary -blocking 0
puts $SOCKET [list login $tcl_platform(user) fact,fuzz]
flush $SOCKET
fileevent $SOCKET readable handle_input
keep_alive
} errmsg]} {
if {[tk_messageBox -icon error -type yesno -parent . -message \
"Unable to connect to server. $errmsg.\n\nTry again?"]=="yes"} {
after 100 connect
}
}
}
connect
# Send the message text contained in the .input.t widget to the server.
#
proc send_message {} {
set txt [.input.t get 1.0 end]
.input.t delete 1.0 end
regsub -all "\[ \t\n\f\r\]+" [string trim $txt] { } txt
if {$txt==""} return
global SOCKET
puts $SOCKET [list message $txt]
flush $SOCKET
}
.mb add cascade -label "Transfer" -underline 0 -menu .mb.files
menu .mb.files -tearoff 0
.mb.files add command -label "Send file..." -command send_file
.mb.files add command -label "Delete files" -command delete_files \
-state disabled
.mb.files add separator
# Encode a string (possibly containing binary and \000 characters) into
# single line of text.
#
proc encode {txt} {
return [string map [list % %25 + %2b " " + \n %0a \t %09 \000 %00] $txt]
}
# Undo the work of encode. Convert an encoded string back into its original
# form.
#
proc decode {txt} {
return [string map [list %00 \000 %09 \t %0a \n + " " %2b + %25 %] $txt]
}
# Delete all of the downloaded files we are currently holding.
#
proc delete_files {} {
global FILES
.mb.files delete 3 end
array unset FILES
.mb.files entryconfigure 1 -state disabled
}
# Prompt the user to select a file from the disk. Then send that
# file to all chat participants.
#
proc send_file {} {
global SOCKET
set openfile [tk_getOpenFile]
if {$openfile==""} return
set f [open $openfile]
fconfigure $f -translation binary
set data [read $f]
close $f
puts $SOCKET [list file [file tail $openfile] [encode $data]]
flush $SOCKET
set time [clock format [clock seconds] -format {%H:%M} -gmt 1]
.msg.t insert end "\[$time\] sent file [file tail $openfile]\
- [string length $data] bytes\n" meta
.msg.t see end
}
# Save the named file to the disk.
#
proc save_file {filename} {
global FILES
set savefile [tk_getSaveFile -initialfile $filename]
if {$savefile==""} return
set f [open $savefile w]
fconfigure $f -translation binary
puts -nonewline $f [decode $FILES($filename)]
close $f
}
# Handle a "file" message from the chat server.
#
proc handle_file {from filename data} {
global FILES
foreach prior [array names FILES] {
if {$filename==$prior} break
}
if {![info exists prior] || $filename!=$prior} {
.mb.files add command -label "Save \"$filename\"" \
-command [list save_file $filename]
}
set FILES($filename) $data
.mb.files entryconfigure 1 -state active
set time [clock format [clock seconds] -format {%H:%M} -gmt 1]
.msg.t insert end "\[$time $from\] " meta "File: \"$filename\"\n" norm
.msg.t see end
}
# Handle input from the server
#
proc handle_input {} {
global SOCKET
if {[eof $SOCKET]} {
disconnect
return
}
set line [gets $SOCKET]
if {$line==""} return
set cmd [lindex $line 0]
if {$cmd=="userlist"} {
set ulist {}
foreach u [lrange $line 1 end] {
append ulist $u\n
}
.who.list config -text [string trim $ulist]
} elseif {$cmd=="message"} {
set time [clock format [clock seconds] -format {%H:%M} -gmt 1]
set from [lindex $line 1]
.msg.t insert end "\[$time $from\] " meta [lindex $line 2]\n norm
.msg.t see end
bell
wm deiconify .
update
raise .
} elseif {$cmd=="noop"} {
# do nothing
} elseif {$cmd=="meta"} {
set now [clock seconds]
set time [clock format $now -format {%H:%M} -gmt 1]
.msg.t insert end "\[$time\] [lindex $line 1]\n" meta
.msg.t see end
} elseif {$cmd=="file"} {
if {[info commands handle_file]=="handle_file"} {
handle_file [lindex $line 1] [lindex $line 2] [lindex $line 3]
}
}
}
# Handle a broken socket connection
#
proc disconnect {} {
global SOCKET
close $SOCKET
set q [tk_messageBox -icon error -type yesno -parent . -message \
"TCP/IP link lost. Try to reconnet?"]
if {$q=="yes"} {
connect
} else {
exit
}
}
# Remove all but the most recent 100 message from the message log
#
proc cleanup_record {} {
.msg.t delete 1.0 {end -100 lines}
}
|
Added win32.txt.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
Fossil on Win32
======================================================================
Setting up the build environment:
----------------------------------------------------------------------
Install:
MinGW 5.1.3
MSYS 1.0.10
Download/compile/install zlib (configure --prefix=/mingw)
Download/compile/install tclsh (configure --prefix=/) (for tests)
Installing zlib and tclsh was done in the MSYS shell so I could
easily use the configure scripts. Tcl is only required to run the
tests. You could install a binary distribution of Tcl/Tk and use
that to run the tests, or you could opt to simply trust that
everything worked and not run the tests.
There is a contributed zlib package on the MinGW site.
Downloading and installing a binary Tcl/Tk package and the
contributed zlib package would remove the requirement of MSYS.
Building on Windows:
----------------------------------------------------------------------
Ensure you have read "Setting up the build environment" first.
Building is as simple as one command:
C:\fossil-src> make -f Makefile.w32
Outstanding Issues:
----------------------------------------------------------------------
* server is totally non-functional - #if/#end'd out of the code
Commands status:
----------------------------------------------------------------------
add OK
cgi Not tested
changes OK
checkout BAD #1
clean OK
clone OK
close OK
commit OK
config OK
deconstruct OK
del OK
descendents OK
diff OK
extra OK
help OK
http Not Tested
info OK
leaves OK
ls OK
merge OK
new OK
open OK
pull OK
push OK
rebuild OK
redo BAD #3
rm OK
server BAD #4
status OK
sync OK
timeline OK
tkdiff OK
undo OK
update OK
user capabilities OK
user default OK
user list OK
user new OK
user password OK
#1 Have a repo where I removed a file. I did a fossil checkout 123abc,
which is the last version that had the file. The file does not
appear. fossil checkout --force 123abc does things, but still the
file does not appear.
Make a new dir, fossil open ../repo.fsl && fossil checkout 123abc and
the file appears.
Is that normal operation?
#3 In test1/ I edited a file, test2/ I updated, type file.txt changes
were there. I then did fossil undo file.txt. The changes were gone
and fossil status said I had edited file.txt. A fossil redo did not
print anything to the screen and the changes for file.txt are not
in the file. fossil status still reports that the file was edited.
There was no commit/update or any other command inbetween these
actions.
#4 There were various difficulties in this function beyond simple socket
problems. The major one being fork. This will probably be the last
command to be functional in fossil on windows.
|
Changes to www/concepts.html.
| ︙ | ︙ | |||
28 29 30 31 32 33 34 | <img src="concept1.gif" align="right" hspace="10"> <p>A software project normally consists of a "source tree". A source tree is a hierarchy of files that are used to generate the end product. The source tree changes over time as the software grows and expands and as features are added and bugs are fixed. A snapshot of the source tree at any point in time | | > | 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | <img src="concept1.gif" align="right" hspace="10"> <p>A software project normally consists of a "source tree". A source tree is a hierarchy of files that are used to generate the end product. The source tree changes over time as the software grows and expands and as features are added and bugs are fixed. A snapshot of the source tree at any point in time is called a "version" or "revision" or a "baseline" of the product. In fossil, we use the name "baseline".</p> <p>A "repository" is a database that contains copies of all historical versions or baselines for a project. Baselines are normally stored in the repository in a highly space-efficient compressed format (delta encoding). But that is an implementation detail that you the user need not worry over. Think of the repository as a safe place where all your old baselines are securely stored away and available for retrieval whenever you need |
| ︙ | ︙ | |||
51 52 53 54 55 56 57 | <p>Each source tree that is controlled by fossil is associated with a single repository on the local disk drive. You can tie two or more source trees to a single repository if you want (though one tree per repository is the most common configuration.) So a single repository can be associated with many source trees, but each source tree is associated with only one repository.</p> | | | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | <p>Each source tree that is controlled by fossil is associated with a single repository on the local disk drive. You can tie two or more source trees to a single repository if you want (though one tree per repository is the most common configuration.) So a single repository can be associated with many source trees, but each source tree is associated with only one repository.</p> <p>Fossil source trees may not overlap. A fossil source tree is identified by a file named "_FOSSIL_" in the root directory of the source tree. Every file that is a sibling of _FOSSIL_ and every file in every subfolder is considered potentially a part of the source tree. The _FOSSIL_ file contains (among other things) the pathname of the repository with which the source tree is associated. On the other hand, the repository has no record of its source trees. So you are free to delete a source tree or move it around without consequence. But if you move or rename or |
| ︙ | ︙ | |||
73 74 75 76 77 78 79 | or through a central server. Changes can "push" from the local repository into a remote repository. Or changes can "pull" from a remote repository into a local repository. Or one can do a "sync" which is a shortcut for doing both a push and a pull at the same time. Fossil also has the concept of "cloning". A "clone" is like a "pull", except that instead of beginning with an existing local repository, a clone begins with nothing and creates a new local repository that | | | | 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | or through a central server. Changes can "push" from the local repository into a remote repository. Or changes can "pull" from a remote repository into a local repository. Or one can do a "sync" which is a shortcut for doing both a push and a pull at the same time. Fossil also has the concept of "cloning". A "clone" is like a "pull", except that instead of beginning with an existing local repository, a clone begins with nothing and creates a new local repository that is a duplicate of a remote repository.</p> <p>Communication between repositories is via HTTP. Remote repositories are identified by URL. You can also point a webbrowser at a repository and get human-readable status, history, and tracking information about the project.</p> <h3>2.1 Identification Of Artifacts</h3> <p>A particular version of a particular file is called an "artifact". Each artifact has a universally unique name which is the <a href="http://en.wikipedia.org/wiki/SHA">SHA1</a> hash of the content of that file expressed as 40 characters of lower-case hexadecimal. Such a hash is referred to as the Universally Unique Identifier or UUID for the artifact. The SHA1 algorithm is created with the purpose of providing a highly forgery-resistent identifier for a file. Given any file it is simple to find the UUID for that file. But given a UUID it is computationally intractable to generate a file that will have that UUID.</p> <p>UUIDs look something like this:</p> <blockquote><b> 6089f0b563a9db0a6d90682fe47fd7161ff867c8<br> 59712614a1b3ccfd84078a37fa5b606e28434326<br> |
| ︙ | ︙ | |||
138 139 140 141 142 143 144 | you look at a "timeline" of changes in fossil, the UUID associated with each check-in or commit is really just the UUID of the manifest for that baseline.</p> <p>Fossil automatically generates a manifest whenever you "commit" a new baseline. So this is not something that you, the developer, need to worry with. The format of a manifest is intentionally | | | > | | 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
you look at a "timeline" of changes in fossil, the UUID associated
with each check-in or commit is really just the UUID of the
manifest for that baseline.</p>
<p>Fossil automatically generates a manifest whenever you "commit"
a new baseline. So this is not something that you, the developer,
need to worry with. The format of a manifest is intentionally
designed to be simple to parse, so that if
you want to read and interpret a manifest, either by hand or
with a script, that is easy to do. But you will probably never
need to do so.</p>
<p>In addition to identifying all files in the baseline, a
manifest also contains a check-in comment, the date and time
when the baseline was established, who created the baseline,
and links to other baselines from which the current baseline
is derived. There is also a couple of checksums used to verify
the integrity of the baseline. And the whole manifest might
be PGP clearsigned.</p>
<h3>2.3 Key concepts</h3>
<ul>
<li>A <b>baseline</b> is a set of files arranged
in a hierarchy.</li>
<li>A <b>repository</b> keeps a record of historical baselines.</li>
<li>Repositories share their changes using <b>push</b>, <b>pull</b>,
<b>sync</b>, and <b>clone</b>.</li>
<li>A particular version of a particular file is an <b>artifact</b>
that is identified by a <b>UUID</b>.</li>
<li>Artifacts tracked by fossil are inherently immutable.</li>
|
| ︙ | ︙ | |||
182 183 184 185 186 187 188 | install any other software in order to use fossil. You do <u>not</u> need CVS, gzip, diff, rsync, Python, Perl, Tcl, Java, apache, PostgreSQL, MySQL, SQLite, patch, or any similar software on your system in order to use fossil effectively. You will want to have some kind of text editor for entering check-in comments. Fossil will use whatever text editor is identified by your VISUAL environment variable. Fossil will also use GPG to clearsign your manifests if you happen to have it installed, | | > > | | 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | install any other software in order to use fossil. You do <u>not</u> need CVS, gzip, diff, rsync, Python, Perl, Tcl, Java, apache, PostgreSQL, MySQL, SQLite, patch, or any similar software on your system in order to use fossil effectively. You will want to have some kind of text editor for entering check-in comments. Fossil will use whatever text editor is identified by your VISUAL environment variable. Fossil will also use GPG to clearsign your manifests if you happen to have it installed, but fossil will skip that step if GPG missing from your system. You can optionally set up fossil to use external "diff" programs, though a perfectly functional "diff" algorithm is built it and works find for most people.</p> <p>To uninstall fossil, simply delete the executable.</p> <p>To upgrade an older version of fossil to a newer version, just replace the old executable with the new one. You might need to run a one-time command to restructure your repositories after an upgrade. Check the instructions that come with the upgrade |
| ︙ | ︙ | |||
273 274 275 276 277 278 279 280 281 282 283 284 285 286 | </p></li> <li><p> Repeat all of the above until you have generated great software. </p></li> </ol> <h2>5.0 Setting Up A Fossil Server</h2> <p>With other configuration management software, setting up a server is a lot of work and normally takes time, patience, and a lot of system knowledge. Fossil is designed to avoid this frustration. Setting up a server with fossil is ridiculously easy. You have three options:</p> | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 | </p></li> <li><p> Repeat all of the above until you have generated great software. </p></li> </ol> <h3>4.1 Variations</h3> <p>The <b>settings</b> lets you view and modify various operating properties of fossil. Among the available settings is "autosync" mode. When autosync is enabled, the push and pull of content from your local server is largely automated. Whenever you use the <b>update</b> command, fossil first does a <b>pull</b> to see if other users have perhaps added new baselines to the central repository. When you <b>commit</b>, fossil also does a <b>pull</b> and issues a warning if your check-in would cause a fork. After a <b>commit</b>, fossil automatically does a <b>push</b> to send your changes up to the central server.</p> <p>With autosync enabled, fossil works like <a href="http://www.nongnu.org/cvs/">CVS</a> or <a href="http://subversion.tigris.org/">Subversion</a>. When autosync disabled, fossil works more like <a href="http://monotone.ca/">Monotone</a>, <a href="http://git.or.cz">GIT</a>, or <a href="http://www.selenic.com/mercurial/wiki/">Mercurial</a>. The fun thing about fossil is that it will work either way, depending on your needs of the moment. You can freely switch between these operating modes using commands like:</p> <blockquote> <b>fossil setting autosync off<br /> fossil setting autosync on</b> </blockquote> <p>For additional information about autosync and other settings using the <b>help</b> command.</p> <h2>5.0 Setting Up A Fossil Server</h2> <p>With other configuration management software, setting up a server is a lot of work and normally takes time, patience, and a lot of system knowledge. Fossil is designed to avoid this frustration. Setting up a server with fossil is ridiculously easy. You have three options:</p> |
| ︙ | ︙ |
Changes to www/fileformat.html.
1 2 3 4 5 6 7 8 9 10 11 12 13 | <html> <head> <title>Fossil File Format</title> </head> <body bgcolor="white"> <p>[ <a href="index.html">Index</a> ]</p> <hr> <h1 align="center"> Fossil File Formats </h1> <p> The global state of a fossil repository is determined by an unordered | | | > > | | | | | | | < > | > | > > | > > | > > > > > > > > | | > | | | | | | | | | | | | | | | | | < | | | | | | | | | | | | | | > > > > | > > > > > > > > > > > > > > > > | > > > | > > > > | > > > > > > > > > | | > | > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | | | > | > > | | > | | > > > > > > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | <html> <head> <title>Fossil File Format</title> </head> <body bgcolor="white"> <p>[ <a href="index.html">Index</a> ]</p> <hr> <h1 align="center"> Fossil File Formats </h1> <p> The global state of a fossil repository is determined by an unordered set of files. A file in fossil is called an "artifact". An artifact might be a source code file, the text of a wiki page, part of a trouble ticket, or one of several special control artifacts used to show the relationships between other artifacts within the project. Artifacts can be text or binary. </p> <p> Each artifact in the repository is named by its SHA1 hash. No prefixes or meta information is added to a artifact before its hash is computed. The name of a artifact in the repository is exactly the same SHA1 hash that is computed by sha1sum on the file as it exists in your source tree.</p> <p> Some artifacts have a particular format which gives them special meaning to fossil. Fossil recognizes:</p> <ul> <li> Manifests </li> <li> Clusters </li> <li> Control Artifacts </li> <li> Wiki Pages </li> <li> Ticket Changes </li> </ul> <p>These five artifact types are described in the sequel.</p> <h2>1.0 The Manifest</h2> <p>A manifest defines a baseline or version of the project source tree. The manifest contains a list of artifacts for each file in the project and the corresponding filenames, as well as information such as parent baselines, the name of the programmer who created the baseline, the date and time when the baseline was created, and any check-in comments associated with the baseline.</p> <p> Any artifact in the repository that follows the syntactic rules of a manifest is a manifest. Note that a manifest can be both a real manifest and also a content file, though this is rare. </p> <p> A manifest is a text file. Newline characters (ASCII 0x0a) separate the file into "cards". Each card begins with a single character "card type". Zero or more arguments may follow the card type. All arguments are separated from each other and from the card-type character by a single space character. There is no surplus white space between arguments and no leading or trailing whitespace except for the newline character that acts as the card separator. </p> <p> All cards of the manifest occur in strict sorted lexicographical order. No card may be duplicated. The entire manifest may be PGP clear-signed, but otherwise it may contain no additional text or data beyond what is described here. </p> <p> Allowed cards in the manifest are as follows: </p> <blockquote> <b>C</b> <i>checkin-comment</i><br> <b>D</b> <i>time-and-date-stamp</i><br> <b>F</b> <i>filename</i> <i>SHA1-hash</i><br> <b>P</b> <i>SHA1-hash</i>+<br> <b>R</b> <i>repository-checksum</i><br> <b>U</b> <i>user-login</i><br> <b>Z</b> <i>manifest-checksum</i> </blockquote> <p> A manifest must have exactly one C-card. The sole argument to the C-card is a check-in comment that describes the check-in that the manifest defines. The check-in comment is text. The following escape sequences are applied to the text: A space (ASCII 0x20) is represented as "\s" (ASCII 0x5C, 0x73). A newline (ASCII 0x0a) is "\n" (ASCII 0x6C, x6E). A backslash (ASCII 0x5C) is represented as two backslashes "\\". Apart from space and newline, no other whitespace characters are allowed in the check-in comment. Nor are any unprintable characters allowed in the comment. </p> <p> A manifest must have exactly one D-card. The sole argument to the D-card is a date-time stamp in the ISO8601 format. The date and time should be in coordinated universal time (UTC). The format is: </p> <blockquote> <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> </blockquote> <p> A manifest has zero or more F-cards. Each F-card defines a file (other than the manifest itself) which is part of the baseline that the manifest defines. There are two arguments. The first argment is the pathname of the file in the baseline relative to the root of the project file hierarchy. No ".." or "." directories are allowed within the filename. Space characters are escaped as in C-card comment text. Backslash characters and newlines are not allowed within filenames. The directory separator character is a forward slash (ASCII 0x2F). The second argument to the F-card is the full 40-character lower-case hexadecimal SHA1 hash of the content artifact. </p> <p> A manifest has zero or one P-cards. Most manifests have one P-card. The P-card has a varying number of arguments that defines other manifests from which the current manifest is derived. Each argument is an 40-character lowercase hexadecimal SHA1 of the predecessor manifest. All arguments to the P-card must be unique to that line. The first predecessor is the direct ancestor of the manifest. Other arguments define manifests with which the first was merged to yield the current manifest. Most manifests have a P-card with a single argument. The first manifest in the project has no ancestors and thus has no P-card. </p> <p> A manifest may optionally have a single R-card. The R-card has a single argument which is the MD5 checksum of all files in the baseline except the manifest itself. The checksum is expressed as 32-characters of lowercase hexadecimal. The checksum is computed as follows: For each file in the baseline (except for the manifest itself) in strict sorted lexicographical order, take the pathname of the file relative to the root of the repository, append a single space (ASCII 0x20), the size of the file in ASCII decimal, a single newline character (ASCII 0x0A), and the complete text of the file. Compute the MD5 checksum of the the result. </p> <p> Each manifest has a single U-card. The argument to the U-card is the login of the user who created the manifest. The login name is encoded using the same character escapes as is used for the check-in comment argument to the C-card. </p> <p> A manifest has an option Z-card as its last line. The argument to the Z-card is a 32-character lowercase hexadecimal MD5 hash of all prior lines of the manifest up to and including the newline character that immediately preceeds the "Z". The Z-card is just a sanity check to prove that the manifest is well-formed and consistent. </p> <h2>2.0 Clusters</h2> <p> A cluster is a artifact that declares the existance of other artifacts. Clusters are used during repository synchronization to help reduce network traffic. </p> <p> Clusters follow a syntax that is very similar to manifests. A Cluster is a line-oriented text file. Newline characters (ASCII 0x0a) separate the artifact into cards. Each card begins with a single character "card type". Zero or more arguments may follow the card type. All arguments are separated from each other and from the card-type character by a single space character. There is no surplus white space between arguments and no leading or trailing whitespace except for the newline character that acts as the card separator. All cards of a cluter occur in strict sorted lexicographical order. No card may be duplicated. The cluster may not contain additional text or data beyond what is described here. Unlike manifests, clusters are never PGP signed. </p> <p> Allowed cards in the cluster are as follows: </p> <blockquote> <b>M</b> <i>uuid</i><br /> <b>Z</b> <i>checksum</i> </blockquote> <p> A cluster contains one or more "M" cards followed by a single "Z" line. Each M card has a single argument which is the UUID of another artifact in the repository. The Z card work exactly like the Z card of a manifest. The argument to the Z card is the lower-case hexadecimal representation of the MD5 checksum of all prior cards in the cluster. Note that the Z card is required on a cluster. </p> <h2>3.0 Control Artifacts</h2> <p> Control artifacts are used to assign properties to other artifacts within the repository. The basic format of a control artifact is the same as a manifest or cluster. A control artifact is a text files divided into cards by newline characters. Each card has a single-character card type followed by arguments. Spaces separate the card type and the arguments. No surplus whitespace is allowed. All cards must occur in strict lexigraphical order. </p> <p> Allowed cards in a control artifact are as follows: </p> <blockquote> <b>D</b> <i>time-and-date-stamp</i><br /> <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name uuid ?value?</i><br /> <b>Z</b> <i>checksum</i><br /> </blockquote> <p> A control artifact must have one D card and one Z card and one or more or more T cards. No other cards or other text is allowed in a control artifact. Control artifacts might be PGP clearsigned.</p> <p>The D card and the Z card of a control artifact are the same as in a manifest.</p> <p>The T card represents a "tag" or property that is applied to some other artifact. The T card has two or three values. The second argument is the 40 character lowercase UUID of the artifact to which the tag is to be applied. The first value is the tag name. The first character of the tag is either "+", "-", or "*". A "+" means the tag should be added to the artifact. The "-" means the tag should be removed. The "*" character means the tag should be added to the artifact and all direct decendents (but not branches) of the artifact down to but not including the first decendent that contains a more recent "-" tag with the same name. The optional third argument is the value of the tag. A tag without a value is a boolean.</p> <p>When two or more tags with the same name are applied to the same artifact, the tag with the latest (most recent) date is used.</p> <p>Some tags have special meaning. The "comment" tag when applied to a baseline will override the check-in comment of that baseline for display purposes.</p> <h2>4.0 Wiki Pages</h2> <p>A wiki page is an artifact with a format similar to manifests, clusters, and control artifacts. The artifact is divided into cards by newline characters. The format of each card is as in manifests, clusters, and control artifacts. Wiki artifacts accept the following card types:</p> <blockquote> <b>D</b> <i>time-and-date-stamp</i><br /> <b>L</b> <i>wiki-title</i><br /> <b>P</b> <i>parent-uuid</i>+<br /> <b>U</b> <i>user-name</i><br /> <b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b><br /> <b>Z</b> <i>checksum</i> </blockquote> <p>The D card is the date and time when the wiki page was edited. The P card specifies the parent wiki pages, if any. The L card gives the name of the wiki page. The U card specifies the login of the user who made this edit to the wiki page. The Z card is the usual checksum over the either artifact.</p> <p>The W card is used to specify the text of the wiki page. The argument to the W card is an integer which is the number of bytes of text in the wiki page. That text follows the newline character that terminates the W card. The wiki text is always followed by one extra newline.</p> <h2>5.0 Ticket Changes</h2> <p>A ticket-change artifact represents a change to a trouble ticket. The following cards are allowed on a ticket change artifact:</p> <blockquote> <b>D</b> <i>time-and-date-stamp</i><br /> <b>J</b> ?<b>+</b>?<i>name value</i><br /> <b>K</b> <i>ticket-uuid</i><br /> <b>U</b> <i>user-name</i><br /> <b>Z</b> <i>checksum</i> </blockquote> <p> The D card is the usual date and time stamp and represents the point in time when the change was entered. The U card is the login of the programmer who entered this change. The Z card is the checksum over the entire artifact.</p> <p> Every ticket has a UUID. The ticket to which this change is applied is specified by the K card. A ticket exists if it contains one or more changes. The first "change" to a ticket is what brings the ticket into existance.</p> <p> J cards specify changes to "fields" of the ticket. Each fossil server has a ticket configuration which specifies the fields its understands. This is not a limit on the fields that can appear on the J cards, however. If a J card specifies a field that a particular fossil server does not recognize, then that J card is simply ignored.</p> <p> The first argument of the J card is the field name. The second value is the field value. If the field name begins with "+" then the value is appended to the prior value. Otherwise, the value on the J card replaces any previous value of the field. The field name and value are both encoded using the character escapes defined for the C card of a manifest. </p> |
Changes to www/quickstart.html.
| ︙ | ︙ | |||
218 219 220 221 222 223 224 |
</blockquote><h2>More Hints</h2><blockquote>
<p>Try these commands:</p>
<blockquote><b>
fossil help<br>
| < | 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
</blockquote><h2>More Hints</h2><blockquote>
<p>Try these commands:</p>
<blockquote><b>
fossil help<br>
fossil test-commands
</b></blockquote>
<p>Explore and have fun!</p>
</blockquote></body></html>
|
Changes to www/selfcheck.html.
| ︙ | ︙ | |||
13 14 15 16 17 18 19 20 21 22 23 24 25 26 | Even though fossil is a relatively new project and still contains many bugs, it is designed with features to give it a high level of integrity so that you can have confidence that you will not lose your files. This note describes the defensive measures that fossil uses to help prevent file loss due to bugs. </p> <h2>Atomic Check-ins With Rollback</h2> <p> The fossil repository is an <a href="http://www.sqlite.org/">SQLite version 3</a> database file. SQLite is very mature and stable and has been in wide-spread use for many years, so we have little worries that it might cause repository | > > > > > > | 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | Even though fossil is a relatively new project and still contains many bugs, it is designed with features to give it a high level of integrity so that you can have confidence that you will not lose your files. This note describes the defensive measures that fossil uses to help prevent file loss due to bugs. </p> <p><i>Follow-up as of 2007-11-24:</i> Fossil has been hosting itself and several other projects for months now. Many bugs have been encountered. But, thanks in large part to the defensive measures described here, no data has been lost. The integrity checks are doing their job well.</p> <h2>Atomic Check-ins With Rollback</h2> <p> The fossil repository is an <a href="http://www.sqlite.org/">SQLite version 3</a> database file. SQLite is very mature and stable and has been in wide-spread use for many years, so we have little worries that it might cause repository |
| ︙ | ︙ | |||
40 41 42 43 44 45 46 | <h2>Verification Of Delta Encodings Prior To Transaction Commit</h2> <p> The content files that comprise the global state of a fossil respository are stored in the repository as a tree. The leaves of the tree are stored as zlib-compressed BLOBs. Interior nodes are deltas from their | | | > | | | | 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | <h2>Verification Of Delta Encodings Prior To Transaction Commit</h2> <p> The content files that comprise the global state of a fossil respository are stored in the repository as a tree. The leaves of the tree are stored as zlib-compressed BLOBs. Interior nodes are deltas from their decendents. A lot of encoding is going on. There is zlib-compression which is relatively well-tested but still might cause corruption if used improperly. And there is the relatively new delta-encoding mechanism designed expressly for fossil. We want to make sure that bugs in these encoding mechanisms do not lead to loss of data. </p> <p> To increase our confidence that everything in the repository is recoverable, fossil makes sure it can extract an exact replicate of every content file that it changes just prior to transaction commit. So during the course of check-in (or other repository operation) many different files in the repository might be modified. Some files are simply compressed. Other files are delta encoded and then compressed. While all this is going on, fossil makes a record of every file that is encoded and the SHA1 hash of the original content of that file. Then just before transaction commit, fossil re-extracts the original content of all files that were written, computes the SHA1 checksum again, and verifies that the checksums match. If anything does not match up, an error message is printed and the transaction rolls back. </p> <p> So, in other words, fossil always checks to make sure it can re-extract a file before it commits a change to that file. Hence bugs in fossil are unlikely to corrupt the repository in a way that prevents us from extracting historical versions of files. </p> <h2>Checksum Over All Files In A Baseline</h2> <p> Manifest artifacts that define a baseline have two fields (the R-card and Z-card) that record MD5 hashs of the manifest itself and of all other files in the manifest. Prior to any check-in commit, these checksums are verified to ensure that the baseline checked in agrees exactly with what is on disk. Similarly, the repository checksum is verified after a checkout to make sure that the entire repository was checked out correctly. Note that these added checks use a different hash (MD5 instead of SHA1) in order to avoid common-mode failures in the hash algorithm implementation. </p> |