Overview
SHA1 Hash: | 2ab2db0bd3be4d7e16bf53f2e6c1386d3961d86e |
---|---|
Date: | 2007-10-05 13:03:22 |
User: | drh |
Comment: | The control file parser now reads ticket changes and wiki pages. |
Timelines: | ancestors | descendants | both | trunk |
Other Links: | files | ZIP archive | manifest |
Tags And Properties
- branch=trunk inherited from [a28c83647d]
- sym-trunk inherited from [a28c83647d]
Changes
[hide diffs]Modified ideas.txt from [0f789b4aa6] to [8ca41c63e4].
@@ -90,17 +90,12 @@ A uuid filename description D datetime P uuid ... U user - W wikipagename uuid - Z md5sum - -On page: - - <title>....</title> - <readonly/> + W size \n text \n + Z cksum Hyperlinks: [lowercasehex] /info/lowercasehex [attachment.gif] inline image
Modified src/manifest.c from [c6998d2022] to [c827402f3d].
@@ -22,11 +22,11 @@ ******************************************************************************* ** ** 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 were added. +** and control files and wiki pages and tickets were added. */ #include "config.h" #include "manifest.h" #include <assert.h> @@ -35,22 +35,35 @@ ** 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 */ @@ -66,10 +79,23 @@ 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 /* @@ -78,29 +104,46 @@ 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 manifest blob into a Manifest object. The Manifest -** object takes over the input blob and will free it when the +** 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 manifest. Return FALSE -** if there are syntax errors. +** 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; @@ -136,10 +179,48 @@ 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. @@ -167,10 +248,33 @@ 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> @@ -206,10 +310,74 @@ } break; } /* + ** J <name> <value> + ** + ** Specifies a name value pair for ticket. + */ + 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 <wikitite> + ** + ** 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); + break; + } + + /* ** M <uuid> ** ** An M-line identifies another artifact by its UUID. M-lines ** occur in clusters only. */ @@ -229,10 +397,52 @@ 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>? @@ -311,57 +521,46 @@ defossilize(p->zUser); break; } /* - ** R <md5sum> + ** W <size> ** - ** Specify the MD5 checksum of the entire baseline in a - ** manifest. + ** 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 'R': { - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( p->zRepoCksum!=0 ) goto manifest_syntax_error; + 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_size(&a1)!=32 ) goto manifest_syntax_error; - p->zRepoCksum = blob_terminate(&a1); - if( !validate16(p->zRepoCksum, 32) ) 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); + if( p->zWiki[size]!='\n' ) goto manifest_syntax_error; + p->zWiki[size] = 0; 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; - } /* ** 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; @@ -370,10 +569,11 @@ 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; } @@ -382,23 +582,62 @@ 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->zUser==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; } @@ -581,11 +820,23 @@ 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; + tag_insert(zTag, 1, 0, rid, m.rDate, rid); + free(zTag); + prior = db_int(0, "SELECT rid FROM tagxref WHERE tagid=%d" + " ORDER BY mtime DESC LIMIT 1 OFFSET 1", tagid); + if( prior ){ + content_deltify(prior, rid, 0); } } db_end_transaction(0); manifest_clear(&m); return 1; }
Modified src/schema.c from [e222adb420] to [62d0eee5f5].
@@ -224,11 +224,11 @@ @ 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); +@ CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime); ; /* ** Predefined tagid values */
Modified src/wikiformat.c from [75615129b5] to [5309fd8240].
@@ -758,11 +758,10 @@ 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 - || strncmp(zTarget, "gopher:", 7)==0 ){ blob_appendf(p->pOut, zTarget); }else{ blob_appendf(p->pOut, "%s/wiki/%T", g.zBaseURL, zTarget); } @@ -993,11 +992,10 @@ popStack(&renderer); } blob_append(pOut, "\n", 1); free(renderer.aStack); } - /* ** COMMAND: test-wiki-render */ void test_wiki_render(void){