/* ** Output the differences between two check-ins. */ static void diff_all_two_versions( const char *zFrom, const char *zTo, const char *zDiffCmd, int ignoreEolWs ){ Manifest mFrom, mTo; int iFrom, iTo; manifest_from_name(zFrom, &mFrom); manifest_from_name(zTo, &mTo); iFrom = iTo = 0; while( iFrom<mFrom.nFile && iTo<mTo.nFile ){ int cmp; if( iFrom>=mFrom.nFile ){ cmp = +1; }else if( iTo>=mTo.nFile ){ cmp = -1; }else{ cmp = strcmp(mFrom.aFile[iFrom].zName, mTo.aFile[iTo].zName); } if( cmp<0 ){ printf("DELETED %s\n", mFrom.aFile[iFrom].zName); iFrom++; }else if( cmp>0 ){ printf("ADDED %s\n", mTo.aFile[iTo].zName); iTo++; }else if( strcmp(mFrom.aFile[iFrom].zUuid, mTo.aFile[iTo].zUuid)==0 ){ /* No changes */ iFrom++; iTo++; }else{ Blob f1, f2; int rid; printf("CHANGED %s\n", mFrom.aFile[iFrom].zName); printf("Index: %s\n=======================================" "============================\n", mFrom.aFile[iFrom].zName ); rid = uuid_to_rid(mFrom.aFile[iFrom].zUuid, 0); content_get(rid, &f1); rid = uuid_to_rid(mTo.aFile[iTo].zUuid, 0); content_get(rid, &f2); diff_file_mem(&f1, &f2, mFrom.aFile[iFrom].zName, zDiffCmd, ignoreEolWs); blob_reset(&f1); blob_reset(&f2); iFrom++; iTo++; } } manifest_clear(&mFrom); manifest_clear(&mTo); }
/* ** Change the storage of rid so that it is a delta of srcid. ** ** If rid is already a delta from some other place then no ** conversion occurs and this is a no-op unless force==1. ** ** Never generate a delta that carries a private artifact into a public ** artifact. Otherwise, when we go to send the public artifact on a ** sync operation, the other end of the sync will never be able to receive ** the source of the delta. It is OK to delta private->private and ** public->private and public->public. Just no private->public delta. ** ** If srcid is a delta that depends on rid, then srcid is ** converted to undeltaed text. ** ** If either rid or srcid contain less than 50 bytes, or if the ** resulting delta does not achieve a compression of at least 25% ** the rid is left untouched. ** ** Return 1 if a delta is made and 0 if no delta occurs. */ int content_deltify(int rid, int srcid, int force){ int s; Blob data, src, delta; Stmt s1, s2; int rc = 0; if( srcid==rid ) return 0; if( !force && findSrcid(rid)>0 ) return 0; if( content_is_private(srcid) && !content_is_private(rid) ){ return 0; } s = srcid; while( (s = findSrcid(s))>0 ){ if( s==rid ){ content_undelta(srcid); break; } } content_get(srcid, &src); if( blob_size(&src)<50 ){ blob_reset(&src); return 0; } content_get(rid, &data); if( blob_size(&data)<50 ){ blob_reset(&src); blob_reset(&data); return 0; } blob_delta_create(&src, &data, &delta); if( blob_size(&delta) <= blob_size(&data)*0.75 ){ blob_compress(&delta, &delta); db_prepare(&s1, "UPDATE blob SET content=:data WHERE rid=%d", rid); db_prepare(&s2, "REPLACE INTO delta(rid,srcid)VALUES(%d,%d)", rid, srcid); db_bind_blob(&s1, ":data", &delta); db_begin_transaction(); db_exec(&s1); db_exec(&s2); db_end_transaction(0); db_finalize(&s1); db_finalize(&s2); verify_before_commit(rid); rc = 1; } blob_reset(&src); blob_reset(&data); blob_reset(&delta); return rc; }
/* ** Compute an aggregate MD5 checksum over the repository image of every ** file in vid. The file names are part of the checksum. The resulting ** checksum is suitable for the R-card of a manifest. ** ** Return the resulting checksum in blob pOut. */ void vfile_aggregate_checksum_repository(int vid, Blob *pOut){ Blob file; Stmt q; char zBuf[100]; db_must_be_within_tree(); db_prepare(&q, "SELECT pathname, origname, rid, file_is_selected(id)" " FROM vfile" " WHERE (NOT deleted OR NOT file_is_selected(id))" " AND rid>0 AND vid=%d" " ORDER BY pathname /*scan*/", vid); blob_zero(&file); md5sum_init(); while( db_step(&q)==SQLITE_ROW ){ const char *zName = db_column_text(&q, 0); const char *zOrigName = db_column_text(&q, 1); int rid = db_column_int(&q, 2); int isSelected = db_column_int(&q, 3); if( zOrigName && !isSelected ) zName = zOrigName; md5sum_step_text(zName, -1); content_get(rid, &file); sqlite3_snprintf(sizeof(zBuf), zBuf, " %d\n", blob_size(&file)); md5sum_step_text(zBuf, -1); /*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/ md5sum_step_blob(&file); blob_reset(&file); } db_finalize(&q); md5sum_finish(pOut); }
ssize_t handler_get(gfcontext_t *ctx, char *path, void* arg){ int fildes; ssize_t file_len, bytes_transferred; ssize_t read_len, write_len; char buffer[BUFFER_SIZE]; if( 0 > (fildes = content_get(path))) return gfs_sendheader(ctx, GF_FILE_NOT_FOUND, 0); /* Calculating the file size */ file_len = lseek(fildes, 0, SEEK_END); gfs_sendheader(ctx, GF_OK, file_len); /* Sending the file contents chunk by chunk. */ bytes_transferred = 0; while(bytes_transferred < file_len){ read_len = pread(fildes, buffer, BUFFER_SIZE, bytes_transferred); if (read_len <= 0){ fprintf(stderr, "handle_with_file read error, %zd, %zu, %zu", read_len, bytes_transferred, file_len ); gfs_abort(ctx); return -1; } write_len = gfs_send(ctx, buffer, read_len); if (write_len != read_len){ fprintf(stderr, "handle_with_file write error"); gfs_abort(ctx); return -1; } bytes_transferred += write_len; } return bytes_transferred; }
/* * Process a client request on a newly opened file descriptor. */ void client_process(int fd) { struct http_req *r; char *response; int len; /* * This code will be used to get the request and respond to * it. This should probably be in the worker * threads/processes. */ r = newfd_create_req(fd); if (!r || !r->path) { close(fd); return; } assert(r); assert(r->path); response = content_get(r->path, &len); if (!response) { shttp_free_req(r); return; } respond_and_free_req(r, response, len); }
/* ** Do a file-by-file comparison of the content of the repository and ** the working check-out on disk. Report any errors. */ void vfile_compare_repository_to_disk(int vid){ int rc; Stmt q; Blob disk, repo; char *zOut; db_must_be_within_tree(); db_prepare(&q, "SELECT %Q || pathname, pathname, rid FROM vfile" " WHERE NOT deleted AND vid=%d AND is_selected(id)" " ORDER BY if_selected(id, pathname, origname) /*scan*/", g.zLocalRoot, vid ); md5sum_init(); while( db_step(&q)==SQLITE_ROW ){ const char *zFullpath = db_column_text(&q, 0); const char *zName = db_column_text(&q, 1); int rid = db_column_int(&q, 2); blob_zero(&disk); if( file_wd_islink(zFullpath) ){ rc = blob_read_link(&disk, zFullpath); }else{ rc = blob_read_from_file(&disk, zFullpath); } if( rc<0 ){ fossil_print("ERROR: cannot read file [%s]\n", zFullpath); blob_reset(&disk); continue; } blob_zero(&repo); content_get(rid, &repo); if( blob_size(&repo)!=blob_size(&disk) ){ fossil_print("ERROR: [%s] is %d bytes on disk but %d in the repository\n", zName, blob_size(&disk), blob_size(&repo)); zOut = write_blob_to_temp_file(&repo); fossil_print("NOTICE: Repository version of [%s] stored in [%s]\n", zName, zOut); sqlite3_free(zOut); blob_reset(&disk); blob_reset(&repo); continue; } if( blob_compare(&repo, &disk) ){ fossil_print( "ERROR: [%s] is different on disk compared to the repository\n", zName); zOut = write_blob_to_temp_file(&repo); fossil_print("NOTICE: Repository version of [%s] stored in [%s]\n", zName, zOut); sqlite3_free(zOut); } blob_reset(&disk); blob_reset(&repo); } db_finalize(&q); }
/* ** COMMAND: test-missing ** ** Usage: %fossil test-missing ** ** Look at every artifact in the repository and verify that ** all references are satisfied. Report any referenced artifacts ** that are missing or shunned. ** ** Options: ** ** --notshunned Do not report shunned artifacts ** --quiet Only show output if there are errors */ void test_missing(void){ Stmt q; Blob content; int nErr = 0; int nArtifact = 0; int i; Manifest *p; unsigned flags = 0; int quietFlag; if( find_option("notshunned", 0, 0)!=0 ) flags |= MISSING_SHUNNED; quietFlag = find_option("quiet","q",0)!=0; db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); db_prepare(&q, "SELECT mid FROM mlink UNION " "SELECT srcid FROM tagxref WHERE srcid>0 UNION " "SELECT rid FROM tagxref UNION " "SELECT rid FROM attachment JOIN blob ON src=uuid UNION " "SELECT objid FROM event"); while( db_step(&q)==SQLITE_ROW ){ int rid = db_column_int(&q, 0); content_get(rid, &content); p = manifest_parse(&content, rid, 0); if( p ){ nArtifact++; nErr += check_exists(p->zBaseline, flags, p, "baseline of", 0); nErr += check_exists(p->zAttachSrc, flags, p, "file of", 0); for(i=0; i<p->nFile; i++){ nErr += check_exists(p->aFile[i].zUuid, flags, p, "file of", p->aFile[i].zName); } for(i=0; i<p->nParent; i++){ nErr += check_exists(p->azParent[i], flags, p, "parent of", 0); } for(i=0; i<p->nCherrypick; i++){ nErr += check_exists(p->aCherrypick[i].zCPTarget+1, flags, p, "cherry-pick target of", 0); nErr += check_exists(p->aCherrypick[i].zCPBase, flags, p, "cherry-pick baseline of", 0); } for(i=0; i<p->nCChild; i++){ nErr += check_exists(p->azCChild[i], flags, p, "in", 0); } for(i=0; i<p->nTag; i++){ nErr += check_exists(p->aTag[i].zUuid, flags, p, "target of", 0); } manifest_destroy(p); } } db_finalize(&q); if( nErr>0 || quietFlag==0 ){ fossil_print("%d missing or shunned references in %d control artifacts\n", nErr, nArtifact); } }
/* ** WEBPAGE: dir ** ** Query parameters: ** ** name=PATH Directory to display. Required. ** ci=LABEL Show only files in this check-in. Optional. */ void page_dir(void){ const char *zD = P("name"); int mxLen; int nCol, nRow; int cnt, i; char *zPrefix; Stmt q; const char *zCI = P("ci"); int rid = 0; Blob content; Blob dirname; Manifest m; const char *zSubdirLink; login_check_credentials(); if( !g.okHistory ){ login_needed(); return; } style_header("File List"); sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, pathelementFunc, 0, 0); /* If the name= parameter is an empty string, make it a NULL pointer */ if( zD && strlen(zD)==0 ){ zD = 0; } /* If a specific check-in is requested, fetch and parse it. */ if( zCI && (rid = name_to_rid(zCI))!=0 && content_get(rid, &content) ){ if( !manifest_parse(&m, &content) || m.type!=CFTYPE_MANIFEST ){ zCI = 0; } } /* Compute the title of the page */ blob_zero(&dirname); if( zD ){ blob_append(&dirname, "in directory ", -1); hyperlinked_path(zD, &dirname); zPrefix = mprintf("%h/", zD); }else{ blob_append(&dirname, "in the top-level directory", -1); zPrefix = ""; } if( zCI ){ char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); char zShort[20]; memcpy(zShort, zUuid, 10); zShort[10] = 0; @ <h2>Files of check-in [<a href="vinfo?name=%T(zUuid)">%s(zShort)</a>] @ %s(blob_str(&dirname))</h2> zSubdirLink = mprintf("%s/dir?ci=%S&name=%T", g.zTop, zUuid, zPrefix); if( zD ){ style_submenu_element("Top", "Top", "%s/dir?ci=%S", g.zTop, zUuid); style_submenu_element("All", "All", "%s/dir?name=%t", g.zTop, zD); }else{ style_submenu_element("All", "All", "%s/dir", g.zBaseURL); } }else{
/* determine whether a file block is mapped */ int fblock_stored(const char *name, int fblock) { Content *f; if (strlen(name)>255) { flog("name %s too long",name); return FALSE; } f = content_get(name); if (!f) return FALSE; Map *b = map_fblock_stored(f->bhead,fblock); return b!=0; }
/* ** Get the contents of a file within the check-in "revision". If ** revision==NULL then get the file content for the current checkout. */ int historical_version_of_file( const char *revision, /* The check-in containing the file */ const char *file, /* Full treename of the file */ Blob *content, /* Put the content here */ int *pIsLink, /* Set to true if file is link. */ int *pIsExe, /* Set to true if file is executable */ int *pIsBin, /* Set to true if file is binary */ int errCode /* Error code if file not found. Panic if <= 0. */ ) { Manifest *pManifest; ManifestFile *pFile; int rid=0; if( revision ) { rid = name_to_typed_rid(revision,"ci"); } else if( !g.localOpen ) { rid = name_to_typed_rid(db_get("main-branch","trunk"),"ci"); } else { rid = db_lget_int("checkout", 0); } if( !is_a_version(rid) ) { if( errCode>0 ) return errCode; fossil_fatal("no such check-in: %s", revision); } pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0); if( pManifest ) { pFile = manifest_file_find(pManifest, file); if( pFile ) { int rc; rid = uuid_to_rid(pFile->zUuid, 0); if( pIsExe ) *pIsExe = ( manifest_file_mperm(pFile)==PERM_EXE ); if( pIsLink ) *pIsLink = ( manifest_file_mperm(pFile)==PERM_LNK ); manifest_destroy(pManifest); rc = content_get(rid, content); if( rc && pIsBin ) { *pIsBin = looks_like_binary(content); } return rc; } manifest_destroy(pManifest); if( errCode<=0 ) { fossil_fatal("file %s does not exist in check-in: %s", file, revision); } } else if( errCode<=0 ) { if( revision==0 ) { revision = db_text("current", "SELECT uuid FROM blob WHERE rid=%d", rid); } fossil_fatal("could not parse manifest for check-in: %s", revision); } return errCode; }
/* forget all records for a file */ int forget_file(const char *name) { Content *f; if (strlen(name)>255) { flog("name %s too long",name); return FALSE; } f = content_get(name); if (f==NULL) { flog("no file '%s' to forget",name); return FALSE; } clist_forget(f); return TRUE; }
/* ** COMMAND: artifact* ** ** Usage: %fossil artifact ARTIFACT-ID ?OUTPUT-FILENAME? ?OPTIONS? ** ** Extract an artifact by its SHA1 hash and write the results on ** standard output, or if the optional 4th argument is given, in ** the named output file. ** ** Options: ** -R|--repository FILE Extract artifacts from repository FILE ** ** See also: finfo */ void artifact_cmd(void){ int rid; Blob content; const char *zFile; db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); if( g.argc!=4 && g.argc!=3 ) usage("ARTIFACT-ID ?FILENAME? ?OPTIONS?"); zFile = g.argc==4 ? g.argv[3] : "-"; rid = name_to_rid(g.argv[2]); if( rid==0 ){ fossil_fatal("%s",g.zErrMsg); } content_get(rid, &content); blob_write_to_file(&content, zFile); }
/* ** Generates a diff between two versions (zFrom and zTo), using nContext ** content lines in the output. On success, returns a new JSON String ** object. On error it sets g.json's error state and returns NULL. ** ** If fSbs is true (non-0) them side-by-side diffs are used. ** ** If fHtml is true then HTML markup is added to the diff. */ cson_value * json_generate_diff(const char *zFrom, const char *zTo, int nContext, char fSbs, char fHtml){ int fromid; int toid; int outLen; Blob from = empty_blob, to = empty_blob, out = empty_blob; cson_value * rc = NULL; int flags = (DIFF_CONTEXT_MASK & nContext) | (fSbs ? DIFF_SIDEBYSIDE : 0) | (fHtml ? DIFF_HTML : 0); fromid = name_to_typed_rid(zFrom, "*"); if(fromid<=0){ json_set_err(FSL_JSON_E_UNRESOLVED_UUID, "Could not resolve 'from' ID."); return NULL; } toid = name_to_typed_rid(zTo, "*"); if(toid<=0){ json_set_err(FSL_JSON_E_UNRESOLVED_UUID, "Could not resolve 'to' ID."); return NULL; } content_get(fromid, &from); content_get(toid, &to); blob_zero(&out); text_diff(&from, &to, &out, 0, flags); blob_reset(&from); blob_reset(&to); outLen = blob_size(&out); if(outLen>=0){ rc = cson_value_new_string(blob_buffer(&out), (unsigned int)blob_size(&out)); } blob_reset(&out); return rc; }
/* ** 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); }
/* ** Make sure the content at rid is the original content and is not a ** delta. */ void content_undelta(int rid){ if( findSrcid(rid)>0 ){ Blob x; if( content_get(rid, &x) ){ Stmt s; db_prepare(&s, "UPDATE blob SET content=:c, size=%d WHERE rid=%d", blob_size(&x), rid); blob_compress(&x, &x); db_bind_blob(&s, ":c", &x); db_exec(&s); db_finalize(&s); blob_reset(&x); db_multi_exec("DELETE FROM delta WHERE rid=%d", rid); } } }
/* forget one file block */ int forget_fblock(const char *name, int fblock) { Content *f; Map *b; if (strlen(name)>255) { flog("name %s too long",name); return FALSE; } f = content_get(name); if (f==NULL) { flog("no blocks for name %s",name); return FALSE; } if ((b=map_fblock_stored(f->bhead,fblock))==NULL) { flog("block %d of name %s not stored",name,fblock); return FALSE; } map_forget_fblock(&f->bhead,fblock); return TRUE; }
/* ** Implementation of the "content(X)" SQL function. Return the complete ** content of artifact identified by X as a blob. */ static void sqlcmd_content( sqlite3_context *context, int argc, sqlite3_value **argv ){ int rid; Blob cx; const char *zName; assert( argc==1 ); zName = (const char*)sqlite3_value_text(argv[0]); if( zName==0 ) return; g.db = sqlite3_context_db_handle(context); g.repositoryOpen = 1; rid = name_to_rid(zName); if( rid==0 ) return; if( content_get(rid, &cx) ){ sqlite3_result_blob(context, blob_buffer(&cx), blob_size(&cx), SQLITE_TRANSIENT); blob_reset(&cx); } }
/* ** Load the record identify by rid. Make sure we can reproduce it ** without error. ** ** Panic if anything goes wrong. If this procedure returns it means ** that everything is OK. */ static void verify_rid(int rid){ Blob uuid, hash, content; if( content_size(rid, 0)<0 ){ return; /* No way to verify phantoms */ } blob_zero(&uuid); db_blob(&uuid, "SELECT uuid FROM blob WHERE rid=%d", rid); if( blob_size(&uuid)!=UUID_SIZE ){ fossil_fatal("not a valid rid: %d", rid); } if( content_get(rid, &content) ){ sha1sum_blob(&content, &hash); blob_reset(&content); if( blob_compare(&uuid, &hash) ){ fossil_fatal("hash of rid %d (%b) does not match its uuid (%b)", rid, &hash, &uuid); } blob_reset(&hash); } blob_reset(&uuid); }
/* ** Compute an aggregate MD5 checksum over the repository image of every ** file in manifest vid. The file names are part of the checksum. The ** resulting checksum is suitable for use as the R-card of a manifest. ** ** Return the resulting checksum in blob pOut. ** ** If pManOut is not NULL then fill it with the checksum found in the ** "R" card near the end of the manifest. ** ** In a well-formed manifest, the two checksums computed here, pOut and ** pManOut, should be identical. */ void vfile_aggregate_checksum_manifest(int vid, Blob *pOut, Blob *pManOut){ int fid; Blob file; Blob err; Manifest *pManifest; ManifestFile *pFile; char zBuf[100]; blob_zero(pOut); blob_zero(&err); if( pManOut ){ blob_zero(pManOut); } db_must_be_within_tree(); pManifest = manifest_get(vid, CFTYPE_MANIFEST, &err); if( pManifest==0 ){ fossil_fatal("manifest file (%d) is malformed:\n%s\n", vid, blob_str(&err)); } manifest_file_rewind(pManifest); while( (pFile = manifest_file_next(pManifest,0))!=0 ){ if( pFile->zUuid==0 ) continue; fid = uuid_to_rid(pFile->zUuid, 0); md5sum_step_text(pFile->zName, -1); content_get(fid, &file); sqlite3_snprintf(sizeof(zBuf), zBuf, " %d\n", blob_size(&file)); md5sum_step_text(zBuf, -1); md5sum_step_blob(&file); blob_reset(&file); } if( pManOut ){ if( pManifest->zRepoCksum ){ blob_append(pManOut, pManifest->zRepoCksum, -1); }else{ blob_zero(pManOut); } } manifest_destroy(pManifest); md5sum_finish(pOut); }
/* ** Rebuild an entire entry in the TICKET table */ void ticket_rebuild_entry(const char *zTktUuid){ char *zTag = mprintf("tkt-%s", zTktUuid); int tagid = tag_findid(zTag, 1); Stmt q; Manifest manifest; Blob content; int createFlag = 1; db_multi_exec( "DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid ); db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid); while( db_step(&q)==SQLITE_ROW ){ int rid = db_column_int(&q, 0); content_get(rid, &content); manifest_parse(&manifest, &content); ticket_insert(&manifest, createFlag, rid); manifest_ticket_event(rid, &manifest, createFlag, tagid); manifest_clear(&manifest); createFlag = 0; } db_finalize(&q); }
/* read file block from cache */ int get_fblock(const char *name, int fblock, char *block) { Content *f = content_get(name); if (strlen(name)>255) { flog("name %s too long",name); return FALSE; } if (f<0) { flog("no blocks for name %s",name); return FALSE; } if (!block) { flog("can't write to NULL pointer"); return FALSE; } Map *b = content_fblock_stored(f,fblock); if (b==NULL) { flog("block %d of %s not stored",fblock,name); return FALSE; } storage_get(b->cblock,block); flog("retrieved block %d of %s (cblock %d)",fblock,name,b->cblock); return TRUE; }
/* ** COMMAND: test-integrity ?OPTIONS? ** ** Verify that all content can be extracted from the BLOB table correctly. ** If the BLOB table is correct, then the repository can always be ** successfully reconstructed using "fossil rebuild". ** ** Options: ** ** --parse Parse all manifests, wikis, tickets, events, and ** so forth, reporting any errors found. */ void test_integrity(void){ Stmt q; Blob content; Blob cksum; int n1 = 0; int n2 = 0; int nErr = 0; int total; int nCA = 0; int anCA[10]; int bParse = find_option("parse",0,0)!=0; db_find_and_open_repository(OPEN_ANY_SCHEMA, 2); memset(anCA, 0, sizeof(anCA)); /* Make sure no public artifact is a delta from a private artifact */ 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" ); 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); fossil_print( "public artifact %S (%d) is a delta from private artifact %S (%d)\n", zId, rid, zSrc, srcid ); nErr++; } db_finalize(&q); db_prepare(&q, "SELECT rid, uuid, size FROM blob ORDER BY rid"); total = db_int(0, "SELECT max(rid) FROM blob"); while( db_step(&q)==SQLITE_ROW ){ int rid = db_column_int(&q, 0); const char *zUuid = db_column_text(&q, 1); int size = db_column_int(&q, 2); n1++; fossil_print(" %d/%d\r", n1, total); fflush(stdout); if( size<0 ){ fossil_print("skip phantom %d %s\n", rid, zUuid); continue; /* Ignore phantoms */ } content_get(rid, &content); if( blob_size(&content)!=size ){ fossil_print("size mismatch on artifact %d: wanted %d but got %d\n", rid, size, blob_size(&content)); nErr++; } sha1sum_blob(&content, &cksum); if( fossil_strcmp(blob_str(&cksum), zUuid)!=0 ){ fossil_print("checksum mismatch on artifact %d: wanted %s but got %s\n", rid, zUuid, blob_str(&cksum)); nErr++; } if( bParse && looks_like_control_artifact(&content) ){ Blob err; int i, n; char *z; Manifest *p; char zFirstLine[400]; blob_zero(&err); z = blob_buffer(&content); n = blob_size(&content); for(i=0; i<n && z[i] && z[i]!='\n' && i<sizeof(zFirstLine)-1; i++){} memcpy(zFirstLine, z, i); zFirstLine[i] = 0; p = manifest_parse(&content, 0, &err); if( p==0 ){ fossil_print("manifest_parse failed for %s:\n%s\n", blob_str(&cksum), blob_str(&err)); if( strncmp(blob_str(&err), "line 1:", 7)==0 ){ fossil_print("\"%s\"\n", zFirstLine); } }else{ anCA[p->type]++; manifest_destroy(p); nCA++; } blob_reset(&err); }else{ blob_reset(&content); } blob_reset(&cksum); n2++; } db_finalize(&q); fossil_print("%d non-phantom blobs (out of %d total) checked: %d errors\n", n2, n1, nErr); if( bParse ){ const char *azType[] = { 0, "manifest", "cluster", "control", "wiki", "ticket", "attachment", "event" }; int i; fossil_print("%d total control artifacts\n", nCA); for(i=1; i<count(azType); i++){ if( anCA[i] ) fossil_print(" %d %ss\n", anCA[i], azType[i]); } } }
/* ** COMMAND: merge ** ** Usage: %fossil merge ?OPTIONS? ?VERSION? ** ** The argument VERSION is a version that should be merged into the ** current checkout. All changes from VERSION back to the nearest ** common ancestor are merged. Except, if either of the --cherrypick or ** --backout options are used only the changes associated with the ** single check-in VERSION are merged. The --backout option causes ** the changes associated with VERSION to be removed from the current ** checkout rather than added. ** ** If the VERSION argument is omitted, then Fossil attempts to find ** a recent fork on the current branch to merge. ** ** Only file content is merged. The result continues to use the ** file and directory names from the current checkout even if those ** names might have been changed in the branch being merged in. ** ** Other options: ** ** --baseline BASELINE Use BASELINE as the "pivot" of the merge instead ** of the nearest common ancestor. This allows ** a sequence of changes in a branch to be merged ** without having to merge the entire branch. ** ** --binary GLOBPATTERN Treat files that match GLOBPATTERN as binary ** and do not try to merge parallel changes. This ** option overrides the "binary-glob" setting. ** ** --case-sensitive BOOL Override the case-sensitive setting. If false, ** files whose names differ only in case are taken ** to be the same file. ** ** -f|--force Force the merge even if it would be a no-op. ** ** --force-missing Force the merge even if there is missing content. ** ** --integrate Merged branch will be closed when committing. ** ** -n|--dry-run If given, display instead of run actions ** ** -v|--verbose Show additional details of the merge */ void merge_cmd(void){ int vid; /* Current version "V" */ int mid; /* Version we are merging from "M" */ int pid; /* The pivot version - most recent common ancestor P */ int verboseFlag; /* True if the -v|--verbose option is present */ int integrateFlag; /* True if the --integrate option is present */ int pickFlag; /* True if the --cherrypick option is present */ int backoutFlag; /* True if the --backout option is present */ int dryRunFlag; /* True if the --dry-run or -n option is present */ int forceFlag; /* True if the --force or -f option is present */ int forceMissingFlag; /* True if the --force-missing option is present */ const char *zBinGlob; /* The value of --binary */ const char *zPivot; /* The value of --baseline */ int debugFlag; /* True if --debug is present */ int nChng; /* Number of file name changes */ int *aChng; /* An array of file name changes */ int i; /* Loop counter */ int nConflict = 0; /* Number of conflicts seen */ int nOverwrite = 0; /* Number of unmanaged files overwritten */ Stmt q; /* Notation: ** ** V The current checkout ** M The version being merged in ** P The "pivot" - the most recent common ancestor of V and M. */ undo_capture_command_line(); verboseFlag = find_option("verbose","v",0)!=0; forceMissingFlag = find_option("force-missing",0,0)!=0; if( !verboseFlag ){ verboseFlag = find_option("detail",0,0)!=0; /* deprecated */ } pickFlag = find_option("cherrypick",0,0)!=0; integrateFlag = find_option("integrate",0,0)!=0; backoutFlag = find_option("backout",0,0)!=0; debugFlag = find_option("debug",0,0)!=0; zBinGlob = find_option("binary",0,1); dryRunFlag = find_option("dry-run","n",0)!=0; if( !dryRunFlag ){ dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */ } forceFlag = find_option("force","f",0)!=0; zPivot = find_option("baseline",0,1); verify_all_options(); db_must_be_within_tree(); if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0); vid = db_lget_int("checkout", 0); if( vid==0 ){ fossil_fatal("nothing is checked out"); } /* Find mid, the artifactID of the version to be merged into the current ** check-out */ if( g.argc==3 ){ /* Mid is specified as an argument on the command-line */ mid = name_to_typed_rid(g.argv[2], "ci"); if( mid==0 || !is_a_version(mid) ){ fossil_fatal("not a version: %s", g.argv[2]); } }else if( g.argc==2 ){ /* No version specified on the command-line so pick the most recent ** leaf that is (1) not the version currently checked out and (2) ** has not already been merged into the current checkout and (3) ** the leaf is not closed and (4) the leaf is in the same branch ** as the current checkout. */ Stmt q; if( pickFlag || backoutFlag || integrateFlag){ fossil_fatal("cannot use --backout, --cherrypick or --integrate with a fork merge"); } mid = db_int(0, "SELECT leaf.rid" " FROM leaf, event" " WHERE leaf.rid=event.objid" " AND leaf.rid!=%d" /* Constraint (1) */ " AND leaf.rid NOT IN (SELECT merge FROM vmerge)" /* Constraint (2) */ " AND NOT EXISTS(SELECT 1 FROM tagxref" /* Constraint (3) */ " WHERE rid=leaf.rid" " AND tagid=%d" " AND tagtype>0)" " AND (SELECT value FROM tagxref" /* Constraint (4) */ " WHERE tagid=%d AND rid=%d AND tagtype>0) =" " (SELECT value FROM tagxref" " WHERE tagid=%d AND rid=leaf.rid AND tagtype>0)" " ORDER BY event.mtime DESC LIMIT 1", vid, TAG_CLOSED, TAG_BRANCH, vid, TAG_BRANCH ); if( mid==0 ){ fossil_fatal("no unmerged forks of branch \"%s\"", db_text(0, "SELECT value FROM tagxref" " WHERE tagid=%d AND rid=%d AND tagtype>0", TAG_BRANCH, vid) ); } db_prepare(&q, "SELECT blob.uuid," " datetime(event.mtime%s)," " coalesce(ecomment, comment)," " coalesce(euser, user)" " FROM event, blob" " WHERE event.objid=%d AND blob.rid=%d", timeline_utc(), mid, mid ); if( db_step(&q)==SQLITE_ROW ){ char *zCom = mprintf("Merging fork [%S] at %s by %s: \"%s\"", db_column_text(&q, 0), db_column_text(&q, 1), db_column_text(&q, 3), db_column_text(&q, 2)); comment_print(zCom, db_column_text(&q,2), 0, -1, g.comFmtFlags); fossil_free(zCom); } db_finalize(&q); }else{ usage("?OPTIONS? ?VERSION?"); return; } if( zPivot ){ pid = name_to_typed_rid(zPivot, "ci"); if( pid==0 || !is_a_version(pid) ){ fossil_fatal("not a version: %s", zPivot); } if( pickFlag ){ fossil_fatal("incompatible options: --cherrypick & --baseline"); } }else if( pickFlag || backoutFlag ){ if( integrateFlag ){ fossil_fatal("incompatible options: --integrate & --cherrypick or --backout"); } pid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", mid); if( pid<=0 ){ fossil_fatal("cannot find an ancestor for %s", g.argv[2]); } }else{ pivot_set_primary(mid); pivot_set_secondary(vid); db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0"); while( db_step(&q)==SQLITE_ROW ){ pivot_set_secondary(db_column_int(&q,0)); } db_finalize(&q); pid = pivot_find(); if( pid<=0 ){ fossil_fatal("cannot find a common ancestor between the current " "checkout and %s", g.argv[2]); } } if( backoutFlag ){ int t = pid; pid = mid; mid = t; } if( !is_a_version(pid) ){ fossil_fatal("not a version: record #%d", pid); } if( !forceFlag && mid==pid ){ fossil_print("Merge skipped because it is a no-op. " " Use --force to override.\n"); return; } if( integrateFlag && !is_a_leaf(mid)){ fossil_warning("ignoring --integrate: %s is not a leaf", g.argv[2]); integrateFlag = 0; } if( verboseFlag ){ print_checkin_description(mid, 12, integrateFlag?"integrate:":"merge-from:"); print_checkin_description(pid, 12, "baseline:"); } vfile_check_signature(vid, CKSIG_ENOTFILE); db_begin_transaction(); if( !dryRunFlag ) undo_begin(); if( load_vfile_from_rid(mid) && !forceMissingFlag ){ fossil_fatal("missing content, unable to merge"); } if( load_vfile_from_rid(pid) && !forceMissingFlag ){ fossil_fatal("missing content, unable to merge"); } if( debugFlag ){ char *z; z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pid); fossil_print("P=%d %z\n", pid, z); z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); fossil_print("M=%d %z\n", mid, z); z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); fossil_print("V=%d %z\n", vid, z); } /* ** The vfile.pathname field is used to match files against each other. The ** FV table contains one row for each each unique filename in ** in the current checkout, the pivot, and the version being merged. */ db_multi_exec( "DROP TABLE IF EXISTS fv;" "CREATE TEMP TABLE fv(" " fn TEXT PRIMARY KEY %s," /* The filename */ " idv INTEGER," /* VFILE entry for current version */ " idp INTEGER," /* VFILE entry for the pivot */ " idm INTEGER," /* VFILE entry for version merging in */ " chnged BOOLEAN," /* True if current version has been edited */ " ridv INTEGER," /* Record ID for current version */ " ridp INTEGER," /* Record ID for pivot */ " ridm INTEGER," /* Record ID for merge */ " isexe BOOLEAN," /* Execute permission enabled */ " fnp TEXT %s," /* The filename in the pivot */ " fnm TEXT %s," /* the filename in the merged version */ " islinkv BOOLEAN," /* True if current version is a symlink */ " islinkm BOOLEAN" /* True if merged version in is a symlink */ ");", filename_collation(), filename_collation(), filename_collation() ); /* Add files found in V */ db_multi_exec( "INSERT OR IGNORE" " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)" " SELECT pathname, pathname, pathname, id, 0, 0, rid, 0, 0, isexe, chnged " " FROM vfile WHERE vid=%d", vid ); /* ** Compute name changes from P->V */ find_filename_changes(pid, vid, 0, &nChng, &aChng, debugFlag ? "P->V" : 0); if( nChng ){ for(i=0; i<nChng; i++){ char *z; z = db_text(0, "SELECT name FROM filename WHERE fnid=%d", aChng[i*2]); db_multi_exec( "UPDATE fv SET fnp=%Q, fnm=%Q" " WHERE fn=(SELECT name FROM filename WHERE fnid=%d)", z, z, aChng[i*2+1] ); free(z); } fossil_free(aChng); db_multi_exec("UPDATE fv SET fnm=fnp WHERE fnp!=fn"); } /* Add files found in P but not in V */ db_multi_exec( "INSERT OR IGNORE" " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)" " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, isexe, 0 " " FROM vfile" " WHERE vid=%d AND pathname %s NOT IN (SELECT fnp FROM fv)", pid, filename_collation() ); /* ** Compute name changes from P->M */ find_filename_changes(pid, mid, 0, &nChng, &aChng, debugFlag ? "P->M" : 0); if( nChng ){ if( nChng>4 ) db_multi_exec("CREATE INDEX fv_fnp ON fv(fnp)"); for(i=0; i<nChng; i++){ db_multi_exec( "UPDATE fv SET fnm=(SELECT name FROM filename WHERE fnid=%d)" " WHERE fnp=(SELECT name FROM filename WHERE fnid=%d)", aChng[i*2+1], aChng[i*2] ); } fossil_free(aChng); } /* Add files found in M but not in P or V. */ db_multi_exec( "INSERT OR IGNORE" " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)" " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, isexe, 0 " " FROM vfile" " WHERE vid=%d" " AND pathname %s NOT IN (SELECT fnp FROM fv UNION SELECT fnm FROM fv)", mid, filename_collation() ); /* ** Compute the file version ids for P and M. */ db_multi_exec( "UPDATE fv SET" " idp=coalesce((SELECT id FROM vfile WHERE vid=%d AND fnp=pathname),0)," " ridp=coalesce((SELECT rid FROM vfile WHERE vid=%d AND fnp=pathname),0)," " idm=coalesce((SELECT id FROM vfile WHERE vid=%d AND fnm=pathname),0)," " ridm=coalesce((SELECT rid FROM vfile WHERE vid=%d AND fnm=pathname),0)," " islinkv=coalesce((SELECT islink FROM vfile" " WHERE vid=%d AND fnm=pathname),0)," " islinkm=coalesce((SELECT islink FROM vfile" " WHERE vid=%d AND fnm=pathname),0)", pid, pid, mid, mid, vid, mid ); if( debugFlag ){ db_prepare(&q, "SELECT rowid, fn, fnp, fnm, chnged, ridv, ridp, ridm, " " isexe, islinkv, islinkm FROM fv" ); while( db_step(&q)==SQLITE_ROW ){ fossil_print("%3d: ridv=%-4d ridp=%-4d ridm=%-4d chnged=%d isexe=%d " " islinkv=%d islinkm=%d\n", db_column_int(&q, 0), db_column_int(&q, 5), db_column_int(&q, 6), db_column_int(&q, 7), db_column_int(&q, 4), db_column_int(&q, 8), db_column_int(&q, 9), db_column_int(&q, 10)); fossil_print(" fn = [%s]\n", db_column_text(&q, 1)); fossil_print(" fnp = [%s]\n", db_column_text(&q, 2)); fossil_print(" fnm = [%s]\n", db_column_text(&q, 3)); } db_finalize(&q); } /* ** Find files in M and V but not in P and report conflicts. ** The file in M will be ignored. It will be treated as if it ** does not exist. */ db_prepare(&q, "SELECT idm FROM fv WHERE idp=0 AND idv>0 AND idm>0" ); while( db_step(&q)==SQLITE_ROW ){ int idm = db_column_int(&q, 0); char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idm); fossil_warning("WARNING - no common ancestor: %s", zName); free(zName); db_multi_exec("UPDATE fv SET idm=0 WHERE idm=%d", idm); } db_finalize(&q); /* ** Add to V files that are not in V or P but are in M */ db_prepare(&q, "SELECT idm, rowid, fnm FROM fv AS x" " WHERE idp=0 AND idv=0 AND idm>0" ); while( db_step(&q)==SQLITE_ROW ){ int idm = db_column_int(&q, 0); int rowid = db_column_int(&q, 1); int idv; const char *zName; char *zFullName; db_multi_exec( "INSERT INTO vfile(vid,chnged,deleted,rid,mrid,isexe,islink,pathname)" " SELECT %d,%d,0,rid,mrid,isexe,islink,pathname FROM vfile WHERE id=%d", vid, integrateFlag?5:3, idm ); idv = db_last_insert_rowid(); db_multi_exec("UPDATE fv SET idv=%d WHERE rowid=%d", idv, rowid); zName = db_column_text(&q, 2); zFullName = mprintf("%s%s", g.zLocalRoot, zName); if( file_wd_isfile_or_link(zFullName) ){ fossil_print("ADDED %s (overwrites an unmanaged file)\n", zName); nOverwrite++; }else{ fossil_print("ADDED %s\n", zName); } fossil_free(zFullName); if( !dryRunFlag ){ undo_save(zName); vfile_to_disk(0, idm, 0, 0); } } db_finalize(&q); /* ** Find files that have changed from P->M but not P->V. ** Copy the M content over into V. */ db_prepare(&q, "SELECT idv, ridm, fn, islinkm FROM fv" " WHERE idp>0 AND idv>0 AND idm>0" " AND ridm!=ridp AND ridv=ridp AND NOT chnged" ); while( db_step(&q)==SQLITE_ROW ){ int idv = db_column_int(&q, 0); int ridm = db_column_int(&q, 1); const char *zName = db_column_text(&q, 2); int islinkm = db_column_int(&q, 3); /* Copy content from idm over into idv. Overwrite idv. */ fossil_print("UPDATE %s\n", zName); if( !dryRunFlag ){ undo_save(zName); db_multi_exec( "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d " " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, idv ); vfile_to_disk(0, idv, 0, 0); } } db_finalize(&q); /* ** Do a three-way merge on files that have changes on both P->M and P->V. */ db_prepare(&q, "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm FROM fv" " WHERE idp>0 AND idv>0 AND idm>0" " AND ridm!=ridp AND (ridv!=ridp OR chnged)", glob_expr("fv.fn", zBinGlob) ); while( db_step(&q)==SQLITE_ROW ){ int ridm = db_column_int(&q, 0); int idv = db_column_int(&q, 1); int ridp = db_column_int(&q, 2); int ridv = db_column_int(&q, 3); int isBinary = db_column_int(&q, 4); const char *zName = db_column_text(&q, 5); int isExe = db_column_int(&q, 6); int islinkv = db_column_int(&q, 7); int islinkm = db_column_int(&q, 8); int rc; char *zFullPath; Blob m, p, r; /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ if( verboseFlag ){ fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n", zName, ridp, ridm, ridv); }else{ fossil_print("MERGE %s\n", zName); } if( islinkv || islinkm /* || file_wd_islink(zFullPath) */ ){ fossil_print("***** Cannot merge symlink %s\n", zName); nConflict++; }else{ undo_save(zName); zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); content_get(ridp, &p); content_get(ridm, &m); if( isBinary ){ rc = -1; blob_zero(&r); }else{ unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0; rc = merge_3way(&p, zFullPath, &m, &r, mergeFlags); } if( rc>=0 ){ if( !dryRunFlag ){ blob_write_to_file(&r, zFullPath); file_wd_setexe(zFullPath, isExe); } db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv); if( rc>0 ){ fossil_print("***** %d merge conflicts in %s\n", rc, zName); nConflict++; } }else{ fossil_print("***** Cannot merge binary file %s\n", zName); nConflict++; } blob_reset(&p); blob_reset(&m); blob_reset(&r); } db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(%d,%d)", idv,ridm); } db_finalize(&q); /* ** Drop files that are in P and V but not in M */ db_prepare(&q, "SELECT idv, fn, chnged FROM fv" " WHERE idp>0 AND idv>0 AND idm=0" ); while( db_step(&q)==SQLITE_ROW ){ int idv = db_column_int(&q, 0); const char *zName = db_column_text(&q, 1); int chnged = db_column_int(&q, 2); /* Delete the file idv */ fossil_print("DELETE %s\n", zName); if( chnged ){ fossil_warning("WARNING: local edits lost for %s\n", zName); nConflict++; } undo_save(zName); db_multi_exec( "UPDATE vfile SET deleted=1 WHERE id=%d", idv ); if( !dryRunFlag ){ char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName); file_delete(zFullPath); free(zFullPath); } } db_finalize(&q); /* ** Rename files that have taken a rename on P->M but which keep the same ** name o P->V. If a file is renamed on P->V only or on both P->V and ** P->M then we retain the V name of the file. */ db_prepare(&q, "SELECT idv, fnp, fnm FROM fv" " WHERE idv>0 AND idp>0 AND idm>0 AND fnp=fn AND fnm!=fnp" ); while( db_step(&q)==SQLITE_ROW ){ int idv = db_column_int(&q, 0); const char *zOldName = db_column_text(&q, 1); const char *zNewName = db_column_text(&q, 2); fossil_print("RENAME %s -> %s\n", zOldName, zNewName); undo_save(zOldName); undo_save(zNewName); db_multi_exec( "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)" " WHERE id=%d AND vid=%d", zNewName, idv, vid ); if( !dryRunFlag ){ char *zFullOldPath = mprintf("%s%s", g.zLocalRoot, zOldName); char *zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName); if( file_wd_islink(zFullOldPath) ){ symlink_copy(zFullOldPath, zFullNewPath); }else{ file_copy(zFullOldPath, zFullNewPath); } file_delete(zFullOldPath); free(zFullNewPath); free(zFullOldPath); } } db_finalize(&q); /* Report on conflicts */ if( nConflict ){ fossil_warning("WARNING: %d merge conflicts", nConflict); } if( nOverwrite ){ fossil_warning("WARNING: %d unmanaged files were overwritten", nOverwrite); } if( dryRunFlag ){ fossil_warning("REMINDER: this was a dry run -" " no files were actually changed."); } /* ** Clean up the mid and pid VFILE entries. Then commit the changes. */ db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); if( pickFlag ){ db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(-1,%d)",mid); /* For a cherry-pick merge, make the default check-in comment the same ** as the check-in comment on the check-in that is being merged in. */ db_multi_exec( "REPLACE INTO vvar(name,value)" " SELECT 'ci-comment', coalesce(ecomment,comment) FROM event" " WHERE type='ci' AND objid=%d", mid ); }else if( backoutFlag ){ db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(-2,%d)",pid); }else if( integrateFlag ){ db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(-4,%d)",mid); }else{ db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(0,%d)", mid); } undo_finish(); db_end_transaction(dryRunFlag); }
/* ** When a record is converted from a phantom to a real record, ** if that record has other records that are derived by delta, ** then call manifest_crosslink() on those other records. ** ** If the formerly phantom record or any of the other records ** derived by delta from the former phantom are a baseline manifest, ** then also invoke manifest_crosslink() on the delta-manifests ** associated with that baseline. ** ** Tail recursion is used to minimize stack depth. */ void after_dephantomize(int rid, int linkFlag){ Stmt q; int nChildAlloc = 0; int *aChild = 0; Blob content; if( ignoreDephantomizations ) return; while( rid ){ int nChildUsed = 0; int i; /* Parse the object rid itself */ if( linkFlag ){ content_get(rid, &content); manifest_crosslink(rid, &content, MC_NONE); assert( blob_is_reset(&content) ); } /* Parse all delta-manifests that depend on baseline-manifest rid */ db_prepare(&q, "SELECT rid FROM orphan WHERE baseline=%d", rid); while( db_step(&q)==SQLITE_ROW ){ int child = db_column_int(&q, 0); if( nChildUsed>=nChildAlloc ){ nChildAlloc = nChildAlloc*2 + 10; aChild = fossil_realloc(aChild, nChildAlloc*sizeof(aChild)); } aChild[nChildUsed++] = child; } db_finalize(&q); for(i=0; i<nChildUsed; i++){ content_get(aChild[i], &content); manifest_crosslink(aChild[i], &content, MC_NONE); assert( blob_is_reset(&content) ); } if( nChildUsed ){ db_multi_exec("DELETE FROM orphan WHERE baseline=%d", rid); } /* Recursively dephantomize all artifacts that are derived by ** delta from artifact rid and which have not already been ** cross-linked. */ nChildUsed = 0; db_prepare(&q, "SELECT rid FROM delta" " WHERE srcid=%d" " AND NOT EXISTS(SELECT 1 FROM mlink WHERE mid=delta.rid)", rid ); while( db_step(&q)==SQLITE_ROW ){ int child = db_column_int(&q, 0); if( nChildUsed>=nChildAlloc ){ nChildAlloc = nChildAlloc*2 + 10; aChild = fossil_realloc(aChild, nChildAlloc*sizeof(aChild)); } aChild[nChildUsed++] = child; } db_finalize(&q); for(i=1; i<nChildUsed; i++){ after_dephantomize(aChild[i], 1); } /* Tail recursion for the common case where only a single artifact ** is derived by delta from rid... */ rid = nChildUsed>0 ? aChild[0] : 0; linkFlag = 1; } free(aChild); }
/* ** Run a diff between the version zFrom and files on disk. zFrom might ** be NULL which means to simply show the difference between the edited ** files on disk and the check-out on which they are based. */ static void diff_all_against_disk( const char *zFrom, /* Version to difference from */ const char *zDiffCmd, /* Use this diff command. NULL for built-in */ int ignoreEolWs /* Ignore end-of-line whitespace */ ){ int vid; Blob sql; Stmt q; vid = db_lget_int("checkout", 0); vfile_check_signature(vid, 1); blob_zero(&sql); db_begin_transaction(); if( zFrom ){ int rid = name_to_rid(zFrom); if( !is_a_version(rid) ){ fossil_fatal("no such check-in: %s", zFrom); } load_vfile_from_rid(rid); blob_appendf(&sql, "SELECT v2.pathname, v2.deleted, v2.chnged, v2.rid==0, v1.rid" " FROM vfile v1, vfile v2 " " WHERE v1.pathname=v2.pathname AND v1.vid=%d AND v2.vid=%d" " AND (v2.deleted OR v2.chnged OR v1.rid!=v2.rid)" "UNION " "SELECT pathname, 1, 0, 0, 0" " FROM vfile v1" " WHERE v1.vid=%d" " AND NOT EXISTS(SELECT 1 FROM vfile v2" " WHERE v2.vid=%d AND v2.pathname=v1.pathname)" "UNION " "SELECT pathname, 0, 0, 1, 0" " FROM vfile v2" " WHERE v2.vid=%d" " AND NOT EXISTS(SELECT 1 FROM vfile v1" " WHERE v1.vid=%d AND v1.pathname=v2.pathname)" " ORDER BY 1", rid, vid, rid, vid, vid, rid ); }else{ blob_appendf(&sql, "SELECT pathname, deleted, chnged , rid==0, rid" " FROM vfile" " WHERE vid=%d" " AND (deleted OR chnged OR rid==0)" " ORDER BY pathname", vid ); } db_prepare(&q, blob_str(&sql)); while( db_step(&q)==SQLITE_ROW ){ const char *zPathname = db_column_text(&q,0); int isDeleted = db_column_int(&q, 1); int isChnged = db_column_int(&q,2); int isNew = db_column_int(&q,3); char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname); if( isDeleted ){ printf("DELETED %s\n", zPathname); }else if( access(zFullName, 0) ){ printf("MISSING %s\n", zPathname); }else if( isNew ){ printf("ADDED %s\n", zPathname); }else if( isChnged==3 ){ printf("ADDED_BY_MERGE %s\n", zPathname); }else{ int srcid = db_column_int(&q, 4); Blob content; content_get(srcid, &content); printf("Index: %s\n=======================================" "============================\n", zPathname ); diff_file(&content, zFullName, zPathname, zDiffCmd, ignoreEolWs); blob_reset(&content); } free(zFullName); } db_finalize(&q); db_end_transaction(1); /* ROLLBACK */ }
/* ** Extract the content for ID rid and put it into the ** uninitialized blob. Return 1 on success. If the record ** is a phantom, zero pBlob and return 0. */ int content_get(int rid, Blob *pBlob){ int rc; int i; int nextRid; assert( g.repositoryOpen ); blob_zero(pBlob); if( rid==0 ) return 0; /* Early out if we know the content is not available */ if( bag_find(&contentCache.missing, rid) ){ return 0; } /* Look for the artifact in the cache first */ if( bag_find(&contentCache.inCache, rid) ){ for(i=0; i<contentCache.n; i++){ if( contentCache.a[i].rid==rid ){ blob_copy(pBlob, &contentCache.a[i].content); contentCache.a[i].age = contentCache.nextAge++; return 1; } } } nextRid = findSrcid(rid); if( nextRid==0 ){ rc = content_of_blob(rid, pBlob); }else{ int n = 1; int nAlloc = 10; int *a = 0; int mx; Blob delta, next; a = fossil_malloc( sizeof(a[0])*nAlloc ); a[0] = rid; a[1] = nextRid; n = 1; while( !bag_find(&contentCache.inCache, nextRid) && (nextRid = findSrcid(nextRid))>0 ){ n++; if( n>=nAlloc ){ if( n>db_int(0, "SELECT max(rid) FROM blob") ){ fossil_panic("infinite loop in DELTA table"); } nAlloc = nAlloc*2 + 10; a = fossil_realloc(a, nAlloc*sizeof(a[0])); } a[n] = nextRid; } mx = n; rc = content_get(a[n], pBlob); n--; while( rc && n>=0 ){ rc = content_of_blob(a[n], &delta); if( rc ){ blob_delta_apply(pBlob, &delta, &next); blob_reset(&delta); if( (mx-n)%8==0 ){ content_cache_insert(a[n+1], pBlob); }else{ blob_reset(pBlob); } *pBlob = next; } n--; } free(a); if( !rc ) blob_reset(pBlob); } if( rc==0 ){ bag_insert(&contentCache.missing, rid); }else{ bag_insert(&contentCache.available, rid); } return rc; }
/* ** Write all files from vid to the disk. Or if vid==0 and id!=0 ** write just the specific file where VFILE.ID=id. */ void vfile_to_disk( int vid, /* vid to write to disk */ int id, /* Write this one file, if not zero */ int verbose, /* Output progress information */ int promptFlag /* Prompt user to confirm overwrites */ ){ Stmt q; Blob content; int nRepos = strlen(g.zLocalRoot); if( vid>0 && id==0 ){ db_prepare(&q, "SELECT id, %Q || pathname, mrid, isexe, islink" " FROM vfile" " WHERE vid=%d AND mrid>0", g.zLocalRoot, vid); }else{ assert( vid==0 && id>0 ); db_prepare(&q, "SELECT id, %Q || pathname, mrid, isexe, islink" " FROM vfile" " WHERE id=%d AND mrid>0", g.zLocalRoot, id); } while( db_step(&q)==SQLITE_ROW ){ int id, rid, isExe, isLink; const char *zName; id = db_column_int(&q, 0); zName = db_column_text(&q, 1); rid = db_column_int(&q, 2); isExe = db_column_int(&q, 3); isLink = db_column_int(&q, 4); content_get(rid, &content); if( file_is_the_same(&content, zName) ){ blob_reset(&content); if( file_wd_setexe(zName, isExe) ){ db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d", file_wd_mtime(zName), id); } continue; } if( promptFlag && file_wd_size(zName)>=0 ){ Blob ans; char *zMsg; char cReply; zMsg = mprintf("overwrite %s (a=always/y/N)? ", zName); prompt_user(zMsg, &ans); free(zMsg); cReply = blob_str(&ans)[0]; blob_reset(&ans); if( cReply=='a' || cReply=='A' ){ promptFlag = 0; cReply = 'y'; } if( cReply=='n' || cReply=='N' ){ blob_reset(&content); continue; } } if( verbose ) vcs_print("%s\n", &zName[nRepos]); if( file_wd_isdir(zName) == 1 ){ /*TODO(dchest): remove directories? */ vcs_fatal("%s is directory, cannot overwrite\n", zName); } if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(zName)) ){ file_delete(zName); } if( isLink ){ symlink_create(blob_str(&content), zName); }else{ blob_write_to_file(&content, zName); } file_wd_setexe(zName, isExe); blob_reset(&content); db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d", file_wd_mtime(zName), id); } db_finalize(&q); }
/* ** COMMAND: deconstruct* ** ** Usage %fossil deconstruct ?OPTIONS? DESTINATION ** ** ** This command exports all artifacts of a given repository and ** writes all artifacts to the file system. The DESTINATION directory ** will be populated with subdirectories AA and files AA/BBBBBBBBB.., where ** AABBBBBBBBB.. is the 40 character artifact ID, AA the first 2 characters. ** If -L|--prefixlength is given, the length (default 2) of the directory ** prefix can be set to 0,1,..,9 characters. ** ** Options: ** -R|--repository REPOSITORY deconstruct given REPOSITORY ** -L|--prefixlength N set the length of the names of the DESTINATION ** subdirectories to N ** --private Include private artifacts. ** ** See also: rebuild, reconstruct */ void deconstruct_cmd(void){ const char *zDestDir; const char *zPrefixOpt; Stmt s; int privateFlag; /* get and check prefix length argument and build format string */ zPrefixOpt=find_option("prefixlength","L",1); if( !zPrefixOpt ){ prefixLength = 2; }else{ if( zPrefixOpt[0]>='0' && zPrefixOpt[0]<='9' && !zPrefixOpt[1] ){ prefixLength = (int)(*zPrefixOpt-'0'); }else{ fossil_fatal("N(%s) is not a valid prefix length!",zPrefixOpt); } } /* open repository and open query for all artifacts */ db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); privateFlag = find_option("private",0,0)!=0; verify_all_options(); /* check number of arguments */ if( g.argc!=3 ){ usage ("?OPTIONS? DESTINATION"); } /* get and check argument destination directory */ zDestDir = g.argv[g.argc-1]; if( !*zDestDir || !file_isdir(zDestDir)) { fossil_fatal("DESTINATION(%s) is not a directory!",zDestDir); } #ifndef _WIN32 if( file_access(zDestDir, W_OK) ){ fossil_fatal("DESTINATION(%s) is not writeable!",zDestDir); } #else /* write access on windows is not checked, errors will be ** detected on blob_write_to_file */ #endif if( prefixLength ){ zFNameFormat = mprintf("%s/%%.%ds/%%s",zDestDir,prefixLength); }else{ zFNameFormat = mprintf("%s/%%s",zDestDir); } bag_init(&bagDone); ttyOutput = 1; processCnt = 0; if (!g.fQuiet) { fossil_print("0 (0%%)...\r"); fflush(stdout); } totalSize = db_int(0, "SELECT count(*) FROM blob"); db_prepare(&s, "SELECT rid, size FROM blob /*scan*/" " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" " AND NOT EXISTS(SELECT 1 FROM delta WHERE rid=blob.rid) %s", privateFlag==0 ? "AND rid NOT IN private" : "" ); while( db_step(&s)==SQLITE_ROW ){ int rid = db_column_int(&s, 0); int size = db_column_int(&s, 1); if( size>=0 ){ Blob content; content_get(rid, &content); rebuild_step(rid, size, &content); } } db_finalize(&s); db_prepare(&s, "SELECT rid, size FROM blob" " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid) %s", privateFlag==0 ? "AND rid NOT IN private" : "" ); while( db_step(&s)==SQLITE_ROW ){ int rid = db_column_int(&s, 0); int size = db_column_int(&s, 1); if( size>=0 ){ if( !bag_find(&bagDone, rid) ){ Blob content; content_get(rid, &content); rebuild_step(rid, size, &content); } } } db_finalize(&s); if(!g.fQuiet && ttyOutput ){ fossil_print("\n"); } /* free filename format string */ free(zFNameFormat); zFNameFormat = 0; }
/* ** Core function to rebuild the information in the derived tables of a ** fossil repository from the blobs. This function is shared between ** 'rebuild_database' ('rebuild') and 'reconstruct_cmd' ** ('reconstruct'), both of which have to regenerate this information ** from scratch. ** ** If the randomize parameter is true, then the BLOBs are deliberately ** extracted in a random order. This feature is used to test the ** ability of fossil to accept records in any order and still ** construct a sane repository. */ int rebuild_db(int randomize, int doOut, int doClustering){ Stmt s; int errCnt = 0; char *zTable; int incrSize; bag_init(&bagDone); ttyOutput = doOut; processCnt = 0; if (ttyOutput && !g.fQuiet) { percent_complete(0); } rebuild_update_schema(); for(;;){ zTable = db_text(0, "SELECT name FROM sqlite_master /*scan*/" " WHERE type='table'" " AND name NOT IN ('blob','delta','rcvfrom','user'," "'config','shun','private','reportfmt'," "'concealed','accesslog','modreq')" " AND name NOT GLOB 'sqlite_*'" " AND name NOT GLOB 'fx_*'" ); if( zTable==0 ) break; db_multi_exec("DROP TABLE %Q", zTable); free(zTable); } db_multi_exec(zRepositorySchema2); ticket_create_table(0); shun_artifacts(); db_multi_exec( "INSERT INTO unclustered" " SELECT rid FROM blob EXCEPT SELECT rid FROM private" ); db_multi_exec( "DELETE FROM unclustered" " WHERE rid IN (SELECT rid FROM shun JOIN blob USING(uuid))" ); db_multi_exec( "DELETE FROM config WHERE name IN ('remote-code', 'remote-maxid')" ); /* The following should be count(*) instead of max(rid). max(rid) is ** an adequate approximation, however, and is much faster for large ** repositories. */ totalSize = db_int(0, "SELECT max(rid) FROM blob"); incrSize = totalSize/100; totalSize += incrSize*2; db_prepare(&s, "SELECT rid, size FROM blob /*scan*/" " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" " AND NOT EXISTS(SELECT 1 FROM delta WHERE rid=blob.rid)" ); manifest_crosslink_begin(); while( db_step(&s)==SQLITE_ROW ){ int rid = db_column_int(&s, 0); int size = db_column_int(&s, 1); if( size>=0 ){ Blob content; content_get(rid, &content); rebuild_step(rid, size, &content); } } db_finalize(&s); db_prepare(&s, "SELECT rid, size FROM blob" " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" ); while( db_step(&s)==SQLITE_ROW ){ int rid = db_column_int(&s, 0); int size = db_column_int(&s, 1); if( size>=0 ){ if( !bag_find(&bagDone, rid) ){ Blob content; content_get(rid, &content); rebuild_step(rid, size, &content); } }else{ db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid); rebuild_step_done(rid); } } db_finalize(&s); manifest_crosslink_end(MC_NONE); rebuild_tag_trunk(); if( ttyOutput && !g.fQuiet && totalSize>0 ){ processCnt += incrSize; percent_complete((processCnt*1000)/totalSize); } if( doClustering ) create_cluster(); if( ttyOutput && !g.fQuiet && totalSize>0 ){ processCnt += incrSize; percent_complete((processCnt*1000)/totalSize); } if(!g.fQuiet && ttyOutput ){ percent_complete(1000); fossil_print("\n"); } return errCnt; }
/* ** Compute an aggregate MD5 checksum over the disk image of every ** file in vid. The file names are part of the checksum. The resulting ** checksum is the same as is expected on the R-card of a manifest. ** ** This function operates differently if the Global.aCommitFile ** variable is not NULL. In that case, the disk image is used for ** each file in aCommitFile[] and the repository image ** is used for all others). ** ** Newly added files that are not contained in the repository are ** omitted from the checksum if they are not in Global.aCommitFile[]. ** ** Newly deleted files are included in the checksum if they are not ** part of Global.aCommitFile[] ** ** Renamed files use their new name if they are in Global.aCommitFile[] ** and their original name if they are not in Global.aCommitFile[] ** ** Return the resulting checksum in blob pOut. */ void vfile_aggregate_checksum_disk(int vid, Blob *pOut){ FILE *in; Stmt q; char zBuf[4096]; db_must_be_within_tree(); db_prepare(&q, "SELECT %Q || pathname, pathname, origname, file_is_selected(id), rid" " FROM vfile" " WHERE (NOT deleted OR NOT file_is_selected(id)) AND vid=%d" " ORDER BY pathname /*scan*/", g.zLocalRoot, vid ); md5sum_init(); while( db_step(&q)==SQLITE_ROW ){ const char *zFullpath = db_column_text(&q, 0); const char *zName = db_column_text(&q, 1); int isSelected = db_column_int(&q, 3); if( isSelected ){ md5sum_step_text(zName, -1); if( file_wd_islink(zFullpath) ){ /* Instead of file content, use link destination path */ Blob pathBuf; sqlite3_snprintf(sizeof(zBuf), zBuf, " %ld\n", blob_read_link(&pathBuf, zFullpath)); md5sum_step_text(zBuf, -1); md5sum_step_text(blob_str(&pathBuf), -1); blob_reset(&pathBuf); }else{ in = vcs_fopen(zFullpath,"rb"); if( in==0 ){ md5sum_step_text(" 0\n", -1); continue; } fseek(in, 0L, SEEK_END); sqlite3_snprintf(sizeof(zBuf), zBuf, " %ld\n", ftell(in)); fseek(in, 0L, SEEK_SET); md5sum_step_text(zBuf, -1); /*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/ for(;;){ int n; n = fread(zBuf, 1, sizeof(zBuf), in); if( n<=0 ) break; md5sum_step_text(zBuf, n); } fclose(in); } }else{ int rid = db_column_int(&q, 4); const char *zOrigName = db_column_text(&q, 2); char zBuf[100]; Blob file; if( zOrigName ) zName = zOrigName; if( rid>0 ){ md5sum_step_text(zName, -1); blob_zero(&file); content_get(rid, &file); sqlite3_snprintf(sizeof(zBuf), zBuf, " %d\n", blob_size(&file)); md5sum_step_text(zBuf, -1); md5sum_step_blob(&file); blob_reset(&file); } } } db_finalize(&q); md5sum_finish(pOut); }