DELETED bindings/Makefile.in Index: bindings/Makefile.in ================================================================== --- bindings/Makefile.in +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/make # help out emacs -all: -DISTCLEAN_FILES += Makefile -SUBDIRS := -# Subdir cleanup rules and deps list must come before shakenmake.make is included -# or they must be set up manually afterwards... -ifneq (,$(CXX)) -clean-.: clean-cpp -distclean-.: distclean-cpp -SUBDIRS += cpp -all: subdir-cpp -endif - -clean-.: clean-s2 -distclean-.: distclean-s2 -include ../subdir-inc.make -SUBDIRS += s2 -$(eval $(call ShakeNMake.CALL.SUBDIRS,$(SUBDIRS))) -all: subdir-s2 Index: bindings/s2/shell_extend.c ================================================================== --- bindings/s2/shell_extend.c +++ bindings/s2/shell_extend.c @@ -98,20 +98,28 @@ va_end(vargs); return rc; } static int cb_toss_fsl( cwal_callback_args const * args, - fsl_cx * f ){ + fsl_cx * const f, + int fslRc){ int rc; - assert(f && f->error.code); - rc = (FSL_RC_OOM==f->error.code) - ? CWAL_RC_OOM - : cb_toss(args, - f->error.code, - "%.*s", (int)f->error.msg.used, - (char const *)f->error.msg.mem ); - fsl_cx_err_reset(f); + if(!fslRc) fslRc = f->error.code; + if(FSL_RC_OOM==fslRc) rc = CWAL_RC_OOM; + else{ + if(f->error.code){ + rc = cb_toss(args, + f->error.code, + "%.*s", (int)f->error.msg.used, + (char const *)f->error.msg.mem ); + fsl_cx_err_reset(f); + }else{ + rc = cb_toss(args, CWAL_RC_EXCEPTION, + "Fossil result code %s", + fsl_rc_cstr(fslRc)); + } + } return rc; } static int cb_toss_db( cwal_callback_args const * args, fsl_db * db ){ @@ -321,11 +329,10 @@ fsl_dbrole_e role){ cwal_native * n; cwal_value * nv; int rc = 0; char const * fname = NULL; - fsl_size_t nameLen = 0; cwal_value * tmpV = NULL; assert(se && db && rv); n = cwal_new_native(se->e, db, addDtor ? cwal_finalizer_f_fsl_db : NULL, FSL_TYPEID(fsl_db)); @@ -333,16 +340,13 @@ nv = cwal_native_value(n); cwal_value_ref(nv); /* Set up "filename" property. It's problematic because of libfossil's internal DB juggling :/. */ - if(f) fname = fsl_cx_db_file_for_role(f, role, &nameLen); - if(!fname){ - fname = fsl_db_filename(db, &nameLen); - } + fname = fsl_db_filename(db, NULL); if(fname){ - tmpV = cwal_new_string_value(se->e, fname, (cwal_size_t)nameLen); + tmpV = cwal_new_string_value(se->e, fname, fsl_strlen(fname)); cwal_value_ref(tmpV); rc = tmpV ? cwal_prop_set(nv, "filename", 8, tmpV) : CWAL_RC_OOM; cwal_value_unref(tmpV); @@ -352,18 +356,15 @@ } fname = 0; } /* Set up "name" property. */ - if(f) fname = fsl_cx_db_name_for_role(f, role, &nameLen); - if(!fname){ - fname = fsl_db_name(db); - nameLen = fname ? cwal_strlen(fname) : 0; - } + fname = fsl_db_name(db); + if(!fname) fname = fsl_db_role_name(role); if(fname){ tmpV = - cwal_new_string_value(se->e, fname, (cwal_size_t)nameLen); + cwal_new_string_value(se->e, fname, fsl_strlen(fname)); cwal_value_ref(tmpV); rc = tmpV ? cwal_prop_set(nv, "name", 4, tmpV) : CWAL_RC_OOM; cwal_value_unref(tmpV); @@ -1734,26 +1735,30 @@ return rc; } /** - ** If cx has a property named "db", it is returned, else if + ** If fv has a property named "db", it is returned, else if ** createIfNotExists is true then a new native fsl_db Object named ** "db" is inserted into cx and returned. Returns NULL on allocation ** error or if no such property exists and createIfNotExists is ** false. + ** + ** The dbRole arg is only used when creating a new native wrapper + ** and its "name" property is set to fsl_db_role_name(). */ static cwal_value * fsl_cx_db_prop( fsl_cx * f, cwal_value * fv, s2_engine * se, - char createIfNotExists){ + fsl_dbrole_e dbRole, + bool createIfNotExists){ cwal_value * rv; fsl_db * db = fsl_cx_db(f); /* assert(db); */ rv = db ? cwal_prop_get(fv, "db", 2) : NULL; if(db && !rv && createIfNotExists){ - int rc = fsl_db_new_native(se, f, db, 0, &rv, FSL_DBROLE_MAIN); + int rc = fsl_db_new_native(se, f, db, 0, &rv, dbRole); if(rc) return NULL; if(rv){ cwal_value_ref(rv); rc = cwal_prop_set_with_flags(fv, "db", 2, rv, CWAL_VAR_F_CONST); @@ -1814,11 +1819,11 @@ return cb_toss(args, FSL_RC_MISUSE, "Expecting a non-empty string argument."); } rc = fsl_config_open( f, (dbName && *dbName) ? dbName : 0 ); if(rc){ - rc = cb_toss_fsl(args, f); + rc = cb_toss_fsl(args, f, rc); }else{ *rv = args->self; } return rc; } @@ -1843,12 +1848,13 @@ return cb_toss(args, FSL_RC_MISUSE, "Expecting a non-empty string argument."); } rc = fsl_repo_open( f, dbName ); if(rc){ - rc = cb_toss_fsl(args, f); + rc = cb_toss_fsl(args, f, rc); }else{ + fsl_cx_db_prop(f, natV, se, FSL_DBROLE_REPO, true); *rv = args->self; } return rc; } @@ -1859,18 +1865,19 @@ int rc; int checkParentDirs; THIS_F; dbName = args->argc ? cwal_value_get_cstr(args->argv[0], &nameLen) - : 0; + : "."; checkParentDirs = args->argc>1 ? (cwal_value_get_bool(args->argv[1]) ? 1 : 0) : 1; rc = fsl_ckout_open_dir( f, dbName, checkParentDirs ); if(rc){ - rc = cb_toss_fsl(args, f); + rc = cb_toss_fsl(args, f, rc); }else{ + fsl_cx_db_prop(f, natV, se, FSL_DBROLE_CKOUT, true); *rv = args->self; } return rc; } @@ -1949,11 +1956,11 @@ if(!v){ fsl_cx_finalize( f ); return CWAL_RC_OOM; } cwal_value_prototype_set( v, fsl_cx_prototype(se) ); - fsl_cx_db_prop( f, v, se, 1 ) /* initialize this->db */; + //fsl_cx_db_prop( f, v, se, 1 ) /* initialize this->db */; *rv = v; return 0; } /** @@ -1980,11 +1987,11 @@ } } } static int cb_fsl_cx_close( cwal_callback_args const * args, - cwal_value **rv ){ + cwal_value **rv ){ cwal_value * dbNs; THIS_F; /* It's important that we clear the Value/Native bindings to all of the context's databases because clients can do this: @@ -1996,32 +2003,24 @@ not only a stale fsl_db handle, but one which points back to a stale fsl_cx pointer. Thank you, valgrind. */ - dbNs = fsl_cx_db_prop(f, natV, se, 0); + dbNs = fsl_cx_db_prop(f, natV, se, FSL_DBROLE_NONE, false); if(dbNs){ cb_fsl_clear_handles(natV, "db"); } - if(fsl_cx_db_config(f)){ - fsl_config_close(f); - } - if(fsl_cx_db_ckout(f)){ - fsl_ckout_close(f); - /* also closes its repo db */ - }else if(fsl_cx_db_repo(f)){ - fsl_repo_close(f); - } + fsl_cx_close_dbs(f); *rv = args->self; return 0; } static int cb_fsl_cx_finalize( cwal_callback_args const * args, cwal_value **rv ){ THIS_F; - if(fsl_cx_db_prop(f, args->self, se, 0)){ + if(fsl_cx_db_prop(f, args->self, se, FSL_DBROLE_NONE, false)){ cb_fsl_cx_close(args, rv); } cwal_native_clear( nat, 1 ); return 0; } @@ -2069,14 +2068,14 @@ #if 1 if(FSL_RC_NOT_FOUND==rc){ *rv = cwal_value_undefined(); rc = 0; }else{ - rc = cb_toss_fsl(args, f); + rc = cb_toss_fsl(args, f, rc); } #else - rc = cb_toss_fsl(args, f); + rc = cb_toss_fsl(args, f, rc); #endif }else{ if(toUuid){ assert(uuid); *rv = cwal_new_string_value(args->engine, uuid, cwal_strlen(uuid)); @@ -2126,11 +2125,11 @@ rc = (rid>0) ? fsl_content_get(f, rid, &fbuf) : fsl_content_get_sym(f, sym, &fbuf); if(rc){ assert(f->error.code); - rc = cb_toss_fsl(args, f); + rc = cb_toss_fsl(args, f, rc); }else{ cwal_buffer * cbuf = cwal_new_buffer(args->engine, 0); if(!cbuf) rc = CWAL_RC_OOM; else{ cwal_value * cbufV = cwal_buffer_value(cbuf); @@ -2412,11 +2411,11 @@ rc = sym ? fsl_deck_load_sym(f, &mf, sym, mfType) : fsl_deck_load_rid(f, &mf, rid, mfType) ; if(rc){ - if(f->error.code) rc = cb_toss_fsl(args, f); + if(f->error.code) rc = cb_toss_fsl(args, f, rc); else{ rc = sym ? cb_toss(args, rc, "Loading manifest '%s' failed with code %d/%s", sym, rc, fsl_rc_cstr(rc)) : cb_toss(args, rc, "Loading manifest RID %d failed with code %d/%s", @@ -2480,11 +2479,11 @@ rid1 = (fsl_id_t)cwal_value_get_integer(arg); }else if(!(sym = cwal_value_get_cstr(arg,NULL))){ goto misuse; }else{ rc = fsl_sym_to_rid(f, sym, FSL_SATYPE_ANY, &rid1); - if(rc) return cb_toss_fsl(args, f); + if(rc) return cb_toss_fsl(args, f, rc); } /* The v2 arg... */ arg = args->argv[1]; if(cwal_value_is_integer(arg)){ @@ -2491,20 +2490,20 @@ rid2 = (fsl_id_t)cwal_value_get_integer(arg); }else if(!(sym = cwal_value_get_cstr(arg,NULL))){ goto misuse; }else{ rc = fsl_sym_to_rid(f, sym, FSL_SATYPE_ANY, &rid2); - if(rc) return cb_toss_fsl(args, f); + if(rc) return cb_toss_fsl(args, f, rc); } assert(rid1>0); assert(rid2>0); rc = fsl_content_get(f, rid1, &c1); if(!rc) rc = fsl_content_get(f, rid2, &c2); if(rc){ - rc = cb_toss_fsl(args, f); + rc = cb_toss_fsl(args, f, rc); goto end; } diffFlags = FSL_DIFF_LINENO | FSL_DIFF_SIDEBYSIDE; if((args->argc>2) && cwal_props_can((arg = args->argv[2]))){ do{ Index: bindings/s2/unit2/000-200-context.s2 ================================================================== --- bindings/s2/unit2/000-200-context.s2 +++ bindings/s2/unit2/000-200-context.s2 @@ -6,17 +6,18 @@ scope { var f = new Fossil.Context({traceSql:false}); //print('f =',f); assert 'Context' === typename f; assert f inherits Fossil.Context; - const DB = f.db; - assert DB inherits Fossil.Db; - assert DB.name === 'main' /* this is our "main" db */; + assert !f.db; assert f === f.openCheckout(); assert f === f.openConfig(); assert f === f.closeConfig(); + const DB = f.db; + assert DB inherits Fossil.Db; + assert DB.name === 'ckout' /* this is our "main" db */; var counter = 0; assert DB === DB.each({ sql:<<<_SQL SELECT mtime FROM event LIMIT 2 _SQL, Index: f-apps/f-open.c ================================================================== --- f-apps/f-open.c +++ f-apps/f-open.c @@ -302,11 +302,11 @@ } else{ fsl_ckout_version_info(f, &prev_ckout, 0); } assert(dbRepo && dbRepo->dbh); if(!fsl_db_exists(dbRepo,"SELECT 1 FROM %s.event WHERE type='ci'", - fsl_db_role_label(FSL_DBROLE_REPO))){ + fsl_db_role_name(FSL_DBROLE_REPO))){ // Assume there are no checkins in this repo, so nothing for us to do. f_out("Repo contains no checkins, so there is nothing to check out.\n"); goto end; } rc = fsl_sym_to_rid(f, sym, FSL_SATYPE_CHECKIN, &rv); Index: f-apps/f-test-ciwoco.c ================================================================== --- f-apps/f-test-ciwoco.c +++ f-apps/f-test-ciwoco.c @@ -115,15 +115,15 @@ ////////////////////////////////////////////////////////////// // Step 3: add some files... char const * fnames[] = { "f-test-ciwoco.c", - "Makefile.in", + "GNUmakefile", NULL }; for( int i = 0; (fname = fnames[i]); ++i ){ - rc = fsl_buffer_fill_from_filename(&content, fname); + rc = fsl_buffer_fill_from_filename(&content, fname); assert(0==rc); rc = fsl_deck_F_set_content(&d, fname, &content, FSL_FILE_PERM_REGULAR, NULL); assert(0==rc); f_out("Added file: %s\n", fname); @@ -138,12 +138,12 @@ ////////////////////////////////////////////////////////////// // Step 5: ... f_out("Now we'll try again so we can ensure that deltaing " "of parent file content works.\n"); fsl_deck_derive(&d); - setup_deck(&d, "Modified Makefile.in."); - fname = "Makefile.in"; + setup_deck(&d, "Modified GNUmakefile."); + fname = "GNUmakefile"; rc = fsl_buffer_fill_from_filename(&content, fname); assert(0==rc); rc = fsl_buffer_append(&content, "\n# This is an edit. There are many " "like it, but this one is mine.\n", -1); Index: include/fossil-scm/auth.h ================================================================== --- include/fossil-scm/auth.h +++ include/fossil-scm/auth.h @@ -44,11 +44,11 @@ clone." Whether or not that will apply at this level to libfossil remains to be seen. TODO? Does fossil still use SHA1 for this? */ -FSL_EXPORT char * fsl_sha1_shared_secret( fsl_cx * f, char const * zLoginName, char const * zPw ); +FSL_EXPORT char * fsl_sha1_shared_secret( fsl_cx * const f, char const * zLoginName, char const * zPw ); /** Fetches the login group name (if any) for the given context's current repositorty db. If f has no opened repo, 0 is returned. @@ -56,11 +56,11 @@ form of a NUL-terminated string. The returned value (which may be 0) is owned by the caller, who must eventually fsl_free() it. The value (unlike in fossil(1)) is not cached because it may change via modification of the login group. */ -FSL_EXPORT char * fsl_repo_login_group_name(fsl_cx * f); +FSL_EXPORT char * fsl_repo_login_group_name(fsl_cx * const f); /** Fetches the login cookie name associated with the current repository db, or 0 if no repository is opened. @@ -71,11 +71,11 @@ The login cookie name is a string in the form "fossil-XXX", where XXX is the first 16 hex digits of either the repo's 'login-group-code' or 'project-code' config values (in that order). */ -FSL_EXPORT char * fsl_repo_login_cookie_name(fsl_cx * f); +FSL_EXPORT char * fsl_repo_login_cookie_name(fsl_cx * const f); /** Searches for a user ID (from the repo.user.uid DB field) for a given username and password. The password may be either its hashed form or non-hashed form (if it is not exactly 40 bytes long, that is!). @@ -86,11 +86,11 @@ If any of the arguments are NULL, FSL_RC_MISUSE is returned. f must have an opened repo, else FSL_RC_NOT_A_REPO is returned. */ -FSL_EXPORT int fsl_repo_login_search_uid(fsl_cx * f, char const * zUsername, +FSL_EXPORT int fsl_repo_login_search_uid(fsl_cx * const f, char const * zUsername, char const * zPasswd, fsl_id_t * pId); /** Clears all login state for the given user ID. If the ID is <=0 then ALL logins are cleared. Has no effect on the built-in pseudo-users. @@ -101,13 +101,13 @@ f must have an opened repo, or FSL_RC_NOT_A_REPO is returned. TODO: there are currently no APIs for _setting_ the state this function clears! */ -FSL_EXPORT int fsl_repo_login_clear( fsl_cx * f, fsl_id_t userId ); +FSL_EXPORT int fsl_repo_login_clear( fsl_cx * const f, fsl_id_t userId ); #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* ORG_FOSSIL_SCM_FSL_AUTH_H_INCLUDED */ Index: include/fossil-scm/checkout.h ================================================================== --- include/fossil-scm/checkout.h +++ include/fossil-scm/checkout.h @@ -11,11 +11,11 @@ SPDX-FileType: Code Heavily indebted to the Fossil SCM project (https://fossil-scm.org). */ -/** @file fossil-checkout.h +/** @file checkout.h fossil-checkout.h declares APIs specifically dealing with checkout-side state, as opposed to purely repository-db-side state or non-content-related APIs. */ @@ -2800,10 +2800,127 @@ comprised of only 3 elements, {".fslckout", "_FOSSIL_", NULL}, but the order of the non-NULL elements is unspecified by the interface. */ FSL_EXPORT char const ** fsl_ckout_dbnames(void); +/** + Tries to open a checked-out fossil repository db in the given + directory. This routine canonicalizes its dirName argument using + fsl_file_canonical_name(), then passes that and checkParentDirs on + to fsl_ckout_db_search() to find a checkout db, so see that routine + for how it searches. If dirName is NULL, "." is implied. + + If this routine finds/opens a checkout, it also tries to open + the repository database from which the checkout derives, and + fails if it cannot. The library never allows a checkout to be + opened without its corresponding repository partner because + a checkout has hard dependencies on the repo's state. + + Returns 0 on success. If there is an error opening or validating + the checkout or its repository db, f's error state will be + updated. Error codes/conditions include: + + - FSL_RC_MISUSE if f is NULL. + + - FSL_RC_ACCESS if f already has and opened checkout. + + - FSL_RC_OOM if an allocation fails. + + - FSL_RC_NOT_FOUND if no checkout is foud or if a checkout's + repository is not found. + + - FSL_RC_RANGE if dirname is not NULL but has a length of 0. + + - Various codes from fsl_getcwd() (if dirName is NULL). + + - Various codes if opening the associated repository DB fails. + + TODO: there's really nothing in the architecture which restricts a + checkout db to being in the same directory as the checkout, except + for some historical bits which "could" be refactored. It "might be + interesting" to eventually provide a variant which opens a checkout + db file directly. We have the infrastructure, just need some + refactoring. It "shouldn't" require any trickery or + incompatibilities with fossil(1). +*/ +FSL_EXPORT int fsl_ckout_open_dir( fsl_cx * const f, char const * dirName, + bool checkParentDirs ); + + +/** + Searches the given directory (or the current directory if dirName + is 0) for a fossil checkout database file named one of (_FOSSIL_, + .fslckout). If it finds one, it returns 0 and appends the file's + path to pOut if pOut is not 0. If neither is found AND if + checkParentDirs is true an then it moves up the path one directory + and tries again, until it hits the root of the dirPath (see below + for a note/caveat). + + If dirName is NULL then it behaves as if it had been passed the + absolute path of the current directory (as determined by + fsl_getcwd()). + + This function does no normalization of dirName. Because of that... + + Achtung: if dirName is relative, this routine might not find a + checkout where it would find one if given an absolute path (because + it traverses the path string given it instead of its canonical + form). Whether this is a bug or a feature is not yet clear. When in + doubt, use fsl_file_canonical_name() to normalize the directory + name before passing it in here. If it turns out that we always want + that behaviour, this routine will be modified to canonicalize the + name. + + This routine can return at least the following error codes: + + - FSL_RC_NOT_FOUND: either no checkout db was found or the given + directory was not found. + + - FSL_RC_RANGE if dirName is an empty string. (We could arguably + interpret this as a NULL string, i.e. the current directory.) + + - FSL_RC_OOM if allocation of a filename buffer fails. + + TODO? + + - Why was the decision made not to canonicalize dirName from here? + We might want to change that. + +*/ +FSL_EXPORT int fsl_ckout_db_search( char const * dirName, + bool checkParentDirs, + fsl_buffer * const pOut ); + + +/** + If fsl_ckout_open_dir() (or similar) has been used to open a + checkout db, this call closes that db. + + If an associated repository is also opened (as it should always be + when a checkout is opened), it is necessarily closed by this + operation because a checkout db is only valid with its associated + repository. If no checkout is opened but a repository is, this + function DOES NOT close that repository: use fsl_repo_close() or + fsl_cx_close_dbs() to close it (noting that all dbs are implicitly + closed by fsl_cx_finalize()). + + In the general case, fsl_repo_close() or fsl_cx_close_dbs(), + instead of this routine, can be used to close a checkout db. + + Returns 0 on success or if no checkout db is opened. It may + propagate an error from the db layer if closing/detaching the db + fails. Returns FSL_RC_MISUSE if f has any transactions pending. + + Sidebar: this is NOT related to fossil(1)'s "close" feature, which + "closes" a checkout by deleting the checkout database. + + @see fsl_ckout_open() + @see fsl_repo_close() + @see fsl_cx_close_dbs() +*/ +FSL_EXPORT int fsl_ckout_close( fsl_cx * const f ); + #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* ORG_FOSSIL_SCM_FSL_CHECKOUT_H_INCLUDED */ Index: include/fossil-scm/cli.h ================================================================== --- include/fossil-scm/cli.h +++ include/fossil-scm/cli.h @@ -525,20 +525,10 @@ (but might not have an opened checkout/repository) if fsl_setup() succeeds. */ FSL_EXPORT fcli_t fcli; -/** - Equivalent to `fcli_setup_v2(argc,argv,fcli.cliFlags,fcli.appHelp)`. - - @see fcli_pre_setup() - @see fcli_setup_v2() - @see fcli_end_of_main() - @deprecated Its signature will change to fcli_setup_v2()'s at some point. -*/ -FSL_EXPORT int fcli_setup(int argc, char const * const * argv ); - /** Initializes fcli's state and CLI flags processing. MUST BE CALLED BEFORE fsl_malloc() and friends are used, as this swaps out the allocator with one which aborts on OOM. (But see Index: include/fossil-scm/confdb.h ================================================================== --- include/fossil-scm/confdb.h +++ include/fossil-scm/confdb.h @@ -10,11 +10,11 @@ SPDX-ArtifactOfProjectName: Libfossil SPDX-FileType: Code Heavily indebted to the Fossil SCM project (https://fossil-scm.org). */ -/** @file fossil-confdb.h +/** @file confdb.h fossil-confdb.h declares APIs dealing with fossil's various configuration option storage backends. */ Index: include/fossil-scm/core.h ================================================================== --- include/fossil-scm/core.h +++ include/fossil-scm/core.h @@ -11,11 +11,11 @@ SPDX-FileType: Code Heavily indebted to the Fossil SCM project (https://fossil-scm.org). */ -/** @file fossil-core.h +/** @file core.h This file declares the core SCM-related public APIs. */ #include "fossil-scm/util.h" /* MUST come first b/c of config macros */ @@ -115,11 +115,11 @@ attached or not and will fail in their documented ways when the role's corresponding database has not yet been attached. e.g. `fsl_cx_db_repo()` will return `NULL` if a repo database is not attached. - @see fsl_db_role_label() + @see fsl_db_role_name() @see fsl_cx_db_name_for_role() @see fsl_cx_db_file_for_role() */ enum fsl_dbrole_e { /** @@ -947,70 +947,10 @@ /** va_list counterpart to fsl_outputf(). */ FSL_EXPORT int fsl_outputfv( fsl_cx * const f, char const * fmt, va_list args ); -/** - Opens the given db file name as f's repository. Returns 0 on - success. On error it sets f's error state and returns that code - unless the error was FSL_RC_MISUSE (which indicates invalid - arguments and it does not set the error state). - - Returns FSL_RC_ACCESS if f already has an opened repo db (use - fsl_repo_close() or fsl_ckout_close() to close it). - - Returns FSL_RC_NOT_FOUND if repoDbFile is not found, as this - routine cannot create a new repository db. - - Results are undefined if any argument is NULL. - - When a repository is opened, the fossil-level user name - associated with f (if any) is overwritten with the default user - from the repo's login table (the one with uid=1). Thus - fsl_cx_user_get() may return a value even if the client has not - called fsl_cx_user_set(). - - It would be nice to have a parameter specifying that the repo - should be opened read-only. That's not as straightforward as it - sounds because of how the various dbs are internally managed - (via one db handle). Until then, the permissions of the - underlying repo file will determine how it is opened. i.e. a - read-only repo will be opened read-only. - - - Potentially interesting side-effects: - - - On success this re-sets several bits of f's configuration to - match the repository-side settings. - - @see fsl_repo_create() - @see fsl_repo_close() -*/ -FSL_EXPORT int fsl_repo_open( fsl_cx * const f, char const * repoDbFile/*, char readOnlyCurrentlyIgnored*/ ); - -/** - If fsl_repo_open_xxx() has been used to open a respository db, this - call closes that db. - - Returns 0 on success or if no config db is opened. It may propagate - an error from the db layer if closing/detaching the db - fails. Returns FSL_RC_MISUSE if f has any transactions pending or - if f still has a checkout opened (a checkout db is only valid in - conjunction with its repository db). - - If a repository is opened "indirectly" via fsl_ckout_open_dir() - then attempting to close it using this function will result in - FSL_RC_MISUSE and f's error state will hold a description of the - problem (the checkout must be closed before closing its - repository). Such a repository will be closed implicitly when the - checkout db is closed. - - @see fsl_repo_open() - @see fsl_repo_create() -*/ -FSL_EXPORT int fsl_repo_close( fsl_cx * const f ); - /** Sets or clears (if userName is NULL or empty) the default repository user name for operations which require one. Returns 0 on success, FSL_RC_MISUSE if f is NULL, @@ -1049,281 +989,10 @@ @see fsl_cx_user_guess() */ FSL_EXPORT char const * fsl_cx_user_get( fsl_cx const * const f ); -/** - Configuration parameters for fsl_repo_create(). Always - copy-construct these from fsl_repo_create_opt_empty - resp. fsl_repo_create_opt_empty_m in order to ensure proper - behaviour vis-a-vis default values. - - TODOs: - - - Add project name/description, and possibly other - configuration bits. - - - Allow client to set password for default user (currently set - randomly, as fossil(1) does). -*/ -struct fsl_repo_create_opt { - /** - The file name for the new repository. - */ - char const * filename; - /** - Fossil user name for the admin user in the new repo. If NULL, - defaults to the Fossil context's user (see - fsl_cx_user_get()). If that is NULL, it defaults to - "root" for historical reasons. - */ - char const * username; - - /** - The comment text used for the initial commit. If NULL or empty - (starts with a NUL byte) then no initial check is - created. fossil(1) is largely untested with that scenario (but - it seems to work), so for compatibility it is not recommended - that this be set to NULL. - - The default value (when copy-initialized) is "egg". There's a - story behind the use of "egg" as the initial checkin comment, - and it all started with a typo: "initial chicken" - */ - char const * commitMessage; - - /** - Mime type for the commit message (manifest N-card). Manifests - support this but fossil(1) has never (as of 2021-02) made use of - it. It is provided for completeness but should, for - compatibility's sake, probably not be set, as the fossil UI may - not honor it. The implied default is text/x-fossil-wiki. Other - ostensibly legal values include text/plain and text/x-markdown. - This API will accept any value, but results are technically - undefined with any values other than those listed above. - */ - char const * commitMessageMimetype; - - /** - If not NULL and not empty, fsl_repo_create() will use this - repository database to copy the configuration, copying over - the following settings: - - - The reportfmt table, overwriting any existing entries. - - - The user table fields (cap, info, mtime, photo) are copied - for the "system users". The system users are: anonymous, - nobody, developer, reader. - - - The vast majority of the config table is copied, arguably - more than it should (e.g. the 'manifest' setting). - */ - char const * configRepo; - - /** - If false, fsl_repo_create() will fail if this->filename - already exists. - */ - bool allowOverwrite; - -}; -typedef struct fsl_repo_create_opt fsl_repo_create_opt; - -/** Initialized-with-defaults fsl_repo_create_opt struct, intended - for in-struct initialization. */ -#define fsl_repo_create_opt_empty_m { \ - NULL/*filename*/, \ - NULL/*username*/, \ - "egg"/*commitMessage*/, \ - NULL/*commitMessageMimetype*/, \ - NULL/*configRepo*/, \ - false/*allowOverwrite*/ \ - } - -/** Initialized-with-defaults fsl_repo_create_opt struct, intended - for copy-initialization. */ -FSL_EXPORT const fsl_repo_create_opt fsl_repo_create_opt_empty; - -/** - Creates a new repository database using the options provided in the - second argument. If f is not NULL, it must be a valid context - instance, though it need not have an opened checkout/repository. If - f has an opened repo or checkout, this routine closes them but that - closing _will fail_ if a transaction is currently active! - - If f is NULL, a temporary context is used for creating the - repository, in which case the caller will not have access to - detailed error information (only the result code) if this operation - fails. In that case, the resulting repository file will, on - success, be found at the location referred to by opt.filename. - - The opt argument may not be NULL. - - If opt->allowOverwrite is false (0) and the file exists, it fails - with FSL_RC_ALREADY_EXISTS, otherwise is creates/overwrites the - file. This is a destructive operation if opt->allowOverwrite is - true, so be careful: the existing database will be truncated and - re-created. - - This operation installs the various "static" repository schemas - into the db, sets up some default settings, and installs a - default user. - - This operation always closes any repository/checkout opened by f - because setting up the new db requires wiring it to f to set up - some of the db-side infrastructure. The one exception is if - argument validation fails, in which case f's repo/checkout-related - state are not modified. Note that closing will fail if a - transaction is currently active and that, in turn, will cause this - operation to fail. - - See the fsl_repo_create_opt docs for more details regarding the - creation options. - - On success, 0 is returned and f (if not NULL) is left with the - new repository opened and ready for use. On error, f's error - state is updated and any number of the FSL_RC_xxx codes may be - returned - there are no less than 30 different _potential_ error - conditions on the way to creating a new repository. - - If initialization of the repository fails, this routine will - attempt to remove its partially-initialize corpse from the - filesystem but will ignore any errors encountered while doing so. - - Example usage: - - ``` - fsl_repo_create_opt opt = fsl_repo_create_opt_empty; - int rc; - opt.filename = "my.fossil"; - // ... any other opt.xxx you want to set, e.g.: - // opt.user = "fred"; - // Assume fsl is a valid fsl_cx instance: - rc = fsl_repo_create(fsl, &opt); - if(rc) { ...error... } - else { - fsl_db * db = fsl_cx_db_repo(f); - assert(db); // == the new repo db - ... - } - ``` - - @see fsl_repo_open() - @see fsl_repo_close() -*/ -FSL_EXPORT int fsl_repo_create(fsl_cx * f, fsl_repo_create_opt const * opt ); - -/** - UNTESTED. - - Returns true if f has an opened repository database which is - opened in read-only mode, else returns false. -*/ -FSL_EXPORT char fsl_repo_is_readonly(fsl_cx const * f); - -/** - Tries to open a checked-out fossil repository db in the given - directory. This routine canonicalizes its dirName argument using - fsl_file_canonical_name(), then passes that and checkParentDirs on - to fsl_ckout_db_search() to find a checkout db, so see that routine - for how it searches. - - If this routine finds/opens a checkout, it also tries to open - the repository database from which the checkout derives, and - fails if it cannot. The library never allows a checkout to be - opened without its corresponding repository partner because - a checkout has hard dependencies on the repo's state. - - Returns 0 on success. If there is an error opening or validating - the checkout or its repository db, f's error state will be - updated. Error codes/conditions include: - - - FSL_RC_MISUSE if f is NULL. - - - FSL_RC_ACCESS if f already has and opened checkout. - - - FSL_RC_OOM if an allocation fails. - - - FSL_RC_NOT_FOUND if no checkout is foud or if a checkout's - repository is not found. - - - FSL_RC_RANGE if dirname is not NULL but has a length of 0. - - - Various codes from fsl_getcwd() (if dirName is NULL). - - - Various codes if opening the associated repository DB fails. - - TODO: there's really nothing in the architecture which restricts a - checkout db to being in the same directory as the checkout, except - for some historical bits which "could" be refactored. It "might be - interesting" to eventually provide a variant which opens a checkout - db file directly. We have the infrastructure, just need some - refactoring. It "shouldn't" require any trickery or - incompatibilities with fossil(1). -*/ -FSL_EXPORT int fsl_ckout_open_dir( fsl_cx * const f, char const * dirName, - bool checkParentDirs ); - - -/** - Searches the given directory (or the current directory if dirName - is 0) for a fossil checkout database file named one of (_FOSSIL_, - .fslckout). If it finds one, it returns 0 and appends the file's - path to pOut if pOut is not 0. If neither is found AND if - checkParentDirs is true an then it moves up the path one directory - and tries again, until it hits the root of the dirPath (see below - for a note/caveat). - - If dirName is NULL then it behaves as if it had been passed the - absolute path of the current directory (as determined by - fsl_getcwd()). - - This function does no normalization of dirName. Because of that... - - Achtung: if dirName is relative, this routine might not find a - checkout where it would find one if given an absolute path (because - it traverses the path string given it instead of its canonical - form). Whether this is a bug or a feature is not yet clear. When in - doubt, use fsl_file_canonical_name() to normalize the directory - name before passing it in here. If it turns out that we always want - that behaviour, this routine will be modified to canonicalize the - name. - - This routine can return at least the following error codes: - - - FSL_RC_NOT_FOUND: either no checkout db was found or the given - directory was not found. - - - FSL_RC_RANGE if dirName is an empty string. (We could arguably - interpret this as a NULL string, i.e. the current directory.) - - - FSL_RC_OOM if allocation of a filename buffer fails. - - TODO? - - - Why was the decision made not to canonicalize dirName from here? - We might want to change that. - -*/ -FSL_EXPORT int fsl_ckout_db_search( char const * dirName, - bool checkParentDirs, - fsl_buffer * const pOut ); - - -/** - If fsl_ckout_open_dir() (or similar) has been used to open a - checkout db, this call closes that db. - - Returns 0 on success or if no config db is opened. It may propagate - an error from the db layer if closing/detaching the db - fails. Returns FSL_RC_MISUSE if f has any transactions pending. - - This also closes the repository which was implicitly opened for the - checkout. -*/ -FSL_EXPORT int fsl_ckout_close( fsl_cx * const f ); - /** Attempts to close any opened databases (repo/checkout/config). This will fail if any transactions are pending. Any databases which are already closed are silently skipped. This will fail if any cached statements are currently active for the being-closed @@ -1382,39 +1051,10 @@ */ FSL_EXPORT char const * fsl_cx_db_file_for_role(fsl_cx const * f, fsl_dbrole_e r, fsl_size_t * len); -/** - Similar to fsl_cx_db_file_ckout() and friends except that it - applies to DB name (as opposed to DB _file_ name) implied by the - specified role (2nd parameter). If no such role is opened, or the - role is invalid, NULL is returned. - - If the 3rd argument is not NULL, it is set to the length, in bytes, - of the returned string. The returned strings are static and - NUL-terminated. - - This is the "easiest" way to figure out the DB name of the given - role, independent of what order f's databases were opened - (because the first-opened DB is always called "main"). - - The Fossil-standard names of its primary databases are: "localdb" - (checkout), "repository", and "configdb" (global config DB), but - libfossil uses "ckout", "repo", and "cfg", respective. So long as - queries use table names which unambiguously refer to a given - database, the DB name is normally not needed. It is needed when - creating new non-TEMP db tables and views. By default such - tables/views would go into the "main" DB, which is actually a - transient DB in this API, so it's important to use the correct DB - name when creating such constructs. - - Note that the role of FSL_DBROLE_TEMP is invalid here. -*/ -FSL_EXPORT char const * fsl_cx_db_name_for_role(fsl_cx const * f, - fsl_dbrole_e r, - fsl_size_t * len); /** If f has an opened checkout db (from fsl_ckout_open_dir()) then this function returns the directory part of the path for the checkout, including (for historical and internal convenience @@ -1433,11 +1073,11 @@ fsl_size_t * len); /** Returns a handle to f's main db (which may or may not have any relationship to the repo/checkout/config databases - that's - unspecified!), or NULL if f has no opened repo or checkout db. The + unspecified!), or NULL if f has no opened repo or checkout db. The returned object is owned by f and the client MUST NOT do any of the following: - Close the db handle. @@ -1453,15 +1093,18 @@ Note that the global config db uses a separate db handle accessible via fsl_cx_db_config(). Design notes: - The current architecture uses an in-memory db as the "main" db and - attaches the repo and checkout dbs using well-defined names. Even - so, it uses separate fsl_db instances to track each one so that we - "could," if needed, switch back to a multi-db-instance approach if - needed. + The current (2021-12-31) architecture treats one of the REPO or + CHECKOUT dbs as its MAIN db and ATTACHes (if needed) the other + one. Even so, it uses separate fsl_db instances to track each one + so that we can keep track of their file names. Prior to 2021-12-31, + the API internally opened a TEMP/IN-MEMORY db and attached both the + repo and checkout dbs to it. Though easier to work with internally, + that approach has minor locking- and consistency-related issues + which justified rewiring it. @see fsl_cx_db_repo() @see fsl_cx_db_ckout() @see fsl_cx_db_config() */ @@ -1470,18 +1113,22 @@ /** If f is not NULL and has had its repo opened via fsl_repo_open(), fsl_ckout_open_dir(), or similar, this returns a pointer to that database, else it returns NULL. + Note that this may return the same db handle as fsl_cx_db_ckout(). + @see fsl_cx_db() */ FSL_EXPORT fsl_db * fsl_cx_db_repo( fsl_cx * const f ); /** If f is not NULL and has had a checkout opened via fsl_ckout_open_dir() or similar, this returns a pointer to that database, else it returns NULL. + + Note that this may return the same db handle as fsl_cx_db_repo(). @see fsl_cx_db() */ FSL_EXPORT fsl_db * fsl_cx_db_ckout( fsl_cx * const f ); @@ -1564,11 +1211,11 @@ /** If f has an opened configuration db then its handle is returned, else 0 is returned. For API consistency's sake, the db handle's "MAIN" name is aliased - to fsl_db_role_label(FSL_DBROLE_CONFIG). + to fsl_db_role_name(FSL_DBROLE_CONFIG). @see fsl_config_open() @see fsl_config_close() */ FSL_EXPORT fsl_db * fsl_cx_db_config( fsl_cx * const f ); @@ -2030,16 +1677,18 @@ @see fsl_cx_db() */ FSL_EXPORT int fsl_cx_transaction_level(fsl_cx * const f); /** Returns the same as passing fsl_cx_db() to - fsl_db_transaction_begin(). + fsl_db_transaction_begin(). Returns FSL_RC_MISUSE, and updates f's + error state, if f has no repo or checkout db opened. */ FSL_EXPORT int fsl_cx_transaction_begin(fsl_cx * const f); /** Returns the same as passing fsl_cx_db() to - fsl_db_transaction_end(). + fsl_db_transaction_end(). Returns FSL_RC_MISUSE, and updates f's + error state, if f has no repo or checkout db opened. */ FSL_EXPORT int fsl_cx_transaction_end(fsl_cx * const f, bool doRollback); /** Installs or (if f is NULL) uninstalls a confirmation callback for Index: include/fossil-scm/db.h ================================================================== --- include/fossil-scm/db.h +++ include/fossil-scm/db.h @@ -1551,11 +1551,13 @@ int openFlags ); /** Closes the given db handle and frees any resources owned by db. Results are undefined if db is NULL. If db is not opened, - this is a harmless no-op. + this is a harmless no-op but any memory which might be owned + by the db handle (e.g. its filename, which can be set + independently of being opened) will be cleaned up. If db was allocated using fsl_db_malloc() (as determined by examining db->allocStamp) then this routine also fsl_free()s it, otherwise it is assumed to either be on the stack or part of a larger struct and is not freed. @@ -1612,12 +1614,11 @@ Returns a db name string for the given fsl_db_role value. The string is static, guaranteed to live as long as the app. It returns NULL (or asserts in debug builds) if passed FSL_DBROLE_NONE or some value out of range for the enum. */ -FSL_EXPORT const char * fsl_db_role_label(enum fsl_dbrole_e r); - +FSL_EXPORT const char * fsl_db_role_name(enum fsl_dbrole_e r); /** Allocates a new fsl_db instance(). Returns NULL on allocation error. Note that fsl_db instances can often be used from the stack - allocating them dynamically is an uncommon case necessary ADDED include/fossil-scm/deprecated.h Index: include/fossil-scm/deprecated.h ================================================================== --- /dev/null +++ include/fossil-scm/deprecated.h @@ -0,0 +1,212 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +#if !defined(ORG_FOSSIL_SCM_LIBFOSSIL_DEPRECATED_H_INCLUDED) +#define ORG_FOSSIL_SCM_LIBFOSSIL_DEPRECATED_H_INCLUDED +/* + Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt + + SPDX-License-Identifier: BSD-2-Clause-FreeBSD + SPDX-FileCopyrightText: 2021 The Libfossil Authors + SPDX-ArtifactOfProjectName: Libfossil + SPDX-FileType: Code + + Heavily indebted to the Fossil SCM project (https://fossil-scm.org). +*/ + +/** @file deprecated.h + + This file holds APIs which are deprecated or otherwise "on the chopping + block." The libfossil public API has gathered a good deal of cruft + over the years, some of it rather unnecessary. +*/ + +/** + @deprecated fsl_db_role_name() is easier to deal with. + + Similar to fsl_cx_db_file_ckout() and friends except that it + applies to DB name (as opposed to DB _file_ name) implied by the + specified role (2nd parameter). If no such role is opened, or the + role is invalid, NULL is returned. + + If the 3rd argument is not NULL, it is set to the length, in bytes, + of the returned string. The returned strings are NUL-terminated and + are either static or owned by the db handle they correspond to. + + If the client does not care whether the db in question is + actually opened, the name for the corresponding role can be + fetched via fsl_db_role_name(). + + This is the "easiest" way to figure out the DB name of the given + role, independent of what order f's databases were opened + (because the first-opened DB is always called "main"). + + The Fossil-standard names of its primary databases are: "localdb" + (checkout), "repository", and "configdb" (global config DB), but + libfossil uses "ckout", "repo", and "cfg", respective. So long as + queries use table names which unambiguously refer to a given + database, the DB name is normally not needed. It is needed when + creating new non-TEMP db tables and views. By default such + tables/views would go into the "main" DB, and which one is the + "main" db is dependent on the order the DBs are opened, so it's + important to use the correct DB name when creating such constructs. + + Note that the role of FSL_DBROLE_TEMP is invalid here. +*/ +FSL_EXPORT char const * fsl_cx_db_name_for_role(fsl_cx const * const f, + fsl_dbrole_e r, + fsl_size_t * len); + +/** + Flags for use with text-diff generation APIs, + e.g. fsl_diff_text(). + + Maintenance reminders: + + - These values are holy and must not be changed without also + changing the corresponding code in diff.c. + + - Where these entries semantically overlap with their fsl_diff2_flag_e + counterparts, they MUST have the same values because some internal APIs + are used by both of the diff APIs. + + @deprecated Prefer fsl_diff2_flag_e and fsl_diff_v2() instead. +*/ +enum fsl_diff_flag_e { +/** Ignore end-of-line whitespace */ +FSL_DIFF_IGNORE_EOLWS = 0x01, +/** Ignore end-of-line whitespace */ +FSL_DIFF_IGNORE_ALLWS = 0x03, +/** Generate a side-by-side diff */ +FSL_DIFF_SIDEBYSIDE = 0x04, +/** Missing shown as empty files */ +FSL_DIFF_VERBOSE = 0x08, +/** Show filenames only. Not used in this impl! */ +FSL_DIFF_BRIEF = 0x10, +/** Render HTML. */ +FSL_DIFF_HTML = 0x20, +/** Show line numbers. */ +FSL_DIFF_LINENO = 0x40, +/** Suppress optimizations (debug). */ +FSL_DIFF_NOOPT = 0x0100, +/** Invert the diff (debug). */ +FSL_DIFF_INVERT = 0x0200, +/* ACHTUNG: do not use 0x0400 because of semantic + collision with FSL_DIFF2_CONTEXT_ZERO */ +/** Only display if not "too big." */ +FSL_DIFF_NOTTOOBIG = 0x0800, +/** Strip trailing CR */ +FSL_DIFF_STRIP_EOLCR = 0x1000, +/** + This flag tells text-mode diff generation to add ANSI color + sequences to some output. The colors are currently hard-coded + and non-configurable. This has no effect for HTML output, and + that flag trumps this one. It also currently only affects + unified diffs, not side-by-side. + + Maintenance reminder: this one currently has no counterpart in + fossil(1), is not tracked in the same way, and need not map to an + internal flag value. +*/ +FSL_DIFF_ANSI_COLOR = 0x2000 +}; + +/** + Generates a textual diff from two text inputs and writes + it to the given output function. + + pA and pB are the buffers to diff. + + contextLines is the number of lines of context to output. This + parameter has a built-in limit of 2^16, and values larger than + that get truncated. A value of 0 is legal, in which case no + surrounding context is provided. A negative value translates to + some unspecified default value. + + sbsWidth specifies the width (in characters) of the side-by-side + columns. If sbsWidth is not 0 then this function behaves as if + diffFlags contains the FSL_DIFF_SIDEBYSIDE flag. If sbsWidth is + negative, OR if diffFlags explicitly contains + FSL_DIFF_SIDEBYSIDE and sbsWidth is 0, then some default width + is used. This parameter has a built-in limit of 255, and values + larger than that get truncated to 255. + + diffFlags is a mask of fsl_diff_flag_t values. Not all of the + fsl_diff_flag_t flags are yet [sup]ported. + + The output is sent to out(outState,...). If out() returns non-0 + during processing, processing stops and that result is returned + to the caller of this function. + + Returns 0 on success, FSL_RC_OOM on allocation error, + FSL_RC_MISUSE if any arguments are invalid, FSL_RC_TYPE if any + of the content appears to be binary (contains embedded NUL + bytes), FSL_RC_RANGE if some range is exceeded (e.g. the maximum + number of input lines). + + None of (pA, pB, out) may be NULL. + + TODOs: + + - Add a predicate function for outputing only matching + differences, analog to fossil(1)'s regex support (but more + flexible). + + - Expose the raw diff-generation bits via the internal API + to facilitate/enable the creation of custom diff formats. + + @see fsl_diff_v2() + @deprecated Prefer fsl_diff_v2() for new code. +*/ +FSL_EXPORT int fsl_diff_text(fsl_buffer const *pA, fsl_buffer const *pB, + fsl_output_f out, void * outState, + short contextLines, short sbsWidth, + int diffFlags ); + +/** + Functionally equivalent to: + + ``` + fsl_diff_text(pA, pB, fsl_output_f_buffer, pOut, + contextLines, sbsWidth, diffFlags); + ``` + + Except that it returns FSL_RC_MISUSE if !pOut. + + @see fsl_diff_v2() + @deprecated Prefer fsl_diff_v2() for new code. +*/ +FSL_EXPORT int fsl_diff_text_to_buffer(fsl_buffer const *pA, fsl_buffer const *pB, + fsl_buffer *pOut, short contextLines, + short sbsWidth, int diffFlags ); + + +/** + Equivalent to `fcli_setup_v2(argc,argv,fcli.cliFlags,fcli.appHelp)`. + + @see fcli_pre_setup() + @see fcli_setup_v2() + @see fcli_end_of_main() + @deprecated Its signature will change to fcli_setup_v2()'s at some point. +*/ +FSL_EXPORT int fcli_setup(int argc, char const * const * argv ); + +/** @internal + + Performs the same job as fsl_diff_text() but produces the results + in the low-level form of an array of "copy/delete/insert triples." + This is primarily intended for internal use in other + library-internal algorithms, not for client code. Note all + FSL_DIFF_xxx flags apply to this form. + + Returns 0 on success, any number of non-0 codes on error. On + success *outRaw will contain the resulting array, which must + eventually be fsl_free()'d by the caller. On error *outRaw is not + modified. + + @deprecated Use fsl_diff_v2_raw() instead. +*/ +int fsl__diff_text_raw(fsl_buffer const *p1, fsl_buffer const *p2, + int diffFlags, int ** outRaw); + +#endif +/* ORG_FOSSIL_SCM_LIBFOSSIL_DEPRECATED_H_INCLUDED */ Index: include/fossil-scm/internal.h ================================================================== --- include/fossil-scm/internal.h +++ include/fossil-scm/internal.h @@ -281,85 +281,44 @@ struct fsl_cx { /** A pointer to the "main" db handle. Exactly which db IS the main db is, because we have three DBs, not generally knowble. - As of this writing (20141027, updated 20211018) the following + As of this writing (20211231) the following applies: - dbMain always points to &this->dbMem (a temp or ":memory:" - (unspecified!) db opened by fsl_cx_init()), and the - repo/ckout DBs get ATTACHed to that one. Their separate - handles (this->{repo,ckout}.db) are used to store the name - and file path to each one (even though they have no real db - handle associated with them). + dbMain always points to the first one of this->repo.db or + this->ckout.db which is opened. this->dbMain->role holds a + bitmask of the role(s) which is/are connected (FSL_DBROLE_REPO + and FSL_DBROLE_CKOUT, noting that we never open a checkout + without its associated repo, but a repo without a checkout is a + common use case). + + The first db which gets opened gets its name aliased to its + fsl_db_role_name() value so that we needn't concern ourselves, + from SQL code, with which db is "main". As of 20211230, f->config.db is its own handle, not ATTACHed with the others. Its db gets renamed to - fsl_db_role_label(FSL_DBROLE_CONFIG). + fsl_db_role_name(FSL_DBROLE_CONFIG). Internal code should rely as little as possible on the actual arrangement of internal DB handles, and should use fsl_cx_db_repo(), fsl_cx_db_ckout(), and fsl_cx_db_config() to get a handle to the specific db they want. Currently they will - always return NULL or the same handle, but that design decision + always return NULL or may return the same handle, but that design decision might change at some point, so the public API treats them as - separate entities. _That said_: at this point (2021-10-18), - treating them as separate handles often proves to be annoying in - their usage, and newer code will sometimes use (e.g.) - fsl_cx_prepare() in lieu of explicitely using fsl_db_prepare() - with the ostensibly db-specific handle when it knows that the - required db is indeed attached. In other words: the internals, in - some places, are starting to rely on this long-established - convention of having a single sqlite3 object and multiple - attached databases, _and that's okay_. + separate entities. */ fsl_db * dbMain; /** Marker which tells us whether fsl_cx_finalize() needs to fsl_free() this instance or not. */ void const * allocStamp; - /** - A ":memory:" (or "") db to work around - open-vs-attach-vs-main-vs-real-name problems wrt to the - repo/ckout/config dbs. This db handle gets opened automatically - at startup and all others which a fsl_cx manages get ATTACHed to - it. Thus the other internal fsl_db objects, e.g. this->repo.db, - may hold state, such as the path to the current repo db, but they - do NOT hold an sqlite3 db handle. Assigning them the handle of - this->dbMain would indeed simplify certain work but it would - require special care to ensure that we never sqlite3_close() - those out from under this->dbMain. - - Reminder to self 2021-12-03: sqlite has since added an option - to give the MAIN db a name of one's choice: - - ``` - sqlite3_db_config(db, SQLITE_DBCONFIG_MAINDBNAME,...) - ``` - - that could be used to solve our connection-juggling problems by - OPEN()'ing the first (repo/checkout/config) db and ATTACHing the others - to that. Prior to the above API addition, that was not feasible because - it meant that juggling the db names was a true pain in the butt. - - However, the global config throws a wrench in that: we need to be - able to open and close that particular db on demand because it's - used by an arbitrary number of applications and we need to avoid - locking it. If an app (like `f-config`) were to open that db - first, it would become the main db and we could no longer close - it once a repo and/or checkout had been attached. That said, we - could get away with using a completely separate sqlite3 instance - for that particular db if we really wanted to. There's no - pressing need to keep that db on the same connection as the - checkout/repo. - */ - fsl_db dbMem; - /** Holds info directly related to a checkout database. */ struct { /** @@ -752,15 +711,20 @@ f-rebuild's time in less than half. The rest of that time was spent building the SQL strings (indirectly via fsl_appendf()) to figure out if they corresponded to a cached query or not. */ struct { + /** Query fetching [delta].[srcid] for a given [delta].[rid]. */ fsl_stmt deltaSrcId; + /** Query fetching a [blob].[rid] for an exact-match + [blob].[uuid]. */ fsl_stmt uuidToRid; + /** Query fetching [blob].[rid] for a [blob].[uuid] prefix. */ fsl_stmt uuidToRidGlob; + /** Query fetching [blob].[size] for a [blob].[rid] */ fsl_stmt contentSize; - fsl_stmt nextEntry; + fsl_stmt nextEntry/*placeholder*/; } stmt; /** Holds a list of temp-dir names. Must be allocated using fsl_temp_dirs_get() and freed using fsl_temp_dirs_free(). @@ -836,11 +800,10 @@ Initialized-with-defaults fsl_cx struct. */ #define fsl_cx_empty_m { \ NULL /*dbMain*/, \ NULL/*allocStamp*/, \ - fsl_db_empty_m /* dbMem */, \ {/*ckout*/ \ fsl_db_empty_m /*db*/, \ NULL /*dir*/, 0/*dirLen*/, \ -1/*rid*/, NULL/*uuid*/, 0/*mtime*/ \ }, \ @@ -1292,20 +1255,20 @@ Clears and frees all (char*) members of db but leaves the rest intact. If alsoErrorState is true then the error state is also freed, else it is kept as well. */ -void fsl__db_clear_strings(fsl_db * const db, bool alsoErrorState ); +void fsl__db_clear_strings(fsl_db * const db, bool alsoErrorState); /** @internal Returns 0 if db appears to have a current repository schema, 1 if it appears to have an out of date schema, and -1 if it appears to not be a repository. Results are undefined if db is NULL or not opened. */ -int fsl__db_repo_verify_schema(fsl_db * db); +int fsl__db_repo_verify_schema(fsl_db * const db); /** @internal Flags for APIs which add phantom blobs to the repository. The @@ -1688,31 +1651,12 @@ int fsl__search_doc_touch(fsl_cx * const f, fsl_satype_e saType, fsl_id_t rid, const char * docName); /** @internal - Performs the same job as fsl_diff_text() but produces the results - in the low-level form of an array of "copy/delete/insert triples." - This is primarily intended for internal use in other - library-internal algorithms, not for client code. Note all - FSL_DIFF_xxx flags apply to this form. - - Returns 0 on success, any number of non-0 codes on error. On - success *outRaw will contain the resulting array, which must - eventually be fsl_free()'d by the caller. On error *outRaw is not - modified. - - @deprecated Use fsl_diff_v2_raw() instead. -*/ -int fsl__diff_text_raw(fsl_buffer const *p1, fsl_buffer const *p2, - int diffFlags, int ** outRaw); - -/** @internal - - If the given file name is a reserved filename (case-insensitive) on - Windows platforms, a pointer to the reserved part of the name, else - NULL is returned. + Returns true if the given file name is a reserved filename + (case-insensitive) on Windows platforms, else returns false. zPath must be a canonical path with forward-slash directory separators. nameLen is the length of zPath. If negative, fsl_strlen() is used to determine its length. */ @@ -1727,11 +1671,10 @@ else it clears only entries for which the corresponding vfile entries are marked as unchanged and then cleans up remaining merge state if no file-level merge changes are pending. */ int fsl__ckout_clear_merge_state( fsl_cx * const f, bool fullWipe ); - /** @internal Installs or reinstalls the checkout database schema into f's open checkout db. Returns 0 on success, FSL_RC_NOT_A_CKOUT if f has @@ -2316,10 +2259,58 @@ as non-errors (result code 0). If non-0 is returned db's error state is updated with the current sqlite3_errmsg() string. */ int fsl__db_errcode(fsl_db * const db, int sqliteCode); +/** @internal + + Clears various internal caches and resets various + internally-cached values. +*/ +void fsl__cx_clear_repo_caches(fsl_cx * const f); + +/** @internal + + Returns one of the various fsl_db objects owned by f, or NULL. + + ACHTUNG and REMINDER TO SELF: the current (2021-12) design means + that only, at most, the FSL_DBROLE_CONFIG and _one_ of + FSL_DBROLE_REPO/FSL_DBROLE_CKOUT actually have an opened db handle. + + If passed FSL_DBROLE_MAIN, it will return whichever of the + REPO/CKOUT dbs are OPENed (not ATTACHed), or NULL if neither is. + + This returns a handle to the "level of abstraction" we need to + keep track of each db's name and db-specific other state. + + e.g. passing a role of FSL_DBROLE_CKOUT this does NOT return + the same thing as fsl_cx_db_ckout(). +*/ +fsl_db * fsl__cx_db_for_role(fsl_cx * const f, fsl_dbrole_e r); + +/** @internal + + Plug in fsl_cx-specific db functionality into the given db handle. + This must only be passed the MAIN db handle for the context and it + must do so before f->dbMain is assigned. That is, db must be one of + the REPOSITORY or CHECKOUT dbs (whichever gets opened first). +*/ +int fsl__cx_init_db(fsl_cx * const f, fsl_db * const db); + +/** @internal + + Opens or attaches the given db file to f with the given role. This + function "should" be static but we need it in repo.c when creating + a new repository. createIfNotExists specifies to use open mode + of FSL_OPEN_F_RWC instead of FSL_OPEN_F_RWC. Normally that is NOT + what's desired. + + This function has very specific preconditions which are assert()ed. + See the code for details. +*/ +int fsl__cx_attach_role(fsl_cx * const f, const char *zDbName, + fsl_dbrole_e r, bool createIfNotExists); /** @internal Maximum length of a line in a text file, in bytes. (2**15 = 32k) */ Index: include/fossil-scm/repo.h ================================================================== --- include/fossil-scm/repo.h +++ include/fossil-scm/repo.h @@ -11,11 +11,11 @@ SPDX-FileType: Code Heavily indebted to the Fossil SCM project (https://fossil-scm.org). */ -/** @file fossil-repo.h +/** @file repo.h fossil-repo.h declares APIs specifically dealing with repository-db-side state, as opposed to specifically checkout-side state or non-content-related APIs. */ @@ -2347,11 +2347,11 @@ As a special case, if rid==0 then this only returns true if the repository currently has no content in the blob table. */ -FSL_EXPORT char fsl_rid_is_a_checkin(fsl_cx * f, fsl_id_t rid); +FSL_EXPORT char fsl_rid_is_a_checkin(fsl_cx * const f, fsl_id_t rid); /** Fetches the list of all directory names for a given checkin record id or (if rid is negative) the whole repo over all of its combined history. Each name entry in the list is appended to tgt. The @@ -4033,11 +4033,242 @@ require a significantly larger set of possible change-type values and would require much type-specific handling (e.g. maybe reporting J-card differences for a pair of ticket decks). */ FSL_EXPORT int fsl_cidiff(fsl_cx * const f, fsl_cidiff_opt const * const opt); + +/** + Configuration parameters for fsl_repo_create(). Always + copy-construct these from fsl_repo_create_opt_empty + resp. fsl_repo_create_opt_empty_m in order to ensure proper + behaviour vis-a-vis default values. + + TODOs: + + - Add project name/description, and possibly other + configuration bits. + + - Allow client to set password for default user (currently set + randomly, as fossil(1) does). +*/ +struct fsl_repo_create_opt { + /** + The file name for the new repository. + */ + char const * filename; + /** + Fossil user name for the admin user in the new repo. If NULL, + defaults to the Fossil context's user (see + fsl_cx_user_get()). If that is NULL, it defaults to + "root" for historical reasons. + */ + char const * username; + + /** + The comment text used for the initial commit. If NULL or empty + (starts with a NUL byte) then no initial check is + created. fossil(1) is largely untested with that scenario (but + it seems to work), so for compatibility it is not recommended + that this be set to NULL. + + The default value (when copy-initialized) is "egg". There's a + story behind the use of "egg" as the initial checkin comment, + and it all started with a typo: "initial chicken" + */ + char const * commitMessage; + + /** + Mime type for the commit message (manifest N-card). Manifests + support this but fossil(1) has never (as of 2021-02) made use of + it. It is provided for completeness but should, for + compatibility's sake, probably not be set, as the fossil UI may + not honor it. The implied default is text/x-fossil-wiki. Other + ostensibly legal values include text/plain and text/x-markdown. + This API will accept any value, but results are technically + undefined with any values other than those listed above. + */ + char const * commitMessageMimetype; + + /** + If not NULL and not empty, fsl_repo_create() will use this + repository database to copy the configuration, copying over + the following settings: + + - The reportfmt table, overwriting any existing entries. + + - The user table fields (cap, info, mtime, photo) are copied + for the "system users". The system users are: anonymous, + nobody, developer, reader. + + - The vast majority of the config table is copied, arguably + more than it should (e.g. the 'manifest' setting). + */ + char const * configRepo; + + /** + If false, fsl_repo_create() will fail if this->filename + already exists. + */ + bool allowOverwrite; + +}; +typedef struct fsl_repo_create_opt fsl_repo_create_opt; + +/** Initialized-with-defaults fsl_repo_create_opt struct, intended + for in-struct initialization. */ +#define fsl_repo_create_opt_empty_m { \ + NULL/*filename*/, \ + NULL/*username*/, \ + "egg"/*commitMessage*/, \ + NULL/*commitMessageMimetype*/, \ + NULL/*configRepo*/, \ + false/*allowOverwrite*/ \ + } + +/** Initialized-with-defaults fsl_repo_create_opt struct, intended + for copy-initialization. */ +FSL_EXPORT const fsl_repo_create_opt fsl_repo_create_opt_empty; + +/** + Creates a new repository database using the options provided in the + second argument. If f is not NULL, it must be a valid context + instance, though it need not have an opened checkout/repository. If + f has an opened repo or checkout, this routine closes them but that + closing _will fail_ if a transaction is currently active! + + If f is NULL, a temporary context is used for creating the + repository, in which case the caller will not have access to + detailed error information (only the result code) if this operation + fails. In that case, the resulting repository file will, on + success, be found at the location referred to by opt.filename. + + The opt argument may not be NULL. + + If opt->allowOverwrite is false (0) and the file exists, it fails + with FSL_RC_ALREADY_EXISTS, otherwise is creates/overwrites the + file. This is a destructive operation if opt->allowOverwrite is + true, so be careful: the existing database will be truncated and + re-created. + + This operation installs the various "static" repository schemas + into the db, sets up some default settings, and installs a + default user. + + This operation always closes any repository/checkout opened by f + because setting up the new db requires wiring it to f to set up + some of the db-side infrastructure. The one exception is if + argument validation fails, in which case f's repo/checkout-related + state are not modified. Note that closing will fail if a + transaction is currently active and that, in turn, will cause this + operation to fail. + + See the fsl_repo_create_opt docs for more details regarding the + creation options. + + On success, 0 is returned and f (if not NULL) is left with the + new repository opened and ready for use. On error, f's error + state is updated and any number of the FSL_RC_xxx codes may be + returned - there are no less than 30 different _potential_ error + conditions on the way to creating a new repository. + + If initialization of the repository fails, this routine will + attempt to remove its partially-initialize corpse from the + filesystem but will ignore any errors encountered while doing so. + + Example usage: + + ``` + fsl_repo_create_opt opt = fsl_repo_create_opt_empty; + int rc; + opt.filename = "my.fossil"; + // ... any other opt.xxx you want to set, e.g.: + // opt.user = "fred"; + // Assume fsl is a valid fsl_cx instance: + rc = fsl_repo_create(fsl, &opt); + if(rc) { ...error... } + else { + fsl_db * db = fsl_cx_db_repo(f); + assert(db); // == the new repo db + ... + } + ``` + + @see fsl_repo_open() + @see fsl_repo_close() +*/ +FSL_EXPORT int fsl_repo_create(fsl_cx * f, fsl_repo_create_opt const * opt ); + +/** + Opens the given db file name as f's repository. Returns 0 on + success. On error it sets f's error state and returns that code + unless the error was FSL_RC_MISUSE (which indicates invalid + arguments and it does not set the error state). + + Returns FSL_RC_ACCESS if f already has an opened repo db (use + fsl_repo_close() or fsl_ckout_close() to close it). + + Returns FSL_RC_NOT_FOUND if repoDbFile is not found, as this + routine cannot create a new repository db. + + Results are undefined if any argument is NULL. + + When a repository is opened, the fossil-level user name + associated with f (if any) is overwritten with the default user + from the repo's login table (the one with uid=1). Thus + fsl_cx_user_get() may return a value even if the client has not + called fsl_cx_user_set(). + + It would be nice to have a parameter specifying that the repo + should be opened read-only. That's not as straightforward as it + sounds because of how the various dbs are internally managed + (via one db handle). Until then, the permissions of the + underlying repo file will determine how it is opened. i.e. a + read-only repo will be opened read-only. + + + Potentially interesting side-effects: + + - On success this re-sets several bits of f's configuration to + match the repository-side settings. + + @see fsl_repo_create() + @see fsl_repo_close() +*/ +FSL_EXPORT int fsl_repo_open( fsl_cx * const f, char const * repoDbFile/*, char readOnlyCurrentlyIgnored*/ ); + +/** + If fsl_repo_open() has been used to open a respository db, this + call closes that db. If an associated checkout is opened, it is + also closed, since a checkout is only valid with its associated + repository. + + Returns 0 on success or if no db is opened. It may propagate + an error from the db layer if closing/detaching the db + fails. Returns FSL_RC_MISUSE if f has any transactions pending. + + If a repository is opened "indirectly" via fsl_ckout_open_dir() + then attempting to close it using this function will result in + FSL_RC_MISUSE and f's error state will hold a description of the + problem (the checkout must be closed before closing its + repository). Such a repository will be closed implicitly when the + checkout db is closed. + + @see fsl_repo_open() + @see fsl_ckout_close() + @see fsl_repo_create() +*/ +FSL_EXPORT int fsl_repo_close( fsl_cx * const f ); + +/** + UNTESTED. + + Returns true if f has an opened repository database which is + opened in read-only mode, else returns false. +*/ +FSL_EXPORT bool fsl_repo_is_readonly(fsl_cx const * f); + #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* ORG_FOSSIL_SCM_FSL_REPO_H_INCLUDED */ Index: include/fossil-scm/util.h ================================================================== --- include/fossil-scm/util.h +++ include/fossil-scm/util.h @@ -12,11 +12,11 @@ Heavily indebted to the Fossil SCM project (https://fossil-scm.org). */ -/** @file fossil-util.h +/** @file util.h This file declares a number of utility classes and routines used by libfossil. All of them considered "public", suitable for direct use by client code. */ @@ -3392,132 +3392,10 @@ Results are undefined if xlate is NULL. */ FSL_EXPORT fsl_size_t fsl_htmlize_xlate(int c, char const ** xlate); -/** - Flags for use with text-diff generation APIs, - e.g. fsl_diff_text(). - - Maintenance reminders: - - - These values are holy and must not be changed without also - changing the corresponding code in diff.c. - - - Where these entries semantically overlap with their fsl_diff2_flag_e - counterparts, they MUST have the same values because some internal APIs - are used by both of the diff APIs. - - @deprecated Prefer fsl_diff2_flag_e and fsl_diff_v2() instead. -*/ -enum fsl_diff_flag_e { -/** Ignore end-of-line whitespace */ -FSL_DIFF_IGNORE_EOLWS = 0x01, -/** Ignore end-of-line whitespace */ -FSL_DIFF_IGNORE_ALLWS = 0x03, -/** Generate a side-by-side diff */ -FSL_DIFF_SIDEBYSIDE = 0x04, -/** Missing shown as empty files */ -FSL_DIFF_VERBOSE = 0x08, -/** Show filenames only. Not used in this impl! */ -FSL_DIFF_BRIEF = 0x10, -/** Render HTML. */ -FSL_DIFF_HTML = 0x20, -/** Show line numbers. */ -FSL_DIFF_LINENO = 0x40, -/** Suppress optimizations (debug). */ -FSL_DIFF_NOOPT = 0x0100, -/** Invert the diff (debug). */ -FSL_DIFF_INVERT = 0x0200, -/* ACHTUNG: do not use 0x0400 because of semantic - collision with FSL_DIFF2_CONTEXT_ZERO */ -/** Only display if not "too big." */ -FSL_DIFF_NOTTOOBIG = 0x0800, -/** Strip trailing CR */ -FSL_DIFF_STRIP_EOLCR = 0x1000, -/** - This flag tells text-mode diff generation to add ANSI color - sequences to some output. The colors are currently hard-coded - and non-configurable. This has no effect for HTML output, and - that flag trumps this one. It also currently only affects - unified diffs, not side-by-side. - - Maintenance reminder: this one currently has no counterpart in - fossil(1), is not tracked in the same way, and need not map to an - internal flag value. -*/ -FSL_DIFF_ANSI_COLOR = 0x2000 -}; - -/** - Generates a textual diff from two text inputs and writes - it to the given output function. - - pA and pB are the buffers to diff. - - contextLines is the number of lines of context to output. This - parameter has a built-in limit of 2^16, and values larger than - that get truncated. A value of 0 is legal, in which case no - surrounding context is provided. A negative value translates to - some unspecified default value. - - sbsWidth specifies the width (in characters) of the side-by-side - columns. If sbsWidth is not 0 then this function behaves as if - diffFlags contains the FSL_DIFF_SIDEBYSIDE flag. If sbsWidth is - negative, OR if diffFlags explicitly contains - FSL_DIFF_SIDEBYSIDE and sbsWidth is 0, then some default width - is used. This parameter has a built-in limit of 255, and values - larger than that get truncated to 255. - - diffFlags is a mask of fsl_diff_flag_t values. Not all of the - fsl_diff_flag_t flags are yet [sup]ported. - - The output is sent to out(outState,...). If out() returns non-0 - during processing, processing stops and that result is returned - to the caller of this function. - - Returns 0 on success, FSL_RC_OOM on allocation error, - FSL_RC_MISUSE if any arguments are invalid, FSL_RC_TYPE if any - of the content appears to be binary (contains embedded NUL - bytes), FSL_RC_RANGE if some range is exceeded (e.g. the maximum - number of input lines). - - None of (pA, pB, out) may be NULL. - - TODOs: - - - Add a predicate function for outputing only matching - differences, analog to fossil(1)'s regex support (but more - flexible). - - - Expose the raw diff-generation bits via the internal API - to facilitate/enable the creation of custom diff formats. - - @see fsl_diff_v2() - @deprecated Prefer fsl_diff_v2() for new code. -*/ -FSL_EXPORT int fsl_diff_text(fsl_buffer const *pA, fsl_buffer const *pB, - fsl_output_f out, void * outState, - short contextLines, short sbsWidth, - int diffFlags ); - -/** - Functionally equivalent to: - - ``` - fsl_diff_text(pA, pB, fsl_output_f_buffer, pOut, - contextLines, sbsWidth, diffFlags); - ``` - - Except that it returns FSL_RC_MISUSE if !pOut. - - @see fsl_diff_v2() - @deprecated Prefer fsl_diff_v2() for new code. -*/ -FSL_EXPORT int fsl_diff_text_to_buffer(fsl_buffer const *pA, fsl_buffer const *pB, - fsl_buffer *pOut, short contextLines, - short sbsWidth, int diffFlags ); /** @enum fsl_diff2_flag_e Flags for use with the 2021-era text-diff generation APIs (fsl_dibu and friends). This set of flags may still change Index: include/libfossil.h ================================================================== --- include/libfossil.h +++ include/libfossil.h @@ -10,11 +10,11 @@ SPDX-ArtifactOfProjectName: Libfossil SPDX-FileType: Code Heavily indebted to the Fossil SCM project (https://fossil-scm.org). */ -/** @file fossil.h +/** @file libfossil.h This file is the primary header for the public APIs. It includes various other header files. They are split into multiple headers primarily becuase my lower-end systems choke on syntax-highlighting them and browsing their (large) Doxygen output. @@ -33,8 +33,9 @@ #include "fossil-scm/confdb.h" #include "fossil-scm/hash.h" #include "fossil-scm/auth.h" #include "fossil-scm/vpath.h" #include "fossil-scm/cli.h" +#include "fossil-scm/deprecated.h" #endif /* ORG_FOSSIL_SCM_LIBFOSSIL_H_INCLUDED */ Index: make-libf.make.in ================================================================== --- make-libf.make.in +++ make-libf.make.in @@ -262,11 +262,12 @@ $(DIR.inc-fossil)/vpath.h \ $(DIR.inc-fossil)/internal.h \ $(DIR.inc-fossil)/auth.h \ $(DIR.inc-fossil)/forum.h \ $(DIR.inc-fossil)/pages.h \ - $(DIR.inc-fossil)/cli.h + $(DIR.inc-fossil)/cli.h \ + $(DIR.inc-fossil)/deprecated.h $(libfossil.h.SRC): $(libfossil.c.SRC): libfossil.h_MAKEFILES := $(filter-out $(ShakeNMake.CISH_DEPS_FILE),$(MAKEFILE_LIST)) $(libfossil.h): $(libfossil.h_MAKEFILES) $(libfossil.h.SRC) $(libfossil-config.h) $(libfossil.c): $(libfossil.h_MAKEFILES) $(libfossil.h) $(libfossil.c.SRC) Index: src/auth.c ================================================================== --- src/auth.c +++ src/auth.c @@ -17,11 +17,12 @@ #include "fossil-scm/internal.h" #include "fossil-scm/hash.h" #include "fossil-scm/confdb.h" -FSL_EXPORT char * fsl_sha1_shared_secret( fsl_cx * f, char const * zLoginName, +FSL_EXPORT char * fsl_sha1_shared_secret( fsl_cx * const f, + char const * zLoginName, char const * zPw ){ if(!f || !zPw || !zLoginName) return 0; else{ fsl_sha1_cx hash = fsl_sha1_cx_empty; unsigned char zResult[20]; @@ -48,18 +49,18 @@ fsl_sha1_digest_to_base16(zResult, zDigest); return fsl_strndup( zDigest, FSL_STRLEN_SHA1 ); } } -FSL_EXPORT char * fsl_repo_login_group_name(fsl_cx * f){ +FSL_EXPORT char * fsl_repo_login_group_name(fsl_cx * const f){ return f ? fsl_config_get_text(f, FSL_CONFDB_REPO, "login-group-name", 0) : 0; } -FSL_EXPORT char * fsl_repo_login_cookie_name(fsl_cx * f){ +FSL_EXPORT char * fsl_repo_login_cookie_name(fsl_cx * const f){ fsl_db * db; if(!f || !(db = fsl_cx_db_repo(f))) return 0; else{ char const * sql = "SELECT 'fossil-' || substr(value,1,16)" @@ -68,11 +69,11 @@ " ORDER BY name /*sort*/"; return fsl_db_g_text(db, 0, sql); } } -FSL_EXPORT int fsl_repo_login_search_uid(fsl_cx * f, char const * zUsername, +FSL_EXPORT int fsl_repo_login_search_uid(fsl_cx * const f, char const * zUsername, char const * zPasswd, fsl_id_t * userId){ int rc; char * zSecret; fsl_db * db; @@ -96,11 +97,11 @@ zUsername, zSecret, zPasswd); fsl_free(zSecret); return rc; } -FSL_EXPORT int fsl_repo_login_clear( fsl_cx * f, fsl_id_t userId ){ +FSL_EXPORT int fsl_repo_login_clear( fsl_cx * const f, fsl_id_t userId ){ fsl_db * db; if(!f) return FSL_RC_MISUSE; else if(!(db = fsl_needs_repo(f))) return FSL_RC_NOT_A_REPO; else{ int const rc = fsl_db_exec(db, Index: src/checkout.c ================================================================== --- src/checkout.c +++ src/checkout.c @@ -677,11 +677,11 @@ fsl_db * const db = fsl_needs_ckout(f); if(!db) return f->error.code; if(dropIfExists){ char const * t; int i; - char const * dbName = fsl_db_role_label(FSL_DBROLE_CKOUT); + char const * dbName = fsl_db_role_name(FSL_DBROLE_CKOUT); for(i=0; 0!=(t = tNames[i]); ++i){ rc = fsl_db_exec(db, "DROP TABLE IF EXISTS %s.%s /*%s()*/", dbName, t, __func__); if(rc) break; } @@ -740,23 +740,20 @@ "DELETE FROM vvar WHERE name IN" "('checkout','checkout-hash') " "/*%s()*/", __func__); } -fsl_db * fsl_cx_db_for_role(fsl_cx *, fsl_dbrole_e) - /* defined in cx.c */; - /** Updates f->ckout.dir and dirLen based on the current state of f->ckout.db. Returns 0 on success, FSL_RC_OOM on allocation error, some other code if canonicalization of the name fails (e.g. filesystem error or cwd cannot be resolved). */ static int fsl_update_ckout_dir(fsl_cx *f){ int rc; fsl_buffer ckDir = fsl_buffer_empty; - fsl_db * dbC = fsl_cx_db_for_role(f, FSL_DBROLE_CKOUT); + fsl_db * dbC = fsl__cx_db_for_role(f, FSL_DBROLE_CKOUT); assert(dbC->filename); assert(*dbC->filename); rc = fsl_file_canonical_name(dbC->filename, &ckDir, false); if(rc) return rc; char * zCanon = fsl_buffer_take(&ckDir); @@ -845,16 +842,28 @@ */ assert(!fsl_cx_db_ckout(f)); const char * dbName = opt->ckoutDbFile ? opt->ckoutDbFile : fsl_preferred_ckout_db_name(); fsl_cx_err_reset(f); - int fsl_cx_attach_role(fsl_cx * const , const char *, fsl_dbrole_e) - /* defined in cx.c */; - rc = fsl_cx_attach_role(f, dbName, FSL_DBROLE_CKOUT); + +#if 0 + if(!fsl_is_file(dbName)){ + /* A bit of an ugly workaround: create an empty file, if needed, + so that the following ATTACH can work. */ + FILE * const fi = fsl_fopen(dbName, "w"); + if(!fi){ + rc = fsl_cx_err_set(f, FSL_RC_IO, "Cannot create new checkout db: %s", + dbName); + goto end; + } + fsl_fclose(fi); + } +#endif + rc = fsl__cx_attach_role(f, dbName, FSL_DBROLE_CKOUT, true); if(rc) goto end; fsl_db * const theDbC = fsl_cx_db_ckout(f); - dbC = fsl_cx_db_for_role(f, FSL_DBROLE_CKOUT); + dbC = fsl__cx_db_for_role(f, FSL_DBROLE_CKOUT); assert(theDbC != dbC && "Not anymore."); assert(theDbC == f->dbMain); assert(!f->error.code); assert(dbC->name); assert(dbC->filename); @@ -872,11 +881,11 @@ fsl_chdir(fsl_buffer_cstr(cwd)) /* Ignoring error because we have no recovery strategy! */; } fsl__cx_scratchpad_yield(f, cwd); if(!rc){ - fsl_db * const dbR = fsl_cx_db_for_role(f, FSL_DBROLE_REPO); + fsl_db * const dbR = fsl__cx_db_for_role(f, FSL_DBROLE_REPO); assert(dbR); assert(dbR->filename && *dbR->filename); rc = fsl_config_set_text(f, FSL_CONFDB_CKOUT, "repository", dbR->filename); } Index: src/cx.c ================================================================== --- src/cx.c +++ src/cx.c @@ -102,11 +102,10 @@ *f = fsl_cx_empty; f->allocStamp = allocStamp; }else{ f = fsl_cx_malloc(); if(!f) return FSL_RC_OOM; - *tgt = f; } memset(&f->cache.mcache, 0, sizeof(f->cache.mcache)); f->output = param->output; f->cxConfig = param->config; @@ -148,20 +147,16 @@ } #endif #if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 sqlite3_mutex_leave(sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER)); #endif - f->dbMem.f = f; - rc = fsl_db_open( &f->dbMem, "", 0 ); - if(!rc){ - extern int fsl__cx_init_db(fsl_cx * const, fsl_db * const); - rc = fsl__cx_init_db(f, &f->dbMem); - } - if(!rc) rc = fsl__cx_install_timeline_crosslinkers(f); - if(!rc){ - f->cache.tempDirs = fsl_temp_dirs_get(); - if(!f->cache.tempDirs) rc = FSL_RC_OOM; + if(!rc){ + rc = fsl__cx_install_timeline_crosslinkers(f); + if(!rc){ + f->cache.tempDirs = fsl_temp_dirs_get(); + if(!f->cache.tempDirs) rc = FSL_RC_OOM; + } } return rc; } void fsl__cx_mcache_clear(fsl_cx * const f){ @@ -172,35 +167,37 @@ fsl_deck_finalize(&f->cache.mcache.decks[i]); } f->cache.mcache = fsl__mcache_empty; } -static void fsl__cx_reset(fsl_cx * const f, bool closeDatabases){ - fsl_checkin_discard(f); -#define SFREE(X) fsl_free(X); X = NULL +void fsl__cx_clear_repo_caches(fsl_cx * const f){ + fsl__bccache_reset(&f->cache.blobContent); + fsl__cx_mcache_clear(f); + f->cache.allowSymlinks = + f->cache.caseInsensitive = + f->cache.seenDeltaManifest = -1; +} + +static void fsl__cx_finalize_cached_stmt(fsl_cx * const f){ #define STMT(X) fsl_stmt_finalize(&f->cache.stmt.X) STMT(deltaSrcId); STMT(uuidToRid); STMT(uuidToRidGlob); STMT(contentSize); STMT(nextEntry); #undef STMT +} + +static void fsl__cx_reset(fsl_cx * const f, bool closeDatabases){ + fsl_checkin_discard(f); +#define SFREE(X) fsl_free(X); X = NULL if(closeDatabases){ + fsl__cx_finalize_cached_stmt(f); fsl_cx_close_dbs(f); - /* - Reminder: f->dbMem is NOT closed here: it's an internal detail, - not public state. We could arguably close and reopen it here, - but then we introduce a potenital error case (OOM) where we - currently have none (thus the void return). - - 2021-11-09: it turns out we've had an error case all along - here: if any cached statements are opened for one of the dbs, - that can prohibit its detachement. - */ SFREE(f->ckout.dir); f->ckout.dirLen = 0; - /* assert(NULL==f->dbMain); */ + assert(NULL==f->dbMain); } SFREE(f->repo.user); SFREE(f->ckout.uuid); SFREE(f->cache.projectCode); SFREE(f->ticket.titleColumn); @@ -249,11 +246,10 @@ } f->clientState = fsl_state_empty; f->output = fsl_outputer_empty; fsl_temp_dirs_free(f->cache.tempDirs); fsl__cx_reset(f, true); - fsl_db_close(&f->dbMem); *f = fsl_cx_empty; if(&fsl_cx_empty == allocStamp){ fsl_free(f); }else{ f->allocStamp = allocStamp; @@ -276,11 +272,10 @@ } void fsl_cx_err_reset(fsl_cx * const f){ //f->interrupted = 0; // No! ONLY modify this via fsl_cx_interrupt() fsl_error_reset(&f->error); - fsl_db_err_reset(&f->dbMem); fsl_db_err_reset(&f->repo.db); fsl_db_err_reset(&f->config.db); fsl_db_err_reset(&f->ckout.db); } @@ -381,14 +376,19 @@ fsl_db * fsl_cx_db_config( fsl_cx * const f ){ return f->config.db.dbh ? &f->config.db : NULL; } fsl_db * fsl_cx_db_repo( fsl_cx * const f ){ - if(f->dbMain && (FSL_DBROLE_REPO & f->dbMain->role)) return f->dbMain; - else if(f->repo.db.dbh) return &f->repo.db; - else return NULL; + return f->dbMain && (f->dbMain->role & FSL_DBROLE_REPO) + ? f->dbMain : NULL; } + +fsl_db * fsl_cx_db_ckout( fsl_cx * const f ){ + return f->dbMain && (f->dbMain->role & FSL_DBROLE_CKOUT) + ? f->dbMain : NULL; +} + fsl_db * fsl_needs_repo(fsl_cx * const f){ fsl_db * const db = fsl_cx_db_repo(f); if(!db){ fsl_cx_err_set(f, FSL_RC_NOT_A_REPO, @@ -404,43 +404,26 @@ "Fossil context has no opened checkout db."); } return db; } -fsl_db * fsl_cx_db_ckout( fsl_cx * const f ){ - if(f->dbMain && (FSL_DBROLE_CKOUT & f->dbMain->role)) return f->dbMain; - else if(f->ckout.db.dbh) return &f->ckout.db; - else return NULL; -} - fsl_db * fsl_cx_db( fsl_cx * const f ){ return f->dbMain; } -/** @internal - - Returns one of f->db{Config,Repo,Ckout,Mem} - or NULL. - - ACHTUNG and REMINDER TO SELF: the current (2021-03) design means - that none of these handles except for FSL_DBROLE_MAIN actually has - an sqlite3 db handle assigned to it. This returns a handle to the - "level of abstraction" we need to keep track of each db's name and - db-specific other state. - - e.g. passing a role of FSL_DBROLE_CKOUT this does NOT return - the same thing as fsl_cx_db_ckout(). -*/ -fsl_db * fsl_cx_db_for_role(fsl_cx * const f, fsl_dbrole_e r){ + +fsl_db * fsl__cx_db_for_role(fsl_cx * const f, fsl_dbrole_e r){ switch(r){ case FSL_DBROLE_CONFIG: return &f->config.db; case FSL_DBROLE_REPO: return &f->repo.db; case FSL_DBROLE_CKOUT: return &f->ckout.db; case FSL_DBROLE_MAIN: - return &f->dbMem; + return f->repo.db.dbh + ? &f->repo.db + : (f->ckout.db.dbh ? &f->ckout.db : NULL); case FSL_DBROLE_NONE: default: return NULL; } } @@ -447,33 +430,40 @@ /** Detaches the given db role from f->dbMain and removes the role from f->dbMain->role. */ -static int fsl_cx_detach_role(fsl_cx * const f, fsl_dbrole_e r){ +static int fsl__cx_detach_role(fsl_cx * const f, fsl_dbrole_e r){ assert(FSL_DBROLE_CONFIG!=r && "Config db now has its own handle."); - if(NULL==f->dbMain) return FSL_RC_MISUSE; + assert(FSL_DBROLE_REPO==r || FSL_DBROLE_CKOUT==r); + if(NULL==f->dbMain){ + return fsl_cx_err_set(f, FSL_RC_MISUSE, + "Cannot close/detach db: none opened."); + } else if(!(r & f->dbMain->role)){ assert(!"Misuse: cannot detach unattached role."); - return FSL_RC_NOT_FOUND; - } - else{ - fsl_db * const db = fsl_cx_db_for_role(f,r); - int rc; - assert(db && "Internal API misuse."); - assert(f->dbMain != db); - rc = fsl__db_cached_clear_role(f->dbMain, r) - /* Make sure that we destroy any cached statements which are - known to be tied to this db role. This is primarily a kludge - for the global config db to avoid that closing it fails due - to a lock held by those statements. This is a special case - for the global db (as opposed to the repo/ckout dbs) because - exactly when that db is opened and close is not as tightly - controlled/funneled as the other dbs. */; - if(0==rc){ - rc = fsl_db_detach( f->dbMain, fsl_db_role_label(r) ); - //MARKER(("rc=%s %s %s\n", fsl_rc_cstr(rc), fsl_db_role_label(r), + return fsl_cx_err_set(f, FSL_RC_NOT_A_CKOUT, + "Cannot close/detach unattached role: %s", + fsl_db_role_name(r)); + }else{ + fsl_db * const db = fsl__cx_db_for_role(f,r); + int rc = 0; + fsl__cx_finalize_cached_stmt(f); + if(db->dbh){ + /* This is our MAIN db. CLOSE it. If we still have a secondary + db open, this will cause Grief later on. TODO: assert that + our secondary db is not still attached. */ + assert(f->dbMain == db); + fsl_db_close(db); + f->dbMain = NULL; + }else{// This is our secondary db. DETACH it. + assert(f->dbMain != db); + fsl__db_cached_clear_role(f->dbMain, r) + /* Make sure that we destroy any cached statements which are + known to be tied to this db role. */; + rc = fsl_db_detach( f->dbMain, fsl_db_role_name(r) ); + //MARKER(("rc=%s %s %s\n", fsl_rc_cstr(rc), fsl_db_role_name(r), // fsl_buffer_cstr(&f->dbMain->error.msg))); if(rc){ fsl_cx_uplift_db_error(f, f->dbMain); }else{ f->dbMain->role &= ~r; @@ -482,34 +472,17 @@ } return rc; } } - -/** @internal - - Attaches the given db file to f with the given role. This function "should" - be static but we need it in fsl_repo.c when creating a new repository. -*/ -int fsl_cx_attach_role(fsl_cx * const f, const char *zDbName, - fsl_dbrole_e r){ - char const * label = fsl_db_role_label(r); - fsl_db * const db = fsl_cx_db_for_role(f, r); - char ** nameDest = NULL; +int fsl__cx_attach_role(fsl_cx * const f, const char *zDbName, + fsl_dbrole_e r, bool createIfNotExists){ + char const * label = fsl_db_role_name(r); + fsl_db * const db = fsl__cx_db_for_role(f, r); int rc; - assert(FSL_DBROLE_CONFIG!=r && "Config db now has its own handle."); - if(!f->dbMain){ - fsl__fatal(FSL_RC_MISUSE,"Internal API misuse: f->dbMain has " - "not been set, so cannot attach role."); - return FSL_RC_MISUSE; - } - else if(r & f->dbMain->role){ - assert(!"Misuse: role is already attached."); - return fsl_cx_err_set(f, FSL_RC_MISUSE, - "Db role %s is already attached.", - label); - } + assert(!db->dbh && "Internal API misuse: don't call this when db is connected."); + #if 0 MARKER(("r=%s db=%p, ckout.db=%p\n", label, (void*)db, (void*)&f->ckout.db)); MARKER(("r=%s db=%p, repo.db=%p\n", label, (void*)db, (void*)&f->repo.db)); @@ -519,45 +492,76 @@ assert(db); assert(label); assert(f->dbMain != db); assert(!db->filename); assert(!db->name); - nameDest = &db->filename; switch(r){ - case FSL_DBROLE_CONFIG: case FSL_DBROLE_REPO: case FSL_DBROLE_CKOUT: break; + case FSL_DBROLE_CONFIG: case FSL_DBROLE_MAIN: case FSL_DBROLE_NONE: default: assert(!"cannot happen/not legal"); return FSL_RC_RANGE; } - *nameDest = fsl_strdup(zDbName); - db->name = *nameDest ? fsl_strdup(label) : NULL; - if(!db->name){ - rc = FSL_RC_OOM; - /* Design note: we do the strdup() before the ATTACH because if - the attach succeeds and strdup fails, detaching the db will - almost certainly fail because it must allocate for its prepared - statement and other internals. We would end up having to leave - the db attached and returning a failure, which could lead to a - memory leak (or worse) downstream. - */ - }else{ - /*MARKER(("Attached %p role %d %s %s\n", - (void const *)db, r, db->name, db->filename));*/ - rc = fsl_db_attach(f->dbMain, zDbName, label); - if(rc){ - fsl_cx_uplift_db_error(f, f->dbMain); - }else{ - //MARKER(("Attached db %p %s from %s\n", - // (void*)db, label, db->filename)); - f->dbMain->role |= r; - } - } + db->f = f; + if(!f->dbMain){ + // This is our first/main db. OPEN it. + db->name = fsl_strdup(label); + if(!db->name){ + rc = FSL_RC_OOM; + goto end; + } + rc = fsl_db_open( db, zDbName, createIfNotExists + ? FSL_OPEN_F_RWC : FSL_OPEN_F_RW + /* ^^^ RWC is not really what we want here (RW + is preferred), but RWC is currently required + for fsl_repo_create(). */); + if(rc){ + rc = fsl_cx_uplift_db_error2(f, db, rc); + fsl_db_close(db); + goto end; + } + int const sqrc = sqlite3_db_config(db->dbh, + SQLITE_DBCONFIG_MAINDBNAME, + label); + if(sqrc){ + rc = fsl__db_errcode(db, sqrc); + fsl_cx_uplift_db_error2(f, db, rc); + fsl_db_close(db); + }else{ + rc = fsl__cx_init_db(f, db); + db->role |= r; + assert(db == f->dbMain); + /* Should we fsl__cx_detach_role() here? */ + } + }else{ + // This is our secondary db. ATTACH it. + assert(db != f->dbMain); + db->filename = fsl_strdup(zDbName); + db->name = db->filename ? fsl_strdup(label) : NULL; + if(!db->name){ + rc = FSL_RC_OOM; + }else{ + //MARKER(("db is r/o? %s %s %d\n", f->dbMain->name, f->dbMain->filename, + // sqlite3_db_readonly(f->dbMain->dbh, f->dbMain->name))); + //rc = fsl_cx_exec(f, "attach %Q as %Q;", zDbName, label); + //MARKER(("fsl_db_exec(...) rc=%s\n", fsl_rc_cstr(rc))); + rc = fsl_db_attach(f->dbMain, zDbName, label); + //MARKER(("fsl_db_attach(...,%s,%s) rc=%s\n", zDbName, label, fsl_rc_cstr(rc))); + if(rc){ + fsl_cx_uplift_db_error2(f, f->dbMain, rc); + }else{ + /*MARKER(("Attached %p role %d %s %s\n", + (void const *)db, r, db->name, db->filename));*/ + f->dbMain->role |= r; + } + } + } + end: return rc; } void fsl_config_close( fsl_cx * const f ){ fsl_db_close(&f->config.db); @@ -564,81 +568,94 @@ } int fsl_repo_close( fsl_cx * const f ){ if(fsl_cx_transaction_level(f)){ return fsl_cx_err_set(f, FSL_RC_MISUSE, - "Cannot close repo with an opened transaction."); - }else{ - int rc = 0; - fsl_db * const db = &f->repo.db; - if(f->dbMain && (FSL_DBROLE_REPO & f->dbMain->role)){ - /* Repo db is ATTACHed. */ - if(FSL_DBROLE_CKOUT & f->dbMain->role){ - rc = fsl_cx_err_set(f, FSL_RC_MISUSE, - "Cannot close repo while checkout is " - "opened."); - }else{ - assert(f->dbMain!=db); - rc = fsl_cx_detach_role(f, FSL_DBROLE_REPO); - } - }else{ - fsl_db_close(db); - } - assert(!db->dbh); - f->cache.allowSymlinks = - f->cache.caseInsensitive = - f->cache.seenDeltaManifest = -1; + "Cannot close repository with an " + "opened transaction."); + }else if(!fsl_cx_db_repo(f)){ + return 0; + }else{ + int rc = 0; + if(f->ckout.db.dbh){ + /* Repo/checkout pairs are (almost) always opened checkout-first, + the exception being fsl_repo_ckout_open(). */ + assert(f->dbMain == &f->ckout.db); + /* Repo db is ATTACHed to checkout. Need to detach repo before + closing checkout. */ + rc = fsl__cx_detach_role(f, FSL_DBROLE_REPO); + if(0==rc){ + rc = fsl__cx_detach_role(f, FSL_DBROLE_CKOUT); + } + }else{ + fsl_db * const dbC = fsl_cx_db_ckout(f); + assert(&f->repo.db == f->dbMain); + if(dbC){ + /* Checkout is ATTACHed to the repo. Need to detach it before + closing repo. */ + rc = fsl__cx_detach_role(f, FSL_DBROLE_CKOUT); + } + if(0==rc){ + rc = fsl__cx_detach_role(f, FSL_DBROLE_REPO); + } + } + fsl__cx_clear_repo_caches(f); return rc; } } int fsl_ckout_close( fsl_cx * const f ){ if(fsl_cx_transaction_level(f)){ return fsl_cx_err_set(f, FSL_RC_MISUSE, - "Cannot close checkout with opened transaction."); - }else{ - int rc = 0; - fsl_db * const db = &f->ckout.db; - if(f->dbMain && (FSL_DBROLE_CKOUT & f->dbMain->role)){ - /* Checkout db is ATTACHed. */ - rc = fsl_cx_detach_role(f, FSL_DBROLE_CKOUT); - fsl_repo_close(f) - /* Because the repo is implicitly opened, we "should" - implicitly close it. This is debatable but "probably almost - always" desired. i can't currently envisage a reasonable - use-case which requires closing the checkout but keeping - the repo opened. The repo can always be re-opened by - itself. */; - }else{ - fsl_db_close(db); - } - fsl_free(f->ckout.uuid); - f->ckout.uuid = NULL; - f->ckout.rid = 0; - assert(!db->dbh); - return rc; - } + "Cannot close checkout with an " + "opened transaction."); + }else if(!fsl_cx_db_ckout(f)){ + return 0; + }else if(f->repo.db.dbh){ + assert(!"Can't happen: repo is MAIN w/ a repo/ckout pair?!?"); + fsl__fatal(FSL_RC_MISUSE,"Can't happen: repo is MAIN w/ " + "a repo/ckout pair?!?"); + } + int rc; + if(fsl_cx_db_repo(f)){ + /* We're part of a repo/checkout pair (as it should be). Close + both. */ + rc = fsl_repo_close(f) /* will close both */; + if(0==rc){ + assert(!f->ckout.db.dbh); + assert(!f->repo.db.dbh); + assert(!f->dbMain); + } + }else{ + /* This case "really shouldn't" happen: an opened checkout without + an associated repo. For the sake of completeness, though... */ + assert(f->dbMain && (f->dbMain->role & FSL_DBROLE_CKOUT)); + rc = fsl__cx_detach_role(f, FSL_DBROLE_CKOUT); + } + return rc; } /** If zDbName is a valid checkout database file, open it and return 0. - If it is not a valid local database file, return a non-0 code. + If it is not a valid local database file, return a non-0 code and + update f's error state. */ -static int fsl_cx_ckout_open_db(fsl_cx * f, const char *zDbName){ +static int fsl__cx_ckout_open_db(fsl_cx * const f, const char *zDbName){ /* char *zVFileDef; */ int rc; fsl_int_t const lsize = fsl_file_size(zDbName); if( -1 == lsize ){ - return FSL_RC_NOT_FOUND /* might be FSL_RC_ACCESS? */; + return fsl_cx_err_set(f, FSL_RC_NOT_FOUND /* might be FSL_RC_ACCESS? */, + "Cannot stat() db file [%s].", zDbName); } if( lsize%1024!=0 || lsize<4096 ){ - return fsl_cx_err_set(f, FSL_RC_RANGE, + return fsl_cx_err_set(f, FSL_RC_CONSISTENCY, "File's size is not correct for a " "checkout db: %s", zDbName); } - rc = fsl_cx_attach_role(f, zDbName, FSL_DBROLE_CKOUT); + rc = fsl__cx_attach_role(f, zDbName, FSL_DBROLE_CKOUT, false); return rc; } int fsl_cx_execv( fsl_cx * const f, char const * sql, va_list args ){ @@ -721,11 +738,11 @@ static int fsl_config_file_reset(fsl_cx * const f, char const * dbName){ fsl_db DB = fsl_db_empty; fsl_db * db = &DB; int rc = 0; bool isAttached = false; - const char * zPrefix = fsl_db_role_label(FSL_DBROLE_CONFIG); + const char * zPrefix = fsl_db_role_name(FSL_DBROLE_CONFIG); if(-1 != fsl_file_size(dbName)){ rc = fsl_file_unlink(dbName); if(rc){ return fsl_cx_err_set(f, rc, "Error %s while removing old config file (%s)", @@ -734,11 +751,11 @@ } /** Hoop-jumping: because the schema file has a cfg. prefix for the table(s), and we cannot assign an arbitrary name to an open()'d db, we first open the db (making the the "main" db), then - ATTACH it to itself to provide the fsl_db_role_label() alias. + ATTACH it to itself to provide the fsl_db_role_name() alias. */ rc = fsl_db_open(db, dbName, FSL_OPEN_F_RWC); if(rc) goto end; rc = fsl_db_attach(db, dbName, zPrefix); if(rc) goto end; @@ -855,11 +872,11 @@ ? FSL_OPEN_F_TRACE_SQL : 0)); if(0==rc){ int const sqrc = sqlite3_db_config(f->config.db.dbh, SQLITE_DBCONFIG_MAINDBNAME, - fsl_db_role_label(FSL_DBROLE_CONFIG)); + fsl_db_role_name(FSL_DBROLE_CONFIG)); if(sqrc) rc = fsl__db_errcode(&f->config.db, sqrc); } if(rc){ rc = fsl_cx_uplift_db_error2(f, &f->config.db, rc); fsl_db_close(&f->config.db); @@ -1009,11 +1026,11 @@ if(0!=fsl_file_access( repoDbFile, F_OK )){ rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND, "Repository db [%s] not found or cannot be read.", repoDbFile); }else{ - rc = fsl_cx_attach_role(f, repoDbFile, FSL_DBROLE_REPO); + rc = fsl__cx_attach_role(f, repoDbFile, FSL_DBROLE_REPO, false); if(!rc && !(FSL_CX_F_IS_OPENING_CKOUT & f->flags)){ rc = fsl_cx_after_open(f); } if(!rc){ fsl_db * const db = fsl_cx_db_repo(f); @@ -1038,14 +1055,15 @@ /** Tries to open the repository from which the current checkout derives. Returns 0 on success. */ -static int fsl_repo_open_for_ckout(fsl_cx * f){ +static int fsl_repo_open_for_ckout(fsl_cx * const f){ char * repoDb = NULL; int rc; - fsl_buffer nameBuf = fsl_buffer_empty; + fsl_buffer * const nameCanon = fsl__cx_scratchpad(f); + fsl_buffer * const repoDbAbs = fsl__cx_scratchpad(f); fsl_db * db = fsl_cx_db_ckout(f); assert(f); assert(f->ckout.dir); assert(db); rc = fsl_db_get_text(db, &repoDb, NULL, @@ -1053,35 +1071,34 @@ "WHERE name='repository'"); if(rc) fsl_cx_uplift_db_error( f, db ); else if(repoDb){ if(!fsl_is_absolute_path(repoDb)){ /* Make it relative to the checkout db dir */ - rc = fsl_buffer_appendf(&nameBuf, "%s/%s", f->ckout.dir, repoDb); - fsl_free(repoDb); - if(rc) { - fsl_buffer_clear(&nameBuf); - return rc; - } - repoDb = (char*)nameBuf.mem /* transfer ownership */; - nameBuf = fsl_buffer_empty; - } - rc = fsl_file_canonical_name(repoDb, &nameBuf, 0); - fsl_free(repoDb); + rc = fsl_buffer_appendf(repoDbAbs, "%s%s", f->ckout.dir, repoDb); + fsl_free(repoDb); + if(rc) goto end; + repoDb = fsl_buffer_str(repoDbAbs); + } + rc = fsl_file_canonical_name(repoDb, nameCanon, 0); + if(fsl_buffer_str(repoDbAbs) != repoDb){ + fsl_free(repoDb); + } + repoDb = NULL; if(!rc){ - repoDb = fsl_buffer_str(&nameBuf); - assert(repoDb); - rc = fsl_repo_open(f, repoDb); + rc = fsl_repo_open(f, fsl_buffer_cstr(nameCanon)); } - fsl_buffer_reserve(&nameBuf, 0); }else{ /* This can only happen if we are not using a proper checkout db or someone has removed the repo link. */ rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND, "Could not determine this checkout's " "repository db file."); } + end: + fsl__cx_scratchpad_yield(f, nameCanon); + fsl__cx_scratchpad_yield(f, repoDbAbs); return rc; } static void fsl_ckout_mtime_set(fsl_cx * const f){ f->ckout.mtime = f->ckout.rid>0 @@ -1324,10 +1341,13 @@ if(fsl_cx_db_ckout(f)){ rc = fsl_cx_err_set( f, FSL_RC_ACCESS, "A checkout is already opened. " "Close it before opening another."); goto end; + } + else if(!dirName){ + dirName = "."; } rc = fsl_file_canonical_name( dirName, bufD, false ); if(rc) goto end; dirName = fsl_buffer_cstr(bufD); rc = fsl_ckout_db_search(dirName, checkParentDirs, buf); @@ -1339,11 +1359,11 @@ } goto end; } assert(buf->used>1 /* "/" */); zName = fsl_buffer_cstr(buf); - rc = fsl_cx_ckout_open_db(f, zName); + rc = fsl__cx_ckout_open_db(f, zName); if(0==rc){ /* Checkout db is now opened. Fiddle some internal bits... */ unsigned char * end = buf->mem+buf->used-1; @@ -1376,26 +1396,26 @@ char const * fsl_cx_db_file_for_role(fsl_cx const * f, fsl_dbrole_e r, fsl_size_t * len){ - fsl_db const * db = fsl_cx_db_for_role((fsl_cx*)f, r); + fsl_db const * db = fsl__cx_db_for_role((fsl_cx*)f, r); char const * rc = db ? db->filename : NULL; if(len) *len = fsl_strlen(rc); return rc; } -char const * fsl_cx_db_name_for_role(fsl_cx const * f, +char const * fsl_cx_db_name_for_role(fsl_cx const * const f, fsl_dbrole_e r, fsl_size_t * len){ if(FSL_DBROLE_MAIN == r){ /* special case to be removed when f->dbMem bits are finished. */ if(len) *len=4; return "main"; }else{ - fsl_db const * db = fsl_cx_db_for_role((fsl_cx*)f, r); + fsl_db const * db = fsl__cx_db_for_role((fsl_cx*)f, r); char const * rc = db ? db->name : NULL; if(len) *len = rc ? fsl_strlen(rc) : 0; return rc; } } @@ -1650,11 +1670,10 @@ return f ? &f->error : NULL; } int fsl_cx_close_dbs( fsl_cx * const f ){ if(fsl_cx_transaction_level(f)){ - /* Is this really necessary? */ return fsl_cx_err_set(f, FSL_RC_MISUSE, "Cannot close the databases when a " "transaction is pending."); } if(NULL==f->dbMain) return 0; @@ -1666,11 +1685,11 @@ rc1 = fsl_repo_close(f); if(rc1) rc = rc1; fsl_config_close(f); /* Forcibly reset the role and db strings for this case, even if closing ostensibly fails. */ - f->dbMain->role = FSL_DBROLE_MAIN; + assert(NULL==f->dbMain); fsl__db_clear_strings(&f->repo.db, true); fsl__db_clear_strings(&f->ckout.db, true); fsl__db_clear_strings(&f->config.db, true); assert(!f->repo.db.dbh); assert(!f->ckout.db.dbh); @@ -1784,17 +1803,27 @@ ? fsl_db_transaction_level(f->dbMain) : 0; } int fsl_cx_transaction_begin(fsl_cx * const f){ - int const rc = fsl_db_transaction_begin(f->dbMain); - return rc ? fsl_cx_uplift_db_error2(f, f->dbMain, rc) : 0; + if(f->dbMain){ + int const rc = fsl_db_transaction_begin(f->dbMain); + return rc ? fsl_cx_uplift_db_error2(f, f->dbMain, rc) : 0; + }else{ + return fsl_cx_err_set(f, FSL_RC_MISUSE, + "No repo/checkout databases are opened."); + } } int fsl_cx_transaction_end(fsl_cx * const f, bool doRollback){ - int const rc = fsl_db_transaction_end(f->dbMain, doRollback); - return rc ? fsl_cx_uplift_db_error2(f, f->dbMain, rc) : 0; + if(f->dbMain){ + int const rc = fsl_db_transaction_end(f->dbMain, doRollback); + return rc ? fsl_cx_uplift_db_error2(f, f->dbMain, rc) : 0; + }else{ + return fsl_cx_err_set(f, FSL_RC_MISUSE, + "No repo/checkout databases are opened."); + } } void fsl_cx_confirmer(fsl_cx * f, fsl_confirmer const * newConfirmer, fsl_confirmer * prevConfirmer){ Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -77,11 +77,11 @@ but the disadvantage of exposing the 3rd-party error string without any indication that it's coming from a 3rd party. */ : (fsl_error_reset(&db->error), 0); } -void fsl__db_clear_strings(fsl_db * const db, bool alsoErrorState ){ +void fsl__db_clear_strings(fsl_db * const db, bool alsoErrorState){ fsl_free(db->filename); db->filename = NULL; fsl_free(db->name); db->name = NULL; if(alsoErrorState) fsl_error_clear(&db->error); @@ -150,33 +150,36 @@ } void fsl_db_close( fsl_db * const db ){ void const * const allocStamp = db->allocStamp; fsl_cx * const f = db->f; - if(!db->dbh) return; + if(!db->dbh){ + goto end; + } fsl_db_stmt_cache_clear(db); if(db->f && db->f->dbMain==db){ /* Horrible, horrible dependency, and only necessary if the fsl_cx API gets sloppy or repo/checkout/config DBs are - otherwised closed improperly (i.e. not via the fsl_cx API). + otherwise closed improperly (i.e. not via the fsl_cx API). */ assert(0 != db->role); f->dbMain = NULL; } while(db->beginCount>0){ fsl_db_transaction_end(db, 1); } if(0!=db->openStatementCount){ - MARKER(("WARNING: %d open statement(s) left on db [%s].\n", - (int)db->openStatementCount, db->filename)); + MARKER(("WARNING: %d open statement(s) left on db [%s]. Cached?=%p\n", + (int)db->openStatementCount, db->filename, (void*)db->cacheHead)); } if(db->dbh){ - sqlite3_close_v2(db->dbh); - /* Ignore results in the style of "destructors - may not throw.". */ + sqlite3_close_v2(db->dbh) + /* Ignore results in the style of "destructors may not + throw." */; } + end: fsl__db_clear_strings(db, true); fsl_db_cleanup_beforeCommit(db); fsl_buffer_clear(&db->cachePrepBuf); *db = fsl_db_empty; if(&fsl_db_empty == allocStamp){ @@ -194,17 +197,17 @@ } } int fsl_db_attach(fsl_db * const db, const char *zDbName, const char *zLabel){ - return (db && db->dbh && zDbName && *zDbName && zLabel && *zLabel) - ? fsl_db_exec(db, "ATTACH DATABASE %Q AS %s", zDbName, zLabel) + return (db->dbh && zDbName && *zDbName && zLabel && *zLabel) + ? fsl_db_exec(db, "ATTACH DATABASE %Q AS %Q", zDbName, zLabel) : FSL_RC_MISUSE; } int fsl_db_detach(fsl_db * const db, const char *zLabel){ - return (db && db->dbh && zLabel && *zLabel) - ? fsl_db_exec(db, "DETACH DATABASE %s /*%s()*/", zLabel, __func__) + return (db->dbh && zLabel && *zLabel) + ? fsl_db_exec(db, "DETACH DATABASE %Q /*%s()*/", zLabel, __func__) : FSL_RC_MISUSE; } char const * fsl_db_name(fsl_db const * const db){ return db ? db->name : NULL; @@ -211,11 +214,11 @@ } /** Returns the db name for the given role. */ -const char * fsl_db_role_label(fsl_dbrole_e r){ +const char * fsl_db_role_name(fsl_dbrole_e r){ switch(r){ case FSL_DBROLE_CONFIG: return "cfg"; case FSL_DBROLE_REPO: return "repo"; @@ -1102,13 +1105,16 @@ int fsl_db_open( fsl_db * const db, char const * dbFile, int openFlags ){ int rc; fsl_dbh_t * dbh = NULL; int isMem = 0; - if(!db || !dbFile) return FSL_RC_MISUSE; - else if(db->dbh) return FSL_RC_MISUSE; - else if(!(isMem = (!*dbFile || 0==fsl_strcmp(":memory:", dbFile))) + if(!dbFile) return FSL_RC_MISUSE; + else if(db->dbh){ + return fsl_error_set(&db->error, FSL_RC_MISUSE, + "Cannot open db: it's already opened."); + }else if(!(isMem = + (!*dbFile/*TEMP db*/ || 0==fsl_strcmp(":memory:", dbFile))) && !(FSL_OPEN_F_CREATE & openFlags) && fsl_file_access(dbFile, 0)){ return fsl_error_set(&db->error, FSL_RC_NOT_FOUND, "DB file not found: %s", dbFile); } @@ -1890,11 +1896,11 @@ bool fsl_db_table_exists(fsl_db * const db, fsl_dbrole_e whichDb, const char *zTable ){ - const char *zDb = fsl_db_role_label( whichDb ); + const char *zDb = fsl_db_role_name( whichDb ); int rc = db->dbh ? sqlite3_table_column_metadata(db->dbh, zDb, zTable, 0, 0, 0, 0, 0, 0) : !SQLITE_OK; return rc==SQLITE_OK ? true : false; Index: src/repo.c ================================================================== --- src/repo.c +++ src/repo.c @@ -916,12 +916,10 @@ int rc = 0; char const * userName = 0; fsl_time_t const unixNow = (fsl_time_t)time(0); bool fileExists; bool inTrans = 0; - extern int fsl_cx_attach_role(fsl_cx * f, const char *zDbName, fsl_dbrole_e r) - /* Internal routine from cx.c */; if(!opt || !opt->filename) return FSL_RC_MISUSE; fileExists = 0 == fsl_file_access(opt->filename,0); if(fileExists && !opt->allowOverwrite){ return f ? fsl_cx_err_set(f, FSL_RC_ALREADY_EXISTS, @@ -970,11 +968,11 @@ opt->filename); goto end2; } #endif } - rc = fsl_cx_attach_role(f, opt->filename, FSL_DBROLE_REPO); + rc = fsl__cx_attach_role(f, opt->filename, FSL_DBROLE_REPO, true); if(rc){ goto end2; } db = fsl_cx_db(f); if(!f->repo.user){ @@ -1121,20 +1119,22 @@ } inTrans2 = 1; /* Copy all settings from the supplied template repository. */ + char const * zRepoLabel = fsl_db_role_name(FSL_DBROLE_REPO); rc = fsl_db_exec(db, - "INSERT OR REPLACE INTO repo.config" + "INSERT OR REPLACE INTO %s.config" " SELECT name,value,mtime FROM settingSrc.config" " WHERE (name IN %s OR name IN %s)" " AND name NOT GLOB 'project-*';", - inopConfig, inopDb); + zRepoLabel, inopConfig, inopDb); if(rc) goto detach; rc = fsl_db_exec(db, - "REPLACE INTO repo.reportfmt " - "SELECT * FROM settingSrc.reportfmt;"); + "REPLACE INTO %s.reportfmt " + "SELECT * FROM settingSrc.reportfmt;", + zRepoLabel); if(rc) goto detach; /* Copy the user permissions, contact information, last modified time, and photo for all the "system" users from the supplied @@ -1142,20 +1142,21 @@ columns are not copied because they contain security information or other data specific to the other repository. The list of columns copied by this SQL statement may need to be revised in the future. */ - rc = fsl_db_exec(db, "UPDATE repo.user SET" + rc = fsl_db_exec(db, "UPDATE %s.user SET" " cap = (SELECT u2.cap FROM settingSrc.user u2" " WHERE u2.login = user.login)," " info = (SELECT u2.info FROM settingSrc.user u2" " WHERE u2.login = user.login)," " mtime = (SELECT u2.mtime FROM settingSrc.user u2" " WHERE u2.login = user.login)," " photo = (SELECT u2.photo FROM settingSrc.user u2" " WHERE u2.login = user.login)" - " WHERE user.login IN ('anonymous','nobody','developer','reader');" + " WHERE user.login IN ('anonymous','nobody','developer','reader');", + zRepoLabel ); detach: fsl_free(inopConfig); fsl_free(inopDb); @@ -1345,17 +1346,17 @@ return rc; } } /* UNTESTED */ -char fsl_repo_is_readonly(fsl_cx const * f){ - if(!f || !f->dbMain) return 0; +bool fsl_repo_is_readonly(fsl_cx const * f){ + if(!f->dbMain) return 0; else{ int const roleId = f->ckout.db.dbh ? FSL_DBROLE_MAIN : FSL_DBROLE_REPO /* If CKOUT is attached, it is the main DB and REPO is ATTACHed. */ ; - char const * zRole = fsl_db_role_label(roleId); + char const * zRole = fsl_db_role_name(roleId); assert(f->dbMain); return sqlite3_db_readonly(f->dbMain->dbh, zRole) ? 1 : 0; } } @@ -1384,11 +1385,11 @@ int const dbRole = (f->dbMain==&f->config.db) ? FSL_DBROLE_MAIN : FSL_DBROLE_CONFIG; rc = fsl_db_exec(dbConf, "INSERT OR IGNORE INTO %s.global_config(name,value) " "VALUES('repo:%q',1)", - fsl_db_role_label(dbRole), + fsl_db_role_name(dbRole), fsl_buffer_cstr(full)); if(rc){ fsl_cx_uplift_db_error(f, dbConf); goto end; } @@ -1400,11 +1401,11 @@ /* Assumption: if we have an opened checkout, dbR is ATTACHed with the role REPO. */ int ro; assert(dbR); ro = sqlite3_db_readonly(dbR->dbh, - fsl_db_role_label(FSL_DBROLE_REPO)); + fsl_db_role_name(FSL_DBROLE_REPO)); assert(ro>=0); if(!ro){ fsl_buffer localRoot = fsl_buffer_empty; rc = fsl_file_canonical_name(zCDir, &localRoot, 1); if(0==rc){ @@ -1415,20 +1416,20 @@ int const dbRole = (f->dbMain==&f->config.db) ? FSL_DBROLE_MAIN : FSL_DBROLE_CONFIG; rc = fsl_db_exec(dbConf, "REPLACE INTO INTO %s.global_config(name,value) " "VALUES('ckout:%q',1)", - fsl_db_role_label(dbRole), + fsl_db_role_name(dbRole), fsl_buffer_cstr(&localRoot)); } if(0==rc){ /* We know that repo is ATTACHed to ckout here. */ assert(dbR == dbC); rc = fsl_db_exec(dbR, "REPLACE INTO %s.config(name, value, mtime) " "VALUES('ckout:%q', 1, now())", - fsl_db_role_label(FSL_DBROLE_REPO), + fsl_db_role_name(FSL_DBROLE_REPO), fsl_buffer_cstr(&localRoot)); } } fsl_buffer_clear(&localRoot); } @@ -1441,11 +1442,11 @@ fsl__cx_scratchpad_yield(f, full); return rc; } -char fsl_rid_is_a_checkin(fsl_cx * f, fsl_id_t rid){ +char fsl_rid_is_a_checkin(fsl_cx * const f, fsl_id_t rid){ fsl_db * db = f ? fsl_cx_db_repo(f) : NULL; if(!db || (rid<0)) return 0; else if(0==rid){ /* Corner case: empty repo */ return !fsl_db_exists(db, "SELECT 1 FROM blob WHERE rid>0"); @@ -1860,11 +1861,11 @@ (1) Change the CHECK constraint on BLOB.UUID so that the length is greater than or equal to 40, not exactly equal to 40. */ zBlobSchema = fsl_db_g_text(frs->db, NULL, "SELECT sql FROM %!Q.sqlite_schema" - " WHERE name='blob'", fsl_db_role_label(FSL_DBROLE_REPO)); + " WHERE name='blob'", fsl_db_role_name(FSL_DBROLE_REPO)); if(!zBlobSchema){ /* ^^^^ reminder: fossil(1) simply ignores this case, silently doing nothing instead. */ rc = fsl_cx_uplift_db_error(frs->f, frs->db); if(!rc){ @@ -1883,11 +1884,11 @@ sqlite3_db_config(frs->db->dbh, SQLITE_DBCONFIG_DEFENSIVE, 0, &rc2); rc = fsl_cx_exec_multi(frs->f, "PRAGMA writable_schema=ON;" "UPDATE %!Q.sqlite_schema SET sql=%Q WHERE name LIKE 'blob';" "PRAGMA writable_schema=OFF;", - fsl_db_role_label(FSL_DBROLE_REPO), zBlobSchema + fsl_db_role_name(FSL_DBROLE_REPO), zBlobSchema ); sqlite3_db_config(frs->db->dbh, SQLITE_DBCONFIG_DEFENSIVE, 1, &rc2); break; } } @@ -1895,11 +1896,11 @@ rc = fsl_cx_exec(frs->f, "CREATE VIEW IF NOT EXISTS " " %!Q.artifact(rid,rcvid,size,atype,srcid,hash,content) AS " " SELECT blob.rid,rcvid,size,1,srcid,uuid,content" " FROM blob LEFT JOIN delta ON (blob.rid=delta.rid);", - fsl_db_role_label(FSL_DBROLE_REPO) + fsl_db_role_name(FSL_DBROLE_REPO) ); end: fsl_free(zBlobSchema); return rc; @@ -2196,11 +2197,11 @@ "'ticket','ticketchng'," "'subscriber','pending_alert','chat'" ")" " AND name NOT GLOB 'sqlite_*'" " AND name NOT GLOB 'fx_*'", - fsl_db_role_label(FSL_DBROLE_REPO) + fsl_db_role_name(FSL_DBROLE_REPO) ); while( 0==rc && FSL_RC_STEP_ROW==fsl_stmt_step(&q) ){ rc = fsl_buffer_appendf(sql, "DROP TABLE IF EXISTS %!Q;\n", fsl_stmt_g_text(&q, 0, NULL)); } Index: src/udf.c ================================================================== --- src/udf.c +++ src/udf.c @@ -444,19 +444,14 @@ fsl_cx_glob_list(f, globType, &li, false); assert(li); sqlite3_result_int(context, fsl_glob_list_matches(li, p2) ? 1 : 0); } - -/** - Plug in fsl_cx-specific db functionality into the given db handle. - This must only be passed the MAIN db handle for the context. -*/ int fsl__cx_init_db(fsl_cx * const f, fsl_db * const db){ int rc; assert(!f->dbMain); - assert(db==&f->dbMem && "Currently the case - may change later."); + assert((db==&f->repo.db || db==&f->ckout.db)); if(f->cxConfig.traceSql){ fsl_db_sqltrace_enable(db, stdout); } f->dbMain = db; db->role = FSL_DBROLE_MAIN; @@ -464,18 +459,16 @@ /* FIXME: check result codes here. */ sqlite3 * const dbh = db->dbh; sqlite3_busy_timeout(dbh, 5000 /* historical value */); sqlite3_wal_autocheckpoint(dbh, 1); /* Set to checkpoint frequently */ rc = fsl_cx_exec_multi(f, - "PRAGMA foreign_keys=OFF;" + "PRAGMA main.foreign_keys=OFF;" // ^^^ vmerge table relies on this for its magical // vmerge.id values. - "PRAGMA main.temp_store=FILE;" - "PRAGMA main.journal_mode=TRUNCATE;" - // ^^^ note that WAL is not possible on a TEMP db - // and OFF leads to undefined behaviour if - // ROLLBACK is used! + //"PRAGMA main.temp_store=FILE;" + //"PRAGMA main.temp_store=MEMORY;" + //"PRAGMA main.journal_mode=WAL;" ); if(rc) goto end; sqlite3_create_function(dbh, "now", 0, SQLITE_ANY, 0, fsl_db_now_udf, 0, 0); sqlite3_create_function(dbh, "fsl_ci_mtime", 2,