/* ** Mark artifact rid as being available now. Update the cache to ** show that everything that was formerly unavailable because rid ** was missing is now available. */ static void content_mark_available(int rid){ Bag pending; static Stmt q; if( bag_find(&contentCache.available, rid) ) return; bag_init(&pending); bag_insert(&pending, rid); while( (rid = bag_first(&pending))!=0 ){ bag_remove(&pending, rid); bag_remove(&contentCache.missing, rid); bag_insert(&contentCache.available, rid); db_static_prepare(&q, "SELECT rid FROM delta WHERE srcid=:rid"); db_bind_int(&q, ":rid", rid); while( db_step(&q)==SQLITE_ROW ){ int nx = db_column_int(&q, 0); bag_insert(&pending, nx); } db_reset(&q); } bag_clear(&pending); }
/* ** Add to table zTab the record ID (rid) of every check-in that contains ** the file fid. */ void compute_uses_file(const char *zTab, int fid, int usesFlags){ Bag seen; Bag pending; Stmt ins; Stmt q; int rid; bag_init(&seen); bag_init(&pending); db_prepare(&ins, "INSERT OR IGNORE INTO \"%s\" VALUES(:rid)", zTab); db_prepare(&q, "SELECT mid FROM mlink WHERE fid=%d", fid); while( db_step(&q)==SQLITE_ROW ){ int mid = db_column_int(&q, 0); bag_insert(&pending, mid); bag_insert(&seen, mid); db_bind_int(&ins, ":rid", mid); db_step(&ins); db_reset(&ins); } db_finalize(&q); db_prepare(&q, "SELECT mid FROM mlink WHERE pid=%d", fid); while( db_step(&q)==SQLITE_ROW ){ int mid = db_column_int(&q, 0); bag_insert(&seen, mid); if( usesFlags & USESFILE_DELETE ){ db_bind_int(&ins, ":rid", mid); db_step(&ins); db_reset(&ins); } } db_finalize(&q); db_prepare(&q, "SELECT cid FROM plink WHERE pid=:rid"); while( (rid = bag_first(&pending))!=0 ){ bag_remove(&pending, rid); db_bind_int(&q, ":rid", rid); while( db_step(&q)==SQLITE_ROW ){ int mid = db_column_int(&q, 0); if( bag_find(&seen, mid) ) continue; bag_insert(&seen, mid); bag_insert(&pending, mid); db_bind_int(&ins, ":rid", mid); db_step(&ins); db_reset(&ins); } db_reset(&q); } db_finalize(&q); db_finalize(&ins); bag_clear(&seen); bag_clear(&pending); }
/* ** Extract an item from content from the bundle */ static void bundle_extract_item( int blobid, /* ID of the item to extract */ Blob *pOut /* Write the content into this blob */ ){ Stmt q; Blob x, basis, h1, h2; static Bag busy; db_prepare(&q, "SELECT uuid, delta, data FROM bblob" " WHERE blobid=%d", blobid); if( db_step(&q)!=SQLITE_ROW ){ db_finalize(&q); fossil_fatal("no such item: %d", blobid); } if( bag_find(&busy, blobid) ) fossil_fatal("delta loop"); blob_zero(&x); db_column_blob(&q, 2, &x); blob_uncompress(&x, &x); if( db_column_type(&q,1)==SQLITE_INTEGER ){ bundle_extract_item(db_column_int(&q,1), &basis); blob_delta_apply(&basis, &x, pOut); blob_reset(&basis); blob_reset(&x); }else if( db_column_type(&q,1)==SQLITE_TEXT ){ int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", db_column_text(&q,1)); if( rid==0 ){ fossil_fatal("cannot find delta basis %s", db_column_text(&q,1)); } content_get(rid, &basis); db_column_blob(&q, 2, &x); blob_delta_apply(&basis, &x, pOut); blob_reset(&basis); blob_reset(&x); }else{ *pOut = x; } blob_zero(&h1); db_column_blob(&q, 0, &h1); sha1sum_blob(pOut, &h2); if( blob_compare(&h1, &h2)!=0 ){ fossil_fatal("SHA1 hash mismatch - wanted %s, got %s", blob_str(&h1), blob_str(&h2)); } blob_reset(&h1); blob_reset(&h2); bag_remove(&busy, blobid); db_finalize(&q); }
/* ** Remove the oldest element from the content cache */ static void content_cache_expire_oldest(void){ int i; int mnAge = contentCache.nextAge; int mn = -1; for(i=0; i<contentCache.n; i++){ if( contentCache.a[i].age<mnAge ){ mnAge = contentCache.a[i].age; mn = i; } } if( mn>=0 ){ bag_remove(&contentCache.inCache, contentCache.a[mn].rid); contentCache.szTotal -= blob_size(&contentCache.a[mn].content); blob_reset(&contentCache.a[mn].content); contentCache.n--; contentCache.a[mn] = contentCache.a[contentCache.n]; } }
/* ** If there are public BLOBs that deltas from private BLOBs, then ** undeltify the public BLOBs so that the private BLOBs may be safely ** deleted. */ void fix_private_blob_dependencies(int showWarning){ Bag toUndelta; Stmt q; int rid; /* Careful: We are about to delete all BLOB entries that are private. ** So make sure that any no public BLOBs are deltas from a private BLOB. ** Otherwise after the deletion, we won't be able to recreate the public ** BLOBs. */ db_prepare(&q, "SELECT " " rid, (SELECT uuid FROM blob WHERE rid=delta.rid)," " srcid, (SELECT uuid FROM blob WHERE rid=delta.srcid)" " FROM delta" " WHERE srcid in private AND rid NOT IN private" ); bag_init(&toUndelta); while( db_step(&q)==SQLITE_ROW ){ int rid = db_column_int(&q, 0); const char *zId = db_column_text(&q, 1); int srcid = db_column_int(&q, 2); const char *zSrc = db_column_text(&q, 3); if( showWarning ){ fossil_warning( "public artifact %S (%d) is a delta from private artifact %S (%d)", zId, rid, zSrc, srcid ); } bag_insert(&toUndelta, rid); } db_finalize(&q); while( (rid = bag_first(&toUndelta))>0 ){ content_undelta(rid); bag_remove(&toUndelta, rid); } bag_clear(&toUndelta); }
/* ** COMMAND: test-clusters ** ** Verify that all non-private and non-shunned artifacts are accessible ** through the cluster chain. */ void test_clusters_cmd(void){ Bag pending; Stmt q; int n; db_find_and_open_repository(0, 2); bag_init(&pending); db_multi_exec( "CREATE TEMP TABLE xdone(x INTEGER PRIMARY KEY);" "INSERT INTO xdone SELECT rid FROM unclustered;" "INSERT OR IGNORE INTO xdone SELECT rid FROM private;" "INSERT OR IGNORE INTO xdone" " SELECT blob.rid FROM shun JOIN blob USING(uuid);" ); db_prepare(&q, "SELECT rid FROM unclustered WHERE rid IN" " (SELECT rid FROM tagxref WHERE tagid=%d)", TAG_CLUSTER ); while( db_step(&q)==SQLITE_ROW ){ bag_insert(&pending, db_column_int(&q, 0)); } db_finalize(&q); while( bag_count(&pending)>0 ){ Manifest *p; int rid = bag_first(&pending); int i; bag_remove(&pending, rid); p = manifest_get(rid, CFTYPE_CLUSTER, 0); if( p==0 ){ fossil_fatal("bad cluster: rid=%d", rid); } for(i=0; i<p->nCChild; i++){ const char *zUuid = p->azCChild[i]; int crid = name_to_rid(zUuid); if( crid==0 ){ fossil_warning("cluster (rid=%d) references unknown artifact %s", rid, zUuid); continue; } db_multi_exec("INSERT OR IGNORE INTO xdone VALUES(%d)", crid); if( db_exists("SELECT 1 FROM tagxref WHERE tagid=%d AND rid=%d", TAG_CLUSTER, crid) ){ bag_insert(&pending, crid); } } manifest_destroy(p); } n = db_int(0, "SELECT count(*) FROM /*scan*/" " (SELECT rid FROM blob EXCEPT SELECT x FROM xdone)"); if( n==0 ){ fossil_print("all artifacts reachable through clusters\n"); }else{ fossil_print("%d unreachable artifacts:\n", n); db_prepare(&q, "SELECT rid, uuid FROM blob WHERE rid NOT IN xdone"); while( db_step(&q)==SQLITE_ROW ){ fossil_print(" %3d %s\n", db_column_int(&q,0), db_column_text(&q,1)); } db_finalize(&q); } }
/* ** Create a temporary table named "leaves" if it does not ** already exist. Load this table with the RID of all ** check-ins that are leaves which are descended from ** check-in iBase. ** ** A "leaf" is a check-in that has no children in the same branch. ** There is a separate permanent table LEAF that contains all leaves ** in the tree. This routine is used to compute a subset of that ** table consisting of leaves that are descended from a single checkin. ** ** The closeMode flag determines behavior associated with the "closed" ** tag: ** ** closeMode==0 Show all leaves regardless of the "closed" tag. ** ** closeMode==1 Show only leaves without the "closed" tag. ** ** closeMode==2 Show only leaves with the "closed" tag. ** ** The default behavior is to ignore closed leaves (closeMode==0). To ** Show all leaves, use closeMode==1. To show only closed leaves, use ** closeMode==2. */ void compute_leaves(int iBase, int closeMode){ /* Create the LEAVES table if it does not already exist. Make sure ** it is empty. */ db_multi_exec( "CREATE TEMP TABLE IF NOT EXISTS leaves(" " rid INTEGER PRIMARY KEY" ");" "DELETE FROM leaves;" ); if( iBase>0 ){ Bag seen; /* Descendants seen */ Bag pending; /* Unpropagated descendants */ Stmt q1; /* Query to find children of a check-in */ Stmt isBr; /* Query to check to see if a check-in starts a new branch */ Stmt ins; /* INSERT statement for a new record */ /* Initialize the bags. */ bag_init(&seen); bag_init(&pending); bag_insert(&pending, iBase); /* This query returns all non-branch-merge children of check-in :rid. ** ** If a child is a merge of a fork within the same branch, it is ** returned. Only merge children in different branches are excluded. */ db_prepare(&q1, "SELECT cid FROM plink" " WHERE pid=:rid" " AND (isprim" " OR coalesce((SELECT value FROM tagxref" " WHERE tagid=%d AND rid=plink.pid), 'trunk')" "=coalesce((SELECT value FROM tagxref" " WHERE tagid=%d AND rid=plink.cid), 'trunk'))", TAG_BRANCH, TAG_BRANCH ); /* This query returns a single row if check-in :rid is the first ** check-in of a new branch. */ db_prepare(&isBr, "SELECT 1 FROM tagxref" " WHERE rid=:rid AND tagid=%d AND tagtype=2" " AND srcid>0", TAG_BRANCH ); /* This statement inserts check-in :rid into the LEAVES table. */ db_prepare(&ins, "INSERT OR IGNORE INTO leaves VALUES(:rid)"); while( bag_count(&pending) ){ int rid = bag_first(&pending); int cnt = 0; bag_remove(&pending, rid); db_bind_int(&q1, ":rid", rid); while( db_step(&q1)==SQLITE_ROW ){ int cid = db_column_int(&q1, 0); if( bag_insert(&seen, cid) ){ bag_insert(&pending, cid); } db_bind_int(&isBr, ":rid", cid); if( db_step(&isBr)==SQLITE_DONE ){ cnt++; } db_reset(&isBr); } db_reset(&q1); if( cnt==0 && !is_a_leaf(rid) ){ cnt++; } if( cnt==0 ){ db_bind_int(&ins, ":rid", rid); db_step(&ins); db_reset(&ins); } } db_finalize(&ins); db_finalize(&isBr); db_finalize(&q1); bag_clear(&pending); bag_clear(&seen); } if( closeMode==1 ){ db_multi_exec( "DELETE FROM leaves WHERE rid IN" " (SELECT leaves.rid FROM leaves, tagxref" " WHERE tagxref.rid=leaves.rid " " AND tagxref.tagid=%d" " AND tagxref.tagtype>0)", TAG_CLOSED ); }else if( closeMode==2 ){ db_multi_exec( "DELETE FROM leaves WHERE rid NOT IN" " (SELECT leaves.rid FROM leaves, tagxref" " WHERE tagxref.rid=leaves.rid " " AND tagxref.tagid=%d" " AND tagxref.tagtype>0)", TAG_CLOSED ); } }
/* ** There is a TEMP table bix(blobid,delta) containing a set of purgeitems ** that need to be transferred to the BLOB table. This routine does ** all items that have srcid=iSrc. The pBasis blob holds the content ** of the source document if iSrc>0. */ static void bundle_import_elements(int iSrc, Blob *pBasis, int isPriv){ Stmt q; static Bag busy; assert( pBasis!=0 || iSrc==0 ); if( iSrc>0 ){ if( bag_find(&busy, iSrc) ){ fossil_fatal("delta loop while uncompressing bundle artifacts"); } bag_insert(&busy, iSrc); } db_prepare(&q, "SELECT uuid, data, bblob.delta, bix.blobid" " FROM bix, bblob" " WHERE bix.delta=%d" " AND bix.blobid=bblob.blobid;", iSrc ); while( db_step(&q)==SQLITE_ROW ){ Blob h1, h2, c1, c2; int rid; blob_zero(&h1); db_column_blob(&q, 0, &h1); blob_zero(&c1); db_column_blob(&q, 1, &c1); blob_uncompress(&c1, &c1); blob_zero(&c2); if( db_column_type(&q,2)==SQLITE_TEXT && db_column_bytes(&q,2)==40 ){ Blob basis; rid = db_int(0,"SELECT rid FROM blob WHERE uuid=%Q", db_column_text(&q,2)); content_get(rid, &basis); blob_delta_apply(&basis, &c1, &c2); blob_reset(&basis); blob_reset(&c1); }else if( pBasis ){ blob_delta_apply(pBasis, &c1, &c2); blob_reset(&c1); }else{ c2 = c1; } sha1sum_blob(&c2, &h2); if( blob_compare(&h1, &h2)!=0 ){ fossil_fatal("SHA1 hash mismatch - wanted %s, got %s", blob_str(&h1), blob_str(&h2)); } blob_reset(&h2); rid = content_put_ex(&c2, blob_str(&h1), 0, 0, isPriv); if( rid==0 ){ fossil_fatal("%s", g.zErrMsg); }else{ if( !isPriv ) content_make_public(rid); content_get(rid, &c1); manifest_crosslink(rid, &c1, MC_NO_ERRORS); db_multi_exec("INSERT INTO got(rid) VALUES(%d)",rid); } bundle_import_elements(db_column_int(&q,3), &c2, isPriv); blob_reset(&c2); } db_finalize(&q); if( iSrc>0 ) bag_remove(&busy, iSrc); }