Fossil SCM

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

Keyboard Shortcuts

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