Check-in [2ab2db0bd3]
Not logged in
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
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){