Fossil

Diff
Login

Differences From Artifact [8252a01a29]:

To Artifact [c64124efe8]:


50
51
52
53
54
55
56

57
58
59
60
61
62
63
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64







+








/*
** A parsed manifest or cluster.
*/
struct Manifest {
  Blob content;         /* The original content blob */
  int type;             /* Type of artifact.  One of CFTYPE_xxxxx */
  int rid;              /* The blob-id for this manifest */
  char *zBaseline;      /* Baseline manifest.  The B card. */
  Manifest *pBaseline;  /* The actual baseline manifest */
  char *zComment;       /* Decoded comment.  The C card. */
  double rDate;         /* Date and time from D card.  0.0 if no D card. */
  char *zUser;          /* Name of the user from the U card. */
  char *zRepoCksum;     /* MD5 checksum of the baseline content.  R card. */
  char *zWiki;          /* Text of the wiki page.  W card. */
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
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







-
+


-

-
+






-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+





-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+






-
+

+

-
-
-
-
+
+
+
+











-
-
+
+







};
#endif

/*
** A cache of parsed manifests.  This reduces the number of
** calls to manifest_parse() when doing a rebuild.
*/
#define MX_MANIFEST_CACHE 4
#define MX_MANIFEST_CACHE 6
static struct {
  int nxAge;
  int aRid[MX_MANIFEST_CACHE];
  int aAge[MX_MANIFEST_CACHE];
  Manifest aLine[MX_MANIFEST_CACHE];
  Manifest *apManifest[MX_MANIFEST_CACHE];
} manifestCache;


/*
** Clear the memory allocated in a manifest object
*/
static void manifest_clear(Manifest *p){
  blob_reset(&p->content);
  free(p->aFile);
  free(p->azParent);
  free(p->azCChild);
  free(p->aTag);
  free(p->aField);
  if( p->pBaseline ) manifest_clear(p->pBaseline);
  memset(p, 0, sizeof(*p));
void manifest_destroy(Manifest *p){
  if( p ){
    blob_reset(&p->content);
    free(p->aFile);
    free(p->azParent);
    free(p->azCChild);
    free(p->aTag);
    free(p->aField);
    if( p->pBaseline ) manifest_destroy(p->pBaseline);
    fossil_free(p);
  }
}

/*
** Add an element to the manifest cache using LRU replacement.
*/
void manifest_cache_insert(int rid, Manifest *p){
  int i;
  for(i=0; i<MX_MANIFEST_CACHE; i++){
    if( manifestCache.aRid[i]==0 ) break;
  }
  if( i>=MX_MANIFEST_CACHE ){
    int oldest = 0;
    int oldestAge = manifestCache.aAge[0];
    for(i=1; i<MX_MANIFEST_CACHE; i++){
      if( manifestCache.aAge[i]<oldestAge ){
        oldest = i;
        oldestAge = manifestCache.aAge[i];
      }
    }
    manifest_clear(&manifestCache.aLine[oldest]);
    i = oldest;
  }
  manifestCache.aAge[i] = ++manifestCache.nxAge;
  manifestCache.aRid[i] = rid;
  manifestCache.aLine[i] = *p;
void manifest_cache_insert(Manifest *p){
  while( p ){
    int i;
    Manifest *pBaseline = p->pBaseline;
    p->pBaseline = 0;
    for(i=0; i<MX_MANIFEST_CACHE; i++){
      if( manifestCache.apManifest[i]==0 ) break;
    }
    if( i>=MX_MANIFEST_CACHE ){
      int oldest = 0;
      int oldestAge = manifestCache.aAge[0];
      for(i=1; i<MX_MANIFEST_CACHE; i++){
        if( manifestCache.aAge[i]<oldestAge ){
          oldest = i;
          oldestAge = manifestCache.aAge[i];
        }
      }
      manifest_destroy(manifestCache.apManifest[oldest]);
      i = oldest;
    }
    manifestCache.aAge[i] = ++manifestCache.nxAge;
    manifestCache.apManifest[i] = p;
    p = pBaseline;
  }
}

/*
** Try to extract a line from the manifest cache. Return 1 if found.
** Return 0 if not found.
*/
int manifest_cache_find(int rid, Manifest *p){
static Manifest *manifest_cache_find(int rid){
  int i;
  Manifest *p;
  for(i=0; i<MX_MANIFEST_CACHE; i++){
    if( manifestCache.aRid[i]==rid ){
      *p = manifestCache.aLine[i];
      manifestCache.aRid[i] = 0;
      return 1;
    if( manifestCache.apManifest[i] && manifestCache.apManifest[i]->rid==rid ){
      p = manifestCache.apManifest[i];
      manifestCache.apManifest[i] = 0;
      return p;
    }
  }
  return 0;
}

/*
** Clear the manifest cache.
*/
void manifest_cache_clear(void){
  int i;
  for(i=0; i<MX_MANIFEST_CACHE; i++){
    if( manifestCache.aRid[i]>0 ){
      manifest_clear(&manifestCache.aLine[i]);
    if( manifestCache.apManifest[i] ){
      manifest_destroy(manifestCache.apManifest[i]);
    }
  }
  memset(&manifestCache, 0, sizeof(manifestCache));
}

#ifdef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM
# define md5sum_init(X)
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
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







+
-
+














+


+







** 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.
*/
static Manifest *manifest_parse(Blob *pContent, int rid){
int manifest_parse(Manifest *p, Blob *pContent){
  Manifest *p;
  int seenHeader = 0;
  int seenZ = 0;
  int i, lineNo=0;
  Blob line, a1, a2, a3, a4;
  char cPrevType = 0;

  /* Every control artifact ends with a '\n' character.  Exit early
  ** if that is not the case for this artifact. */
  i = blob_size(pContent);
  if( i<=0 || blob_buffer(pContent)[i-1]!='\n' ){
    blob_reset(pContent);
    return 0;
  }

  p = fossil_malloc( sizeof(*p) );
  memset(p, 0, sizeof(*p));
  memcpy(&p->content, pContent, sizeof(p->content));
  p->rid = rid;
  blob_zero(pContent);
  pContent = &p->content;

  blob_zero(&a1);
  blob_zero(&a2);
  blob_zero(&a3);
  md5sum_init();
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
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







-
+




-
+



-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







+
+
+
+
+
+
+
+

-
+







    if( p->nField>0 ) goto manifest_syntax_error;
    if( p->zTicketUuid ) goto manifest_syntax_error;
    if( p->zWikiTitle ) goto manifest_syntax_error;
    if( p->zTicketUuid ) goto manifest_syntax_error;
    p->type = CFTYPE_MANIFEST;
  }
  md5sum_init();
  return 1;
  return p;

manifest_syntax_error:
  /*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/
  md5sum_init();
  manifest_clear(p);
  manifest_destroy(p);
  return 0;
}

/*
** Allocate space for a Manifest object and populate it with a
** parse of pContent.  Return a pointer ot the new object.
**
** If pContent is not a well-formed control artifact, return 0.
**
** pContent is reset, regardless of whether or not a Manifest object
** is returned.
*/
static Manifest *manifest_new(Blob *pContent){
  Manifest *p = fossil_malloc( sizeof(*p) );
  if( manifest_parse(p, pContent)==0 ){
    fossil_free(p);
    p = 0;
  }
  return p;
}

/*
** Get a manifest given the rid for the control artifact.  Return
** a pointer to the manifest on success or NULL if there is a failure.
*/
Manifest *manifest_get(int rid, int cfType){
  Blob content;
  Manifest *p;
  p = manifest_cache_find(rid);
  if( p ){
    if( cfType!=CFTYPE_ANY && cfType!=p->type ){
      manifest_cache_insert(p);
      p = 0;
    }
    return p;
  }
  content_get(rid, &content);
  p = manifest_new(&content);
  p = manifest_parse(&content, rid);
  if( p && cfType!=CFTYPE_ANY && cfType!=p->type ){
    manifest_destroy(p);
    p = 0;
  }
  return p;
}

845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
845
846
847
848
849
850
851










852
853
854
855
856
857
858







-
-
-
-
-
-
-
-
-
-







  p = manifest_get(rid, CFTYPE_MANIFEST);
  if( p==0 ){
    fossil_fatal("cannot parse manifest for checkin: %s", zName);
  }
  return p;
}

/*
** Destroy a Manifest object previously obtained from manifest_new().
*/
void manifest_destroy(Manifest *p){
  if( p ){
    manifest_clear(p);
    fossil_free(p);
  }
}

/*
** COMMAND: test-parse-manifest
**
** Usage: %fossil test-parse-manifest FILENAME ?N?
**
** Parse the manifest and discarded.  Use for testing only.
*/
876
877
878
879
880
881
882
883

884
885
886
887
888
889
890
866
867
868
869
870
871
872

873
874
875
876
877
878
879
880







-
+







  }
  db_must_be_within_tree();
  blob_read_from_file(&b, g.argv[2]);
  if( g.argc>3 ) n = atoi(g.argv[3]);
  for(i=0; i<n; i++){
    Blob b2;
    blob_copy(&b2, &b);
    p = manifest_new(&b2);
    p = manifest_parse(&b2, 0);
    manifest_destroy(p);
  }
}

/*
** Fetch the baseline associated with the delta-manifest p.
** Print a fatal-error and quit if unable to load the baseline.
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
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







-


-
+

+
+

-
-
-
+
+
+
+
+
+


-
+


-
+


-
+


-
+
+







** and/or name going from pid to cid.
**
** Deleted files have mlink.fid=0.
** Added files have mlink.pid=0.
** Edited files have both mlink.pid!=0 and mlink.fid!=0
*/
static void add_mlink(int pid, Manifest *pParent, int cid, Manifest *pChild){
  Manifest other;
  Blob otherContent;
  int otherRid;
  int i;
  int i, rc;
  ManifestFile *pChildFile, *pParentFile;
  Manifest **ppOther;
  static Stmt eq;

  if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", cid) ){
    return;
  }
  db_static_prepare(&eq, "SELECT 1 FROM mlink WHERE mid=:mid");
  db_bind_int(&eq, ":mid", cid);
  rc = db_step(&eq);
  db_reset(&eq);
  if( rc==SQLITE_ROW ) return;

  assert( pParent==0 || pChild==0 );
  if( pParent==0 ){
    pParent = &other;
    ppOther = &pParent;
    otherRid = pid;
  }else{
    pChild = &other;
    ppOther = &pChild;
    otherRid = cid;
  }
  if( manifest_cache_find(otherRid, &other)==0 ){
  if( (*ppOther = manifest_cache_find(otherRid))==0 ){
    content_get(otherRid, &otherContent);
    if( blob_size(&otherContent)==0 ) return;
    if( manifest_parse(&other, &otherContent)==0 ) return;
    *ppOther = manifest_parse(&otherContent, otherRid);
    if( *ppOther==0 ) return;
  }
  if( (pParent->zBaseline==0)==(pChild->zBaseline==0) ){
    content_deltify(pid, cid, 0); 
  }
  
  for(i=0, pChildFile=pChild->aFile; i<pChild->nFile; i++, pChildFile++){
    if( pChildFile->zPrior ){
1153
1154
1155
1156
1157
1158
1159
1160

1161
1162
1163
1164
1165
1166
1167
1148
1149
1150
1151
1152
1153
1154

1155
1156
1157
1158
1159
1160
1161
1162







-
+







         add_one_mlink(cid, 0, pChildFile->zUuid, pChildFile->zName, 0);
       }else if( strcmp(pChildFile->zUuid, pParentFile->zUuid)!=0 ){
         add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid,
                       pChildFile->zName, 0);
       }
    }
  }
  manifest_cache_insert(otherRid, &other);
  manifest_cache_insert(*ppOther);
}

/*
** True if manifest_crosslink_begin() has been called but
** manifest_crosslink_end() is still pending.
*/
static int manifest_crosslink_busy = 0;
1294
1295
1296
1297
1298
1299
1300
1301

1302
1303
1304
1305

1306
1307

1308
1309
1310
1311


1312
1313
1314
1315

1316
1317
1318
1319


1320
1321

1322
1323

1324
1325
1326
1327
1328
1329
1330

1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346


1347
1348
1349
1350
1351
1352
1353

1354
1355
1356
1357
1358
1359
1360

1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371



1372
1373

1374
1375
1376
1377
1378
1379
1380
1381



1382
1383

1384
1385
1386
1387


1388
1389
1390
1391
1392

1393
1394
1395
1396
1397

1398
1399
1400
1401


1402
1403
1404
1405
1406
1407
1408
1409


1410
1411
1412
1413
1414
1415
1416


1417
1418

1419
1420
1421
1422
1423
1424

1425
1426
1427
1428
1429
1430

1431
1432

1433
1434
1435
1436
1437
1438
1439
1440
1441

1442
1443
1444
1445
1446
1447
1448
1449
1450


1451
1452
1453
1454
1455
1456


1457
1458

1459
1460
1461
1462
1463
1464

1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480

1481
1482
1483
1484
1485
1486
1487
1488
1489

1490
1491
1492
1493
1494

1495
1496
1497
1498
1499


1500
1501
1502

1503
1504

1505
1506
1507
1508
1509
1510


1511
1512
1513
1514
1515
1516
1517
1518


1519
1520
1521


1522
1523
1524

1525
1526

1527
1528
1529

1530
1531
1532
1533
1534

1535
1536
1537
1538
1539

1540
1541

1542
1543
1544

1545
1546
1547
1548
1549

1550
1551
1552
1553
1554
1555
1556


1557
1558

1559
1560
1561
1289
1290
1291
1292
1293
1294
1295

1296
1297
1298
1299

1300
1301

1302
1303
1304


1305
1306
1307
1308
1309

1310
1311
1312


1313
1314
1315

1316
1317

1318
1319
1320
1321
1322
1323
1324

1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339


1340
1341
1342
1343
1344
1345
1346
1347

1348
1349
1350
1351
1352
1353
1354

1355
1356
1357
1358
1359
1360
1361
1362
1363



1364
1365
1366
1367

1368
1369
1370
1371
1372
1373



1374
1375
1376
1377

1378
1379
1380


1381
1382
1383
1384
1385
1386

1387
1388
1389
1390
1391

1392
1393
1394


1395
1396
1397
1398
1399
1400
1401
1402


1403
1404
1405
1406
1407
1408
1409


1410
1411
1412

1413
1414
1415
1416
1417
1418

1419
1420
1421
1422
1423
1424

1425
1426

1427
1428
1429
1430
1431
1432
1433
1434
1435

1436
1437
1438
1439
1440
1441
1442
1443


1444
1445
1446
1447
1448
1449


1450
1451
1452

1453
1454
1455
1456
1457
1458

1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474

1475
1476
1477
1478
1479
1480
1481
1482
1483

1484
1485
1486
1487
1488

1489
1490
1491
1492


1493
1494
1495
1496

1497
1498

1499
1500
1501
1502
1503


1504
1505
1506
1507
1508
1509
1510
1511


1512
1513
1514


1515
1516
1517
1518

1519
1520

1521
1522
1523

1524
1525
1526
1527
1528

1529
1530
1531
1532
1533

1534
1535

1536
1537
1538

1539
1540
1541
1542
1543

1544
1545
1546
1547
1548
1549


1550
1551
1552

1553
1554
1555
1556







-
+



-
+

-
+


-
-
+
+



-
+


-
-
+
+

-
+

-
+






-
+














-
-
+
+






-
+






-
+








-
-
-
+
+
+

-
+





-
-
-
+
+
+

-
+


-
-
+
+




-
+




-
+


-
-
+
+






-
-
+
+





-
-
+
+

-
+





-
+





-
+

-
+








-
+







-
-
+
+




-
-
+
+

-
+





-
+















-
+








-
+




-
+



-
-
+
+


-
+

-
+




-
-
+
+






-
-
+
+

-
-
+
+


-
+

-
+


-
+




-
+




-
+

-
+


-
+




-
+





-
-
+
+

-
+



** Historical note:  This routine original processed manifests only.
** Processing for other control artifacts was added later.  The name
** of the routine, "manifest_crosslink", and the name of this source
** file, is a legacy of its original use.
*/
int manifest_crosslink(int rid, Blob *pContent){
  int i;
  Manifest m;
  Manifest *p;
  Stmt q;
  int parentid = 0;

  if( manifest_cache_find(rid, &m) ){
  if( (p = manifest_cache_find(rid))!=0 ){
    blob_reset(pContent);
  }else if( manifest_parse(&m, pContent)==0 ){
  }else if( (p = manifest_parse(pContent, rid))==0 ){
    return 0;
  }
  if( g.xlinkClusterOnly && m.type!=CFTYPE_CLUSTER ){
    manifest_clear(&m);
  if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){
    manifest_destroy(p);
    return 0;
  }
  db_begin_transaction();
  if( m.type==CFTYPE_MANIFEST ){
  if( p->type==CFTYPE_MANIFEST ){
    if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
      char *zCom;
      for(i=0; i<m.nParent; i++){
        int pid = uuid_to_rid(m.azParent[i], 1);
      for(i=0; i<p->nParent; i++){
        int pid = uuid_to_rid(p->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);
                      "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, p->rDate);
        if( i==0 ){
          add_mlink(pid, 0, rid, &m);
          add_mlink(pid, 0, rid, p);
          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);
        add_mlink(rid, p, cid, 0);
      }
      db_finalize(&q);
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment,"
                           "bgcolor,euser,ecomment)"
        "VALUES('ci',"
        "  coalesce("
        "    (SELECT julianday(value) FROM tagxref WHERE tagid=%d AND rid=%d),"
        "    %.17g"
        "  ),"
        "  %d,%Q,%Q,"
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>0),"
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
        TAG_DATE, rid, m.rDate,
        rid, m.zUser, m.zComment, 
        TAG_DATE, rid, p->rDate,
        rid, p->zUser, p->zComment, 
        TAG_BGCOLOR, rid,
        TAG_USER, rid,
        TAG_COMMENT, rid
      );
      zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event"
                        " WHERE rowid=last_insert_rowid()");
      wiki_extract_links(zCom, rid, 0, m.rDate, 1, WIKI_INLINE);
      wiki_extract_links(zCom, rid, 0, p->rDate, 1, WIKI_INLINE);
      free(zCom);

      /* If this is a delta-manifest, record the fact that this repository
      ** contains delta manifests, to free the "commit" logic to generate
      ** new delta manifests.
      */
      if( m.zBaseline!=0 ){
      if( p->zBaseline!=0 ){
        static int once = 0;
        if( !once ){
          db_set_int("seen-delta-manifest", 1, 0);
          once = 0;
        }
      }
    }
  }
  if( m.type==CFTYPE_CLUSTER ){
    tag_insert("cluster", 1, 0, rid, m.rDate, rid);
    for(i=0; i<m.nCChild; i++){
  if( p->type==CFTYPE_CLUSTER ){
    tag_insert("cluster", 1, 0, rid, p->rDate, rid);
    for(i=0; i<p->nCChild; i++){
      int mid;
      mid = uuid_to_rid(m.azCChild[i], 1);
      mid = uuid_to_rid(p->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
   || m.type==CFTYPE_EVENT
  if( p->type==CFTYPE_CONTROL
   || p->type==CFTYPE_MANIFEST
   || p->type==CFTYPE_EVENT
  ){
    for(i=0; i<m.nTag; i++){
    for(i=0; i<p->nTag; i++){
      int tid;
      int type;
      if( m.aTag[i].zUuid ){
        tid = uuid_to_rid(m.aTag[i].zUuid, 1);
      if( p->aTag[i].zUuid ){
        tid = uuid_to_rid(p->aTag[i].zUuid, 1);
      }else{
        tid = rid;
      }
      if( tid ){
        switch( m.aTag[i].zName[0] ){
        switch( p->aTag[i].zName[0] ){
          case '-':  type = 0;  break;  /* Cancel prior occurances */
          case '+':  type = 1;  break;  /* Apply to target only */
          case '*':  type = 2;  break;  /* Propagate to descendants */
          default:
            fossil_fatal("unknown tag type in manifest: %s", m.aTag);
            fossil_fatal("unknown tag type in manifest: %s", p->aTag);
            return 0;
        }
        tag_insert(&m.aTag[i].zName[1], type, m.aTag[i].zValue, 
                   rid, m.rDate, tid);
        tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue, 
                   rid, p->rDate, tid);
      }
    }
    if( parentid ){
      tag_propagate_all(parentid);
    }
  }
  if( m.type==CFTYPE_WIKI ){
    char *zTag = mprintf("wiki-%s", m.zWikiTitle);
  if( p->type==CFTYPE_WIKI ){
    char *zTag = mprintf("wiki-%s", p->zWikiTitle);
    int tagid = tag_findid(zTag, 1);
    int prior;
    char *zComment;
    int nWiki;
    char zLength[40];
    while( fossil_isspace(m.zWiki[0]) ) m.zWiki++;
    nWiki = strlen(m.zWiki);
    while( fossil_isspace(p->zWiki[0]) ) p->zWiki++;
    nWiki = strlen(p->zWiki);
    sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
    tag_insert(zTag, 1, zLength, rid, m.rDate, rid);
    tag_insert(zTag, 1, zLength, rid, p->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
      tagid, p->rDate
    );
    if( prior ){
      content_deltify(prior, rid, 0);
    }
    if( nWiki>0 ){
      zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle);
      zComment = mprintf("Changes to wiki page [%h]", p->zWikiTitle);
    }else{
      zComment = mprintf("Deleted wiki page [%h]", m.zWikiTitle);
      zComment = mprintf("Deleted wiki page [%h]", p->zWikiTitle);
    }
    db_multi_exec(
      "REPLACE INTO event(type,mtime,objid,user,comment,"
      "                  bgcolor,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),"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
      m.rDate, rid, m.zUser, zComment, 
      p->rDate, rid, p->zUser, zComment, 
      TAG_BGCOLOR, rid,
      TAG_BGCOLOR, rid,
      TAG_USER, rid,
      TAG_COMMENT, rid
    );
    free(zComment);
  }
  if( m.type==CFTYPE_EVENT ){
    char *zTag = mprintf("event-%s", m.zEventId);
  if( p->type==CFTYPE_EVENT ){
    char *zTag = mprintf("event-%s", p->zEventId);
    int tagid = tag_findid(zTag, 1);
    int prior, subsequent;
    int nWiki;
    char zLength[40];
    while( fossil_isspace(m.zWiki[0]) ) m.zWiki++;
    nWiki = strlen(m.zWiki);
    while( fossil_isspace(p->zWiki[0]) ) p->zWiki++;
    nWiki = strlen(p->zWiki);
    sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
    tag_insert(zTag, 1, zLength, rid, m.rDate, rid);
    tag_insert(zTag, 1, zLength, rid, p->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
      tagid, p->rDate
    );
    if( prior ){
      content_deltify(prior, rid, 0);
      db_multi_exec(
        "DELETE FROM event"
        " WHERE type='e'"
        "   AND tagid=%d"
        "   AND objid IN (SELECT rid FROM tagxref WHERE tagid=%d)",
        tagid, tagid
      );
    }
    subsequent = db_int(0,
      "SELECT rid FROM tagxref"
      " WHERE tagid=%d AND mtime>%.17g"
      " ORDER BY mtime",
      tagid, m.rDate
      tagid, p->rDate
    );
    if( subsequent ){
      content_deltify(rid, subsequent, 0);
    }else{
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)"
        "VALUES('e',%.17g,%d,%d,%Q,%Q,"
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
        m.rEventDate, rid, tagid, m.zUser, m.zComment, 
        p->rEventDate, rid, tagid, p->zUser, p->zComment, 
        TAG_BGCOLOR, rid
      );
    }
  }
  if( m.type==CFTYPE_TICKET ){
  if( p->type==CFTYPE_TICKET ){
    char *zTag;

    assert( manifest_crosslink_busy==1 );
    zTag = mprintf("tkt-%s", m.zTicketUuid);
    tag_insert(zTag, 1, 0, rid, m.rDate, rid);
    zTag = mprintf("tkt-%s", p->zTicketUuid);
    tag_insert(zTag, 1, 0, rid, p->rDate, rid);
    free(zTag);
    db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
                  m.zTicketUuid);
                  p->zTicketUuid);
  }
  if( m.type==CFTYPE_ATTACHMENT ){
  if( p->type==CFTYPE_ATTACHMENT ){
    db_multi_exec(
       "INSERT INTO attachment(attachid, mtime, src, target,"
                                        "filename, comment, user)"
       "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);",
       rid, m.rDate, m.zAttachSrc, m.zAttachTarget, m.zAttachName,
       (m.zComment ? m.zComment : ""), m.zUser
       rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName,
       (p->zComment ? p->zComment : ""), p->zUser
    );
    db_multi_exec(
       "UPDATE attachment SET isLatest = (mtime=="
          "(SELECT max(mtime) FROM attachment"
          "  WHERE target=%Q AND filename=%Q))"
       " WHERE target=%Q AND filename=%Q",
       m.zAttachTarget, m.zAttachName,
       m.zAttachTarget, m.zAttachName
       p->zAttachTarget, p->zAttachName,
       p->zAttachTarget, p->zAttachName
    );
    if( strlen(m.zAttachTarget)!=UUID_SIZE
     || !validate16(m.zAttachTarget, UUID_SIZE) 
    if( strlen(p->zAttachTarget)!=UUID_SIZE
     || !validate16(p->zAttachTarget, UUID_SIZE) 
    ){
      char *zComment;
      if( m.zAttachSrc && m.zAttachSrc[0] ){
      if( p->zAttachSrc && p->zAttachSrc[0] ){
        zComment = mprintf("Add attachment \"%h\" to wiki page [%h]",
             m.zAttachName, m.zAttachTarget);
             p->zAttachName, p->zAttachTarget);
      }else{
        zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
             m.zAttachName, m.zAttachTarget);
             p->zAttachName, p->zAttachTarget);
      }
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('w',%.17g,%d,%Q,%Q)",
        m.rDate, rid, m.zUser, zComment
        p->rDate, rid, p->zUser, zComment
      );
      free(zComment);
    }else{
      char *zComment;
      if( m.zAttachSrc && m.zAttachSrc[0] ){
      if( p->zAttachSrc && p->zAttachSrc[0] ){
        zComment = mprintf("Add attachment \"%h\" to ticket [%.10s]",
             m.zAttachName, m.zAttachTarget);
             p->zAttachName, p->zAttachTarget);
      }else{
        zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]",
             m.zAttachName, m.zAttachTarget);
             p->zAttachName, p->zAttachTarget);
      }
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('t',%.17g,%d,%Q,%Q)",
        m.rDate, rid, m.zUser, zComment
        p->rDate, rid, p->zUser, zComment
      );
      free(zComment);
    }
  }
  db_end_transaction(0);
  if( m.type==CFTYPE_MANIFEST ){
    manifest_cache_insert(rid, &m);
  if( p->type==CFTYPE_MANIFEST ){
    manifest_cache_insert(p);
  }else{
    manifest_clear(&m);
    manifest_destroy(p);
  }
  return 1;
}