| | @@ -22,11 +22,11 @@ |
| 22 | 22 | ******************************************************************************* |
| 23 | 23 | ** |
| 24 | 24 | ** This file contains code used to cross link control files and |
| 25 | 25 | ** manifests. The file is named "manifest.c" because it was |
| 26 | 26 | ** original only used to parse manifests. Then later clusters |
| 27 | | -** and control files were added. |
| 27 | +** and control files and wiki pages and tickets were added. |
| 28 | 28 | */ |
| 29 | 29 | #include "config.h" |
| 30 | 30 | #include "manifest.h" |
| 31 | 31 | #include <assert.h> |
| 32 | 32 | |
| | @@ -35,22 +35,35 @@ |
| 35 | 35 | ** Types of control files |
| 36 | 36 | */ |
| 37 | 37 | #define CFTYPE_MANIFEST 1 |
| 38 | 38 | #define CFTYPE_CLUSTER 2 |
| 39 | 39 | #define CFTYPE_CONTROL 3 |
| 40 | +#define CFTYPE_WIKI 4 |
| 41 | +#define CFTYPE_TICKET 5 |
| 42 | + |
| 43 | +/* |
| 44 | +** Mode parameter values |
| 45 | +*/ |
| 46 | +#define CFMODE_READ 1 |
| 47 | +#define CFMODE_APPEND 2 |
| 48 | +#define CFMODE_WRITE 3 |
| 40 | 49 | |
| 41 | 50 | /* |
| 42 | 51 | ** A parsed manifest or cluster. |
| 43 | 52 | */ |
| 44 | 53 | struct Manifest { |
| 45 | 54 | Blob content; /* The original content blob */ |
| 46 | 55 | int type; /* Type of file */ |
| 56 | + int mode; /* Access mode */ |
| 47 | 57 | char *zComment; /* Decoded comment */ |
| 48 | 58 | char zUuid[UUID_SIZE+1]; /* Self UUID */ |
| 49 | 59 | double rDate; /* Time in the "D" line */ |
| 50 | 60 | char *zUser; /* Name of the user */ |
| 51 | 61 | char *zRepoCksum; /* MD5 checksum of the baseline content */ |
| 62 | + char *zWiki; /* Text of the wiki page */ |
| 63 | + char *zWikiTitle; /* Name of the wiki page */ |
| 64 | + char *zTicketUuid; /* UUID for a ticket */ |
| 52 | 65 | int nFile; /* Number of F lines */ |
| 53 | 66 | int nFileAlloc; /* Slots allocated in aFile[] */ |
| 54 | 67 | struct { |
| 55 | 68 | char *zName; /* Name of a file */ |
| 56 | 69 | char *zUuid; /* UUID of the file */ |
| | @@ -66,10 +79,23 @@ |
| 66 | 79 | struct { |
| 67 | 80 | char *zName; /* Name of the tag */ |
| 68 | 81 | char *zUuid; /* UUID that the tag is applied to */ |
| 69 | 82 | char *zValue; /* Value if the tag is really a property */ |
| 70 | 83 | } *aTag; |
| 84 | + int nField; /* Number of J lines */ |
| 85 | + int nFieldAlloc; /* Slots allocated in aField[] */ |
| 86 | + struct { |
| 87 | + char *zName; /* Key or field name */ |
| 88 | + char *zValue; /* Value of the field */ |
| 89 | + } *aField; |
| 90 | + int nAttach; /* Number of A lines */ |
| 91 | + int nAttachAlloc; /* Slots allocated in aAttach[] */ |
| 92 | + struct { |
| 93 | + char *zUuid; /* UUID of the attachment */ |
| 94 | + char *zName; /* Name of the attachment */ |
| 95 | + char *zDesc; /* Description of the attachment */ |
| 96 | + } *aAttach; |
| 71 | 97 | }; |
| 72 | 98 | #endif |
| 73 | 99 | |
| 74 | 100 | |
| 75 | 101 | /* |
| | @@ -78,29 +104,46 @@ |
| 78 | 104 | void manifest_clear(Manifest *p){ |
| 79 | 105 | blob_reset(&p->content); |
| 80 | 106 | free(p->aFile); |
| 81 | 107 | free(p->azParent); |
| 82 | 108 | free(p->azCChild); |
| 109 | + free(p->aTag); |
| 110 | + free(p->aField); |
| 111 | + free(p->aAttach); |
| 83 | 112 | memset(p, 0, sizeof(*p)); |
| 84 | 113 | } |
| 85 | 114 | |
| 86 | 115 | /* |
| 87 | | -** Parse a manifest blob into a Manifest object. The Manifest |
| 88 | | -** object takes over the input blob and will free it when the |
| 116 | +** Parse a blob into a Manifest object. The Manifest object |
| 117 | +** takes over the input blob and will free it when the |
| 89 | 118 | ** Manifest object is freed. Zeros are inserted into the blob |
| 90 | 119 | ** as string terminators so that blob should not be used again. |
| 91 | 120 | ** |
| 92 | | -** Return TRUE if the content really is a manifest. Return FALSE |
| 93 | | -** if there are syntax errors. |
| 121 | +** Return TRUE if the content really is a control file of some |
| 122 | +** kind. Return FALSE if there are syntax errors. |
| 123 | +** |
| 124 | +** This routine is strict about the format of a control file. |
| 125 | +** The format must match exactly or else it is rejected. This |
| 126 | +** rule minimizes the risk that a content file will be mistaken |
| 127 | +** for a control file simply because they look the same. |
| 94 | 128 | ** |
| 95 | 129 | ** The pContent is reset. If TRUE is returned, then pContent will |
| 96 | 130 | ** be reset when the Manifest object is cleared. If FALSE is |
| 97 | 131 | ** returned then the Manifest object is cleared automatically |
| 98 | 132 | ** and pContent is reset before the return. |
| 133 | +** |
| 134 | +** The entire file can be PGP clear-signed. The signature is ignored. |
| 135 | +** The file consists of zero or more cards, one card per line. |
| 136 | +** (Except: the content of the W card can extend of multiple lines.) |
| 137 | +** Each card is divided into tokens by a single space character. |
| 138 | +** The first token is a single upper-case letter which is the card type. |
| 139 | +** The card type determines the other parameters to the card. |
| 140 | +** Cards must occur in lexicographical order. |
| 99 | 141 | */ |
| 100 | 142 | int manifest_parse(Manifest *p, Blob *pContent){ |
| 101 | 143 | int seenHeader = 0; |
| 144 | + int seenZ = 0; |
| 102 | 145 | int i, lineNo=0; |
| 103 | 146 | Blob line, token, a1, a2, a3; |
| 104 | 147 | Blob selfuuid; |
| 105 | 148 | char cPrevType = 0; |
| 106 | 149 | |
| | @@ -135,10 +178,48 @@ |
| 135 | 178 | } |
| 136 | 179 | cPrevType = z[0]; |
| 137 | 180 | seenHeader = 1; |
| 138 | 181 | if( blob_token(&line, &token)!=1 ) goto manifest_syntax_error; |
| 139 | 182 | switch( z[0] ){ |
| 183 | + /* |
| 184 | + ** A <uuid> <filename> <description> |
| 185 | + ** |
| 186 | + ** Identifies an attachment to either a wiki page or a ticket. |
| 187 | + ** <uuid> is the artifact that is the attachment. |
| 188 | + */ |
| 189 | + case 'A': { |
| 190 | + char *zName, *zUuid, *zDesc; |
| 191 | + md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 192 | + if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 193 | + if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; |
| 194 | + if( blob_token(&line, &a3)==0 ) goto manifest_syntax_error; |
| 195 | + zUuid = blob_terminate(&a1); |
| 196 | + zName = blob_terminate(&a2); |
| 197 | + zDesc = blob_terminate(&a3); |
| 198 | + if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; |
| 199 | + if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 200 | + defossilize(zName); |
| 201 | + if( !file_is_simple_pathname(zName) ){ |
| 202 | + goto manifest_syntax_error; |
| 203 | + } |
| 204 | + defossilize(zDesc); |
| 205 | + if( p->nAttach>=p->nAttachAlloc ){ |
| 206 | + p->nAttachAlloc = p->nAttachAlloc*2 + 10; |
| 207 | + p->aAttach = realloc(p->aAttach, |
| 208 | + p->nAttachAlloc*sizeof(p->aAttach[0]) ); |
| 209 | + if( p->aAttach==0 ) fossil_panic("out of memory"); |
| 210 | + } |
| 211 | + i = p->nAttach++; |
| 212 | + p->aAttach[i].zUuid = zUuid; |
| 213 | + p->aAttach[i].zName = zName; |
| 214 | + p->aAttach[i].zDesc = zDesc; |
| 215 | + if( i>0 && strcmp(p->aAttach[i-1].zUuid, zUuid)>=0 ){ |
| 216 | + goto manifest_syntax_error; |
| 217 | + } |
| 218 | + break; |
| 219 | + } |
| 220 | + |
| 140 | 221 | /* |
| 141 | 222 | ** C <comment> |
| 142 | 223 | ** |
| 143 | 224 | ** Comment text is fossil-encoded. There may be no more than |
| 144 | 225 | ** one C line. C lines are required for manifests and are |
| | @@ -169,10 +250,33 @@ |
| 169 | 250 | if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 170 | 251 | zDate = blob_terminate(&a1); |
| 171 | 252 | p->rDate = db_double(0.0, "SELECT julianday(%Q)", zDate); |
| 172 | 253 | break; |
| 173 | 254 | } |
| 255 | + |
| 256 | + /* |
| 257 | + ** E <mode> |
| 258 | + ** |
| 259 | + ** Access mode. <mode> can be one of "read", "append", |
| 260 | + ** or "write". |
| 261 | + */ |
| 262 | + case 'E': { |
| 263 | + md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 264 | + if( p->mode!=0 ) goto manifest_syntax_error; |
| 265 | + if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 266 | + if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 267 | + if( blob_eq(&a1, "write") ){ |
| 268 | + p->mode = CFMODE_WRITE; |
| 269 | + }else if( blob_eq(&a1, "append") ){ |
| 270 | + p->mode = CFMODE_APPEND; |
| 271 | + }else if( blob_eq(&a1, "read") ){ |
| 272 | + p->mode = CFMODE_READ; |
| 273 | + }else{ |
| 274 | + goto manifest_syntax_error; |
| 275 | + } |
| 276 | + break; |
| 277 | + } |
| 174 | 278 | |
| 175 | 279 | /* |
| 176 | 280 | ** F <filename> <uuid> |
| 177 | 281 | ** |
| 178 | 282 | ** Identifies a file in a manifest. Multiple F lines are |
| | @@ -204,10 +308,74 @@ |
| 204 | 308 | if( i>0 && strcmp(p->aFile[i-1].zName, zName)>=0 ){ |
| 205 | 309 | goto manifest_syntax_error; |
| 206 | 310 | } |
| 207 | 311 | break; |
| 208 | 312 | } |
| 313 | + |
| 314 | + /* |
| 315 | + ** J <name> <value> |
| 316 | + ** |
| 317 | + ** Specifies a name value pair for ticket. |
| 318 | + */ |
| 319 | + case 'J': { |
| 320 | + char *zName, *zValue; |
| 321 | + md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 322 | + if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 323 | + if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; |
| 324 | + if( blob_token(&line, &a3)!=0 ) goto manifest_syntax_error; |
| 325 | + zName = blob_terminate(&a1); |
| 326 | + zValue = blob_terminate(&a2); |
| 327 | + defossilize(zValue); |
| 328 | + if( p->nField>=p->nFieldAlloc ){ |
| 329 | + p->nFieldAlloc = p->nFieldAlloc*2 + 10; |
| 330 | + p->aField = realloc(p->aField, |
| 331 | + p->nFieldAlloc*sizeof(p->aField[0]) ); |
| 332 | + if( p->aField==0 ) fossil_panic("out of memory"); |
| 333 | + } |
| 334 | + i = p->nField++; |
| 335 | + p->aField[i].zName = zName; |
| 336 | + p->aField[i].zValue = zValue; |
| 337 | + if( i>0 && strcmp(p->aField[i-1].zName, zName)>=0 ){ |
| 338 | + goto manifest_syntax_error; |
| 339 | + } |
| 340 | + break; |
| 341 | + } |
| 342 | + |
| 343 | + |
| 344 | + /* |
| 345 | + ** K <uuid> |
| 346 | + ** |
| 347 | + ** A K-line gives the UUID for the ticket which this control file |
| 348 | + ** is amending. |
| 349 | + */ |
| 350 | + case 'K': { |
| 351 | + char *zUuid; |
| 352 | + md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 353 | + if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 354 | + zUuid = blob_terminate(&a1); |
| 355 | + if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; |
| 356 | + if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 357 | + if( p->zTicketUuid!=0 ) goto manifest_syntax_error; |
| 358 | + p->zTicketUuid = zUuid; |
| 359 | + break; |
| 360 | + } |
| 361 | + |
| 362 | + /* |
| 363 | + ** L <wikitite> |
| 364 | + ** |
| 365 | + ** The wiki page title is fossil-encoded. There may be no more than |
| 366 | + ** one L line. |
| 367 | + */ |
| 368 | + case 'L': { |
| 369 | + md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 370 | + if( p->zWikiTitle!=0 ) goto manifest_syntax_error; |
| 371 | + if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 372 | + if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 373 | + p->zWikiTitle = blob_terminate(&a1); |
| 374 | + defossilize(p->zWikiTitle); |
| 375 | + break; |
| 376 | + } |
| 209 | 377 | |
| 210 | 378 | /* |
| 211 | 379 | ** M <uuid> |
| 212 | 380 | ** |
| 213 | 381 | ** An M-line identifies another artifact by its UUID. M-lines |
| | @@ -231,10 +399,52 @@ |
| 231 | 399 | if( i>0 && strcmp(p->azCChild[i-1], zUuid)>=0 ){ |
| 232 | 400 | goto manifest_syntax_error; |
| 233 | 401 | } |
| 234 | 402 | break; |
| 235 | 403 | } |
| 404 | + |
| 405 | + /* |
| 406 | + ** P <uuid> ... |
| 407 | + ** |
| 408 | + ** Specify one or more other artifacts where are the parents of |
| 409 | + ** this artifact. The first parent is the primary parent. All |
| 410 | + ** others are parents by merge. |
| 411 | + */ |
| 412 | + case 'P': { |
| 413 | + md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 414 | + while( blob_token(&line, &a1) ){ |
| 415 | + char *zUuid; |
| 416 | + if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; |
| 417 | + zUuid = blob_terminate(&a1); |
| 418 | + if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 419 | + if( p->nParent>=p->nParentAlloc ){ |
| 420 | + p->nParentAlloc = p->nParentAlloc*2 + 5; |
| 421 | + p->azParent = realloc(p->azParent, p->nParentAlloc*sizeof(char*)); |
| 422 | + if( p->azParent==0 ) fossil_panic("out of memory"); |
| 423 | + } |
| 424 | + i = p->nParent++; |
| 425 | + p->azParent[i] = zUuid; |
| 426 | + } |
| 427 | + break; |
| 428 | + } |
| 429 | + |
| 430 | + /* |
| 431 | + ** R <md5sum> |
| 432 | + ** |
| 433 | + ** Specify the MD5 checksum of the entire baseline in a |
| 434 | + ** manifest. |
| 435 | + */ |
| 436 | + case 'R': { |
| 437 | + md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 438 | + if( p->zRepoCksum!=0 ) goto manifest_syntax_error; |
| 439 | + if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 440 | + if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 441 | + if( blob_size(&a1)!=32 ) goto manifest_syntax_error; |
| 442 | + p->zRepoCksum = blob_terminate(&a1); |
| 443 | + if( !validate16(p->zRepoCksum, 32) ) goto manifest_syntax_error; |
| 444 | + break; |
| 445 | + } |
| 236 | 446 | |
| 237 | 447 | /* |
| 238 | 448 | ** T (+|*|-)<tagname> <uuid> ?<value>? |
| 239 | 449 | ** |
| 240 | 450 | ** Create or cancel a tag or property. The tagname is fossil-encoded. |
| | @@ -311,57 +521,46 @@ |
| 311 | 521 | defossilize(p->zUser); |
| 312 | 522 | break; |
| 313 | 523 | } |
| 314 | 524 | |
| 315 | 525 | /* |
| 316 | | - ** R <md5sum> |
| 317 | | - ** |
| 318 | | - ** Specify the MD5 checksum of the entire baseline in a |
| 319 | | - ** manifest. |
| 320 | | - */ |
| 321 | | - case 'R': { |
| 322 | | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 323 | | - if( p->zRepoCksum!=0 ) goto manifest_syntax_error; |
| 324 | | - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 325 | | - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 326 | | - if( blob_size(&a1)!=32 ) goto manifest_syntax_error; |
| 327 | | - p->zRepoCksum = blob_terminate(&a1); |
| 328 | | - if( !validate16(p->zRepoCksum, 32) ) goto manifest_syntax_error; |
| 329 | | - break; |
| 330 | | - } |
| 331 | | - |
| 332 | | - /* |
| 333 | | - ** P <uuid> ... |
| 334 | | - ** |
| 335 | | - ** Specify one or more other artifacts where are the parents of |
| 336 | | - ** this artifact. The first parent is the primary parent. All |
| 337 | | - ** others are parents by merge. |
| 338 | | - */ |
| 339 | | - case 'P': { |
| 340 | | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 341 | | - while( blob_token(&line, &a1) ){ |
| 342 | | - char *zUuid; |
| 343 | | - if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; |
| 344 | | - zUuid = blob_terminate(&a1); |
| 345 | | - if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 346 | | - if( p->nParent>=p->nParentAlloc ){ |
| 347 | | - p->nParentAlloc = p->nParentAlloc*2 + 5; |
| 348 | | - p->azParent = realloc(p->azParent, p->nParentAlloc*sizeof(char*)); |
| 349 | | - if( p->azParent==0 ) fossil_panic("out of memory"); |
| 350 | | - } |
| 351 | | - i = p->nParent++; |
| 352 | | - p->azParent[i] = zUuid; |
| 353 | | - } |
| 354 | | - break; |
| 355 | | - } |
| 526 | + ** W <size> |
| 527 | + ** |
| 528 | + ** The next <size> bytes of the file contain the text of the wiki |
| 529 | + ** page. There is always an extra \n before the start of the next |
| 530 | + ** record. |
| 531 | + */ |
| 532 | + case 'W': { |
| 533 | + int size; |
| 534 | + Blob wiki; |
| 535 | + md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 536 | + if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 537 | + if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 538 | + if( !blob_is_int(&a1, &size) ) goto manifest_syntax_error; |
| 539 | + if( size<0 ) goto manifest_syntax_error; |
| 540 | + if( p->zWiki!=0 ) goto manifest_syntax_error; |
| 541 | + blob_zero(&wiki); |
| 542 | + if( blob_extract(pContent, size+1, &wiki)!=size+1 ){ |
| 543 | + goto manifest_syntax_error; |
| 544 | + } |
| 545 | + p->zWiki = blob_buffer(&wiki); |
| 546 | + if( p->zWiki[size]!='\n' ) goto manifest_syntax_error; |
| 547 | + p->zWiki[size] = 0; |
| 548 | + break; |
| 549 | + } |
| 550 | + |
| 356 | 551 | |
| 357 | 552 | /* |
| 358 | 553 | ** Z <md5sum> |
| 359 | 554 | ** |
| 360 | 555 | ** MD5 checksum on this control file. The checksum is over all |
| 361 | 556 | ** lines (other than PGP-signature lines) prior to the current |
| 362 | 557 | ** line. This must be the last record. |
| 558 | + ** |
| 559 | + ** This card is required for all control file types except for |
| 560 | + ** Manifest. It is not required for manifest only for historical |
| 561 | + ** compatibility reasons. |
| 363 | 562 | */ |
| 364 | 563 | case 'Z': { |
| 365 | 564 | int rc; |
| 366 | 565 | Blob hash; |
| 367 | 566 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| | @@ -370,10 +569,11 @@ |
| 370 | 569 | if( !validate16(blob_buffer(&a1), 32) ) goto manifest_syntax_error; |
| 371 | 570 | md5sum_finish(&hash); |
| 372 | 571 | rc = blob_compare(&hash, &a1); |
| 373 | 572 | blob_reset(&hash); |
| 374 | 573 | if( rc!=0 ) goto manifest_syntax_error; |
| 574 | + seenZ = 1; |
| 375 | 575 | break; |
| 376 | 576 | } |
| 377 | 577 | default: { |
| 378 | 578 | goto manifest_syntax_error; |
| 379 | 579 | } |
| | @@ -382,23 +582,62 @@ |
| 382 | 582 | if( !seenHeader ) goto manifest_syntax_error; |
| 383 | 583 | |
| 384 | 584 | if( p->nFile>0 ){ |
| 385 | 585 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 386 | 586 | if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 587 | + if( p->nField>0 ) goto manifest_syntax_error; |
| 588 | + if( p->zTicketUuid ) goto manifest_syntax_error; |
| 589 | + if( p->nAttach>0 ) goto manifest_syntax_error; |
| 590 | + if( p->zWiki ) goto manifest_syntax_error; |
| 591 | + if( p->zWikiTitle ) goto manifest_syntax_error; |
| 592 | + if( p->zTicketUuid ) goto manifest_syntax_error; |
| 387 | 593 | p->type = CFTYPE_MANIFEST; |
| 388 | 594 | }else if( p->nCChild>0 ){ |
| 389 | 595 | if( p->rDate>0.0 ) goto manifest_syntax_error; |
| 390 | 596 | if( p->zComment!=0 ) goto manifest_syntax_error; |
| 391 | 597 | if( p->zUser!=0 ) goto manifest_syntax_error; |
| 392 | 598 | if( p->nTag>0 ) goto manifest_syntax_error; |
| 393 | 599 | if( p->nParent>0 ) goto manifest_syntax_error; |
| 394 | 600 | if( p->zRepoCksum!=0 ) goto manifest_syntax_error; |
| 601 | + if( p->nField>0 ) goto manifest_syntax_error; |
| 602 | + if( p->zTicketUuid ) goto manifest_syntax_error; |
| 603 | + if( p->nAttach>0 ) goto manifest_syntax_error; |
| 604 | + if( p->zWiki ) goto manifest_syntax_error; |
| 605 | + if( p->zWikiTitle ) goto manifest_syntax_error; |
| 606 | + if( !seenZ ) goto manifest_syntax_error; |
| 395 | 607 | p->type = CFTYPE_CLUSTER; |
| 608 | + }else if( p->nField>0 ){ |
| 609 | + if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 610 | + if( p->zRepoCksum!=0 ) goto manifest_syntax_error; |
| 611 | + if( p->zWiki ) goto manifest_syntax_error; |
| 612 | + if( p->zWikiTitle ) goto manifest_syntax_error; |
| 613 | + if( p->nCChild>0 ) goto manifest_syntax_error; |
| 614 | + if( p->nTag>0 ) goto manifest_syntax_error; |
| 615 | + if( p->zTicketUuid==0 ) goto manifest_syntax_error; |
| 616 | + if( p->zUser==0 ) goto manifest_syntax_error; |
| 617 | + if( !seenZ ) goto manifest_syntax_error; |
| 618 | + p->type = CFTYPE_TICKET; |
| 619 | + }else if( p->zWiki!=0 ){ |
| 620 | + if( p->rDate==0.0 ) goto manifest_syntax_error; |
| 621 | + if( p->zRepoCksum!=0 ) goto manifest_syntax_error; |
| 622 | + if( p->nCChild>0 ) goto manifest_syntax_error; |
| 623 | + if( p->nTag>0 ) goto manifest_syntax_error; |
| 624 | + if( p->zTicketUuid!=0 ) goto manifest_syntax_error; |
| 625 | + if( p->zUser==0 ) goto manifest_syntax_error; |
| 626 | + if( p->zWikiTitle==0 ) goto manifest_syntax_error; |
| 627 | + if( !seenZ ) goto manifest_syntax_error; |
| 628 | + p->type = CFTYPE_WIKI; |
| 396 | 629 | }else if( p->nTag>0 ){ |
| 397 | 630 | if( p->rDate<=0.0 ) goto manifest_syntax_error; |
| 398 | 631 | if( p->zRepoCksum!=0 ) goto manifest_syntax_error; |
| 399 | 632 | if( p->nParent>0 ) goto manifest_syntax_error; |
| 633 | + if( p->nAttach>0 ) goto manifest_syntax_error; |
| 634 | + if( p->nField>0 ) goto manifest_syntax_error; |
| 635 | + if( p->zWiki ) goto manifest_syntax_error; |
| 636 | + if( p->zWikiTitle ) goto manifest_syntax_error; |
| 637 | + if( p->zTicketUuid ) goto manifest_syntax_error; |
| 638 | + if( !seenZ ) goto manifest_syntax_error; |
| 400 | 639 | p->type = CFTYPE_CONTROL; |
| 401 | 640 | }else{ |
| 402 | 641 | goto manifest_syntax_error; |
| 403 | 642 | } |
| 404 | 643 | |
| | @@ -582,10 +821,22 @@ |
| 582 | 821 | rid, m.rDate, tid); |
| 583 | 822 | } |
| 584 | 823 | if( parentid ){ |
| 585 | 824 | tag_propagate_all(parentid); |
| 586 | 825 | } |
| 826 | + } |
| 827 | + if( m.type==CFTYPE_WIKI ){ |
| 828 | + char *zTag = mprintf("wiki-%s", m.zWikiTitle); |
| 829 | + int tagid = tag_findid(zTag, 1); |
| 830 | + int prior; |
| 831 | + tag_insert(zTag, 1, 0, rid, m.rDate, rid); |
| 832 | + free(zTag); |
| 833 | + prior = db_int(0, "SELECT rid FROM tagxref WHERE tagid=%d" |
| 834 | + " ORDER BY mtime DESC LIMIT 1 OFFSET 1", tagid); |
| 835 | + if( prior ){ |
| 836 | + content_deltify(prior, rid, 0); |
| 837 | + } |
| 587 | 838 | } |
| 588 | 839 | db_end_transaction(0); |
| 589 | 840 | manifest_clear(&m); |
| 590 | 841 | return 1; |
| 591 | 842 | } |
| 592 | 843 | |