/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* ** Copyright (c) 2013 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** This file houses most of main the v2 API routines. */ #include "fossil/fossil.h" #include "fsl_internal.h" #include #include #include #include #include const fsl_buffer fsl_buffer_empty = fsl_buffer_empty_m; const fsl_cx fsl_cx_empty = fsl_cx_empty_m; const fsl_cx_config fsl_cx_config_empty = fsl_cx_config_empty_m; const fsl_db fsl_db_empty = fsl_db_empty_m; const fsl_deck fsl_cardset_empty = fsl_deck_empty_m; const fsl_deck fsl_deck_empty = fsl_deck_empty_m; const fsl_error fsl_error_empty = fsl_error_empty_m; const fsl_fstat fsl_fstat_empty = fsl_fstat_empty_m; const fsl_init_param fsl_init_param_default = fsl_init_param_default_m; const fsl_init_param fsl_init_param_empty = fsl_init_param_empty_m; const fsl_list fsl_list_empty = fsl_list_empty_m; const fsl_mf fsl_mf_empty = fsl_mf_empty_m; const fsl_mf_file fsl_mf_file_empty = fsl_mf_file_empty_m; const fsl_mf_tag fsl_mf_tag_empty = fsl_mf_tag_empty_m; const fsl_outputer fsl_outputer_FILE = fsl_outputer_FILE_m; const fsl_pair_buf fsl_pair_buf_empty = fsl_pair_buf_empty_m; const fsl_stmt fsl_stmt_empty = { NULL/*db*/, NULL/*stmt*/, fsl_buffer_empty_m/*sql*/, 0/*colCount*/, 0/*paramCount*/, NULL/*allocStamp*/ }; #define fsl_db_record_empty_m { \ FSL_TYPE_INVALID /*typeId*/,\ 0/*dbId*/,\ NULL/*next*/\ } const fsl_db_record fsl_db_record_empty = fsl_db_record_empty_m; const fsl_user_r fsl_user_r_empty = { fsl_db_record_empty_m /*base*/, fsl_buffer_empty_m /* name */, FSL_ROLE_GUEST /* roles */, 0/*mtime*/ }; const fsl_tag_r fsl_tag_r_empty = { fsl_db_record_empty_m /*base*/, fsl_buffer_empty_m /* key */, fsl_buffer_empty_m /* value */ }; const fsl_blob_r fsl_blob_r_empty = { fsl_db_record_empty_m /*base*/, 0/*mtime*/, {/*sha1*/0} }; const fsl_allocator fsl_allocator_stdalloc = { fsl_realloc_f_stdalloc, NULL }; fsl_allocator fsl_memory_allocator = { fsl_realloc_f_stdalloc, NULL }; void * fsl_malloc( fsl_size_t n ){ return n ? fsl_memory_allocator.f(fsl_memory_allocator.state, NULL, n) : NULL; } void fsl_free( void * mem ){ if(mem) fsl_memory_allocator.f(fsl_memory_allocator.state, mem, 0); } void * fsl_realloc( void * mem, fsl_size_t n ){ if(!mem){ return fsl_malloc(n); }else if(!n){ fsl_free(mem); return NULL; }else{ return fsl_memory_allocator.f(fsl_memory_allocator.state, mem, n); } } void * fsl_realloc_f_stdalloc(void * state, void * mem, fsl_size_t n){ if(!mem){ return malloc(n); }else if(!n){ free(mem); return NULL; }else{ return realloc(mem, n); } } int fsl_flush_f_FILE(void * _FILE){ return _FILE ? (fflush((FILE*)_FILE) ? FSL_RC_IO : 0) : FSL_RC_MISUSE; } int fsl_output_f_FILE( void * state, void const * src, fsl_size_t n ){ return !state ? FSL_RC_MISUSE : ((1 == fwrite(src, n, 1, state ? (FILE*)state : stdout)) ? 0 : FSL_RC_IO); } int fsl_input_FILE( void * state, void * dest, fsl_size_t * n ){ FILE * f = (FILE*) state; if( !state || !dest || !n ) return FSL_RC_MISUSE; else if( !*n ) return FSL_RC_RANGE; *n = (fsl_size_t)fread( dest, 1, *n, f ); return !*n ? (feof(f) ? 0 : FSL_RC_IO) : 0; } void fsl_finalizer_f_FILE( void * state, void * mem ){ if(mem && (stdout != mem) && (stderr != mem)){ fclose((FILE*)mem); } } int fsl_output_f_buffer( void * state, void const * src, fsl_size_t n ){ return !state ? FSL_RC_MISUSE : fsl_buffer_append((fsl_buffer*)state, src, n); } int fsl_finalizer_f_buffer( void * state, void * mem ){ fsl_buffer * b = (fsl_buffer*)mem; fsl_buffer_reserve(b, 0); *b = fsl_buffer_empty; return 0; } int fsl_cx_init( fsl_cx ** tgt, fsl_init_param * param ){ static fsl_init_param paramDefaults = fsl_init_param_default_m; int rc; fsl_cx * f; if(!tgt) return FSL_RC_MISUSE; else if(!param){ if(!paramDefaults.output.state.state){ paramDefaults.output.state.state = stdout; } param = ¶mDefaults; } f = (fsl_cx*)fsl_malloc(sizeof(fsl_cx)); if(!f) return FSL_RC_OOM; *tgt = f; *f = fsl_cx_empty; f->output = param->output; f->config = param->config; f->dbMain.f = f; rc = fsl_db_open(&f->dbMain, /*"/tmp/foo.db"*/ ":memory:", FSL_OPEN_F_RWC) /* i'd prefer to use a temporary file here, because sqlite does not allow us to create TEMP views/tables in a non-main db. e.g. CREATE TEMPORARY VIEW foo.BAR ... is not legal. */; if(rc){ fsl_error_move( &f->dbMain.error, &f->error ); }else{ f->dbMain.role = FSL_DB_ROLE_MAIN; } return rc; } int fsl_cx_finalize( fsl_cx * f ){ if(!f) return FSL_RC_MISUSE; else{ if(f->clientState.finalize.f){ f->clientState.finalize.f( f->clientState.finalize.state, f->clientState.state ); } if(f->output.state.finalize.f){ f->output.state.finalize.f( f->output.state.finalize.state, f->output.state.state ); } fsl_error_clean(&f->error); fsl_db_close(&f->dbMain); fsl_buffer_reserve(&f->fileConfig, 0); fsl_buffer_reserve(&f->fileRepo, 0); fsl_buffer_reserve(&f->fileCkout, 0); fsl_buffer_reserve(&f->scratch, 0); fsl_buffer_reserve(&f->dirCkout, 0); *f = fsl_cx_empty; fsl_free(f); return 0; } } int fsl_output( fsl_cx * cx, void const * src, fsl_size_t n ){ if(!cx || !src) return FSL_RC_MISUSE; else if(!n || !cx->output.out) return 0; else return cx->output.out( cx->output.state.state, src, n ); } void fsl_error_clean( fsl_error * err ){ if(err){ if(err->msg.mem) fsl_buffer_reserve(&err->msg, 0); *err = fsl_error_empty; } } void fsl_error_move( fsl_error * lower, fsl_error * higher ){ fsl_error const err = *lower; *lower = *higher; lower->code = 0; lower->msg.used = 0; *higher = err; } int fsl_error_setv( fsl_error * err, int code, char const * fmt, va_list args ){ if(!err) return FSL_RC_MISUSE; else if(!code){ /* clear error state */ err->code = 0; err->msg.used = 0; if(err->msg.mem){ err->msg.mem[0] = 0; } return 0; }else{ int rc = 0; err->msg.used = 0; err->code = code; if(FSL_RC_OOM!=code){ rc = fmt ? fsl_buffer_appendfv(&err->msg, fmt, args) : fsl_buffer_append(&err->msg, fsl_rc_cstr(code), -1); } return rc ? rc : code; } } int fsl_error_set( fsl_error * err, int code, char const * fmt, ... ){ int rc; va_list args; va_start(args,fmt); rc = fsl_error_setv(err, code, fmt, args); va_end(args); return rc; } int fsl_error_get( fsl_error * err, char const ** str, fsl_size_t * len ){ if(!err) return FSL_RC_MISUSE; else{ if(str) *str = err->msg.used ? (char const *)err->msg.mem : NULL; if(len) *len = err->msg.used; return err->code; } } int fsl_cx_err_set_e( fsl_cx * f, fsl_error * err ){ if(!f) return FSL_RC_MISUSE; else if(!err){ return fsl_cx_err_set(f, 0, NULL); }else{ fsl_error_move(err, &f->error); fsl_error_clean(err); return f->error.code; } } int fsl_cx_err_setv( fsl_cx * f, int code, char const * fmt, va_list args ){ return f ? fsl_error_setv( &f->error, code, fmt, args ) : FSL_RC_MISUSE; } int fsl_cx_err_set( fsl_cx * f, int code, char const * fmt, ... ){ if(!f) return FSL_RC_MISUSE; else{ int rc; va_list args; va_start(args,fmt); rc = fsl_error_setv( &f->error, code, fmt, args ); va_end(args); return rc; } } int fsl_cx_err_get( fsl_cx * f, char const ** str, fsl_size_t * len ){ return f ? fsl_error_get( &f->error, str, len ) : FSL_RC_MISUSE; } fsl_id_t fsl_cx_last_insert_id(fsl_cx * f){ return (f && f->dbMain.dbh) ? fsl_db_last_insert_id(&f->dbMain) : -1; } /* ** fsl_appendf_f() impl which sends its output to fsl_output(). state ** must be a (fsl_cx*). */ static fsl_int_t fsl_appendf_f_fsl_output( void * state, char const * s, fsl_int_t n ){ fsl_cx * f = (fsl_cx *)state; return fsl_output( f, s, (fsl_size_t)n ) ? -1 : n; } int fsl_outputfv( fsl_cx * f, char const * fmt, va_list args ){ if(!f || !fmt) return FSL_RC_MISUSE; else if(!*fmt) return FSL_RC_RANGE; else{ long const prc = fsl_appendfv( fsl_appendf_f_fsl_output, f, fmt, args ); return (prc>=0) ? 0 : FSL_RC_IO; } } int fsl_outputf( fsl_cx * f, char const * fmt, ... ){ if(!f || !fmt) return FSL_RC_MISUSE; else if(!*fmt) return FSL_RC_RANGE; else{ int rc; va_list args; va_start(args,fmt); rc = fsl_outputfv( f, fmt, args ); va_end(args); return rc; } } char const * fsl_rc_cstr(int rc){ fsl_rc_t const RC = (fsl_rc_t)rc /* we do this so that gcc will warn if the switch() below is missing any fsl_rc_t entries. */ ; switch(RC){ #define STR(T) case FSL_RC_##T: return "FSL_RC_" #T STR(ACCESS); STR(ALREADY_EXISTS); STR(AMBIGUOUS); STR(BREAK); STR(CHECKSUM_MISMATCH); STR(CONSISTENCY); STR(DB); STR(DELTA_INVALID_OPERATOR); STR(DELTA_INVALID_SEPARATOR); STR(DELTA_INVALID_SIZE); STR(DELTA_INVALID_TERMINATOR); STR(ERROR); STR(IO); STR(MF_SYNTAX); STR(MISUSE); STR(NOT_A_CHECKOUT); STR(NOT_A_REPO); STR(NOT_FOUND); STR(NYI); STR(OK); STR(OOM); STR(RANGE); STR(REPO_MISMATCH); STR(REPO_NEEDS_REBUILD); STR(REPO_VERSION); STR(SIZE_MISMATCH); STR(STEP_DONE); STR(STEP_ERROR); STR(STEP_ROW); STR(TYPE); STR(TRAILING_COMMA_KLUDGE); #undef STR } return "Unknown result code"; } char const * fsl_library_version(){ return FSL_LIBRARY_VERSION; } char fsl_library_version_matches(char const * yourLibVersion){ return 0 == fsl_strcmp(FSL_LIBRARY_VERSION, yourLibVersion); } fsl_size_t fsl_list_reserve( fsl_list * self, fsl_size_t n ) { if( !self ) return 0; else if(0 == n){ if(0 == self->capacity) return 0; fsl_free(self->list); self->list = NULL; self->capacity = self->used = 0; return 0; } else if( self->capacity >= n ){ return self->capacity; } else{ size_t const sz = sizeof(void*) * n; void* * m = (void**)fsl_realloc( self->list, sz ); if( !m ) return self->capacity; memset( m + self->capacity, 0, (sizeof(void*)*(n-self->capacity))); self->capacity = n; self->list = m; return n; } } int fsl_list_append( fsl_list * self, void* cp ){ if( !self ) return FSL_RC_MISUSE; else if( self->capacity > fsl_list_reserve(self, self->used+1)){ return FSL_RC_OOM; } else{ self->list[self->used++] = cp; if(self->used< self->capacity-1) self->list[self->used]=0; return 0; } } int fsl_list_v_fsl_free(void * obj, void * visitorState ){ if(obj) fsl_free( obj ); return 0; } int fsl_list_visit( fsl_list const * self, char order, fsl_list_visitor_f visitor, void * visitorState ){ int rc = FSL_RC_OK; if( self && self->used && visitor ){ int i = 0; int pos = (order<0) ? self->used-1 : 0; int step = (order<0) ? -1 : 1; for( rc = 0; (i < self->used) && (0 == rc); ++i, pos+=step ){ void* obj = self->list[pos]; if(obj) rc = visitor( obj, visitorState ); if( obj != self->list[pos] ){ --i; if(order>=0) pos -= step; } } } return rc; } int fsl_list_visit_p( fsl_list * self, char order, char shiftIfNulled, fsl_list_visitor_f visitor, void * visitorState ) { int rc = FSL_RC_OK; if( self && self->used && visitor ){ int i = 0; int pos = (order<0) ? self->used-1 : 0; int step = (order<0) ? -1 : 1; for( rc = 0; (i < (int)self->used) && (0 == rc); ++i, pos+=step ){ void* obj = self->list[pos]; if(obj) { assert((order<0) && "TEST THAT THIS WORKS WITH IN-ORDER!"); rc = visitor( &self->list[pos], visitorState ); if( shiftIfNulled && !self->list[pos]){ int x = pos; int const to = self->used-pos; assert( to < (int) self->capacity ); for( ; x < to; ++x ) self->list[x] = self->list[x+1]; if( x < (int)self->capacity ) self->list[x] = 0; --i; --self->used; if(order>=0) pos -= step; } } } } return rc; } fsl_double_t fsl_unix_to_julian( fsl_time_t unix ){ return (unix * 1.0 / 86400.0 ) + 2440587.5; } int fsl_strcmp(const char *zA, const char *zB){ if( zA==0 ){ if( zB==0 ) return 0; return -1; }else if( zB==0 ){ return +1; }else{ int a, b; do{ a = *zA++; b = *zB++; }while( a==b && a!=0 ); return ((unsigned char)a) - (unsigned char)b; } } int fsl_strncmp(const char *zA, const char *zB, fsl_size_t nByte){ if( !zA ) return zB ? -1 : 0; else if( !zB ) return +1; else if(!nByte) return 0; else{ int a, b; do{ a = *zA++; b = *zB++; }while( a==b && a!=0 && (--nByte)>0 ); return ((unsigned char)a) - (unsigned char)b; } } /* ** Case insensitive string comparison. */ int fsl_strnicmp(const char *zA, const char *zB, fsl_int_t nByte){ if( zA==0 ){ if( zB==0 ) return 0; return -1; }else if( zB==0 ){ return +1; } if( nByte<0 ) nByte = (fsl_int_t)fsl_strlen(zB); return sqlite3_strnicmp(zA, zB, nByte); } int fsl_stricmp(const char *zA, const char *zB){ fsl_int_t nByte; int rc; if( zA==0 ){ if( zB==0 ) return 0; return -1; }else if( zB==0 ){ return +1; } nByte = (fsl_int_t)fsl_strlen(zB); rc = sqlite3_strnicmp(zA, zB, nByte); if( rc==0 && zA[nByte] ) rc = 1; return rc; } fsl_size_t fsl_strlen( char const * src ){ fsl_size_t i = 0; if(src) for( ; *src; ++i, ++src ){} return i; } char * fsl_strndup( char const * src, fsl_int_t len ){ fsl_buffer b = fsl_buffer_empty; if(!src) return NULL; else if(len<0) len = (fsl_int_t)fsl_strlen(src); fsl_buffer_append( &b, len ? src : "\0", len ? len : 1 ) /*special case for 0-length strings, to avoid returning NULL there. */; return (char*)b.mem; } char * fsl_strdup( char const * src ){ return fsl_strndup(src, -1); } char fsl_str_glob(const char *zGlob, const char *z){ int c, c2; int invert; int seen; while( (c = (*(zGlob++)))!=0 ){ if( c=='*' ){ while( (c=(*(zGlob++))) == '*' || c=='?' ){ if( c=='?' && (*(z++))==0 ) return 0; } if( c==0 ){ return 1; }else if( c=='[' ){ while( *z && fsl_str_glob(zGlob-1,z)==0 ){ z++; } return (*z)!=0; } while( (c2 = (*(z++)))!=0 ){ while( c2!=c ){ c2 = *(z++); if( c2==0 ) return 0; } if( fsl_str_glob(zGlob,z) ) return 1; } return 0; }else if( c=='?' ){ if( (*(z++))==0 ) return 0; }else if( c=='[' ){ int prior_c = 0; seen = 0; invert = 0; c = *(z++); if( c==0 ) return 0; c2 = *(zGlob++); if( c2=='^' ){ invert = 1; c2 = *(zGlob++); } if( c2==']' ){ if( c==']' ) seen = 1; c2 = *(zGlob++); } while( c2 && c2!=']' ){ if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ c2 = *(zGlob++); if( c>=prior_c && c<=c2 ) seen = 1; prior_c = 0; }else{ if( c==c2 ){ seen = 1; } prior_c = c2; } c2 = *(zGlob++); } if( c2==0 || (seen ^ invert)==0 ) return 0; }else{ if( c!=(*(z++)) ) return 0; } } return *z==0; } fsl_int32_t fsl_cx_lget_int32( fsl_cx * f, fsl_int32_t dflt, char const * key ){ fsl_int32_t rv = dflt; fsl_db * db = fsl_cx_db_checkout(f); if(db){ fsl_db_get_int32(db, &rv, "SELECT value FROM %s.vvar WHERE name=%Q", fsl_db_role_label(FSL_DB_ROLE_CHECKOUT), key); } return rv; } fsl_int64_t fsl_cx_lget_int64( fsl_cx * f, fsl_int64_t dflt, char const * key ){ fsl_int64_t rv = dflt; fsl_db * db = fsl_cx_db_checkout(f); if(db){ fsl_db_get_int64(db, &rv, "SELECT value FROM %s.vvar WHERE name=%Q", fsl_db_role_label(FSL_DB_ROLE_CHECKOUT), key); } return rv; } fsl_id_t fsl_cx_lget_id( fsl_cx * f, fsl_id_t dflt, char const * key ){ fsl_id_t rv = dflt; fsl_db * db = fsl_cx_db_checkout(f); if(db){ fsl_db_get_id(db, &rv, "SELECT value FROM %s.vvar WHERE name=%Q", fsl_db_role_label(FSL_DB_ROLE_CHECKOUT), key); } return rv; } fsl_double_t fsl_cx_lget_double( fsl_cx * f, fsl_double_t dflt, char const * key ){ fsl_double_t rv = dflt; fsl_db * db = fsl_cx_db_checkout(f); if(db){ fsl_db_get_double(db, &rv, "SELECT value FROM %s.vvar WHERE name=%Q", fsl_db_role_label(FSL_DB_ROLE_CHECKOUT), key); } return rv; } char * fsl_cx_lget_text( fsl_cx * f, fsl_size_t * len, char const * key ){ char * rv = NULL; fsl_db * db = fsl_cx_db_checkout(f); if(db){ fsl_db_get_text(db, &rv, len, "SELECT value FROM %s.vvar WHERE name=%Q", fsl_db_role_label(FSL_DB_ROLE_CHECKOUT), key); } return rv; } /* ** Return TRUE if the string begins with something that looks roughly ** like an ISO date/time string. The SQLite date/time functions will ** have the final say-so about whether or not the date/time string is ** well-formed. */ char fsl_str_is_date(const char *z){ if(!z || !*z) return 0; if( !fsl_isdigit(z[0]) ) return 0; if( !fsl_isdigit(z[1]) ) return 0; if( !fsl_isdigit(z[2]) ) return 0; if( !fsl_isdigit(z[3]) ) return 0; if( z[4]!='-') return 0; if( !fsl_isdigit(z[5]) ) return 0; if( !fsl_isdigit(z[6]) ) return 0; if( z[7]!='-') return 0; if( !fsl_isdigit(z[8]) ) return 0; if( !fsl_isdigit(z[9]) ) return 0; return 1; } char const * fsl_atype_cstr(fsl_artifact_t t){ switch(t){ case FSL_ATYPE_ANY: return "*"; case FSL_ATYPE_CHECKIN: return "ci"; case FSL_ATYPE_EVENT: return "e"; case FSL_ATYPE_G: return "g"; case FSL_ATYPE_TICKET: return "t"; case FSL_ATYPE_WIKI: return "w"; default: return NULL; } } int fsl_cx_sym_to_rid( fsl_cx * f, char const * sym, fsl_artifact_t type, fsl_id_t * rv ){ fsl_id_t rid = 0; fsl_id_t vid; fsl_size_t symLen; /* fsl_int_t i; */ fsl_db * dbR = fsl_cx_db_repo(f); fsl_db * dbC = fsl_cx_db_checkout(f); if(!f || !sym || !*sym || !rv) return FSL_RC_MISUSE; else if(!dbR) return FSL_RC_NOT_A_REPO; /* special keyword: "tip" */ if( 0==fsl_strcmp(sym,"tip") && (FSL_ATYPE_ANY==type || FSL_ATYPE_CHECKIN==type)){ rid = fsl_db_g_id(dbR, 0, "SELECT objid FROM event" " WHERE type='ci'" " ORDER BY event.mtime DESC" " LIMIT 1"); if(rid>0) goto gotit; } /* special keywords: "prev", "previous", "current", and "next". These require a checkout. */ vid = dbC ? fsl_cx_lget_id(f, 0, "checkout") : 0; if( vid>0){ if( 0==fsl_strcmp(sym, "current") ){ rid = vid; } else if( 0==fsl_strcmp(sym, "prev") || 0==fsl_strcmp(sym, "previous") ){ rid = fsl_db_g_id(dbR, 0, "SELECT pid FROM plink WHERE " "cid=%"FSL_ID_T_PFMT" AND isprim", vid); } else if( 0==fsl_strcmp(sym, "next") ){ rid = fsl_db_g_id(dbR, 0, "SELECT cid FROM plink WHERE " "pid=%"FSL_ID_T_PFMT " ORDER BY isprim DESC, mtime DESC", vid); } if(rid>0) goto gotit; } /* Date and times */ if( 0==memcmp(sym, "date:", 5) ){ rid = fsl_db_g_id(dbR, 0, "SELECT objid FROM event" " WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'" " ORDER BY mtime DESC LIMIT 1", &sym[5], sym); *rv = rid; return 0; } if( fsl_str_is_date(sym) ){ rid = fsl_db_g_id(dbR, 0, "SELECT objid FROM event" " WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'" " ORDER BY mtime DESC LIMIT 1", sym, fsl_atype_cstr(type)); if(rid>0) goto gotit; } /* Deprecated time formats elided: local:..., utc:... */ /* "tag:" + symbolic-name */ if( memcmp(sym, "tag:", 4)==0 ){ rid = fsl_db_g_id(dbR, 0, "SELECT event.objid, max(event.mtime)" " FROM tag, tagxref, event" " WHERE tag.tagname='sym-%q' " " AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 " " AND event.objid=tagxref.rid " " AND event.type GLOB '%q'", &sym[4], fsl_atype_cstr(type) ); goto gotit; } #if 0 /* TODO */ /* root:TAG -> The origin of the branch */ if( memcmp(zTag, "root:", 5)==0 ){ Stmt q; int rc; char *zBr; rid = symbolic_name_to_rid(zTag+5, zType); zBr = db_text("trunk","SELECT value FROM tagxref" " WHERE rid=%d AND tagid=%d" " AND tagtype>0", rid, TAG_BRANCH); db_prepare(&q, "SELECT pid, EXISTS(SELECT 1 FROM tagxref" " WHERE tagid=%d AND tagtype>0" " AND value=%Q AND rid=plink.pid)" " FROM plink" " WHERE cid=:cid AND isprim", TAG_BRANCH, zBr ); fossil_free(zBr); do{ db_reset(&q); db_bind_int(&q, ":cid", rid); rc = db_step(&q); if( rc!=SQLITE_ROW ) break; rid = db_column_int(&q, 0); }while( db_column_int(&q, 1)==1 && rid>0 ); db_finalize(&q); return rid; } #endif symLen = fsl_strlen(sym); /* SHA1 hash or prefix */ if( symLen>=4 && symLen<=FSL_UUID_STRLEN && fsl_validate16(sym, symLen) ){ fsl_stmt q = fsl_stmt_empty; char zUuid[FSL_UUID_STRLEN+1]; memcpy(zUuid, sym, symLen); zUuid[symLen] = 0; fsl_canonical16(zUuid, symLen); rid = 0; if( FSL_ATYPE_ANY==type ){ fsl_stmt_prepare(dbR, &q, "SELECT rid FROM blob WHERE uuid GLOB '%s*'", zUuid); }else{ fsl_stmt_prepare(dbR, &q, "SELECT blob.rid" " FROM blob, event" " WHERE blob.uuid GLOB '%s*'" " AND event.objid=blob.rid" " AND event.type GLOB '%q'", zUuid, fsl_atype_cstr(type) ); } if( fsl_stmt_step(&q)==FSL_RC_STEP_ROW ){ fsl_int64_t r64 = 0; fsl_stmt_get_int64(&q, 0, &r64); if( fsl_stmt_step(&q)==FSL_RC_STEP_ROW ) rid = -1 /* Ambiguous results */ ; else rid = (fsl_id_t)r64; } fsl_stmt_finalize(&q); if(rid<0) return FSL_RC_AMBIGUOUS; else if(rid>0) goto gotit; } /* Symbolic name ... */ rid = fsl_db_g_id(dbR, 0, "SELECT event.objid, max(event.mtime)" " FROM tag, tagxref, event" " WHERE tag.tagname='sym-%q' " " AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 " " AND event.objid=tagxref.rid " " AND event.type GLOB '%q'", sym, fsl_atype_cstr(type) ); if( rid>0 ) goto gotit; #if 0 /* v1 has this undocumented feature, but it is being elided for now. */ /* Undocumented: numeric tags get translated directly into the RID */ for(i=0; fsl_isdigit(sym[i]); i++){} if( sym[i]==0 ){ if( FSL_ATYPE_ANY==type ){ rid = atoi(sym); }else{ rid = fsl_db_g_id(dbR, 0, "SELECT event.objid" " FROM event" " WHERE event.objid=%s" " AND event.type GLOB '%q'", zTag, zType); } if( rid>0 ) goto gotit; } #endif return FSL_RC_NOT_FOUND; gotit: *rv = rid; return 0; } int fsl_cx_sym_to_uuid( fsl_cx * f, char const * sym, fsl_artifact_t type, char ** rv, fsl_id_t * rvId ){ fsl_id_t rid = 0; int rc = fsl_cx_sym_to_rid(f, sym, type, &rid); if(rvId) *rvId = rid; return rc ? rc : fsl_db_get_text( &f->dbMain, rv, NULL, "SELECT uuid FROM blob WHERE rid=%"FSL_ID_T_PFMT, rid ); }