/* ** Do all pending leaf checks. */ void leaf_do_pending_checks(void){ int rid; for(rid=bag_first(&needToCheck); rid; rid=bag_next(&needToCheck,rid)){ leaf_check(rid); } bag_clear(&needToCheck); }
/* ** 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); }
/* ** This routine is called just prior to each commit operation. ** ** Invoke verify_rid() on every record that has been added or modified ** in the repository, in order to make sure that the repository is sane. */ static int verify_at_commit(void){ int rid; content_clear_cache(); inFinalVerify = 1; rid = bag_first(&toVerify); while( rid>0 ){ verify_rid(rid); rid = bag_next(&toVerify, rid); } bag_clear(&toVerify); inFinalVerify = 0; return 0; }
/* ** 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); }
/* ** 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); } }
/* ** Rebuild cross-referencing information for the artifact ** rid with content pBase and all of its descendants. This ** routine clears the content buffer before returning. ** ** If the zFNameFormat variable is set, then this routine is ** called to run "fossil deconstruct" instead of the usual ** "fossil rebuild". In that case, instead of rebuilding the ** cross-referencing information, write the file content out ** to the appropriate directory. ** ** In both cases, this routine automatically recurses to process ** other artifacts that are deltas off of the current artifact. ** This is the most efficient way to extract all of the original ** artifact content from the Fossil repository. */ static void rebuild_step(int rid, int size, Blob *pBase){ static Stmt q1; Bag children; Blob copy; Blob *pUse; int nChild, i, cid; while( rid>0 ){ /* Fix up the "blob.size" field if needed. */ if( size!=blob_size(pBase) ){ db_multi_exec( "UPDATE blob SET size=%d WHERE rid=%d", blob_size(pBase), rid ); } /* Find all children of artifact rid */ db_static_prepare(&q1, "SELECT rid FROM delta WHERE srcid=:rid"); db_bind_int(&q1, ":rid", rid); bag_init(&children); while( db_step(&q1)==SQLITE_ROW ){ int cid = db_column_int(&q1, 0); if( !bag_find(&bagDone, cid) ){ bag_insert(&children, cid); } } nChild = bag_count(&children); db_reset(&q1); /* Crosslink the artifact */ if( nChild==0 ){ pUse = pBase; }else{ blob_copy(©, pBase); pUse = © } if( zFNameFormat==0 ){ /* We are doing "fossil rebuild" */ manifest_crosslink(rid, pUse, MC_NONE); }else{ /* We are doing "fossil deconstruct" */ char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); char *zFile = mprintf(zFNameFormat, zUuid, zUuid+prefixLength); blob_write_to_file(pUse,zFile); free(zFile); free(zUuid); blob_reset(pUse); } assert( blob_is_reset(pUse) ); rebuild_step_done(rid); /* Call all children recursively */ rid = 0; for(cid=bag_first(&children), i=1; cid; cid=bag_next(&children, cid), i++){ static Stmt q2; int sz; db_static_prepare(&q2, "SELECT content, size FROM blob WHERE rid=:rid"); db_bind_int(&q2, ":rid", cid); if( db_step(&q2)==SQLITE_ROW && (sz = db_column_int(&q2,1))>=0 ){ Blob delta, next; db_ephemeral_blob(&q2, 0, &delta); blob_uncompress(&delta, &delta); blob_delta_apply(pBase, &delta, &next); blob_reset(&delta); db_reset(&q2); if( i<nChild ){ rebuild_step(cid, sz, &next); }else{ /* Tail recursion */ rid = cid; size = sz; blob_reset(pBase); *pBase = next; } }else{ db_reset(&q2); blob_reset(pBase); } } bag_clear(&children); } }
/* ** 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 ); } }
/* ** COMMAND: all ** ** Usage: %fossil all (list|ls|pull|push|rebuild|sync) ** ** The ~/.fossil file records the location of all repositories for a ** user. This command performs certain operations on all repositories ** that can be useful before or after a period of disconnected operation. ** ** On Win32 systems, the file is named "_fossil" and is located in ** %LOCALAPPDATA%, %APPDATA% or %HOMEPATH%. ** ** Available operations are: ** ** ignore Arguments are repositories that should be ignored ** by subsequent list, pull, push, rebuild, and sync. ** ** list | ls Display the location of all repositories. ** The --ckout option causes all local checkouts to be ** list instead. ** ** changes Shows all local checkouts that have uncommitted changes ** ** pull Run a "pull" operation on all repositories ** ** push Run a "push" on all repositories ** ** rebuild Rebuild on all repositories ** ** sync Run a "sync" on all repositories ** ** Repositories are automatically added to the set of known repositories ** when one of the following commands are run against the repository: clone, ** info, pull, push, or sync. Even previously ignored repositories are ** added back to the list of repositories by these commands. */ void all_cmd(void){ int n; Stmt q; const char *zCmd; char *zSyscmd; char *zFossil; char *zQFilename; int useCheckouts = 0; int quiet = 0; int testRun = 0; int stopOnError = find_option("dontstop",0,0)==0; int rc; Bag outOfDate; /* The undocumented --test option causes no changes to occur to any ** repository, but instead show what would have happened. Intended for ** test and debugging use. */ testRun = find_option("test",0,0)!=0; if( g.argc<3 ){ usage("changes|list|ls|pull|push|rebuild|sync"); } n = strlen(g.argv[2]); db_open_config(1); zCmd = g.argv[2]; if( strncmp(zCmd, "list", n)==0 || strncmp(zCmd,"ls",n)==0 ){ zCmd = "list"; useCheckouts = find_option("ckout","c",0)!=0; }else if( strncmp(zCmd, "push", n)==0 ){ zCmd = "push -autourl -R"; }else if( strncmp(zCmd, "pull", n)==0 ){ zCmd = "pull -autourl -R"; }else if( strncmp(zCmd, "rebuild", n)==0 ){ zCmd = "rebuild"; }else if( strncmp(zCmd, "sync", n)==0 ){ zCmd = "sync -autourl -R"; }else if( strncmp(zCmd, "test-integrity", n)==0 ){ zCmd = "test-integrity"; }else if( strncmp(zCmd, "changes", n)==0 ){ zCmd = "changes --quiet --header --chdir"; useCheckouts = 1; stopOnError = 0; quiet = 1; }else if( strncmp(zCmd, "ignore", n)==0 ){ int j; verify_all_options(); db_begin_transaction(); for(j=3; j<g.argc; j++){ char *zSql = mprintf("DELETE FROM global_config" " WHERE name GLOB 'repo:%q'", g.argv[j]); if( testRun ){ fossil_print("%s\n", zSql); }else{ db_multi_exec("%s", zSql); } fossil_free(zSql); } db_end_transaction(0); return; }else{ fossil_fatal("\"all\" subcommand should be one of: " "changes ignore list ls push pull rebuild sync"); } verify_all_options(); zFossil = quoteFilename(g.nameOfExe); if( useCheckouts ){ db_prepare(&q, "SELECT substr(name, 7) COLLATE nocase, max(rowid)" " FROM global_config" " WHERE substr(name, 1, 6)=='ckout:'" " GROUP BY 1 ORDER BY 1" ); }else{ db_prepare(&q, "SELECT substr(name, 6) COLLATE nocase, max(rowid)" " FROM global_config" " WHERE substr(name, 1, 5)=='repo:'" " GROUP BY 1 ORDER BY 1" ); } bag_init(&outOfDate); while( db_step(&q)==SQLITE_ROW ){ const char *zFilename = db_column_text(&q, 0); int rowid = db_column_int(&q, 1); if( file_access(zFilename, 0) || !file_is_canonical(zFilename) ){ bag_insert(&outOfDate, rowid); continue; } if( useCheckouts && file_isdir(zFilename)!=1 ){ bag_insert(&outOfDate, rowid); continue; } if( zCmd[0]=='l' ){ fossil_print("%s\n", zFilename); continue; } zQFilename = quoteFilename(zFilename); zSyscmd = mprintf("%s %s %s", zFossil, zCmd, zQFilename); if( !quiet || testRun ){ fossil_print("%s\n", zSyscmd); fflush(stdout); } rc = testRun ? 0 : fossil_system(zSyscmd); free(zSyscmd); free(zQFilename); if( stopOnError && rc ){ break; } } db_finalize(&q); /* If any repositories whose names appear in the ~/.fossil file could not ** be found, remove those names from the ~/.fossil file. */ if( bag_count(&outOfDate)>0 ){ Blob sql; char *zSep = "("; int rowid; blob_zero(&sql); blob_appendf(&sql, "DELETE FROM global_config WHERE rowid IN "); for(rowid=bag_first(&outOfDate); rowid>0; rowid=bag_next(&outOfDate,rowid)){ blob_appendf(&sql, "%s%d", zSep, rowid); zSep = ","; } blob_appendf(&sql, ")"); if( testRun ){ fossil_print("%s\n", blob_str(&sql)); }else{ db_multi_exec(blob_str(&sql)); } blob_reset(&sql); } }