Fossil SCM

fossil-scm / src / content.c
Blame History Raw 1366 lines
1
/*
2
** Copyright (c) 2006 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
**
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** Procedures store and retrieve records from the repository
19
*/
20
#include "config.h"
21
#include "content.h"
22
#include <assert.h>
23
24
/*
25
** The artifact retrieval cache
26
*/
27
static int cacheMaxN = 0; /* Max cache entries */
28
static i64 cacheMaxSz = 0; /* Max cache bytes */
29
30
static struct {
31
i64 szTotal; /* Total size of all entries in the cache */
32
int n; /* Current number of cache entries */
33
int nAlloc; /* Number of slots allocated in a[] */
34
int nextAge; /* Age counter for implementing LRU */
35
struct cacheLine { /* One instance of this for each cache entry */
36
int rid; /* Artifact id */
37
int age; /* Age. Newer is larger */
38
Blob content; /* Content of the artifact */
39
} *a; /* The positive cache */
40
Bag inCache; /* Set of artifacts currently in cache */
41
42
/*
43
** The missing artifact cache.
44
**
45
** Artifacts whose record ID are in missingCache cannot be retrieved
46
** either because they are phantoms or because they are a delta that
47
** depends on a phantom. Artifacts whose content we are certain is
48
** available are in availableCache. If an artifact is in neither cache
49
** then its current availability is unknown.
50
*/
51
Bag missing; /* Cache of artifacts that are incomplete */
52
Bag available; /* Cache of artifacts that are complete */
53
} contentCache;
54
55
/*
56
** Remove the oldest element from the content cache
57
*/
58
static void content_cache_expire_oldest(void){
59
int i;
60
int mnAge = contentCache.nextAge;
61
int mn = -1;
62
for(i=0; i<contentCache.n; i++){
63
if( contentCache.a[i].age<mnAge ){
64
mnAge = contentCache.a[i].age;
65
mn = i;
66
}
67
}
68
if( mn>=0 ){
69
bag_remove(&contentCache.inCache, contentCache.a[mn].rid);
70
contentCache.szTotal -= blob_size(&contentCache.a[mn].content);
71
blob_reset(&contentCache.a[mn].content);
72
contentCache.n--;
73
contentCache.a[mn] = contentCache.a[contentCache.n];
74
}
75
}
76
77
/*
78
** Add an entry to the content cache.
79
**
80
** This routines hands responsibility for the artifact over to the cache.
81
** The cache will deallocate memory when it has finished with it.
82
*/
83
void content_cache_insert(int rid, Blob *pBlob){
84
struct cacheLine *p;
85
86
if( cacheMaxN==0 ){ /* FIXME TEST Not to be merged to trunk FIXME */
87
const char *z;
88
89
cacheMaxN = 500; /* old hardcoded value */
90
z = getenv("FOSSIL_CACHE_N");
91
if( z!=0 ){
92
int val = atoi(z);
93
if( val>0 ) cacheMaxN = val;
94
}
95
96
cacheMaxSz = (i64)50000000; /*old hardcoded value */
97
z = getenv("FOSSIL_CACHE_SZ");
98
if( z!=0 ){
99
i64 val = (i64)atoll(z);
100
if( val>0 ) cacheMaxSz = val;
101
}
102
}
103
104
if( cacheMaxN==0 ){
105
const char *z;
106
z = getenv("FOSSIL_CACHE_N");
107
cacheMaxN = (z!=0 && atoi(z)>0) ? atoi(z) : 500;
108
z = getenv("FOSSIL_CACHE_SZ");
109
cacheMaxSz = (z!=0 && atoll(z)>0) ? (i64)atoll(z) : (i64)50000000;
110
}
111
if( contentCache.n>cacheMaxN || contentCache.szTotal>cacheMaxSz ){
112
i64 szBefore;
113
do{
114
szBefore = contentCache.szTotal;
115
content_cache_expire_oldest();
116
}while( contentCache.szTotal>cacheMaxSz && contentCache.szTotal<szBefore );
117
}
118
if( contentCache.n>=contentCache.nAlloc ){
119
contentCache.nAlloc = contentCache.nAlloc*2 + 10;
120
contentCache.a = fossil_realloc(contentCache.a,
121
contentCache.nAlloc*sizeof(contentCache.a[0]));
122
}
123
p = &contentCache.a[contentCache.n++];
124
p->rid = rid;
125
p->age = contentCache.nextAge++;
126
contentCache.szTotal += blob_size(pBlob);
127
p->content = *pBlob;
128
blob_zero(pBlob);
129
bag_insert(&contentCache.inCache, rid);
130
}
131
132
/*
133
** Clear the content cache. If it is passed true, it
134
** also frees all associated memory, otherwise it may
135
** retain parts for future uses of the cache.
136
*/
137
void content_clear_cache(int bFreeIt){
138
int i;
139
for(i=0; i<contentCache.n; i++){
140
blob_reset(&contentCache.a[i].content);
141
}
142
bag_clear(&contentCache.missing);
143
bag_clear(&contentCache.available);
144
bag_clear(&contentCache.inCache);
145
contentCache.n = 0;
146
contentCache.szTotal = 0;
147
if(bFreeIt){
148
fossil_free(contentCache.a);
149
contentCache.a = 0;
150
contentCache.nAlloc = 0;
151
}
152
}
153
154
/*
155
** Return the srcid associated with rid. Or return 0 if rid is
156
** original content and not a delta.
157
*/
158
int delta_source_rid(int rid){
159
static Stmt q;
160
int srcid;
161
db_static_prepare(&q, "SELECT srcid FROM delta WHERE rid=:rid");
162
db_bind_int(&q, ":rid", rid);
163
if( db_step(&q)==SQLITE_ROW ){
164
srcid = db_column_int(&q, 0);
165
}else{
166
srcid = 0;
167
}
168
db_reset(&q);
169
return srcid;
170
}
171
172
/*
173
** Return the blob.size field given blob.rid
174
*/
175
int content_size(int rid, int dflt){
176
static Stmt q;
177
int sz = dflt;
178
db_static_prepare(&q, "SELECT size FROM blob WHERE rid=:r");
179
db_bind_int(&q, ":r", rid);
180
if( db_step(&q)==SQLITE_ROW ){
181
sz = db_column_int(&q, 0);
182
}
183
db_reset(&q);
184
return sz;
185
}
186
187
/*
188
** Check to see if content is available for artifact "rid". Return
189
** true if it is. Return false if rid is a phantom or depends on
190
** a phantom.
191
*/
192
int content_is_available(int rid){
193
int srcid;
194
int depth = 0; /* Limit to recursion depth */
195
while( depth++ < 10000000 ){
196
if( bag_find(&contentCache.missing, rid) ){
197
return 0;
198
}
199
if( bag_find(&contentCache.available, rid) ){
200
return 1;
201
}
202
if( content_size(rid, -1)<0 ){
203
bag_insert(&contentCache.missing, rid);
204
return 0;
205
}
206
srcid = delta_source_rid(rid);
207
if( srcid==0 ){
208
bag_insert(&contentCache.available, rid);
209
return 1;
210
}
211
rid = srcid;
212
}
213
fossil_panic("delta-loop in repository");
214
return 0;
215
}
216
217
/*
218
** Mark artifact rid as being available now. Update the cache to
219
** show that everything that was formerly unavailable because rid
220
** was missing is now available.
221
*/
222
static void content_mark_available(int rid){
223
Bag pending;
224
static Stmt q;
225
if( bag_find(&contentCache.available, rid) ) return;
226
bag_init(&pending);
227
bag_insert(&pending, rid);
228
while( (rid = bag_first(&pending))!=0 ){
229
bag_remove(&pending, rid);
230
bag_remove(&contentCache.missing, rid);
231
bag_insert(&contentCache.available, rid);
232
db_static_prepare(&q, "SELECT rid FROM delta WHERE srcid=:rid");
233
db_bind_int(&q, ":rid", rid);
234
while( db_step(&q)==SQLITE_ROW ){
235
int nx = db_column_int(&q, 0);
236
bag_insert(&pending, nx);
237
}
238
db_reset(&q);
239
}
240
bag_clear(&pending);
241
}
242
243
/*
244
** Get the blob.content value for blob.rid=rid. Return 1 on success or
245
** 0 on failure.
246
*/
247
static int content_of_blob(int rid, Blob *pBlob){
248
static Stmt q;
249
int rc = 0;
250
db_static_prepare(&q, "SELECT content FROM blob WHERE rid=:rid AND size>=0");
251
db_bind_int(&q, ":rid", rid);
252
if( db_step(&q)==SQLITE_ROW ){
253
db_ephemeral_blob(&q, 0, pBlob);
254
blob_uncompress(pBlob, pBlob);
255
rc = 1;
256
}
257
db_reset(&q);
258
return rc;
259
}
260
261
/*
262
** Extract the content for ID rid and put it into the
263
** uninitialized blob. Return 1 on success. If the record
264
** is a phantom, zero pBlob and return 0.
265
*/
266
int content_get(int rid, Blob *pBlob){
267
int rc;
268
int i;
269
int nextRid;
270
271
assert( g.repositoryOpen );
272
blob_zero(pBlob);
273
if( rid==0 ) return 0;
274
275
/* Early out if we know the content is not available */
276
if( bag_find(&contentCache.missing, rid) ){
277
return 0;
278
}
279
280
/* Look for the artifact in the cache first */
281
if( bag_find(&contentCache.inCache, rid) ){
282
for(i=0; i<contentCache.n; i++){
283
if( contentCache.a[i].rid==rid ){
284
blob_copy(pBlob, &contentCache.a[i].content);
285
contentCache.a[i].age = contentCache.nextAge++;
286
return 1;
287
}
288
}
289
}
290
291
nextRid = delta_source_rid(rid);
292
if( nextRid==0 ){
293
rc = content_of_blob(rid, pBlob);
294
}else{
295
int n = 1;
296
int nAlloc = 10;
297
int *a = 0;
298
int mx;
299
Blob delta, next;
300
301
a = fossil_malloc( sizeof(a[0])*nAlloc );
302
a[0] = rid;
303
a[1] = nextRid;
304
n = 1;
305
while( !bag_find(&contentCache.inCache, nextRid)
306
&& (nextRid = delta_source_rid(nextRid))>0 ){
307
n++;
308
if( n>=nAlloc ){
309
if( n>db_int(0, "SELECT max(rid) FROM blob") ){
310
fossil_panic("infinite loop in DELTA table");
311
}
312
nAlloc = nAlloc*2 + 10;
313
a = fossil_realloc(a, nAlloc*sizeof(a[0]));
314
}
315
a[n] = nextRid;
316
}
317
mx = n;
318
rc = content_get(a[n], pBlob);
319
n--;
320
while( rc && n>=0 ){
321
rc = content_of_blob(a[n], &delta);
322
if( rc ){
323
if( blob_delta_apply(pBlob, &delta, &next)<0 ){
324
rc = 1;
325
}else{
326
blob_reset(&delta);
327
if( (mx-n)%8==0 ){
328
content_cache_insert(a[n+1], pBlob);
329
}else{
330
blob_reset(pBlob);
331
}
332
*pBlob = next;
333
}
334
}
335
n--;
336
}
337
free(a);
338
if( !rc ) blob_reset(pBlob);
339
}
340
if( rc==0 ){
341
bag_insert(&contentCache.missing, rid);
342
}else{
343
bag_insert(&contentCache.available, rid);
344
}
345
return rc;
346
}
347
348
/*
349
** COMMAND: artifact*
350
**
351
** Usage: %fossil artifact ARTIFACT-ID ?OUTPUT-FILENAME? ?OPTIONS?
352
**
353
** Extract an artifact by its artifact hash and write the results on
354
** standard output, or if the optional second argument is given, in
355
** the named output file.
356
**
357
** Options:
358
** -R|--repository REPO Extract artifacts from repository REPO
359
**
360
** See also: [[finfo]]
361
*/
362
void artifact_cmd(void){
363
int rid;
364
Blob content;
365
const char *zFile;
366
db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
367
if( g.argc!=4 && g.argc!=3 ) usage("ARTIFACT-ID ?FILENAME? ?OPTIONS?");
368
zFile = g.argc==4 ? g.argv[3] : "-";
369
rid = name_to_rid(g.argv[2]);
370
if( rid==0 ){
371
fossil_fatal("%s",g.zErrMsg);
372
}
373
content_get(rid, &content);
374
blob_write_to_file(&content, zFile);
375
}
376
377
/*
378
** COMMAND: test-content-rawget
379
**
380
** Extract a blob from the database and write it into a file. This
381
** version does not expand the delta.
382
*/
383
void test_content_rawget_cmd(void){
384
int rid;
385
Blob content;
386
const char *zFile;
387
if( g.argc!=4 && g.argc!=3 ) usage("RECORDID ?FILENAME?");
388
zFile = g.argc==4 ? g.argv[3] : "-";
389
db_must_be_within_tree();
390
rid = name_to_rid(g.argv[2]);
391
blob_zero(&content);
392
db_blob(&content, "SELECT content FROM blob WHERE rid=%d", rid);
393
blob_uncompress(&content, &content);
394
blob_write_to_file(&content, zFile);
395
}
396
397
/*
398
** The following flag is set to disable the automatic calls to
399
** manifest_crosslink() when a record is dephantomized. This
400
** flag can be set (for example) when doing a clone when we know
401
** that rebuild will be run over all records at the conclusion
402
** of the operation.
403
*/
404
static int ignoreDephantomizations = 0;
405
406
/*
407
** When a record is converted from a phantom to a real record,
408
** if that record has other records that are derived by delta,
409
** then call manifest_crosslink() on those other records.
410
**
411
** If the formerly phantom record or any of the other records
412
** derived by delta from the former phantom are a baseline manifest,
413
** then also invoke manifest_crosslink() on the delta-manifests
414
** associated with that baseline.
415
**
416
** Tail recursion is used to minimize stack depth.
417
*/
418
void after_dephantomize(int rid, int linkFlag){
419
Stmt q;
420
int nChildAlloc = 0;
421
int *aChild = 0;
422
Blob content;
423
424
if( ignoreDephantomizations ) return;
425
while( rid ){
426
int nChildUsed = 0;
427
int i;
428
429
/* Parse the object rid itself */
430
if( linkFlag ){
431
content_get(rid, &content);
432
manifest_crosslink(rid, &content, MC_NONE);
433
assert( blob_is_reset(&content) );
434
}
435
436
/* Parse all delta-manifests that depend on baseline-manifest rid */
437
db_prepare(&q, "SELECT rid FROM orphan WHERE baseline=%d", rid);
438
while( db_step(&q)==SQLITE_ROW ){
439
int child = db_column_int(&q, 0);
440
if( nChildUsed>=nChildAlloc ){
441
nChildAlloc = nChildAlloc*2 + 10;
442
aChild = fossil_realloc(aChild, nChildAlloc*sizeof(aChild));
443
}
444
aChild[nChildUsed++] = child;
445
}
446
db_finalize(&q);
447
for(i=0; i<nChildUsed; i++){
448
content_get(aChild[i], &content);
449
manifest_crosslink(aChild[i], &content, MC_NONE);
450
assert( blob_is_reset(&content) );
451
}
452
if( nChildUsed ){
453
db_multi_exec("DELETE FROM orphan WHERE baseline=%d", rid);
454
}
455
456
/* Recursively dephantomize all artifacts that are derived by
457
** delta from artifact rid and which have not already been
458
** cross-linked. */
459
nChildUsed = 0;
460
db_prepare(&q,
461
"SELECT rid FROM delta"
462
" WHERE srcid=%d"
463
" AND NOT EXISTS(SELECT 1 FROM mlink WHERE mid=delta.rid)",
464
rid
465
);
466
while( db_step(&q)==SQLITE_ROW ){
467
int child = db_column_int(&q, 0);
468
if( nChildUsed>=nChildAlloc ){
469
nChildAlloc = nChildAlloc*2 + 10;
470
aChild = fossil_realloc(aChild, nChildAlloc*sizeof(aChild));
471
}
472
aChild[nChildUsed++] = child;
473
}
474
db_finalize(&q);
475
for(i=1; i<nChildUsed; i++){
476
after_dephantomize(aChild[i], 1);
477
}
478
479
/* Tail recursion for the common case where only a single artifact
480
** is derived by delta from rid... */
481
rid = nChildUsed>0 ? aChild[0] : 0;
482
linkFlag = 1;
483
}
484
free(aChild);
485
}
486
487
/*
488
** Turn dephantomization processing on or off.
489
*/
490
void content_enable_dephantomize(int onoff){
491
ignoreDephantomizations = !onoff;
492
}
493
494
/*
495
** Make sure the g.rcvid global variable has been initialized.
496
**
497
** If the g.zIpAddr variable has not been set when this routine is
498
** called, use zSrc as the source of content for the rcvfrom
499
** table entry.
500
*/
501
void content_rcvid_init(const char *zSrc){
502
if( g.rcvid==0 ){
503
user_select();
504
if( g.zIpAddr ) zSrc = g.zIpAddr;
505
db_multi_exec(
506
"INSERT INTO rcvfrom(uid, mtime, nonce, ipaddr)"
507
"VALUES(%d, julianday('now'), %Q, %Q)",
508
g.userUid, g.zNonce, zSrc
509
);
510
g.rcvid = db_last_insert_rowid();
511
}
512
}
513
514
/*
515
** Write content into the database. Return the record ID. If the
516
** content is already in the database, just return the record ID.
517
**
518
** If srcId is specified, then pBlob is delta content from
519
** the srcId record. srcId might be a phantom.
520
**
521
** pBlob is normally uncompressed text. But if nBlob>0 then the
522
** pBlob value has already been compressed and nBlob is its uncompressed
523
** size. If nBlob>0 then zUuid must be valid.
524
**
525
** zUuid is the UUID of the artifact, if it is specified. When srcId is
526
** specified then zUuid must always be specified. If srcId is zero,
527
** and zUuid is zero then the correct zUuid is computed from pBlob.
528
**
529
** If the record already exists but is a phantom, the pBlob content
530
** is inserted and the phantom becomes a real record.
531
**
532
** The original content of pBlob is not disturbed. The caller continues
533
** to be responsible for pBlob. This routine does *not* take over
534
** responsibility for freeing pBlob.
535
*/
536
int content_put_ex(
537
Blob *pBlob, /* Content to add to the repository */
538
const char *zUuid, /* artifact hash of reconstructed pBlob */
539
int srcId, /* pBlob is a delta from this entry */
540
int nBlob, /* pBlob is compressed. Original size is this */
541
int isPrivate /* The content should be marked private */
542
){
543
int size;
544
int rid;
545
Stmt s1;
546
Blob cmpr;
547
Blob hash;
548
int markAsUnclustered = 0;
549
int isDephantomize = 0;
550
551
assert( g.repositoryOpen );
552
assert( pBlob!=0 );
553
assert( srcId==0 || zUuid!=0 );
554
db_begin_transaction();
555
if( zUuid==0 ){
556
assert( nBlob==0 );
557
/* First check the auxiliary hash to see if there is already an artifact
558
** that uses the auxiliary hash name */
559
hname_hash(pBlob, 1, &hash);
560
rid = fast_uuid_to_rid(blob_str(&hash));
561
if( rid==0 ){
562
/* No existing artifact with the auxiliary hash name. Therefore, use
563
** the primary hash name. */
564
blob_reset(&hash);
565
hname_hash(pBlob, 0, &hash);
566
}
567
}else{
568
blob_init(&hash, zUuid, -1);
569
}
570
if( g.eHashPolicy==HPOLICY_AUTO && blob_size(&hash)>HNAME_LEN_SHA1 ){
571
g.eHashPolicy = HPOLICY_SHA3;
572
db_set_int("hash-policy", HPOLICY_SHA3, 0);
573
}
574
if( nBlob ){
575
size = nBlob;
576
}else{
577
size = blob_size(pBlob);
578
if( srcId ){
579
size = delta_output_size(blob_buffer(pBlob), size);
580
}
581
}
582
583
/* Check to see if the entry already exists and if it does whether
584
** or not the entry is a phantom
585
*/
586
db_prepare(&s1, "SELECT rid, size FROM blob WHERE uuid=%B", &hash);
587
if( db_step(&s1)==SQLITE_ROW ){
588
rid = db_column_int(&s1, 0);
589
if( db_column_int(&s1, 1)>=0 ){
590
/* The entry is not a phantom. There is nothing for us to do
591
** other than return the RID. */
592
db_finalize(&s1);
593
db_end_transaction(0);
594
return rid;
595
}
596
}else{
597
rid = 0; /* No entry with the same hash currently exists */
598
markAsUnclustered = 1;
599
}
600
db_finalize(&s1);
601
602
/* Construct a received-from ID if we do not already have one */
603
content_rcvid_init(0);
604
605
if( nBlob ){
606
cmpr = pBlob[0];
607
}else{
608
blob_compress(pBlob, &cmpr);
609
}
610
if( rid>0 ){
611
/* We are just adding data to a phantom */
612
db_prepare(&s1,
613
"UPDATE blob SET rcvid=%d, size=%d, content=:data WHERE rid=%d",
614
g.rcvid, size, rid
615
);
616
db_bind_blob(&s1, ":data", &cmpr);
617
db_exec(&s1);
618
db_multi_exec("DELETE FROM phantom WHERE rid=%d", rid);
619
if( srcId==0 || content_is_available(srcId) ){
620
isDephantomize = 1;
621
content_mark_available(rid);
622
}
623
}else{
624
/* We are creating a new entry */
625
db_prepare(&s1,
626
"INSERT INTO blob(rcvid,size,uuid,content)"
627
"VALUES(%d,%d,'%q',:data)",
628
g.rcvid, size, blob_str(&hash)
629
);
630
db_bind_blob(&s1, ":data", &cmpr);
631
db_exec(&s1);
632
rid = db_last_insert_rowid();
633
if( !pBlob ){
634
assert(!"cannot happen: pBlob is always non-NULL");
635
db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid);
636
}
637
}
638
if( g.markPrivate || isPrivate ){
639
db_multi_exec("INSERT OR IGNORE INTO private VALUES(%d)", rid);
640
markAsUnclustered = 0;
641
}
642
if( nBlob==0 ) blob_reset(&cmpr);
643
644
/* If the srcId is specified, then the data we just added is
645
** really a delta. Record this fact in the delta table.
646
*/
647
if( srcId ){
648
db_multi_exec("REPLACE INTO delta(rid,srcid) VALUES(%d,%d)", rid, srcId);
649
}
650
if( !isDephantomize && bag_find(&contentCache.missing, rid) &&
651
(srcId==0 || content_is_available(srcId)) ){
652
content_mark_available(rid);
653
}
654
if( isDephantomize ){
655
after_dephantomize(rid, 0);
656
}
657
658
/* Add the element to the unclustered table if has never been
659
** previously seen.
660
*/
661
if( markAsUnclustered ){
662
db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d)", rid);
663
}
664
665
/* Finish the transaction and cleanup */
666
db_finalize(&s1);
667
db_end_transaction(0);
668
blob_reset(&hash);
669
670
/* Make arrangements to verify that the data can be recovered
671
** before we commit */
672
verify_before_commit(rid);
673
return rid;
674
}
675
676
/*
677
** This is the simple common case for inserting content into the
678
** repository. pBlob is the content to be inserted.
679
**
680
** pBlob is uncompressed and is not deltaed. It is exactly the content
681
** to be inserted.
682
**
683
** The original content of pBlob is not disturbed. The caller continues
684
** to be responsible for pBlob. This routine does *not* take over
685
** responsibility for freeing pBlob.
686
*/
687
int content_put(Blob *pBlob){
688
return content_put_ex(pBlob, 0, 0, 0, 0);
689
}
690
691
692
/*
693
** Create a new phantom with the given hash and return its artifact ID.
694
*/
695
int content_new(const char *zUuid, int isPrivate){
696
int rid;
697
static Stmt s1, s2, s3;
698
699
assert( g.repositoryOpen );
700
db_begin_transaction();
701
if( uuid_is_shunned(zUuid) ){
702
db_end_transaction(0);
703
return 0;
704
}
705
db_static_prepare(&s1,
706
"INSERT INTO blob(rcvid,size,uuid,content)"
707
"VALUES(0,-1,:uuid,NULL)"
708
);
709
db_bind_text(&s1, ":uuid", zUuid);
710
db_exec(&s1);
711
rid = db_last_insert_rowid();
712
db_static_prepare(&s2,
713
"INSERT INTO phantom VALUES(:rid)"
714
);
715
db_bind_int(&s2, ":rid", rid);
716
db_exec(&s2);
717
if( g.markPrivate || isPrivate ){
718
db_multi_exec("INSERT INTO private VALUES(%d)", rid);
719
}else{
720
db_static_prepare(&s3,
721
"INSERT INTO unclustered VALUES(:rid)"
722
);
723
db_bind_int(&s3, ":rid", rid);
724
db_exec(&s3);
725
}
726
bag_insert(&contentCache.missing, rid);
727
db_end_transaction(0);
728
return rid;
729
}
730
731
732
/*
733
** COMMAND: test-content-put
734
**
735
** Usage: %fossil test-content-put [OPTIONS] FILE ...
736
**
737
** Read the content of all FILE argments and add them to the Blob
738
** table as a new artifact using a direct call to content_put().
739
**
740
** Options:
741
**
742
** --moderate Make corresponding entries to the MODREQ table
743
** -R REPO Add the content to repository file REPO
744
** -x|--crosslink Assume all inputs are artifacts and crosslink them.
745
*/
746
void test_content_put_cmd(void){
747
int rid;
748
Blob content;
749
int i;
750
int bModreq = find_option("moderate",0,0)!=0;
751
int bCrosslink = find_option("crosslink","x",0)!=0;
752
db_find_and_open_repository(0,0);
753
user_select();
754
verify_all_options();
755
db_begin_transaction();
756
if( bModreq ) moderation_table_create();
757
if( bCrosslink ){
758
manifest_crosslink_begin();
759
}
760
for(i=2; i<g.argc; i++){
761
const char *zFile = g.argv[i];
762
blob_read_from_file(&content, zFile, ExtFILE);
763
rid = content_put(&content);
764
if( bCrosslink ){
765
manifest_crosslink(rid, &content, MC_NONE);
766
}else{
767
blob_reset(&content);
768
}
769
if( bModreq ){
770
db_multi_exec("INSERT INTO modreq(objid) VALUES(%d)",rid);
771
}
772
fossil_print("inserted as record %d: %s\n", rid, zFile);
773
}
774
if( bCrosslink ){
775
manifest_crosslink_end(MC_NONE);
776
}
777
db_end_transaction(0);
778
}
779
780
/*
781
** Make sure the content at rid is the original content and is not a
782
** delta.
783
*/
784
void content_undelta(int rid){
785
if( delta_source_rid(rid)>0 ){
786
Blob x;
787
if( content_get(rid, &x) ){
788
Stmt s;
789
db_prepare(&s, "UPDATE blob SET content=:c, size=%d WHERE rid=%d",
790
blob_size(&x), rid);
791
blob_compress(&x, &x);
792
db_bind_blob(&s, ":c", &x);
793
db_exec(&s);
794
db_finalize(&s);
795
blob_reset(&x);
796
db_multi_exec("DELETE FROM delta WHERE rid=%d", rid);
797
}
798
}
799
}
800
801
/*
802
** COMMAND: test-content-undelta
803
**
804
** Make sure the content at RECORDID is not a delta
805
*/
806
void test_content_undelta_cmd(void){
807
int rid;
808
if( g.argc!=3 ) usage("RECORDID");
809
db_must_be_within_tree();
810
rid = atoi(g.argv[2]);
811
content_undelta(rid);
812
}
813
814
/*
815
** Return true if the given RID is marked as PRIVATE.
816
*/
817
int content_is_private(int rid){
818
static Stmt s1;
819
int rc;
820
db_static_prepare(&s1,
821
"SELECT 1 FROM private WHERE rid=:rid"
822
);
823
db_bind_int(&s1, ":rid", rid);
824
rc = db_step(&s1);
825
db_reset(&s1);
826
return rc==SQLITE_ROW;
827
}
828
829
/*
830
** Make sure an artifact is public.
831
*/
832
void content_make_public(int rid){
833
static Stmt s1;
834
db_static_prepare(&s1,
835
"DELETE FROM private WHERE rid=:rid"
836
);
837
db_bind_int(&s1, ":rid", rid);
838
db_exec(&s1);
839
}
840
841
/*
842
** Make sure an artifact is private
843
*/
844
void content_make_private(int rid){
845
static Stmt s1;
846
db_static_prepare(&s1,
847
"INSERT OR IGNORE INTO private(rid) VALUES(:rid)"
848
);
849
db_bind_int(&s1, ":rid", rid);
850
db_exec(&s1);
851
}
852
853
/*
854
** Try to change the storage of rid so that it is a delta from one
855
** of the artifacts given in aSrc[0]..aSrc[nSrc-1]. The aSrc[*] that
856
** gives the smallest delta is chosen.
857
**
858
** If rid is already a delta from some other place then no
859
** conversion occurs and this is a no-op unless force==1. If force==1,
860
** then nSrc must also be 1.
861
**
862
** If rid refers to a phantom, no delta is created.
863
**
864
** Never generate a delta that carries a private artifact into a public
865
** artifact. Otherwise, when we go to send the public artifact on a
866
** sync operation, the other end of the sync will never be able to receive
867
** the source of the delta. It is OK to delta private->private and
868
** public->private and public->public. Just no private->public delta.
869
**
870
** If aSrc[bestSrc] is already a delta that depends on rid, then it is
871
** converted to undeltaed text before the aSrc[bestSrc]->rid delta is
872
** created, in order to prevent a delta loop.
873
**
874
** If either rid or aSrc[i] contain less than 50 bytes, or if the
875
** resulting delta does not achieve a compression of at least 25%
876
** the rid is left untouched.
877
**
878
** Return the number of bytes by which the storage associated with rid
879
** is reduced. A return of 0 means no new deltification occurs.
880
*/
881
int content_deltify(int rid, int *aSrc, int nSrc, int force){
882
int s;
883
Blob data; /* Content of rid */
884
Blob src; /* Content of aSrc[i] */
885
Blob delta; /* Delta from aSrc[i] to rid */
886
Blob bestDelta; /* Best delta seen so far */
887
int bestSrc = 0; /* Which aSrc is the source of the best delta */
888
int rc = 0; /* Value to return */
889
int i; /* Loop variable for aSrc[] */
890
891
/*
892
** Historically this routine gracefully ignored the rid 0, but the
893
** addition of a call to content_is_available() in [188ffef2] caused
894
** rid 0 to trigger an assert via bag_find(). Rather than track down
895
** all such calls (e.g. the one via /technoteedit), we'll continue
896
** to gracefully ignore rid 0 here.
897
*/
898
if( 0==rid ) return 0;
899
900
/* If rid is already a child (a delta) of some other artifact, return
901
** immediately if the force flags is false
902
*/
903
if( !force && delta_source_rid(rid)>0 ) return 0;
904
905
/* If rid refers to a phantom, skip deltification. */
906
if( 0==content_is_available(rid) ) return 0;
907
908
/* Get the complete content of the object to be delta-ed. If the size
909
** is less than 50 bytes, then there really is no point in trying to do
910
** a delta, so return immediately
911
*/
912
content_get(rid, &data);
913
if( blob_size(&data)<50 ){
914
/* Do not try to create a delta for objects smaller than 50 bytes */
915
blob_reset(&data);
916
return 0;
917
}
918
blob_init(&bestDelta, 0, 0);
919
920
/* Loop over all candidate delta sources */
921
for(i=0; i<nSrc; i++){
922
int srcid = aSrc[i];
923
if( srcid==rid ) continue;
924
if( content_is_private(srcid) && !content_is_private(rid) ) continue;
925
926
/* Compute all ancestors of srcid and make sure rid is not one of them.
927
** If rid is an ancestor of srcid, then making rid a descendant of srcid
928
** would create a delta loop. */
929
s = srcid;
930
while( (s = delta_source_rid(s))>0 ){
931
if( s==rid ){
932
content_undelta(srcid);
933
break;
934
}
935
}
936
if( s!=0 ) continue;
937
938
content_get(srcid, &src);
939
if( blob_size(&src)<50 ){
940
/* The source is smaller then 50 bytes, so don't bother trying to use it*/
941
blob_reset(&src);
942
continue;
943
}
944
blob_delta_create(&src, &data, &delta);
945
if( blob_size(&delta) < blob_size(&data)*0.75
946
&& (bestSrc<=0 || blob_size(&delta)<blob_size(&bestDelta))
947
){
948
/* This is the best delta seen so far. Remember it */
949
blob_reset(&bestDelta);
950
bestDelta = delta;
951
bestSrc = srcid;
952
}else{
953
/* This delta is not a candidate for becoming the new parent of rid */
954
blob_reset(&delta);
955
}
956
blob_reset(&src);
957
}
958
959
/* If there is a winning candidate for the new parent of rid, then
960
** make that candidate the new parent now */
961
if( bestSrc>0 ){
962
Stmt s1, s2; /* Statements used to create the delta */
963
blob_compress(&bestDelta, &bestDelta);
964
db_prepare(&s1, "UPDATE blob SET content=:data WHERE rid=%d", rid);
965
db_prepare(&s2, "REPLACE INTO delta(rid,srcid)VALUES(%d,%d)", rid, bestSrc);
966
db_bind_blob(&s1, ":data", &bestDelta);
967
db_begin_transaction();
968
rc = db_int(0, "SELECT octet_length(content) FROM blob WHERE rid=%d", rid);
969
db_exec(&s1);
970
db_exec(&s2);
971
db_end_transaction(0);
972
db_finalize(&s1);
973
db_finalize(&s2);
974
verify_before_commit(rid);
975
rc -= blob_size(&bestDelta);
976
}
977
blob_reset(&data);
978
blob_reset(&bestDelta);
979
return rc;
980
}
981
982
/*
983
** COMMAND: test-content-deltify
984
**
985
** Usage: %fossil RID SRCID SRCID ... [-force]
986
**
987
** Convert the content at RID into a delta one of the from SRCIDs.
988
*/
989
void test_content_deltify_cmd(void){
990
int nSrc;
991
int *aSrc;
992
int i;
993
int bForce = find_option("force",0,0)!=0;
994
if( g.argc<3 ) usage("[--force] RID SRCID SRCID...");
995
aSrc = fossil_malloc( (g.argc-2)*sizeof(aSrc[0]) );
996
nSrc = 0;
997
for(i=2; i<g.argc; i++) aSrc[nSrc++] = atoi(g.argv[i]);
998
db_must_be_within_tree();
999
content_deltify(atoi(g.argv[2]), aSrc, nSrc, bForce);
1000
}
1001
1002
/*
1003
** Return true if Blob p looks like it might be a parsable control artifact.
1004
*/
1005
static int looks_like_control_artifact(Blob *p){
1006
const char *z = blob_buffer(p);
1007
int n = blob_size(p);
1008
if( n<10 ) return 0;
1009
if( strncmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)==0 ) return 1;
1010
if( strncmp(z, "-----BEGIN SSH SIGNED MESSAGE-----", 34)==0 ) return 1;
1011
if( z[0]<'A' || z[0]>'Z' || z[1]!=' ' || z[0]=='I' ) return 0;
1012
if( z[n-1]!='\n' ) return 0;
1013
return 1;
1014
}
1015
1016
/*
1017
** COMMAND: test-integrity
1018
**
1019
** Verify that all content can be extracted from the BLOB table correctly.
1020
** If the BLOB table is correct, then the repository can always be
1021
** successfully reconstructed using "fossil rebuild".
1022
**
1023
** Options:
1024
** -d|--db-only Run "PRAGMA integrity_check" on the database only.
1025
** No other validation is performed.
1026
** --parse Parse all manifests, wikis, tickets, events, and
1027
** so forth, reporting any errors found.
1028
** --quick Run "PRAGMA quick_check" on the database only.
1029
** No other validation is performed.
1030
*/
1031
void test_integrity(void){
1032
Stmt q;
1033
Blob content;
1034
int n1 = 0;
1035
int n2 = 0;
1036
int nErr = 0;
1037
int total;
1038
int nCA = 0;
1039
int anCA[10];
1040
int bParse = find_option("parse",0,0)!=0;
1041
int bDbOnly = find_option("db-only","d",0)!=0;
1042
int bQuick = find_option("quick",0,0)!=0;
1043
db_find_and_open_repository(OPEN_ANY_SCHEMA, 2);
1044
if( bDbOnly || bQuick ){
1045
const char *zType = bQuick ? "quick" : "integrity";
1046
char *zRes;
1047
zRes = db_text(0,"PRAGMA repository.%s_check", zType/*safe-for-%s*/);
1048
if( fossil_strcmp(zRes,"ok")!=0 ){
1049
fossil_print("%s_check failed!\n", zType);
1050
exit(1);
1051
}else{
1052
fossil_print("ok\n");
1053
}
1054
return;
1055
}
1056
memset(anCA, 0, sizeof(anCA));
1057
1058
/* Make sure no public artifact is a delta from a private artifact */
1059
db_prepare(&q,
1060
"SELECT "
1061
" rid, (SELECT uuid FROM blob WHERE rid=delta.rid),"
1062
" srcid, (SELECT uuid FROM blob WHERE rid=delta.srcid)"
1063
" FROM delta"
1064
" WHERE srcid in private AND rid NOT IN private"
1065
);
1066
while( db_step(&q)==SQLITE_ROW ){
1067
int rid = db_column_int(&q, 0);
1068
const char *zId = db_column_text(&q, 1);
1069
int srcid = db_column_int(&q, 2);
1070
const char *zSrc = db_column_text(&q, 3);
1071
fossil_print(
1072
"public artifact %S (%d) is a delta from private artifact %S (%d)\n",
1073
zId, rid, zSrc, srcid
1074
);
1075
nErr++;
1076
}
1077
db_finalize(&q);
1078
1079
db_prepare(&q, "SELECT rid, uuid, size FROM blob ORDER BY rid");
1080
total = db_int(0, "SELECT max(rid) FROM blob");
1081
while( db_step(&q)==SQLITE_ROW ){
1082
int rid = db_column_int(&q, 0);
1083
const char *zUuid = db_column_text(&q, 1);
1084
int nUuid = db_column_bytes(&q, 1);
1085
int size = db_column_int(&q, 2);
1086
n1++;
1087
fossil_print(" %d/%d\r", n1, total);
1088
fflush(stdout);
1089
if( size<0 ){
1090
fossil_print("skip phantom %d %s\n", rid, zUuid);
1091
continue; /* Ignore phantoms */
1092
}
1093
content_get(rid, &content);
1094
if( (int)blob_size(&content)!=size ){
1095
fossil_print("size mismatch on artifact %d: wanted %d but got %d\n",
1096
rid, size, blob_size(&content));
1097
nErr++;
1098
}
1099
if( !hname_verify_hash(&content, zUuid, nUuid) ){
1100
fossil_print("wrong hash on artifact %d\n",rid);
1101
nErr++;
1102
}
1103
if( bParse && looks_like_control_artifact(&content) ){
1104
Blob err;
1105
int i, n;
1106
char *z;
1107
Manifest *p;
1108
char zFirstLine[400];
1109
blob_zero(&err);
1110
1111
z = blob_buffer(&content);
1112
n = blob_size(&content);
1113
for(i=0; i<n && z[i] && z[i]!='\n' && i<(int)sizeof(zFirstLine)-1; i++){}
1114
memcpy(zFirstLine, z, i);
1115
zFirstLine[i] = 0;
1116
p = manifest_parse(&content, 0, &err);
1117
if( p==0 ){
1118
fossil_print("manifest_parse failed for %s:\n%s\n",
1119
zUuid, blob_str(&err));
1120
if( strncmp(blob_str(&err), "line 1:", 7)==0 ){
1121
fossil_print("\"%s\"\n", zFirstLine);
1122
}
1123
}else{
1124
anCA[p->type]++;
1125
manifest_destroy(p);
1126
nCA++;
1127
}
1128
blob_reset(&err);
1129
}else{
1130
blob_reset(&content);
1131
}
1132
n2++;
1133
}
1134
db_finalize(&q);
1135
fossil_print("%d non-phantom blobs (out of %d total) checked: %d errors\n",
1136
n2, n1, nErr);
1137
if( bParse ){
1138
static const char *const azType[] = { 0, "manifest", "cluster",
1139
"control", "wiki", "ticket", "attachment", "event" };
1140
int i;
1141
fossil_print("%d total control artifacts\n", nCA);
1142
for(i=1; i<count(azType); i++){
1143
if( anCA[i] ) fossil_print(" %d %ss\n", anCA[i], azType[i]);
1144
}
1145
}
1146
fossil_print("low-level database integrity-check: ");
1147
fossil_print("%s\n", db_text(0, "PRAGMA integrity_check(10)"));
1148
}
1149
1150
/*
1151
** COMMAND: test-orphans
1152
**
1153
** Search the repository for orphaned artifacts.
1154
*/
1155
void test_orphans(void){
1156
Stmt q;
1157
int cnt = 0;
1158
1159
db_find_and_open_repository(0, 0);
1160
db_multi_exec(
1161
"CREATE TEMP TABLE used(id INTEGER PRIMARY KEY ON CONFLICT IGNORE);"
1162
"INSERT INTO used SELECT mid FROM mlink;" /* Manifests */
1163
"INSERT INTO used SELECT fid FROM mlink;" /* Files */
1164
"INSERT INTO used SELECT srcid FROM tagxref WHERE srcid>0;" /* Tags */
1165
"INSERT INTO used SELECT rid FROM tagxref;" /* Wiki & tickets */
1166
"INSERT INTO used SELECT rid FROM attachment JOIN blob ON src=uuid;"
1167
"INSERT INTO used SELECT attachid FROM attachment;"
1168
"INSERT INTO used SELECT objid FROM event;"
1169
);
1170
db_prepare(&q, "SELECT rid, uuid, size FROM blob WHERE rid NOT IN used");
1171
while( db_step(&q)==SQLITE_ROW ){
1172
fossil_print("%7d %s size: %d\n",
1173
db_column_int(&q, 0),
1174
db_column_text(&q, 1),
1175
db_column_int(&q,2));
1176
cnt++;
1177
}
1178
db_finalize(&q);
1179
fossil_print("%d orphans\n", cnt);
1180
}
1181
1182
/* Allowed flags for check_exists */
1183
#define MISSING_SHUNNED 0x0001 /* Do not report shunned artifacts */
1184
1185
/* This is a helper routine for test-artifacts.
1186
**
1187
** Check to see that the artifact hash referenced by zUuid exists in the
1188
** repository. If it does, return 0. If it does not, generate an error
1189
** message and return 1.
1190
*/
1191
static int check_exists(
1192
const char *zUuid, /* Hash of the artifact we are checking for */
1193
unsigned flags, /* Flags */
1194
Manifest *p, /* The control artifact that references zUuid */
1195
const char *zRole, /* Role of zUuid in p */
1196
const char *zDetail /* Additional information, such as a filename */
1197
){
1198
static Stmt q;
1199
int rc = 0;
1200
1201
db_static_prepare(&q, "SELECT size FROM blob WHERE uuid=:uuid");
1202
if( zUuid==0 || zUuid[0]==0 ) return 0;
1203
db_bind_text(&q, ":uuid", zUuid);
1204
if( db_step(&q)==SQLITE_ROW ){
1205
int size = db_column_int(&q, 0);
1206
if( size<0 ) rc = 2;
1207
}else{
1208
rc = 1;
1209
}
1210
db_reset(&q);
1211
if( rc ){
1212
const char *zCFType = "control artifact";
1213
char *zSrc;
1214
char *zDate;
1215
const char *zErrType = "MISSING";
1216
if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
1217
if( flags & MISSING_SHUNNED ) return 0;
1218
zErrType = "SHUNNED";
1219
}
1220
switch( p->type ){
1221
case CFTYPE_MANIFEST: zCFType = "check-in"; break;
1222
case CFTYPE_CLUSTER: zCFType = "cluster"; break;
1223
case CFTYPE_CONTROL: zCFType = "tag"; break;
1224
case CFTYPE_WIKI: zCFType = "wiki"; break;
1225
case CFTYPE_TICKET: zCFType = "ticket"; break;
1226
case CFTYPE_ATTACHMENT: zCFType = "attachment"; break;
1227
case CFTYPE_EVENT: zCFType = "event"; break;
1228
}
1229
zSrc = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", p->rid);
1230
if( p->rDate>0.0 ){
1231
zDate = db_text(0, "SELECT datetime(%.17g)", p->rDate);
1232
}else{
1233
zDate = db_text(0,
1234
"SELECT datetime(rcvfrom.mtime)"
1235
" FROM blob, rcvfrom"
1236
" WHERE blob.rcvid=rcvfrom.rcvid"
1237
" AND blob.rid=%d", p->rid);
1238
}
1239
fossil_print("%s: %s\n %s %s %S (%d) %s\n",
1240
zErrType, zUuid, zRole, zCFType, zSrc, p->rid, zDate);
1241
if( zDetail && zDetail[0] ){
1242
fossil_print(" %s\n", zDetail);
1243
}
1244
fossil_free(zSrc);
1245
fossil_free(zDate);
1246
rc = 1;
1247
}
1248
return rc;
1249
}
1250
1251
/*
1252
** COMMAND: test-missing
1253
**
1254
** Usage: %fossil test-missing
1255
**
1256
** Look at every artifact in the repository and verify that
1257
** all references are satisfied. Report any referenced artifacts
1258
** that are missing or shunned.
1259
**
1260
** Options:
1261
** --notshunned Do not report shunned artifacts
1262
** -q|--quiet Only show output if there are errors
1263
*/
1264
void test_missing(void){
1265
Stmt q;
1266
Blob content;
1267
int nErr = 0;
1268
int nArtifact = 0;
1269
int i;
1270
Manifest *p;
1271
unsigned flags = 0;
1272
int quietFlag;
1273
1274
if( find_option("notshunned", 0, 0)!=0 ) flags |= MISSING_SHUNNED;
1275
quietFlag = g.fQuiet;
1276
db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
1277
db_prepare(&q,
1278
"SELECT mid FROM mlink UNION "
1279
"SELECT srcid FROM tagxref WHERE srcid>0 UNION "
1280
"SELECT rid FROM tagxref UNION "
1281
"SELECT rid FROM attachment JOIN blob ON src=uuid UNION "
1282
"SELECT objid FROM event");
1283
while( db_step(&q)==SQLITE_ROW ){
1284
int rid = db_column_int(&q, 0);
1285
content_get(rid, &content);
1286
p = manifest_parse(&content, rid, 0);
1287
if( p ){
1288
nArtifact++;
1289
nErr += check_exists(p->zBaseline, flags, p, "baseline of", 0);
1290
nErr += check_exists(p->zAttachSrc, flags, p, "file of", 0);
1291
for(i=0; i<p->nFile; i++){
1292
nErr += check_exists(p->aFile[i].zUuid, flags, p, "file of",
1293
p->aFile[i].zName);
1294
}
1295
for(i=0; i<p->nParent; i++){
1296
nErr += check_exists(p->azParent[i], flags, p, "parent of", 0);
1297
}
1298
for(i=0; i<p->nCherrypick; i++){
1299
nErr += check_exists(p->aCherrypick[i].zCPTarget+1, flags, p,
1300
"cherry-pick target of", 0);
1301
nErr += check_exists(p->aCherrypick[i].zCPBase, flags, p,
1302
"cherry-pick baseline of", 0);
1303
}
1304
for(i=0; i<p->nCChild; i++){
1305
nErr += check_exists(p->azCChild[i], flags, p, "in", 0);
1306
}
1307
for(i=0; i<p->nTag; i++){
1308
nErr += check_exists(p->aTag[i].zUuid, flags, p, "target of", 0);
1309
}
1310
manifest_destroy(p);
1311
}
1312
}
1313
db_finalize(&q);
1314
if( nErr>0 || quietFlag==0 ){
1315
fossil_print("%d missing or shunned references in %d control artifacts\n",
1316
nErr, nArtifact);
1317
}
1318
}
1319
1320
/*
1321
** COMMAND: test-content-erase
1322
**
1323
** Usage: %fossil test-content-erase RID ....
1324
**
1325
** Remove all traces of one or more artifacts from the local repository.
1326
**
1327
** WARNING: This command destroys data and can cause you to lose work.
1328
** Make sure you have a backup copy before using this command!
1329
**
1330
** WARNING: You must run "fossil rebuild" after this command to rebuild
1331
** the metadata.
1332
**
1333
** Note that the arguments are the integer raw RID values from the BLOB table,
1334
** not artifact hashes or labels.
1335
*/
1336
void test_content_erase(void){
1337
int i;
1338
Blob x;
1339
char c;
1340
Stmt q;
1341
prompt_user("This command erases information from the repository and\n"
1342
"might irrecoverably damage the repository. Make sure you\n"
1343
"have a backup copy!\n"
1344
"Continue? (y/N)? ", &x);
1345
c = blob_str(&x)[0];
1346
blob_reset(&x);
1347
if( c!='y' && c!='Y' ) return;
1348
db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
1349
db_begin_transaction();
1350
db_prepare(&q, "SELECT rid FROM delta WHERE srcid=:rid");
1351
for(i=2; i<g.argc; i++){
1352
int rid = atoi(g.argv[i]);
1353
fossil_print("Erasing artifact %d (%s)\n",
1354
rid, db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid));
1355
db_bind_int(&q, ":rid", rid);
1356
while( db_step(&q)==SQLITE_ROW ){
1357
content_undelta(db_column_int(&q,0));
1358
}
1359
db_reset(&q);
1360
db_multi_exec("DELETE FROM blob WHERE rid=%d", rid);
1361
db_multi_exec("DELETE FROM delta WHERE rid=%d", rid);
1362
}
1363
db_finalize(&q);
1364
db_end_transaction(0);
1365
}
1366

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button