/* -*- 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 the manifest/control-artifact-related APIs.
*/
#include "fossil-scm/fossil-internal.h"
#include <assert.h>
#include <memory.h> /* memcmp() */
/* Only for debugging */
#define MARKER(pfexp) \
do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
printf pfexp; \
} while(0)
/**
If mem is NULL or inside d->content.mem then this function does
nothing, else it passes mem to fsl_free(). Intended to be used to
clean up d->XXX string members (or sub-members) which have been
optimized away via d->content.
*/
static void fsl_deck_free_string(fsl_deck * d, char * mem){
assert(d);
if(mem
&& (!d->content.used
|| !(((unsigned char const *)mem >=d->content.mem)
&&
((unsigned char const *)mem < (d->content.mem+d->content.capacity)))
)){
fsl_free(mem);
}/* else do nothing - the memory is NULL or owned by d->content. */
}
/**
fsl_list_visitor_f() impl which frees fsl_list-of-(char*) card entries
in ((fsl_deck*)visitorState).
*/
static int fsl_list_v_card_string_free(void * mCard, void * visitorState ){
fsl_deck_free_string( (fsl_deck*)visitorState, mCard );
return 0;
}
void fsl_card_F_clean(fsl_card_F *t){
if(t){
if(!t->externalStrings){
fsl_free(t->name);
fsl_free(t->uuid);
fsl_free(t->priorName);
}
*t = fsl_card_F_empty;
}
}
void fsl_card_F_free(fsl_card_F *t){
if(t){
fsl_card_F_clean(t);
fsl_free(t);
}
}
fsl_card_Q * fsl_card_Q_malloc(int type,
fsl_uuid_cstr target,
fsl_uuid_cstr baseline){
if(!type || !target || !fsl_is_uuid(target)
|| (baseline && !fsl_is_uuid(baseline))) return NULL;
else{
fsl_card_Q * c =
(fsl_card_Q*)fsl_malloc(sizeof(fsl_card_Q));
if(c){
int rc = 0;
*c = fsl_card_Q_empty;
c->type = type;
c->target = fsl_strndup(target, FSL_UUID_STRLEN);
if(!c->target) rc = FSL_RC_OOM;
else if(baseline){
c->baseline = fsl_strndup( baseline, FSL_UUID_STRLEN);
if(!c->baseline) rc = FSL_RC_OOM;
}
if(rc){
fsl_card_Q_free(c);
c = NULL;
}
}
return c;
}
}
void fsl_card_Q_free( fsl_card_Q * cp ){
if(cp){
fsl_free(cp->target);
fsl_free(cp->baseline);
*cp = fsl_card_Q_empty;
fsl_free(cp);
}
}
fsl_card_J * fsl_card_J_malloc(char isAppend,
char const * field,
char const * value){
if(!field || !*field) return NULL;
else{
fsl_card_J * c =
(fsl_card_J*)fsl_malloc(sizeof(fsl_card_J));
if(c){
int rc = 0;
fsl_size_t const lF = fsl_strlen(field);
fsl_size_t const lV = value ? fsl_strlen(value) : 0;
*c = fsl_card_J_empty;
c->append = isAppend ? 1 : 0;
c->field = fsl_strndup(field, (fsl_int_t)lF);
if(!c->field) rc = FSL_RC_OOM;
else if(value && *value){
c->value = fsl_strndup(value, (fsl_int_t)lV);
if(!c->value) rc = FSL_RC_OOM;
}
if(rc){
fsl_card_J_free(c);
c = NULL;
}
}
return c;
}
}
void fsl_card_J_free( fsl_card_J * cp ){
if(cp){
fsl_free(cp->field);
fsl_free(cp->value);
*cp = fsl_card_J_empty;
fsl_free(cp);
}
}
fsl_card_F * fsl_card_F_malloc(char const * name,
char const * uuid,
fsl_file_perm_t perm,
char const * oldName){
fsl_card_F * t;
if(!name || !*name) return NULL;
else if(uuid && !fsl_is_uuid(uuid)) return NULL;
t = (fsl_card_F *)fsl_malloc(sizeof(fsl_card_F));
if(t){
int rc = 0;
*t = fsl_card_F_empty;
t->perm = perm;
t->name = fsl_strdup(name);
if(!t->name) rc = FSL_RC_OOM;
if(!rc && uuid){
t->uuid = fsl_strdup(uuid);
if(!t->uuid) rc = FSL_RC_OOM;
}
if(!rc && oldName){
t->priorName = fsl_strdup(oldName);
if(!t->priorName) rc = FSL_RC_OOM;
}
if(rc){
fsl_card_F_free(t);
t = NULL;
}
}
return t;
}
/**
fsl_list_visitor_f() impl which requires that obj be-a (fsl_card_T*),
which this function passes to fsl_card_T_free().
*/
static int fsl_list_v_card_T_free(void * obj, void * visitorState ){
if(obj) fsl_card_T_free( (fsl_card_T*)obj );
return 0;
}
static int fsl_list_v_card_F_free(void * obj, void * visitorState ){
if(obj) fsl_card_F_free( (fsl_card_F*)obj );
return 0;
}
static int fsl_list_v_card_Q_free(void * obj, void * visitorState ){
if(obj) fsl_card_Q_free( (fsl_card_Q*)obj );
return 0;
}
static int fsl_list_v_card_J_free(void * obj, void * visitorState ){
if(obj) fsl_card_J_free( (fsl_card_J*)obj );
return 0;
}
void fsl_card_J_list_free( fsl_list * li, char alsoListMem ){
if(li->used) fsl_list_visit(li, 0, fsl_list_v_card_J_free, NULL );
if(alsoListMem) fsl_list_reserve(li, 0);
else li->used = 0;
}
fsl_deck * fsl_deck_malloc(){
fsl_deck * rc = (fsl_deck *)fsl_malloc(sizeof(fsl_deck));
if(rc){
*rc = fsl_deck_empty;
rc->allocStamp = &fsl_deck_empty;
}
return rc;
}
void fsl_deck_init( fsl_cx * f, fsl_deck * cards, fsl_catype_t type ){
void const * allocStamp = cards->allocStamp;
*cards = fsl_deck_empty;
cards->allocStamp = allocStamp;
cards->f = f;
cards->type = type;
}
void fsl_deck_clean(fsl_deck *m){
if(!m) return;
if(m->B.baseline){
assert(!m->B.baseline->B.uuid && "Baselines cannot have a B-card. API misuse?");
fsl_deck_finalize(m->B.baseline);
m->B.baseline = NULL;
}
#define CBUF(X) fsl_buffer_clear(&m->X)
#define SFREE(X) if(m->X) fsl_deck_free_string(m, m->X); m->X = NULL
#define SLIST(X) fsl_list_clear(&m->X, fsl_list_v_card_string_free, m)
SFREE(uuid);
SFREE(A.name);
SFREE(A.tgt);
SFREE(A.src);
SFREE(B.uuid);
SFREE(C);
SFREE(E.uuid);
fsl_list_clear(&m->F.list, fsl_list_v_card_F_free, NULL);
fsl_card_J_list_free(&m->J, 1);
SFREE(K);
SFREE(L);
SLIST(M);
SFREE(N);
SLIST(P);
fsl_list_clear(&m->Q, fsl_list_v_card_Q_free, NULL);
SFREE(R);
fsl_list_clear(&m->T, fsl_list_v_card_T_free, NULL);
SFREE(U);
CBUF(W);
CBUF(content) /* content must be after all cards because some point
back into it and we need this memory intact in
order to know that!
*/;
fsl_error_clear(&m->error);
{
void const * allocStampKludge = m->allocStamp;
fsl_cx * f = m->f;
*m = fsl_deck_empty;
m->allocStamp = allocStampKludge;
m->f = f;
}
#undef CBUF
#undef SFREE
#undef SLIST
}
void fsl_deck_finalize(fsl_deck *m){
void const * allocStamp;
if(!m) return;
allocStamp = m->allocStamp;
fsl_deck_clean(m);
if(allocStamp == &fsl_deck_empty){
fsl_free(m);
}else{
m->allocStamp = allocStamp;
}
}
int fsl_card_is_legal( fsl_catype_t t, char card ){
/*
Implements this table:
http://fossil-scm.org/index.html/doc/trunk/www/fileformat.wiki#summary
*/
if('Z'==card) return 1;
else switch(t){
case FSL_CATYPE_ANY:
switch(card){
case 'A': case 'B': case 'C': case 'D':
case 'E': case 'F': case 'J': case 'K':
case 'L': case 'M': case 'N': case 'P':
case 'Q': case 'R': case 'T': case 'U':
case 'W':
return -1;
default:
return 0;
}
case FSL_CATYPE_ATTACHMENT:
switch(card){
case 'A': case 'D':
return 1;
case 'C': case 'N': case 'U':
return -1;
default:
return 0;
};
case FSL_CATYPE_CLUSTER:
return 'M'==card ? 1 : 0;
case FSL_CATYPE_CONTROL:
switch(card){
case 'D': case 'T': case 'U':
return 1;
default:
return 0;
};
case FSL_CATYPE_EVENT:
switch(card){
case 'D': case 'E':
case 'W':
return 1;
case 'C': case 'N':
case 'P': case 'T':
case 'U':
return -1;
default:
return 0;
};
case FSL_CATYPE_CHECKIN:
switch(card){
case 'C': case 'D':
case 'U':
return 1;
case 'B': case 'F':
case 'N': case 'P':
case 'Q': case 'R':
case 'T':
return -1;
default:
return 0;
};
case FSL_CATYPE_TICKET:
switch(card){
case 'D': case 'J':
case 'K': case 'U':
return 1;
default:
return 0;
};
case FSL_CATYPE_WIKI:
switch(card){
case 'D': case 'L':
case 'U': case 'W':
return 1;
case 'N': case 'P':
return -1;
default:
return 0;
};
default:
assert(!"Invalid fsl_catype_t.");
return 0;
};
}
char fsl_deck_has_required_cards( fsl_deck const * d ){
return d ? fsl_deck_required_cards_check(d, NULL) : 0;
}
char fsl_deck_required_cards_check( fsl_deck const * d,
fsl_error * err){
if(!d) return 0;
else switch(d->type){
case FSL_CATYPE_ANY:
return 0;
#define NEED(CARD,COND) \
if(!(COND)) { \
if(err) fsl_error_set(err, FSL_RC_CA_SYNTAX, \
"Required %c-card is missing or invalid.", \
*#CARD); \
return 0; \
} (void)0
case FSL_CATYPE_ATTACHMENT:
NEED(A,d->A.name);
NEED(A,d->A.tgt);
NEED(D,d->D > 0);
return 1;
case FSL_CATYPE_CLUSTER:
NEED(M,d->M.used);
return 1;
case FSL_CATYPE_CONTROL:
NEED(D,d->D > 0);
NEED(U,d->U);
NEED(T,d->T.used>0);
return 1;
case FSL_CATYPE_EVENT:
NEED(D,d->D > 0);
NEED(E,d->E.julian>0);
NEED(E,d->E.uuid);
NEED(W,d->W.used);
return 1;
case FSL_CATYPE_CHECKIN:
/*
Historically we need both or neither of F- and R-cards, but
the R-card has become optional because it's so expensive to
calculate and verify.
Manifest #1 has an empty file list and an R-card with a
constant (repo/manifest-independent) hash
(d41d8cd98f00b204e9800998ecf8427e, the initial MD5 hash
state).
R-card calculation is runtime-configurable option. Rather
than rely on d->f being set (so d->f->flags can tell us
whether or not to calculate an R-card), we'll rely on
downstream code to check for an R-chard if needed.
*/
NEED(D,d->D > 0);
NEED(C,d->C);
NEED(C,d->U);
NEED(F,d->F.list.used || d->R/*with initial-state md5 hash!*/);
if(d->f
&& !d->R
&& (FSL_CX_F_CALC_R_CARD & d->f->flags)){
if(err) fsl_error_set(err, FSL_RC_CA_SYNTAX,
"%s deck is missing an R-card, "
"yet R-card calculation is enabled.",
fsl_catype_cstr(d->type));
return 0;
}else if(d->R
&& !d->F.list.used
&& 0!=fsl_uuidcmp(d->R, FSL_MD5_INITIAL_HASH)
){
if(err) fsl_error_set(err, FSL_RC_CA_SYNTAX,
"Deck has no F-cards, so we expect its "
"R-card is to have the initial-state MD5 "
"hash (%.12s...). Instead we got: %s",
FSL_MD5_INITIAL_HASH, d->R);
return 0;
}
return 1;
case FSL_CATYPE_TICKET:
NEED(D,d->D > 0);
NEED(K,d->K);
NEED(U,d->U);
NEED(J,d->J.used)
/*
Is a J strictly required?
Spec is not clear but DRH confirms the
current fossil(1) code expects a J card.
*/
;
return 1;
case FSL_CATYPE_WIKI:
NEED(D,d->D > 0);
NEED(L,d->L);
NEED(U,d->U);
NEED(W,d->W.used);
return 1;
case FSL_CATYPE_INVALID:
default:
assert(!"Invalid fsl_catype_t.");
return 0;
}
#undef NEED
}
char const * fsl_catype_cstr(fsl_catype_t t){
switch(t){
#define C(X) case FSL_CATYPE_##X: return #X
C(ANY);
C(CHECKIN);
C(CLUSTER);
C(CONTROL);
C(WIKI);
C(TICKET);
C(ATTACHMENT);
C(EVENT);
C(INVALID);
default:
assert(!"UNHANDLED fsl_catype_t");
return "!UNKNOWN!";
}
}
char const * fsl_catype_event_cstr(fsl_catype_t t){
switch(t){
case FSL_CATYPE_ANY: return "*";
case FSL_CATYPE_CHECKIN: return "ci";
case FSL_CATYPE_EVENT: return "e";
case FSL_CATYPE_CONTROL: return "g";
case FSL_CATYPE_TICKET: return "t";
case FSL_CATYPE_WIKI: return "w";
default:
return NULL;
}
}
/**
If fsl_card_is_legal(d->type, card), returns non-0 (true),
else updated d->error with a description of the constraint
violation and returns 0.
*/
static char fsl_deck_check_type( fsl_deck * d, char card ){
if(fsl_card_is_legal(d->type, card)) return 1;
else{
fsl_error_set(&d->error, FSL_RC_TYPE,
"Card type '%c' is not allowed "
"in control artifacts of type %s.",
card, fsl_catype_cstr(d->type));
return 0;
}
}
/*
Implements fsl_deck_LETTER_set() for certain letters: those
implemented as a fsl_uuid_str or an md5, holding a single hex string
value.
The function returns FSL_RC_RANGE if (valLen!=ASSERTLEN). ASSERTLEN
is assumed to be either an SHA1 or MD5 hash value and it is
validated against fsl_validate16(value,valLen), returning
FSL_RC_CA_SYNTAX if that check fails.
*/
static int fsl_deck_sethex_impl( fsl_deck * mf, fsl_uuid_cstr value,
char letter,
fsl_size_t assertLen,
char ** mfMember ){
assert(mf);
assert( assertLen==FSL_UUID_STRLEN || assertLen==FSL_MD5_STRLEN );
if(!fsl_deck_check_type(mf,letter)) return FSL_RC_TYPE;
else if(!value){
fsl_deck_free_string(mf, *mfMember);
*mfMember = NULL;
return 0;
}else if(fsl_strlen(value) != (assertLen)){
return fsl_error_set(&mf->error, FSL_RC_RANGE,
"Invalid length for %c-card: expecting %d.",
letter, (int)assertLen);
}else if(!fsl_validate16(value, assertLen)) {
return fsl_error_set(&mf->error, FSL_RC_CA_SYNTAX,
"Invalid hexadecimal value for %c-card.", letter);
}else{
fsl_deck_free_string(mf, *mfMember);
*mfMember = fsl_strndup( value, assertLen );
return *mfMember ? 0 : FSL_RC_OOM;
}
}
/**
Implements fsl_set_set_XXX() where XXX is a fsl_buffer member of fsl_deck.
*/
static int fsl_deck_b_setuffer_impl( fsl_deck * mf, char const * value,
fsl_int_t valLen,
char letter, fsl_buffer * buf){
assert(mf);
if(!fsl_deck_check_type(mf,letter)) return FSL_RC_TYPE;
else if(valLen<0) valLen = (fsl_int_t)fsl_strlen(value);
buf->used = 0;
if(value && (valLen>0)){
return fsl_buffer_append( buf, value, valLen );
}else{
if(buf->mem) buf->mem[0] = 0;
return 0;
}
}
int fsl_deck_B_set( fsl_deck * mf, fsl_uuid_cstr uuidBaseline){
if(!mf) return FSL_RC_MISUSE;
else{
if(mf->B.baseline){
fsl_deck_finalize(mf->B.baseline);
mf->B.baseline = NULL;
}
return fsl_deck_sethex_impl(mf, uuidBaseline, 'B',
FSL_UUID_STRLEN, &mf->B.uuid);
}
}
/**
Internal impl for card setters which consider of a simple (char *)
member.
*/
static int fsl_deck_set_string( fsl_deck * mf, char letter, char ** member, char const * v, fsl_int_t n ){
if(!fsl_deck_check_type(mf, letter)) return FSL_RC_TYPE;
fsl_deck_free_string(mf, *member);
*member = v ? fsl_strndup(v, n) : NULL;
if(v && !*member) return FSL_RC_OOM;
else return 0;
}
int fsl_deck_C_set( fsl_deck * mf, char const * v, fsl_int_t n){
return mf
? fsl_deck_set_string( mf, 'C', &mf->C, v, n )
: FSL_RC_MISUSE;
}
int fsl_deck_J_add( fsl_deck * mf, char isAppend,
char const * field, char const * value){
if(!mf || !field) return FSL_RC_MISUSE;
else if(!*field) return FSL_RC_RANGE;
else if(!fsl_deck_check_type(mf,'J')) return FSL_RC_TYPE;
else{
int rc;
fsl_card_J * cp = fsl_card_J_malloc(isAppend, field, value);
if(!cp) rc = FSL_RC_OOM;
else if( 0 != (rc = fsl_list_append(&mf->J, cp))){
fsl_card_J_free(cp);
}
return rc;
}
}
int fsl_deck_K_set( fsl_deck * mf, fsl_uuid_cstr uuid){
return mf
? fsl_deck_sethex_impl(mf, uuid, 'K', FSL_UUID_STRLEN, &mf->K)
: FSL_RC_MISUSE;
}
int fsl_deck_L_set( fsl_deck * mf, char const * v, fsl_int_t n){
return mf
? fsl_deck_set_string(mf, 'L', &mf->L, v, n)
: FSL_RC_MISUSE;
}
int fsl_deck_M_add( fsl_deck * mf, char const *uuid){
int rc;
char * dupe;
if(!mf || !uuid) return FSL_RC_MISUSE;
else if(!fsl_deck_check_type(mf, 'M')) return FSL_RC_TYPE;
else if(!fsl_is_uuid(uuid)) return FSL_RC_RANGE;
dupe = fsl_strndup(uuid, FSL_UUID_STRLEN);
if(!dupe) rc = FSL_RC_OOM;
else{
rc = fsl_list_append( &mf->M, dupe );
if(rc){
fsl_free(dupe);
}
}
return rc;
}
int fsl_deck_N_set( fsl_deck * mf, char const * v, fsl_int_t n){
return mf
? fsl_deck_set_string( mf, 'N', &mf->N, v, n )
: FSL_RC_MISUSE;
}
int fsl_deck_P_add( fsl_deck * mf, char const *parentUuid){
int rc;
char * dupe;
if(!mf || !parentUuid || !*parentUuid) return FSL_RC_MISUSE;
else if(!fsl_deck_check_type(mf, 'P')) return FSL_RC_TYPE;
else if(!fsl_is_uuid(parentUuid)){
return fsl_error_set(&mf->error, FSL_RC_RANGE,
"Invalid UUID for P-card.");
}
dupe = fsl_strndup(parentUuid, FSL_UUID_STRLEN);
if(!dupe) rc = FSL_RC_OOM;
else{
rc = fsl_list_append( &mf->P, dupe );
if(rc){
fsl_free(dupe);
}
}
return rc;
}
fsl_id_t fsl_deck_P_get_id(fsl_deck * d, int index){
if(!d || !d->f) return -1;
else if(index>(int)d->P.used) return 0;
else return fsl_uuid_to_rid(d->f, (char const *)d->P.list[index]);
}
int fsl_deck_Q_add( fsl_deck * mf, int type,
fsl_uuid_cstr target,
fsl_uuid_cstr baseline ){
if(!mf || !target) return FSL_RC_MISUSE;
else if(!fsl_deck_check_type(mf,'Q')) return FSL_RC_TYPE;
else if(!type || !fsl_is_uuid(target)
|| (baseline && !fsl_is_uuid(baseline))) return FSL_RC_RANGE;
else{
int rc;
fsl_card_Q * cp = fsl_card_Q_malloc(type, target, baseline);
if(!cp) rc = FSL_RC_OOM;
else if( 0 != (rc = fsl_list_append(&mf->Q, cp))){
fsl_card_Q_free(cp);
}
return rc;
}
}
/**
A comparison routine for qsort(3) which compares fsl_card_F
instances in a lexical manner based on their names. The order is
important for card ordering in generated manifests.
It expects that each argument is a (fsl_card_F const **). Note
the pointer-to-pointer (because this was initially only used
in a fsl_list context).
*/
static int fsl_card_F_cmp_pp( void const * lhs, void const * rhs ){
fsl_card_F const * l = *((fsl_card_F const **)lhs);
fsl_card_F const * r = *((fsl_card_F const **)rhs);
/* Compare NULL as larger so that NULLs move to the right. That said,
we aren't expecting any NULLs. */
assert(l);
assert(r);
if(!l) return r ? 1 : 0;
else if(!r) return -1;
else return fsl_strcmp(l->name, r->name);
}
static void fsl_deck_F_sort(fsl_deck * mf){
fsl_list_sort(&mf->F.list, fsl_card_F_cmp_pp );
}
int fsl_card_F_compare( fsl_card_F const * lhs,
fsl_card_F const * rhs){
return (lhs == rhs) ? 0 : fsl_card_F_cmp_pp( &lhs, &rhs );
}
int fsl_deck_R_set( fsl_deck * mf, fsl_uuid_cstr md5){
return mf
? fsl_deck_sethex_impl(mf, md5, 'R', FSL_MD5_STRLEN, &mf->R)
: FSL_RC_MISUSE;
}
int fsl_deck_R_calc(fsl_deck * mf){
fsl_cx * f = mf ? mf->f : NULL;
if(!mf || !f) return FSL_RC_MISUSE;
else if(!fsl_deck_check_type(mf,'R')) {
assert(mf->error.code);
return fsl_cx_err_set_e(f, &mf->error);
}
else if(!mf->F.list.used){
return fsl_deck_R_set(mf, FSL_MD5_INITIAL_HASH);
}else{
int rc = 0;
fsl_card_F const * fc;
fsl_id_t fileRid;
fsl_buffer * buf = &f->fileContent;
unsigned char digest[16];
char hex[FSL_MD5_STRLEN+1];
fsl_md5_cx md5 = fsl_md5_cx_empty;
enum { NumBufSize = 40 };
char numBuf[NumBufSize] = {0};
assert(mf->f);
assert(!buf->used && "Misuse of f->fileContent buffer.");
/* memset(numBuf,0,NumBufSize); */
fsl_deck_F_sort(mf);
rc = fsl_deck_F_rewind(mf);
if(rc) return rc;
/*
TODO:
Missing functionality:
- The "wd" (working directory) family of functions, needed for
symlink handling.
*/
while(1){
rc = fsl_deck_F_next(mf, &fc);
/* MARKER(("R rc=%s file #%d: %s %s\n", fsl_rc_cstr(rc), ++i, fc ? fc->name : "<END>", fc ? fc->uuid : NULL)); */
if(rc || !fc) break;
if(!fc->uuid) continue;
fileRid = fsl_uuid_to_rid( f, fc->uuid );
if(0==fileRid){
rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
"Cannot resolve RID for F-card UUID [%s].",
fc->uuid);
goto end;
}else if(fileRid<0){
assert(f->error.code);
rc = f->error.code
? f->error.code
: fsl_cx_err_set(f, FSL_RC_ERROR,
"Error resolving RID for F-card UUID [%s].",
fc->uuid);
goto end;
}
fsl_md5_update_cstr(&md5, fc->name, -1);
rc = fsl_content_get(f, fileRid, buf);
if(rc){
goto end;
}
numBuf[0] = 0;
fsl_snprintf(numBuf, NumBufSize,
" %"FSL_SIZE_T_PFMT"\n",
(fsl_size_t)buf->used);
fsl_md5_update_cstr(&md5, numBuf, -1);
fsl_md5_update_buffer(&md5, buf);
}
if(!rc){
fsl_md5_final(&md5, digest);
fsl_md5_digest_to_base16(digest, hex);
}
end:
fsl_cx_yield_file_buffer(f);
assert(0==buf->used);
return rc ? rc : fsl_deck_R_set(mf, hex);
}
}
int fsl_deck_T_add2( fsl_deck * mf, fsl_card_T * t){
return (mf && t)
? (fsl_deck_check_type(mf, 'T')
? fsl_list_append(&mf->T, t)
: FSL_RC_TYPE)
: FSL_RC_MISUSE;
}
int fsl_deck_T_add( fsl_deck * mf, fsl_tag_type tagType,
char const * uuid, char const * name,
char const * value){
if(!mf || !name) return FSL_RC_MISUSE;
else if(!fsl_deck_check_type(mf, 'T')) return FSL_RC_TYPE;
else if(!*name || (uuid &&!fsl_is_uuid(uuid))) return FSL_RC_RANGE;
else switch(tagType){
case FSL_TAGTYPE_CANCEL:
case FSL_TAGTYPE_ADD:
case FSL_TAGTYPE_PROPAGATING:{
int rc;
fsl_card_T * t;
t = fsl_card_T_malloc(tagType, uuid, name, value);
if(!t) return FSL_RC_OOM;
rc = fsl_deck_T_add2( mf, t );
if(rc) fsl_card_T_free(t);
return rc;
}
default:
assert(!"Invalid tagType value");
return fsl_error_set(&mf->error, FSL_RC_RANGE,
"Invalid tag-type value: %d",
(int)tagType);
}
}
int fsl_deck_U_set( fsl_deck * mf, char const * v, fsl_int_t n){
return mf
? fsl_deck_set_string( mf, 'U', &mf->U, v, n )
: FSL_RC_MISUSE;
}
int fsl_deck_W_set( fsl_deck * mf, char const * v, fsl_int_t n){
return mf
? fsl_deck_b_setuffer_impl(mf, v, n, 'W', &mf->W)
: FSL_RC_MISUSE;
}
int fsl_deck_A_set( fsl_deck * mf, char const * name,
char const * tgt,
char const * uuidSrc ){
if(!mf || !name || !tgt) return FSL_RC_MISUSE;
else if(!fsl_deck_check_type(mf, 'A')) return FSL_RC_TYPE;
else if(!*tgt){
return fsl_error_set(&mf->error, FSL_RC_RANGE,
"Invalid target name in A card.");
}
/* TODO: validate tgt based on mf->type and require UUID
for types EVENT/TICKET.
*/
else if(uuidSrc && *uuidSrc && !fsl_is_uuid(uuidSrc)){
return fsl_error_set(&mf->error, FSL_RC_RANGE,
"Invalid source UUID in A card.");
}
else{
int rc = 0;
fsl_deck_free_string(mf, mf->A.tgt);
fsl_deck_free_string(mf, mf->A.src);
fsl_deck_free_string(mf, mf->A.name);
mf->A.name = mf->A.src = NULL;
if(! (mf->A.tgt = fsl_strdup(tgt))) rc = FSL_RC_OOM;
else if( !(mf->A.name = fsl_strdup(name))) rc = FSL_RC_OOM;
else if(uuidSrc && *uuidSrc){
mf->A.src = fsl_strndup(uuidSrc,FSL_UUID_STRLEN);
if(!mf->A.src) rc = FSL_RC_OOM
/* Leave mf->A.tgt/name for downstream cleanup. */;
}
return rc;
}
}
int fsl_deck_D_set( fsl_deck * mf, fsl_double_t date){
if(!mf) return FSL_RC_MISUSE;
else if(date<=0) return FSL_RC_RANGE;
else if(!fsl_deck_check_type(mf, 'D')) return FSL_RC_TYPE;
else{
mf->D = date;
return 0;
}
}
int fsl_deck_E_set( fsl_deck * mf, fsl_double_t date, char const * uuid){
if(!mf || !uuid) return FSL_RC_MISUSE;
else if(date<=0){
return fsl_error_set(&mf->error, FSL_RC_RANGE,
"Invalid date value for E card.");
}else if(!fsl_is_uuid(uuid)){
return fsl_error_set(&mf->error, FSL_RC_RANGE,
"Invalid UUID for E card.");
}
else{
mf->E.julian = date;
fsl_deck_free_string(mf, mf->E.uuid);
mf->E.uuid = fsl_strndup(uuid, FSL_UUID_STRLEN);
return mf->E.uuid ? 0 : FSL_RC_OOM;
}
}
int fsl_deck_F_add2( fsl_deck * mf, fsl_card_F * t){
if(!mf || !t) return FSL_RC_MISUSE;
else if(!fsl_deck_check_type(mf, 'F')) return FSL_RC_TYPE;
else{
int rc = 0;
if(mf->F.list.used == mf->F.list.capacity){
rc = fsl_list_reserve(&mf->F.list, mf->F.list.capacity
? mf->F.list.capacity*2 : 20);
}
/*
Should we search/replace entries now or fail later when we go to
output dupes? The O(N) search could get expensive on large
repos. Or we use fsl_deck_F_seek()?
*/
return rc ? rc : fsl_list_append(&mf->F.list, t);
}
}
int fsl_deck_F_add( fsl_deck * mf, char const * name,
char const * uuid,
fsl_file_perm_t perms,
char const * oldName){
if(!mf || !name) return FSL_RC_MISUSE;
else if(!fsl_deck_check_type(mf, 'F')) return FSL_RC_TYPE;
else if(!*name){
return fsl_error_set(&mf->error, FSL_RC_RANGE,
"F-card name may not be empty.");
}
else if(!fsl_is_simple_pathname(name, 1)
|| (oldName && !fsl_is_simple_pathname(oldName, 1))){
return fsl_error_set(&mf->error, FSL_RC_RANGE,
"Invalid filename for F-card (simple form required).");
}
else if(uuid && !fsl_is_uuid(uuid)){
return fsl_error_set(&mf->error, FSL_RC_RANGE,
"Invalid UUID for F-card.");
}
else {
int rc;
fsl_card_F * t;
switch(perms){
case FSL_FILE_PERM_EXE:
case FSL_FILE_PERM_LINK:
case FSL_FILE_PERM_REGULAR:
break;
default:
assert(!"Invalid fsl_file_perm_t value");
return fsl_error_set(&mf->error, FSL_RC_RANGE,
"Invalid fsl_file_perm_t value "
"(%d) for file [%s].",
perms, name);
}
t = fsl_card_F_malloc(name, uuid, perms, oldName);
if(!t) rc = FSL_RC_OOM;
else{
rc = fsl_deck_F_add2( mf, t );
if(rc) fsl_card_F_free(t);
}
return rc;
}
}
int fsl_deck_F_foreach( fsl_deck * d, char includeBaseline,
fsl_card_F_visitor_f cb, void * visitorState ){
if(!d || !cb) return FSL_RC_MISUSE;
if(!includeBaseline || !d->B.uuid){
fsl_card_F const * fc;
fsl_size_t i;
int rc = 0;
for( i = 0; !rc && (i<d->F.list.used); ++i ){
fc = (fsl_card_F const *)d->F.list.list[i];
rc = cb(fc, visitorState);
}
return (FSL_RC_BREAK==rc) ? 0 : rc;
}else{
int rc;
fsl_card_F const * fc;
rc = fsl_deck_F_rewind(d);
if(rc) return rc;
while( !rc && !(rc=fsl_deck_F_next(d, &fc)) && fc) {
rc = cb( fc, visitorState );
}
return rc;
}
}
/**
Output state for fsl_appendf_f_mf() and friends. Used for managing
the output of a fsl_deck.
*/
struct fsl_deck_out_state {
/**
The set of cards being output. We use this to delegate certain
output bits.
*/
fsl_deck const * d;
/**
Output routine to send manifest to.
*/
fsl_output_f out;
/**
State to pass as the first arg of this->out().
*/
void * outState;
/**
The previously-visited card, for confirming that all cards are in
proper lexical order.
*/
fsl_card_F const * prevCard;
/**
f() result code, so that we can feed the code back through the
fsl_appendf() layer. If this is non-0, processing must stop. We
"could" use this->error.code instead, but this is simple.
*/
int rc;
/**
Counter for list-visiting routines. Must be re-set before each
visit loop if the visitor makes use of this (most do not).
*/
fsl_int_t counter;
/**
Incrementally-calculated MD5 sum of all output sent via
fsl_appendf_f_mf().
*/
fsl_md5_cx md5;
/* Holds error state for propagating back to the client. */
fsl_error error;
/**
Scratch buffer for fossilzing bytes and other temporary work.
*/
fsl_buffer scratch;
};
typedef struct fsl_deck_out_state fsl_deck_out_state;
static const fsl_deck_out_state fsl_deck_out_state_empty = {
NULL/*d*/,
NULL/*out*/,
NULL/*outState*/,
NULL/*prevCard*/,
0/*rc*/,
0/*counter*/,
fsl_md5_cx_empty_m/*md5*/,
fsl_error_empty_m/*error*/,
fsl_buffer_empty_m/*scratch*/
};
/**
fsl_appendf_f() impl which forwards its data to arg->out(). arg
must be a (fsl_deck_out_state *). Updates arg->rc to the result of
calling arg->out(fp->fState, data, n). If arg->out() succeeds then
arg->md5 is updated to reflect the given data. i.e. this is where
the Z-card gets calculated incrementally during output of a deck.
*/
static fsl_int_t fsl_appendf_f_mf( void * arg,
char const * data,
fsl_int_t n ){
fsl_deck_out_state * os = (fsl_deck_out_state *)arg;
if((n>0)
&& !(os->rc = os->out(os->outState, data, (fsl_size_t)n))
&& (os->md5.isInit)){
fsl_md5_update( &os->md5, data, (fsl_size_t)n );
}
return (0==os->rc)
? n
: -1;
}
/**
Internal helper for fsl_mf_output(). Appends formatted output to
os->out() via fsl_appendf_f_mf(). Returns os->rc (0 on success).
*/
static int fsl_deck_append( fsl_deck_out_state * os,
char const * fmt, ... ){
fsl_int_t rc;
va_list args;
assert(os);
assert(fmt && *fmt);
va_start(args,fmt);
rc = fsl_appendfv( fsl_appendf_f_mf, os, fmt, args);
va_end(args);
if(rc<0 && !os->rc) os->rc = FSL_RC_IO;
return os->rc;
}
/**
Fossilizes (inp, inp+len] bytes to os->scratch,
overwriting any existing contents.
Updates and returns os->rc.
*/
static int fsl_deck_fossilize( fsl_deck_out_state * os,
unsigned char const * inp,
fsl_int_t len){
fsl_buffer_reset(&os->scratch);
return os->rc = len
? fsl_bytes_fossilize(inp, len, &os->scratch)
: 0;
}
static char fsl_deck_out_tcheck(fsl_deck_out_state * os, char letter){
if(!fsl_card_is_legal(os->d->type, letter)){
os->rc = fsl_error_set(&os->error, FSL_RC_TYPE,
"%c-card is not valid for deck type %s.",
letter, fsl_catype_cstr(os->d->type));
}
return os->rc ? 0 : 1;
}
#if 0
/**
Fossilizes b->mem to os->scratch. Returns 0 on success and all
that.
*/
static int fsl_deck_fossilize_b( fsl_deck_out_state * os,
fsl_buffer const * b ){
return b->used
? fsl_deck_fossilize( os, b->mem, (fsl_int_t)b->used)
: 0;
}
/**
If doFossilize, fossilizes b to os->scratch and passes that result
to fsl_deck_append(), otherwise uses b's contents directly for
appending a card (with the given letter) to os:
LETTER CONTENT\\n
This is only useful for cards with simple buffer values.
*/
static int fsl_deck_out_letter_buf( fsl_deck_out_state * os,
char letter,
fsl_buffer const * b,
char doFossilize){
if(b->used && fsl_deck_out_tcheck(os, letter)){
if(doFossilize){
#if 0
fsl_deck_append(os, "%c %F\n", letter,
(char const *)b->mem );
#else
/* This is functionally identical but is more efficient b/c we
re-use os->scratch. */
fsl_deck_fossilize_b(os, b);
if(!os->rc) fsl_deck_append(os, "%c %b\n", letter, &os->scratch);
#endif
}else{
fsl_deck_append(os, "%c %.*s\n", letter,
(int)b->used,
(char const *)b->mem );
}
}
return os->rc;
}
#endif
/* Appends the B card to os from os->d->B. */
static int fsl_deck_out_B( fsl_deck_out_state * os ){
if(os->d->B.uuid && fsl_deck_out_tcheck(os, 'B')){
if(!fsl_is_uuid(os->d->B.uuid)){
os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
"Malformed UUID in B card.");
}
else{
fsl_deck_append(os, "B %s\n", os->d->B.uuid);
}
}
return os->rc;
}
/* Appends the A card to os from os->d->A. */
static int fsl_deck_out_A( fsl_deck_out_state * os ){
if(os->d->A.name && fsl_deck_out_tcheck(os, 'A')){
if(!os->d->A.name || !*os->d->A.name){
os->rc = fsl_error_set(&os->error, FSL_RC_CA_SYNTAX,
"A-card is missing its name property");
}else if(!os->d->A.tgt || !*os->d->A.tgt){
os->rc = fsl_error_set(&os->error, FSL_RC_CA_SYNTAX,
"A-card is missing its tgt property: %s",
os->d->A.name);
}else if(os->d->A.src && !fsl_is_uuid(os->d->A.src)){
os->rc = fsl_error_set(&os->error, FSL_RC_TYPE,
"Invalid src UUID in A-card: name=%s, "
"invalid uuid=%s",
os->d->A.name, os->d->A.src);
}else{
fsl_deck_append(os, "A %F %F",
os->d->A.name, os->d->A.tgt);
if(!os->rc){
if(os->d->A.src){
fsl_deck_append(os, " %s", os->d->A.src);
}
if(!os->rc) fsl_deck_append(os, "\n");
}
}
}
return os->rc;
}
/**
Internal helper for outputing cards which are simple strings.
str is the card to output (NULL values are ignored), letter is
the card letter being output. If doFossilize is true then
the output gets fossilize-formatted.
*/
static int fsl_deck_out_letter_str( fsl_deck_out_state * os, char letter,
char const * str, char doFossilize ){
if(str && fsl_deck_out_tcheck(os, letter)){
if(doFossilize){
fsl_deck_fossilize(os, (unsigned char const *)str, -1);
if(!os->rc){
fsl_deck_append(os, "%c %b\n", letter, &os->scratch);
}
}else{
fsl_deck_append(os, "%c %s\n", letter, str);
}
}
return os->rc;
}
/* Appends the C card to os from os->d->C. */
static int fsl_deck_out_C( fsl_deck_out_state * os ){
return fsl_deck_out_letter_str( os, 'C', os->d->C, 1 );
}
/* Appends the D card to os from os->d->D. */
static int fsl_deck_out_D( fsl_deck_out_state * os ){
if((os->d->D > 0.0) && fsl_deck_out_tcheck(os, 'D')){
char ds[24];
if(!fsl_julian_to_iso8601(os->d->D, ds, 1)){
os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
"D-card contains invalid "
"Julian Day value.");
}else{
fsl_deck_append(os, "D %s\n", ds);
}
}
return os->rc;
}
/* Appends the E card to os from os->d->E. */
static int fsl_deck_out_E( fsl_deck_out_state * os ){
if(os->d->E.uuid && fsl_deck_out_tcheck(os, 'E')){
char ds[24];
char msPrecision = FSL_CATYPE_EVENT!=os->d->type
/* The timestamps on Events historically have seconds precision,
not ms.
*/;
if(!fsl_is_uuid(os->d->E.uuid)){
os->rc = fsl_error_set(&os->error, FSL_RC_TYPE,
"Invalid UUID in E-card: %s",
os->d->E.uuid);
}
else if(!fsl_julian_to_iso8601(os->d->E.julian, ds, msPrecision)){
os->rc = fsl_error_set(&os->error, FSL_RC_TYPE,
"Invalid Julian Day value in E-card.");
}
else{
fsl_deck_append(os, "E %s %s\n", ds, os->d->E.uuid);
}
}
return os->rc;
}
/**
fsl_list_visitor_f() impl for outputing F cards. obj must be a
(fsl_card_F *). visitorState must be a (fsl_deck_out_state*).
*/
static int fsl_list_v_mf_output_card_F(void * obj, void * visitorState ){
fsl_deck_out_state * os = (fsl_deck_out_state *)visitorState;
fsl_card_F const * f = (fsl_card_F const *)obj;
int rc;
char hasOldName;
char const * zPerm;
assert(f);
if(os->prevCard){
int const cmp = fsl_strcmp(os->prevCard->name, f->name);
if(0==cmp){
return fsl_error_set(&os->error, FSL_RC_RANGE,
"Duplicate F-card name: %s",
f->name);
}else if(cmp>0){
return fsl_error_set(&os->error, FSL_RC_RANGE,
"Out-of-order F-card names: %s before %s",
os->prevCard->name, f->name);
}
}
if(!fsl_is_simple_pathname(f->name, 1)){
return fsl_error_set(&os->error, FSL_RC_RANGE,
"Filename is invalid as F-card: %s",
f->name);
}
if(!f->uuid && !os->d->B.uuid){
return fsl_error_set(&os->error, FSL_RC_MISUSE,
"Baseline manifests may not have F-cards "
"without UUIDs (file deletion entries). To "
"delete files, simply do not inject an F-card "
"for them. Delta manifests, however, require "
"NULL UUIDs for deletion entries! File: %s",
f->name);
}
rc = fsl_deck_fossilize(os, (unsigned char const *)f->name, -1);
if(!rc) rc = fsl_deck_append( os, "F %b", &os->scratch);
if(!rc && f->uuid){
assert(fsl_is_uuid(f->uuid));
rc = fsl_deck_append( os, " %s", f->uuid);
if(rc) return rc;
}
if(f->uuid){
hasOldName = f->priorName && (0!=fsl_strcmp(f->name,f->priorName));
switch(f->perm){
case FSL_FILE_PERM_EXE: zPerm = " x"; break;
case FSL_FILE_PERM_LINK: zPerm = " l"; break;
default:
/* When hasOldName, we have to inject an otherwise optional
'w' to avoid an ambiguity. Or at least that's what the v1
F-card-generating code i looked at does.
*/
zPerm = hasOldName ? " w" : ""; break;
}
if(*zPerm) rc = fsl_deck_append( os, "%s", zPerm);
if(!rc && hasOldName){
assert(*zPerm);
rc = fsl_deck_fossilize(os, (unsigned char const *)f->priorName, -1);
if(!rc) rc = fsl_deck_append( os, " %b", &os->scratch);
}
}
if(!rc) fsl_appendf_f_mf(os, "\n", 1);
return os->rc;
}
static int fsl_deck_out_list_obj( fsl_deck_out_state * os,
char letter,
fsl_list const * li,
fsl_list_visitor_f visitor){
if(li->used && fsl_deck_out_tcheck(os, letter)){
os->rc = fsl_list_visit( li, 0, visitor, os );
}
return os->rc;
}
static int fsl_deck_out_F( fsl_deck_out_state * os ){
return fsl_deck_out_list_obj(os, 'F', &os->d->F.list,
fsl_list_v_mf_output_card_F);
}
/**
A comparison routine for qsort(3) which compares fsl_card_J
instances in a lexical manner based on their names. The order is
important for card ordering in generated manifests.
*/
int fsl_qsort_cmp_J_cards( void const * lhs, void const * rhs ){
fsl_card_J const * l = *((fsl_card_J const **)lhs);
fsl_card_J const * r = *((fsl_card_J const **)rhs);
/* Compare NULL as larger so that NULLs move to the right. That said,
we aren't expecting any NULLs. */
assert(l);
assert(r);
if(!l) return r ? 1 : 0;
else if(!r) return -1;
else{
/* The '+' sorts before any legal field name bits (letters). */
if(l->append != r->append) return r->append - l->append
/* Footnote: that will break if, e.g. l->isAppend==2 and
r->isAppend=1, or some such. Shame C89 doesn't have a true
boolean.
*/;
else return fsl_strcmp(l->field, r->field);
}
}
/**
fsl_list_visitor_f() impl for outputing J cards. obj must
be a (fsl_card_J *).
*/
static int fsl_list_v_mf_output_card_J(void * obj, void * visitorState ){
fsl_deck_out_state * os = (fsl_deck_out_state *)visitorState;
fsl_card_J const * c = (fsl_card_J const *)obj;
fsl_deck_fossilize( os, (unsigned char const *)c->field, -1 );
if(!os->rc){
fsl_deck_append(os, "J %s%b", c->append ? "+" : "", &os->scratch);
if(!os->rc){
if(c->value && *c->value){
fsl_deck_fossilize( os, (unsigned char const *)c->value, -1 );
if(!os->rc){
fsl_deck_append(os, " %b\n", &os->scratch);
}
}else{
fsl_deck_append(os, "\n");
}
}
}
return os->rc;
}
static int fsl_deck_out_J( fsl_deck_out_state * os ){
return fsl_deck_out_list_obj(os, 'J', &os->d->J,
fsl_list_v_mf_output_card_J);
}
/* Appends the K card to os from os->d->K. */
static int fsl_deck_out_K( fsl_deck_out_state * os ){
if(os->d->K && fsl_deck_out_tcheck(os, 'K')){
if(!fsl_is_uuid(os->d->K)){
os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
"Invalid UUID in K card.");
}
else{
fsl_deck_append(os, "K %s\n", os->d->K);
}
}
return os->rc;
}
/* Appends the L card to os from os->d->L. */
static int fsl_deck_out_L( fsl_deck_out_state * os ){
return fsl_deck_out_letter_str(os, 'L', os->d->L, 1);
}
/* Appends the N card to os from os->d->N. */
static int fsl_deck_out_N( fsl_deck_out_state * os ){
return fsl_deck_out_letter_str( os, 'N', os->d->N, 1 );
}
/**
fsl_list_visitor_f() impl for outputing P cards. obj must
be a (fsl_deck_out_state *) and obj->counter must be
set to 0 before running the visit iteration.
*/
static int fsl_list_v_mf_output_card_P(void * obj, void * visitorState ){
fsl_deck_out_state * os = (fsl_deck_out_state *)visitorState;
char const * uuid = (char const *)obj;
fsl_size_t len;
assert(uuid);
if(!fsl_is_uuid(uuid)){
os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
"Invalid UUID in P card.");
}
else if(!os->counter++) fsl_appendf_f_mf(os, "P ", 2);
else fsl_appendf_f_mf(os, " ", 1);
/* Reminder: fsl_appendf_f_mf() updates os->rc. */
if(!os->rc){
len = (fsl_size_t)fsl_strlen(uuid);
assert(FSL_UUID_STRLEN==len) /* is enforced by fsl_mf_add_P() */;
fsl_appendf_f_mf(os, uuid, len);
}
return os->rc;
}
static int fsl_deck_out_P( fsl_deck_out_state * os ){
if(!fsl_deck_out_tcheck(os, 'P')) return os->rc;
else if(os->d->P.used){
os->counter = 0;
os->rc = fsl_list_visit( &os->d->P, 0, fsl_list_v_mf_output_card_P, os );
assert(os->counter);
if(!os->rc) fsl_appendf_f_mf(os, "\n", 1);
}else if(FSL_CATYPE_CHECKIN==os->d->type){
/*
Evil ugly hack, primarily for round-trip compatibility with
manifest #1, which has an empty P card.
fossil(1) ignores empty P-cards in all cases, and must continue
to do so for backwards compatibility with rid #1 in all repos.
Pedantic note: there must be no space between the 'P' and the
newline.
*/
fsl_deck_append(os, "P\n");
}
return os->rc;
}
/**
A comparison routine for qsort(3) which compares fsl_card_Q
instances in a lexical manner. The order is important for card
ordering in generated manifests.
*/
static int qsort_cmp_Q_cards( void const * lhs, void const * rhs ){
fsl_card_Q const * l = *((fsl_card_Q const **)lhs);
fsl_card_Q const * r = *((fsl_card_Q const **)rhs);
/* Compare NULL as larger so that NULLs move to the right. That said,
we aren't expecting any NULLs. */
assert(l);
assert(r);
if(!l) return r ? 1 : 0;
else if(!r) return -1;
else{
/* Lexical sorting must account for the +/- characters, and a '+'
sorts before '-', which is why this next part may seem
backwards at first.
*/
assert(l->type);
assert(r->type);
if(l->type<0 && r->type>0) return 1;
else if(l->type>0 && r->type<0) return -1;
else return fsl_strcmp(l->target, r->target);
}
}
/**
fsl_list_visitor_f() impl for outputing Q cards. obj must
be a (fsl_deck_out_state *) and obj->counter must be
set to 0 before running this visit iteration.
*/
static int fsl_list_v_mf_output_card_Q(void * obj, void * visitorState ){
fsl_deck_out_state * os = (fsl_deck_out_state *)visitorState;
fsl_card_Q const * cp = (fsl_card_Q const *)obj;
char const prefix = (cp->type<0) ? '-' : '+';
assert(cp->type);
assert(cp->target);
if(!cp->type){
os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
"Invalid type value in Q-card.");
}else if(!fsl_card_is_legal(os->d->type, 'Q')){
os->rc = fsl_error_set(&os->error, FSL_RC_TYPE,
"Q-card is not valid for deck type %s",
fsl_catype_cstr(os->d->type));
}else if(!fsl_is_uuid(cp->target)){
os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
"Invalid target UUID in Q-card: %s",
cp->target);
}else if(cp->baseline){
if(!fsl_is_uuid(cp->baseline)){
os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
"Invalid baseline UUID in Q-card: %s",
cp->baseline);
}else{
fsl_deck_append(os, "Q %c%s %s\n", prefix, cp->target, cp->baseline);
}
}else{
fsl_deck_append(os, "Q %c%s\n", prefix, cp->target);
}
return os->rc;
}
static int fsl_deck_out_Q( fsl_deck_out_state * os ){
return fsl_deck_out_list_obj(os, 'Q', &os->d->Q,
fsl_list_v_mf_output_card_Q);
}
/**
Appends the R card from os->d->R to os.
*/
static int fsl_deck_out_R( fsl_deck_out_state * os ){
if(os->d->R && fsl_deck_out_tcheck(os, 'R')){
if((FSL_MD5_STRLEN!=fsl_strlen(os->d->R))
|| !fsl_validate16(os->d->R, FSL_MD5_STRLEN)){
os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
"Malformed MD5 in R-card.");
}
else{
fsl_deck_append(os, "R %s\n", os->d->R);
}
}
return os->rc;
}
/**
fsl_list_visitor_f() impl for outputing T cards. obj must
be a (fsl_deck_out_state *).
*/
static int fsl_list_v_mf_output_card_T(void * obj, void * visitorState ){
fsl_deck_out_state * os = (fsl_deck_out_state *)visitorState;
fsl_card_T * t = (fsl_card_T *)obj;
char prefix = 0;
/* Determine the prefix character... */
switch(t->type){
case FSL_TAGTYPE_CANCEL: prefix = '-'; break;
case FSL_TAGTYPE_ADD: prefix = '+'; break;
case FSL_TAGTYPE_PROPAGATING: prefix = '*'; break;
default:
return os->rc = fsl_error_set(&os->error, FSL_RC_TYPE,
"Invalid tag type #%d in T-card.",
t->type);
}
/*
Fossilize and output the prefix, name, and uuid, or a '*' if no
uuid is set (which is only legal when tagging the current
artifact, as '*' is a placeholder for the current artifact's
UUID, which is not yet known).
*/
os->scratch.used = 0;
fsl_deck_fossilize(os, (unsigned const char *)t->name, -1);
if(os->rc) return os->rc;
if(t->uuid && !fsl_is_uuid(t->uuid)){
return os->rc = fsl_error_set(&os->error, FSL_RC_TYPE,
"Malformed UUID in T-card: %s",
t->uuid);
}
os->rc = fsl_deck_append(os, "T %c%s %s", prefix,
(char const*)os->scratch.mem,
t->uuid ? t->uuid : "*");
if(os->rc) return os->rc;
if(/*(t->type != FSL_TAGTYPE_CANCEL) &&*/t->value && *t->value){
/* CANCEL tags historically don't store a value but
the spec doesn't disallow it and they are harmless
for (aren't used by) fossil(1). */
fsl_deck_fossilize(os, (unsigned char const *)t->value, -1);
if(!os->rc) fsl_appendf_f_mf(os, " ", 1);
if(!os->rc) fsl_appendf_f_mf(os, (char const*)os->scratch.mem,
(fsl_int_t)os->scratch.used);
}
if(!os->rc){
fsl_appendf_f_mf(os, "\n", 1);
}
return os->rc;
}
char fsl_tag_prefix_char( fsl_tag_type t ){
switch(t){
case FSL_TAGTYPE_CANCEL: return '-';
case FSL_TAGTYPE_ADD: return '+';
case FSL_TAGTYPE_PROPAGATING: return '*';
default:
return 0;
}
}
/**
A comparison routine for qsort(3) which compares fsl_card_T
instances in a lexical manner based on (type, name, uuid, value).
The order of those is important for card ordering in generated
manifests. Interestingly, CANCEL tags (with a '-' prefix) sort
last, meaning it is possible to cancel a tag set in the same
manifest because crosslinking processes them in the order given
(which will be lexical order for all legal manifests).
Reminder: lhs and rhs must be (fsl_card_T**), as we use this to
qsort() such lists. When using it to compare two tags, make sure
to pass ptr-to-ptr.
*/
static int fsl_card_T_cmp( void const * lhs, void const * rhs ){
fsl_card_T const * l = *((fsl_card_T const **)lhs);
fsl_card_T const * r = *((fsl_card_T const **)rhs);
/* Compare NULL as larger so that NULLs move to the right. That said,
we aren't expecting any NULLs. */
assert(l);
assert(r);
if(!l) return r ? 1 : 0;
else if(!r) return -1;
else if(l->type != r->type){
char const lc = fsl_tag_prefix_char(l->type);
char const rc = fsl_tag_prefix_char(r->type);
return (lc<rc) ? -1 : 1;
}else{
int rc = fsl_strcmp(l->name, r->name);
if(rc) return rc;
else {
rc = fsl_uuidcmp(l->uuid, r->uuid);
return rc
? rc
: fsl_strcmp(l->value, r->value);
}
}
}
/**
Confirms that any T-cards in d are properly sorted. If not,
returns non-0. If err is not NULL, it is updated with a
description of the problem.
*/
static int fsl_deck_T_verify_order( fsl_deck const * d, fsl_error * err ){
if(d->T.used<2) return 0;
else{
fsl_size_t i = 0;
int rc = 0;
fsl_card_T const * tag;
fsl_card_T const * prev = NULL;
for( i = 0; i < d->T.used; ++i, prev = tag, rc = 0){
tag = (fsl_card_T const *)d->T.list[i];
if(prev){
if( (rc = fsl_card_T_cmp(&prev, &tag)) >= 0 ){
if(!err) rc = FSL_RC_CA_SYNTAX;
else{
rc = rc
? fsl_error_set(err, FSL_RC_CA_SYNTAX,
"Invalid T-card order: "
"[%c%s] must precede [%c%s]",
fsl_tag_prefix_char(prev->type),
prev->name,
fsl_tag_prefix_char(tag->type),
tag->name)
: fsl_error_set(err, FSL_RC_CA_SYNTAX,
"Duplicate T-card: %c%s",
fsl_tag_prefix_char(prev->type),
prev->name)
;
}
break;
}
}
}
return rc;
}
}
/* Appends the T cards to os from os->d->T. */
static int fsl_deck_out_T( fsl_deck_out_state * os ){
os->rc = fsl_deck_T_verify_order( os->d, &os->error);
return os->rc
? os->rc
: fsl_deck_out_list_obj(os, 'T', &os->d->T,
fsl_list_v_mf_output_card_T);
}
/* Appends the U card to os from os->d->U. */
static int fsl_deck_out_U( fsl_deck_out_state * os ){
return fsl_deck_out_letter_str(os, 'U', os->d->U, 1);
}
/* Appends the W card to os from os->d->W. */
static int fsl_deck_out_W( fsl_deck_out_state * os ){
if(os->d->W.used && fsl_deck_out_tcheck(os, 'W')){
fsl_deck_append(os, "W %"FSL_SIZE_T_PFMT"\n%b\n",
(fsl_size_t)os->d->W.used,
&os->d->W );
}
return os->rc;
}
/* Appends the Z card to os from os' accummulated md5 hash. */
static int fsl_deck_out_Z( fsl_deck_out_state * os ){
unsigned char digest[16];
char md5[FSL_MD5_STRLEN+1];
fsl_md5_final(&os->md5, digest);
fsl_md5_digest_to_base16(digest, md5);
assert(!md5[32]);
os->md5.isInit = 0 /* Keep further output from updating the MD5 */;
return fsl_deck_append(os, "Z %.*s\n", FSL_MD5_STRLEN, md5);
}
static int qsort_cmp_strings( void const * lhs, void const * rhs ){
char const * l = *((char const **)lhs);
char const * r = *((char const **)rhs);
return fsl_strcmp(l,r);
}
static int fsl_list_v_mf_output_card_M(void * obj, void * visitorState ){
fsl_deck_out_state * os = (fsl_deck_out_state *)visitorState;
char const * m = (char const *)obj;
return fsl_deck_append(os, "M %s\n", m);
}
static int fsl_deck_output_cluster( fsl_deck_out_state * os ){
if(!os->d->M.used){
os->rc = fsl_error_set(&os->error, FSL_RC_RANGE,
"M-card list may not be empty.");
}else{
fsl_deck_out_list_obj(os, 'M', &os->d->M,
fsl_list_v_mf_output_card_M);
}
return os->rc;
}
/* Helper for fsl_deck_output_CATYPE() */
#define DOUT(LETTER) rc = fsl_deck_out_##LETTER(os); \
if(rc || os->rc) return os->rc ? os->rc : rc
static int fsl_deck_output_control( fsl_deck_out_state * os ){
int rc;
/* Reminder: cards must be output in strict lexical order. */
DOUT(D);
DOUT(T);
DOUT(U);
return os->rc;
}
static int fsl_deck_output_event( fsl_deck_out_state * os ){
int rc = 0;
/* Reminder: cards must be output in strict lexical order. */
DOUT(C);
DOUT(D);
DOUT(E);
DOUT(N);
DOUT(P);
DOUT(T);
DOUT(U);
DOUT(W);
return os->rc;
}
static int fsl_deck_output_mf( fsl_deck_out_state * os ){
int rc = 0;
/* Reminder: cards must be output in strict lexical order. */
DOUT(B);
DOUT(C);
DOUT(D);
DOUT(F);
DOUT(K);
DOUT(L);
DOUT(N);
DOUT(P);
DOUT(Q);
DOUT(R);
DOUT(T);
DOUT(U);
DOUT(W);
return os->rc;
}
static int fsl_deck_output_ticket( fsl_deck_out_state * os ){
int rc;
/* Reminder: cards must be output in strict lexical order. */
DOUT(D);
DOUT(J);
DOUT(K);
DOUT(U);
return os->rc;
}
static int fsl_deck_output_wiki( fsl_deck_out_state * os ){
int rc;
/* Reminder: cards must be output in strict lexical order. */
DOUT(D);
DOUT(L);
DOUT(N);
DOUT(P);
DOUT(U);
DOUT(W);
return os->rc;
}
static int fsl_deck_output_attachment( fsl_deck_out_state * os ){
int rc = 0;
/* Reminder: cards must be output in strict lexical order. */
DOUT(A);
DOUT(C);
DOUT(D);
DOUT(N);
DOUT(U);
return os->rc;
}
/**
Only for testing/debugging purposes, as it allows constructs which
are not semantically legal and are CERTAINLY not legal to stuff in
the database.
*/
static int fsl_deck_output_any( fsl_deck_out_state * os ){
int rc = 0;
/* Reminder: cards must be output in strict lexical order. */
DOUT(B);
DOUT(C);
DOUT(D);
DOUT(E);
DOUT(F);
DOUT(J);
DOUT(K);
DOUT(L);
DOUT(N);
DOUT(P);
DOUT(Q);
DOUT(R);
DOUT(T);
DOUT(U);
DOUT(W);
return os->rc;
}
#undef DOUT
int fsl_deck_unshuffle( fsl_deck * d, char calculateRCard ){
fsl_list * li;
int rc = 0;
if(!d) return FSL_RC_MISUSE;
#define SORT(CARD,CMP) li = &d->CARD; fsl_list_sort(li, CMP )
SORT(J,fsl_qsort_cmp_J_cards);
SORT(M,qsort_cmp_strings);
SORT(Q,qsort_cmp_Q_cards);
SORT(T,fsl_card_T_cmp);
#undef SORT
if(FSL_CATYPE_CHECKIN!=d->type){
assert(!fsl_card_is_legal(d->type,'R'));
assert(!fsl_card_is_legal(d->type,'F'));
}else{
assert(fsl_card_is_legal(d->type, 'R') && "in-lib unit testing");
if(calculateRCard){
rc = fsl_deck_R_calc(d) /* F-card list is sorted there */;
}else{
fsl_deck_F_sort(d);
if(!d->R){
rc = fsl_deck_R_set(d,
(d->F.list.used || d->B.uuid || d->P.used)
? NULL
: FSL_MD5_INITIAL_HASH)
/* Special case: for manifests with no (B,F,P)-cards we inject
the initial-state R-card, analog to the initial checkin
(RID 1). We need one of (B,F,P,R) to unambiguously identify
a MANIFEST from a CONTROL, but RID 1 has an empty P-card,
no F-cards, and no B-card, so it _needs_ an R-card in order
to be unambiguously a Manifest. That said, that ambiguity
is/would be harmless in practice because CONTROLs go
through most of the same crosslinking processes as
MANIFESTs (the ones which are important for this purpose,
anyway).
*/;
}
}
}
return rc;
}
int fsl_deck_output( fsl_deck const * cards,
fsl_output_f out, void * outputState,
fsl_error * err ){
static const char allowTypeAny = 0
/* Only enable for debugging/testing. Allows outputing decks of
type FSL_CATYPE_ANY, which bypasses some validation checks and
may trigger other validation assertions. And may allow you to
inject garbage into the repo. So be careful.
*/;
fsl_deck_out_state OS = fsl_deck_out_state_empty;
fsl_deck_out_state * os = &OS;
int rc = 0;
if(!cards || !out) return FSL_RC_MISUSE;
else if(!fsl_deck_required_cards_check(cards,err)){
return FSL_RC_CA_SYNTAX;
}
else if(FSL_CATYPE_ANY==cards->type){
if(!allowTypeAny){
return err
? fsl_error_set(err, FSL_RC_TYPE,
"Control artifact type ANY cannot be "
"output unless it is enabled in this "
"code (it's dangerous).")
: FSL_RC_TYPE;
}
/* fall through ... */
}
os->d = cards;
os->out = out;
os->outState = outputState;
switch(cards->type){
case FSL_CATYPE_CLUSTER:
rc = fsl_deck_output_cluster(os);
break;
case FSL_CATYPE_CONTROL:
rc = fsl_deck_output_control(os);
break;
case FSL_CATYPE_EVENT:
rc = fsl_deck_output_event(os);
break;
case FSL_CATYPE_CHECKIN:
rc = fsl_deck_output_mf(os);
break;
case FSL_CATYPE_TICKET:
rc = fsl_deck_output_ticket(os);
break;
case FSL_CATYPE_WIKI:
rc = fsl_deck_output_wiki(os);
break;
case FSL_CATYPE_ANY:
assert(allowTypeAny);
rc = fsl_deck_output_any(os);
break;
case FSL_CATYPE_ATTACHMENT:
rc = fsl_deck_output_attachment(os);
break;
default:
rc = err
? fsl_error_set(err, FSL_RC_TYPE,
"Invalid/unhandled deck type (#%d).",
cards->type)
: FSL_RC_TYPE;
goto end;
}
if(!rc){
rc = fsl_deck_out_Z( os );
}
end:
if(err && os->rc){
fsl_error_move(&os->error, err);
}
fsl_buffer_clear(&os->scratch);
fsl_error_clear(&os->error);
return os->rc ? os->rc : rc;
}
/* Timestamps might be adjusted slightly to ensure that checkins appear
on the timeline in chronological order. This is the maximum amount
of the adjustment window, in days.
*/
#define AGE_FUDGE_WINDOW (2.0/86400.0) /* 2 seconds */
/* This is increment (in days) by which timestamps are adjusted for
use on the timeline.
*/
#define AGE_ADJUST_INCREMENT (25.0/86400000.0) /* 25 milliseconds */
int fsl_deck_crosslink_end(fsl_cx * f){
int rc = 0;
fsl_db * db = fsl_cx_db_repo(f);
fsl_stmt q = fsl_stmt_empty;
fsl_stmt u = fsl_stmt_empty;
int i;
assert(f);
assert(db);
assert(f->cache.isCrosslinking);
if(!f->cache.isCrosslinking) return FSL_RC_RANGE;
f->cache.isCrosslinking = 0;
assert(db->beginCount > 0);
/* TODO: port in manifest.c:manifest_crosslink_end() */
#if 0
/* MISSING: ticket_rebuild_entry()
*/
rc = fsl_db_prepare(db, &q, "SELECT uuid FROM pending_tkt");
if(rc) goto end;
while( FSL_RC_STEP_ROW==fsl_stmt_step(&q) ){
const char *zUuid = fsl_stmt_g_text(&q, 0, NULL);
ticket_rebuild_entry(zUuid);
}
db_finalize(&q);
#endif
rc = fsl_db_exec(db, "DROP TABLE pending_tkt");
if(rc) goto end;
/* If multiple check-ins happen close together in time, adjust their
times by a few milliseconds to make sure they appear in chronological
order.
*/
rc = fsl_db_prepare(db, &q,
"UPDATE time_fudge SET m1=m2-:incr "
"WHERE m1>=m2 AND m1<m2+:window"
);
if(rc) goto end;
fsl_stmt_bind_double_name(&q, ":incr", AGE_ADJUST_INCREMENT);
fsl_stmt_bind_double_name(&q, ":window", AGE_FUDGE_WINDOW);
rc = fsl_db_prepare(db, &u,
"UPDATE time_fudge SET m2="
"(SELECT x.m1 FROM time_fudge AS x"
" WHERE x.mid=time_fudge.cid)");
for(i=0; !rc && i<30; i++){ /* where does 30 come from? */
rc = fsl_stmt_step(&q);
if(FSL_RC_STEP_DONE==rc) rc=0;
else break;
fsl_stmt_reset(&q);
if( fsl_db_changes_recent(db)==0 ) break;
rc = fsl_stmt_step(&u);
if(FSL_RC_STEP_DONE==rc) rc=0;
else break;
fsl_stmt_reset(&u);
}
fsl_stmt_finalize(&q);
fsl_stmt_finalize(&u);
if(!rc){
rc = fsl_db_exec(db, "UPDATE event SET "
"mtime=(SELECT m1 FROM time_fudge WHERE mid=objid) "
"WHERE objid IN (SELECT mid FROM time_fudge)");
}
end:
fsl_db_exec(db, "DROP TABLE time_fudge");
if(rc) fsl_db_transaction_rollback(db);
else rc = fsl_db_transaction_commit(db);
if(rc && !f->error.code) fsl_cx_uplift_db_error(f, db);
return rc;
}
int fsl_deck_crosslink_begin(fsl_cx * f){
int rc;
fsl_db * db = fsl_cx_db_repo(f);
assert(f);
assert(db);
assert(0==f->cache.isCrosslinking);
if(f->cache.isCrosslinking) return FSL_RC_MISUSE;
rc = fsl_db_transaction_begin(db);
if(rc) return rc;
rc = fsl_db_exec_multi(db,
"CREATE TEMP TABLE pending_tkt(uuid TEXT UNIQUE);"
"CREATE TEMP TABLE time_fudge("
" mid INTEGER PRIMARY KEY," /* The rid of a manifest */
" m1 REAL," /* The timestamp on mid */
" cid INTEGER," /* A child or mid */
" m2 REAL" /* Timestamp on the child */
");");
if(!rc) f->cache.isCrosslinking = 1;
if(rc) fsl_cx_uplift_db_error(f, db);
return rc;
}
/** @internal
Add a single entry to the mlink table. Also add the filename to
the filename table if it is not there already.
Parameters:
pmid: Record for parent manifest
zFromUuid: UUID for the content in parent (the new ==mlink.pid). 0
or "" to add file.
mid: The record ID of the manifest
zToUuid:UUID for the mlink.fid. "" to delete
zFilename: Filename
zPrior: Previous filename. NULL if unchanged
isPublic:True if mid is not a private manifest
isPrimary: true if pmid is the primary parent of mid.
mperm: permissions
*/
static
int fsl_repo_mlink_add_one( fsl_cx * f,
fsl_id_t pmid,
fsl_uuid_cstr zFromUuid,
fsl_id_t mid,
fsl_uuid_cstr zToUuid,
char const * zFilename,
char const * zPrior,
int isPublic,
int isPrimary,
fsl_file_perm_t mperm){
fsl_id_t fnid, pfnid, pid, fid;
fsl_db * db = fsl_cx_db_repo(f);
fsl_stmt * s1 = NULL;
int rc;
assert(f);
assert(db);
assert(db->beginCount>0);
rc = fsl_repo_filename_fnid2(f, zFilename, &fnid, 1);
if(rc) return rc;
if( zPrior && *zPrior ){
rc = fsl_repo_filename_fnid2(f, zPrior, &pfnid, 1);
if(rc) return rc;
}else{
pfnid = 0;
}
if( zFromUuid && *zFromUuid ){
pid = fsl_uuid_to_rid2(f, zFromUuid, FSL_PHANTOM_PUBLIC);
if(pid<0){
assert(f->error.code);
return f->error.code;
}
assert(pid>0);
}else{
pid = 0;
}
if( zToUuid && *zToUuid ){
fid = fsl_uuid_to_rid2(f, zToUuid, FSL_PHANTOM_PUBLIC);
if(fid<0){
assert(f->error.code);
return f->error.code;
}else if( isPublic ){
rc = fsl_content_make_public(f, fid);
if(rc) return rc;
}
}else{
fid = 0;
}
rc = fsl_db_prepare_cached(db, &s1,
"INSERT INTO mlink("
"mid,fid,pmid,pid,"
"fnid,pfnid,mperm,isaux"
")VALUES("
":m,:f,:pm,:p,:n,:pfn,:mp,:isaux"
")");
if(!rc){
fsl_stmt_bind_id_name(s1, ":m", mid);
fsl_stmt_bind_id_name(s1, ":f", fid);
fsl_stmt_bind_id_name(s1, ":pm", pmid);
fsl_stmt_bind_id_name(s1, ":p", pid);
fsl_stmt_bind_id_name(s1, ":n", fnid);
fsl_stmt_bind_id_name(s1, ":pfn", pfnid);
fsl_stmt_bind_id_name(s1, ":mp", mperm);
fsl_stmt_bind_int32_name(s1, ":isaux", isPrimary==0);
rc = fsl_stmt_step(s1);
fsl_stmt_cached_yield(s1);
if(FSL_RC_STEP_DONE==rc){
rc = 0;
if(pid && fid){
rc = fsl_content_deltify(f, pid, fid, 0);
}
}else{
fsl_cx_uplift_db_error(f, db);
}
}
return rc;
}
/**
Do a binary search to find a file in d->F.list.
As an optimization, guess that the file we seek is at index
d->F.cursor. That will usually be the case. If it is not found
there, then do the actual binary search.
Update d->F.cursor to be the index of the file that is found.
If d->f is NULL then this perform a case-sensitive search,
otherwise it uses case-sensitive or case-insensitive,
depending on f->cache.caseInsensitive.
Reminder to self: if this requires a non-const deck (and it does
right now) then the whole downstream chain will require a
non-const instance or they'll have to make local copies to make
the manipulation of d->F.cursor legal (but that would break
following of baselines without yet more trickery).
Reminder to self:
Fossil(1) added a 3rd parameter to this since it was ported.
See the bottom part of this change:
http://www.fossil-scm.org/index.html/info/2e51be8ec2df7cdf0dee3b33b63c46c73dcec624
*/
static fsl_card_F * fsl_deck_F_seek_base(fsl_deck * d,
char const * zName){
/* Maintenance reminder: this algo relies on the various
counters being signed.
*/
fsl_int_t lwr, upr;
int c;
fsl_int_t i;
assert(d);
assert(zName && *zName);
if(!d->F.list.used) return NULL;
#define FCARD(NDX) ((fsl_card_F *)d->F.list.list[NDX])
lwr = 0;
upr = d->F.list.used-1;
if( d->F.cursor>=lwr && d->F.cursor<upr ){
c = (d->f && d->f->cache.caseInsensitive)
? fsl_stricmp(FCARD(d->F.cursor+1)->name, zName)
: fsl_strcmp(FCARD(d->F.cursor+1)->name, zName);
if( c==0 ){
return FCARD(++d->F.cursor);
}else if( c>0 ){
upr = d->F.cursor;
}else{
lwr = d->F.cursor+1;
}
}
while( lwr<=upr ){
i = (lwr+upr)/2;
c = (d->f && d->f->cache.caseInsensitive)
? fsl_stricmp(FCARD(i)->name, zName)
: fsl_strcmp(FCARD(i)->name, zName);
if( c<0 ){
lwr = i+1;
}else if( c>0 ){
upr = i-1;
}else{
d->F.cursor = i;
return FCARD(i);
}
}
return NULL;
#undef FCARD
}
fsl_card_F * fsl_deck_F_seek(fsl_deck * const d, const char *zName){
fsl_card_F *pFile;
assert(d);
assert(zName && *zName);
if(!d || (FSL_CATYPE_CHECKIN!=d->type) || !zName || !*zName
|| !d->F.list.used) return NULL;
pFile = fsl_deck_F_seek_base(d, zName);
if( !pFile &&
(d->B.baseline /* we have a baseline or... */
|| (d->f && d->B.uuid) /* we can load the baseline */
)){
/* Check baseline manifest...
Sidebar: while the delta manifest model outwardly appears
to support recursive delta manifests, fossil(1) does not
use them and there would seem to be little practical use
for them (no notable size benefit for the majority of
cases), so we're not recursing here.
*/
int const rc = d->B.baseline ? 0 : fsl_deck_baseline_fetch(d);
if(rc){
assert(d->f->error.code);
}else if( d->B.baseline ){
assert(d->B.baseline->f && "How can this happen?");
assert((d->B.baseline->f == d->f) &&
"Universal laws are out of balance.");
pFile = fsl_deck_F_seek_base(d->B.baseline, zName);
if(pFile){
assert(pFile->uuid &&
"Per fossil-dev thread with DRH on 20140422, "
"baselines never have removed files.");
}
}
}
return pFile;
}
fsl_card_F const * fsl_deck_F_search(fsl_deck *d, const char *zName){
assert(d);
return fsl_deck_F_seek(d, zName);
}
/**
Returns true if repo contains an mlink entry where mid=rid,
else false.
*/
static char fsl_repo_has_mlink_mid( fsl_db * repo, fsl_id_t rid ){
fsl_stmt * st = NULL;
int rc = fsl_db_prepare_cached(repo, &st,
"SELECT 1 FROM mlink WHERE mid=?");
if(!rc){
fsl_stmt_bind_id(st, 1, rid);
rc = fsl_stmt_step(st);
fsl_stmt_cached_yield(st);
if( rc==FSL_RC_STEP_ROW ) rc = 0;
}
/* MARKER(("fsl_repo_has_mlink_mid(%d) rc=%d\n", (int)rid, rc)); */
return rc ? 0 : 1;
}
/**
Add mlink table entries associated with manifest cid, pChild. The
parent manifest is pid, pParent. One of either pChild or pParent
will be NULL and it will be computed based on cid/pid.
A single mlink entry is added for every file that changed content,
name, and/or permissions going from pid to cid.
Deleted files have mlink.fid=0.
Added files have mlink.pid=0.
File added by merge have mlink.pid=-1.
Edited files have both mlink.pid!=0 and mlink.fid!=0
*/
static
int fsl_repo_mlink_add( fsl_cx * f,
fsl_id_t pmid, fsl_deck /*const*/ * pParent,
fsl_id_t cid, fsl_deck /*const*/ * pChild,
int isPrimary){
fsl_buffer otherContent = fsl_buffer_empty;
fsl_id_t otherRid;
fsl_size_t i = 0;
int rc = 0;
fsl_card_F const * pChildFile = NULL;
fsl_card_F const * pParentFile = NULL;
fsl_deck ** ppOther;
fsl_db * db = fsl_cx_db_repo(f);
char isPublic;
assert(db);
assert(db->beginCount>0);
/* If mlink table entires are already set for cid, then abort early
doing no work.
*/
if(fsl_repo_has_mlink_mid(db, cid)) return 0;
/* Compute the value of the missing pParent or pChild parameter.
Fetch the baseline checkins for both.
*/
assert( pParent==0 || pChild==0 );
if( pParent ){
ppOther = &pChild;
otherRid = cid;
}else{
ppOther = &pParent;
otherRid = pmid;
}
rc = fsl_content_get(f, otherRid, &otherContent);
if(rc) goto end;
if( !otherContent.used ){
assert(!"Is zero correct here? Can this happen? "
"Loaded non-manifest?");
return 0;
}
*ppOther = fsl_deck_malloc();
if(!*ppOther){
rc = FSL_RC_OOM;
goto end;
}
(*ppOther)->f = f;
rc = fsl_deck_parse2(*ppOther, &otherContent, otherRid);
if(rc) goto end;
if( (rc=fsl_deck_baseline_fetch(pParent))
|| (rc=fsl_deck_baseline_fetch(pChild))){
goto end;
}
rc = 0;
isPublic = !fsl_content_is_private(f, cid);
/* Try to make the parent manifest a delta from the child, if that
is an appropriate thing to do. For a new baseline, make the
previous baseline a delta from the current baseline.
*/
if( (pParent->B.uuid==0)==(pChild->B.uuid==0) ){
rc = fsl_content_deltify(f, pmid, cid, 0);
}else if( pChild->B.uuid==NULL && pParent->B.uuid!=NULL ){
rc = fsl_content_deltify(f, pParent->B.baseline->rid, cid, 0);
}
if(rc) goto end;
/* Remember all children less than a few seconds younger than their parent,
as we might want to fudge the times for those children.
*/
if( f->cache.isCrosslinking &&
(pChild->D < pParent->D+AGE_FUDGE_WINDOW)
){
rc = fsl_db_exec(db, "INSERT OR REPLACE INTO time_fudge VALUES"
"(%"FSL_ID_T_PFMT", %"FSL_JULIAN_T_PFMT
", %"FSL_ID_T_PFMT", %"FSL_JULIAN_T_PFMT");",
(fsl_id_t)pParent->rid, pParent->D,
(fsl_id_t)pChild->rid, pChild->D);
if(rc) goto end;
}
/* First look at all files in pChild, ignoring its baseline. This
is where most of the changes will be found.
*/
#define FCARD(DECK,NDX) \
((((NDX)<(DECK)->F.list.used)) \
? ((fsl_card_F const *)(DECK)->F.list.list[NDX]) \
: NULL)
/* First look at all files in pChild, ignoring its baseline. This
is where most of the changes will be found.
*/
for(i=0, pChildFile=FCARD(pChild,0);
i<pChild->F.list.used;
++i, pChildFile=FCARD(pChild,i)){
fsl_file_perm_t const mperm = pChildFile->perm;
if( pChildFile->priorName ){
pParentFile = fsl_deck_F_seek(pParent,
pChildFile->priorName);
if( pParentFile ){
/* File with name change */
/*
libfossil checkin 8625a31eff708dea93b16582e4ec5d583794d1af
contains these two interesting F-cards:
F src/net/wanderinghorse/libfossil/FossilCheckout.java
F src/org/fossil_scm/libfossil/Checkout.java 6e58a47089d3f4911c9386c25bac36c8e98d4d21 w src/net/wanderinghorse/libfossil/FossilCheckout.java
Note the placement of FossilCheckout.java (twice).
Up until then, i thought a delete/rename combination was not possible.
*/
rc = fsl_repo_mlink_add_one(f,
pmid,
pParentFile->uuid,
cid,
pChildFile->uuid,
pChildFile->name,
pChildFile->priorName,
isPublic,
isPrimary,
mperm);
}else{
/* File name changed, but the old name is not found in the parent!
Treat this like a new file. */
rc = fsl_repo_mlink_add_one(f, pmid, 0, cid, pChildFile->uuid,
pChildFile->name, 0,
isPublic, isPrimary, mperm);
}
}else{
pParentFile = fsl_deck_F_seek(pParent, pChildFile->name);
if(!pParentFile || !pParentFile->uuid){
/* Parent does not have it or it was removed in parent. */
if( pChildFile->uuid ){
/* A new or re-added file */
rc = fsl_repo_mlink_add_one(f, pmid, 0, cid, pChildFile->uuid,
pChildFile->name, 0,
isPublic, isPrimary, mperm);
}
}
else if( fsl_strcmp(pChildFile->uuid, pParentFile->uuid)!=0
|| (pParentFile->perm!=mperm) ){
/* Changes in file content or permissions */
rc = fsl_repo_mlink_add_one(f, pmid, pParentFile->uuid,
cid, pChildFile->uuid,
pChildFile->name, 0,
isPublic, isPrimary, mperm);
}
}
} /* end pChild card list loop */
if(rc) goto end;
else if( pParent->B.uuid && pChild->B.uuid ){
/* Both parent and child are delta manifests. Look for files that
are deleted or modified in the parent but which reappear or revert
to baseline in the child and show such files as being added or changed
in the child. */
for(i=0, pParentFile=FCARD(pParent,0);
i<pParent->F.list.used;
++i, pParentFile = FCARD(pParent,i)){
if( pParentFile->uuid ){
pChildFile = fsl_deck_F_seek_base(pChild, pParentFile->name);
if( !pChildFile || !pChildFile->uuid){
/* The child file reverts to baseline or is deleted.
Show this as a change. */
if(!pChildFile){
pChildFile = fsl_deck_F_seek(pChild, pParentFile->name);
}
if( pChildFile && pChildFile->uuid ){
rc = fsl_repo_mlink_add_one(f, pmid, pParentFile->uuid, cid,
pChildFile->uuid, pChildFile->name,
0, isPublic, isPrimary,
pChildFile->perm);
}
}
}else{
/* Was deleted in the parent. */
pChildFile = fsl_deck_F_seek(pChild, pParentFile->name);
if( pChildFile && pChildFile->uuid ){
/* File resurrected in the child after having been deleted in
the parent. Show this as an added file. */
rc = fsl_repo_mlink_add_one(f, pmid, 0, cid, pChildFile->uuid,
pChildFile->name, 0, isPublic,
isPrimary, pChildFile->perm);
}
}
}
}else if( !pChild->B.uuid ){
/* pChild is a baseline. Look for files that are present in pParent
but are missing from pChild and mark them as having been deleted. */
fsl_card_F const * cfc = NULL;
fsl_deck_F_rewind(pParent);
while( (0==(rc=fsl_deck_F_next(pParent,&cfc))) && cfc){
pParentFile = cfc;
pChildFile = fsl_deck_F_seek(pChild, pParentFile->name);
if( (!pChildFile || !pChildFile->uuid) && pParentFile->uuid ){
rc = fsl_repo_mlink_add_one(f, pmid, pParentFile->uuid, cid, 0,
pParentFile->name, 0, isPublic,
isPrimary, pParentFile->perm);
}
}
}
end:
if(*ppOther){
/* TODO? cache *ppOther here, like v1 does. */
fsl_deck_finalize(*ppOther);
}
fsl_buffer_clear(&otherContent);
if(rc && db->error.code && !f->error.code){
fsl_cx_uplift_db_error(f, db);
}
return rc;
#undef FCARD
}
int fsl_deck_crosslink( fsl_deck /* const */ * d ){
int rc;
fsl_cx * f = d ? d->f : NULL;
fsl_db * db = f ? fsl_needs_repo(f) : NULL;
fsl_id_t parentid = 0;
fsl_double_t mtime;
fsl_int_t const rid = d ? d->rid : -1;
/*
TODO: split this beast up into multiple (static) routines.
We now have fsl_xlink_listener(), so we can/should at least farm
out the parts which update the event table.
*/
assert(f);
assert(d);
if(!f || !d) return FSL_RC_MISUSE;
else if(rid<=0){
return fsl_cx_err_set(f, FSL_RC_RANGE,
"Invalid RID for crosslink: %"FSL_ID_T_PFMT,
(fsl_id_t)rid);
}
else if(!db) return FSL_RC_NOT_A_REPO;
else if(!fsl_deck_required_cards_check(d, &d->f->error)){
assert(d->f->error.code);
return d->f->error.code;
}else if(f->cache.xlinkClustersOnly && (FSL_CATYPE_CLUSTER!=d->type)){
return 0;
}
mtime = (d->D>0)
? d->D
: fsl_db_julian_now(db);
if(FSL_CATYPE_CHECKIN==d->type){
/* TODO: try to load baseline, and fail if we can't. Doh, deck is
const. So require that the caller to load it. Or load it
automatically somewhere along the line, maybe at parse-time.
But since it has to be non-const for other reasons, we'll go ahead
and load the baseline here...
*/
if(d->B.uuid && !d->B.baseline){
rc = fsl_deck_baseline_fetch(d);
if(rc) goto end;
assert(d->B.baseline);
}
}
rc = fsl_db_transaction_begin(db);
if(rc) goto end;
if(FSL_CATYPE_CHECKIN == d->type){
fsl_size_t i;
fsl_stmt q = fsl_stmt_empty;
/* TODO: convert these queries to cached statements, for
the sake of rebuild and friends. And bind() doubles
instead of %FSL_JULIAN_T_PFMT'ing them.
*/
if(!fsl_repo_has_mlink_mid(db, rid)){
/* legacy (see comments below): char *zCom; */
char zBaseId[30] = {0};
if(d->B.uuid){
fsl_id_t const baseid = d->B.baseline
? d->B.baseline->rid
: fsl_uuid_to_rid(f, d->B.uuid);
if(baseid<0){
rc = f->error.code;
assert(0 != rc);
goto end;
}
assert(baseid>0);
fsl_snprintf( zBaseId, sizeof(zBaseId),
"%"FSL_ID_T_PFMT,
(fsl_id_t)baseid );
}else{
fsl_snprintf( zBaseId, sizeof(zBaseId), "NULL" );
}
for(i=0; i<d->P.used; i++){
char const * parentUuid = (char const *)d->P.list[i];
fsl_id_t pid = fsl_uuid_to_rid2(f, parentUuid, FSL_PHANTOM_PUBLIC);
if(pid<0){
assert(f->error.code);
rc = f->error.code;
goto end;
}
rc = fsl_db_exec(db, "INSERT OR IGNORE "
"INTO plink(pid, cid, isprim, mtime, baseid) "
"VALUES(%"FSL_ID_T_PFMT", %"FSL_ID_T_PFMT
", %d, %"FSL_JULIAN_T_PFMT", %s)",
(fsl_id_t)pid, (fsl_id_t)rid,
((i==0) ? 1 : 0), d->D, zBaseId/*safe-for-%s*/);
if(rc) goto end;
rc = fsl_repo_mlink_add(f, pid, NULL, rid, d, 0==i);
if(rc) goto end;
if( i==0 ){
parentid = pid;
}
}
if(d->P.used>1){
/* http://www.fossil-scm.org/index.html/info/8e44cf6f4df4f9f0 */
/* Change MLINK.PID from 0 to -1 for files that are added by merge. */
rc = fsl_db_exec(db,
"UPDATE mlink SET pid=-1"
" WHERE mid=%"FSL_ID_T_PFMT
" AND pid=0"
" AND fnid IN "
" (SELECT fnid FROM mlink WHERE mid=%"FSL_ID_T_PFMT
" GROUP BY fnid"
" HAVING count(*)<%d)",
(fsl_id_t)rid, (fsl_id_t)rid, (int)d->P.used
);
if(rc) goto end;
}
rc = fsl_db_prepare(db, &q,
"SELECT cid, isprim FROM plink "
"WHERE pid=%"FSL_ID_T_PFMT" AND isprim",
(fsl_id_t)rid);
while( !rc && (FSL_RC_STEP_ROW==(rc=fsl_stmt_step(&q))) ){
fsl_id_t const cid = fsl_stmt_g_id(&q, 0);
int const isPrim = fsl_stmt_g_int32(&q, 1);
assert(cid>0);
rc = fsl_repo_mlink_add(f, rid, d, cid, NULL, isPrim);
}
if(FSL_RC_STEP_DONE==rc) rc = 0;
fsl_stmt_finalize(&q);
if(rc) goto end;
if( !d->P.used ){
/* For root files (files without parents) add mlink entries
showing all content as new. */
char const isPublic = !fsl_content_is_private(f, rid);
for(i=0; !rc && (i<d->F.list.used); ++i){
fsl_card_F const * fc = (fsl_card_F const *)d->F.list.list[i];
rc = fsl_repo_mlink_add_one(f, 0, 0, rid, fc->uuid, fc->name, 0,
isPublic, 1, fc->perm);
}
}
if(rc) goto end;
/* FIXME: use a cached statement */
rc = fsl_db_exec(db,
"REPLACE INTO event(type,mtime,objid,user,comment,"
"bgcolor,euser,ecomment,omtime)"
"VALUES('ci',"
" coalesce(" /*mtime*/
" (SELECT julianday(value) FROM tagxref "
" WHERE tagid=%d AND rid=%"FSL_ID_T_PFMT
" ),"
" %"FSL_JULIAN_T_PFMT""
" ),"
" %"FSL_ID_T_PFMT","/*objid*/
" %Q," /*user*/
" %Q," /*comment. No, the comment _field_. */
" (SELECT value FROM tagxref " /*bgcolor*/
" WHERE tagid=%d AND rid=%"FSL_ID_T_PFMT
" AND tagtype>0"
" ),"
" (SELECT value FROM tagxref " /*euser*/
" WHERE tagid=%d AND rid=%"FSL_ID_T_PFMT
" ),"
" (SELECT value FROM tagxref " /*ecomment*/
" WHERE tagid=%d AND rid=%"FSL_ID_T_PFMT
" ),"
" %"FSL_JULIAN_T_PFMT/*omtime*/
")",
/* The (fsl_id_t) casts here are to work
around an inexplicable memory corruption
i am seeing in the va_list handling,
and kdbg doesn't work on Ubuntu 13.04 :-<
*/
(int)FSL_TAGID_DATE, (fsl_id_t)rid, d->D,
(fsl_id_t)rid, d->U, d->C,
(int)FSL_TAGID_BGCOLOR, (fsl_id_t)rid,
(int)FSL_TAGID_USER, (fsl_id_t)rid,
(int)FSL_TAGID_COMMENT, (fsl_id_t)rid, d->D
);
if(rc) goto end;
#if 0
/*
Legacy: need an option or crosslink callback for this.
Reserve name 'fsl-timeline-wiki-links'
but we have the problem that the last_insert_rowid() is likely
to not be valid after this point. So how do we communicate
that to the callback? Any links we generate are necessarily
app-specific, meaning either we have to dictate paths to
clients or require them to parse their own wiki content.
*/
zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event"
" WHERE rowid=last_insert_rowid()");
wiki_extract_links(zCom, rid, 0, d->rDate, 1, WIKI_INLINE);
free(zCom);
#endif
/* If this is a delta-manifest, record the fact that this repository
contains delta manifests, to free the "commit" logic to generate
new delta manifests.
*/
if( d->B.uuid && (f->cache.seenManifest <= 0) ){
f->cache.seenManifest = 1;
rc = fsl_config_set_int32(f, FSL_CONFDB_REPO,
"seen-delta-manifest", 1);
if(rc) goto end;
}
assert(!rc);
}/*!exists mlink*/
}/*MANIFEST*/
else if( FSL_CATYPE_CLUSTER == d->type ){
/* Clean up the unclustered table... */
fsl_size_t i;
fsl_stmt * st = NULL;
rc = fsl_db_prepare_cached(db, &st,
"DELETE FROM unclustered WHERE rid=?");
if(rc) goto end;
assert(st);
for( i = 0; i < d->M.used; ++i ){
fsl_id_t mid;
char const * uuid = (char const *)d->M.list[i];
mid = fsl_uuid_to_rid(f, uuid);
if(mid>0){
fsl_stmt_bind_id(st, 1, mid);
fsl_stmt_step(st);
fsl_stmt_reset(st);
}
}
fsl_stmt_cached_yield(st);
}/*CLUSTER*/
if( d->type==FSL_CATYPE_CONTROL
|| d->type==FSL_CATYPE_CHECKIN
|| d->type==FSL_CATYPE_EVENT
){
/* Apply tags... */
fsl_size_t i;
fsl_list const * li = &d->T;
fsl_double_t tagTime = d->D;
if(li->used && tagTime<=0){
tagTime = fsl_db_julian_now(db);
if(tagTime<=0){
rc = FSL_RC_DB;
goto end;
}
}
for( i = 0; !rc && (i < li->used); ++i){
fsl_id_t tid;
fsl_card_T const * tag = (fsl_card_T const *)li->list[i];
assert(tag);
if(!tag->uuid){
tid = rid;
}else{
tid = fsl_uuid_to_rid( f, tag->uuid);
}
if(tid<0){
assert(f->error.code);
rc = f->error.code;
goto end;
}else if(0==tid){
rc = fsl_cx_err_set(f, FSL_RC_RANGE,
"Could not get RID for [%.12s].",
tag->uuid);
goto end;
}
rc = fsl_tag_insert(f, tag->type,
tag->name, tag->value,
rid, tagTime, tid, NULL);
}
if( !rc && (parentid>0) ){
rc = fsl_tag_propagate_all(f, parentid);
}
if(rc) goto end;
}/*CONTROL | CHECKIN | EVENT*/
else if( d->type==FSL_CATYPE_WIKI ){
fsl_buffer buf = fsl_buffer_empty;
char zLength[40] = {0};
fsl_id_t tagid;
fsl_id_t prior;
char const * zWiki;
char const * zTag;
fsl_size_t nWiki = 0;
fsl_buffer * cbuf = &f->scratch;
rc = fsl_buffer_appendf(&buf, "wiki-%s", d->L);
if(rc) goto wiki_end;
zTag = fsl_buffer_cstr(&buf);
tagid = fsl_tag_id( f, zTag, 1 );
if(tagid<=0){
rc = f->error.code ? f->error.code :
fsl_cx_err_set(f, FSL_RC_RANGE,
"Got unexpected RID (%"FSL_ID_T_PFMT") "
"for tag: %s. Grep the fsl_tag_id() "
"sources to determine its meaning.",
(fsl_id_t)tagid, zTag);
goto wiki_end;
}
zWiki = d->W.used ? fsl_buffer_cstr(&d->W) : "";
while( *zWiki && fsl_isspace(*zWiki) ){
++zWiki;
/* Historical behaviour: strip leading spaces. */
}
nWiki = fsl_strlen(zWiki)
/* Reminder: use strlen instead of d->W.used just in case that
one contains embedded NULs in the content. "Shouldn't
happen," but the API doesn't explicitly prohibit it.
*/;
fsl_snprintf(zLength, sizeof(zLength), "%"FSL_SIZE_T_PFMT,
(fsl_size_t)nWiki);
rc = fsl_tag_insert(f, FSL_TAGTYPE_ADD, zTag, zLength,
rid, d->D, rid, NULL );
if(rc) goto wiki_end;
zTag = zWiki = NULL;
/* TODO: cached statement */
prior = fsl_db_g_id(db, 0,
"SELECT rid FROM tagxref"
" WHERE tagid=%"FSL_ID_T_PFMT
" AND mtime<%"FSL_JULIAN_T_PFMT
" ORDER BY mtime DESC",
(fsl_id_t)tagid, d->D);
if(prior>0){
rc = fsl_content_deltify(f, prior, rid, 0);
if(rc) goto wiki_end;
}
/* WIKI constructs currently (201308) do not allow a C card.
When/if they do, use it here.
*/
cbuf->used = 0 /* re-use memory for comment text */;
if( nWiki > 0 ){
rc = fsl_buffer_appendf(cbuf,
"Changes to wiki page [%h]",
d->L);
}else{
rc = fsl_buffer_appendf(cbuf,
"Emptied wiki page [%h]",
d->L);
}
if(!rc){
/* TODO: cached statement */
fsl_db_exec(db,
"REPLACE INTO event(type,mtime,objid,user,comment,"
" bgcolor,euser,ecomment) "
"VALUES("
" 'w',"
" %"FSL_JULIAN_T_PFMT","
" %"FSL_ID_T_PFMT","
" %Q, %B,"
" (SELECT value FROM tagxref "
" WHERE tagid=%"FSL_ID_T_PFMT
" AND rid=%"FSL_ID_T_PFMT
" AND tagtype>1),"
" (SELECT value FROM tagxref "
" WHERE tagid=%"FSL_ID_T_PFMT
" AND rid=%"FSL_ID_T_PFMT"),"
" (SELECT value FROM tagxref WHERE "
" tagid=%"FSL_ID_T_PFMT
" AND rid=%"FSL_ID_T_PFMT")"
");",
d->D, (fsl_id_t)rid, d->U, cbuf,
FSL_TAGID_BGCOLOR, (fsl_id_t)rid,
FSL_TAGID_BGCOLOR, (fsl_id_t)rid,
FSL_TAGID_USER, (fsl_id_t)rid,
FSL_TAGID_COMMENT, (fsl_id_t)rid
);
}
wiki_end:
fsl_buffer_clear(&buf);
if(rc) goto end;
}/*WIKI*/
if( d->type==FSL_CATYPE_EVENT ){
char buf[FSL_UUID_STRLEN + 7 /* event-UUID */] = {0};
char zLength[40] = {0};
fsl_id_t tagid;
fsl_id_t prior, subsequent;
char const * zWiki;
char const * zTag;
fsl_size_t nWiki = 0;
fsl_snprintf(buf, sizeof(buf), "event-%s", d->E.uuid);
zTag = buf;
tagid = fsl_tag_id( f, zTag, 1 );
if(tagid<=0){
rc = f->error.code ? f->error.code :
fsl_cx_err_set(f, FSL_RC_RANGE,
"Got unexpected RID (%"FSL_ID_T_PFMT") "
"for tag [%s].",
(fsl_id_t)tagid, zTag);
goto event_end;
}
zWiki = d->W.used ? fsl_buffer_cstr(&d->W) : "";
while( *zWiki && fsl_isspace(*zWiki) ){
++zWiki;
/* Historical behaviour: strip leading spaces. */
}
nWiki = fsl_strlen(zWiki);
fsl_snprintf( zLength, sizeof(zLength), "%"FSL_SIZE_T_PFMT,
(fsl_size_t)nWiki);
rc = fsl_tag_insert(f, FSL_TAGTYPE_ADD, zTag, zLength,
rid, d->D, rid, NULL );
if(rc) goto event_end;
/* TODO: cached statement */
prior = fsl_db_g_id(db, 0,
"SELECT rid FROM tagxref"
" WHERE tagid=%"FSL_ID_T_PFMT
" AND mtime<%"FSL_JULIAN_T_PFMT
" AND rid!=%"FSL_ID_T_PFMT
" ORDER BY mtime DESC",
(fsl_id_t)tagid, d->D, (fsl_id_t)rid);
if(prior<0){
assert(db->error.code);
rc = fsl_cx_uplift_db_error(f, db);
goto event_end;
}
subsequent = fsl_db_g_id(db, 0,
"SELECT rid FROM tagxref"
" WHERE tagid=%"FSL_ID_T_PFMT
" AND mtime>=%"FSL_JULIAN_T_PFMT
" AND rid!=%"FSL_ID_T_PFMT
" ORDER BY mtime",
(fsl_id_t)tagid, d->D, (fsl_id_t)rid);
if(subsequent<0){
assert(db->error.code);
rc = fsl_cx_uplift_db_error(f, db);
goto event_end;
}
else if( prior > 0 ){
rc = fsl_content_deltify(f, prior, rid, 0);
if( !rc && !subsequent ){
rc = fsl_db_exec(db,
"DELETE FROM event"
" WHERE type='e'"
" AND tagid=%"FSL_ID_T_PFMT
" AND objid IN"
" (SELECT rid FROM tagxref "
" WHERE tagid=%"FSL_ID_T_PFMT")",
(fsl_id_t)tagid, (fsl_id_t)tagid);
}
}
if(rc) goto event_end;
if( subsequent>0 ){
rc = fsl_content_deltify(f, rid, subsequent, 0);
}else{
/* TODO: cached statement */
rc = fsl_db_exec(db,
"REPLACE INTO event("
"type,mtime,"
"objid,tagid,"
"user,comment,bgcolor"
")VALUES("
"'e',%"FSL_JULIAN_T_PFMT","
"%"FSL_ID_T_PFMT",%"FSL_ID_T_PFMT","
"%Q,%Q,"
" (SELECT value FROM tagxref WHERE "
" tagid=%d"
" AND rid=%"FSL_ID_T_PFMT")"
");",
d->E.julian, (fsl_id_t)rid,
(fsl_id_t)tagid,
d->U, d->C,
(int)FSL_TAGID_BGCOLOR,
(fsl_id_t)rid
);
}
event_end:
if(rc) goto end;
}/*EVENT*/
else if( d->type==FSL_CATYPE_TICKET ){
/*
TODO: huge block from manifest_crosslink(). A full port
requires other infrastructure for collapsing relatively close
time values into the same time for timeline purposes. i'd prefer
to farm this out to a crosslink callback. Even then, the future
of tickets in libfossil is uncertain, but we should crosslink
them so that repos stay compatible with fossil(1) without
requiring a rebuild using fossil(1).
*/
rc = fsl_cx_err_set(f, FSL_RC_NYI,
"MISSING: a huge block of TICKET stuff from "
"manifest_crosslink(). It requires infrastructure "
"libfossil does not yet have.");
if(rc) goto end;
}/*TICKET*/
else if( d->type==FSL_CATYPE_ATTACHMENT ){
fsl_buffer comment = fsl_buffer_empty;
char const attachedToType = fsl_is_uuid(d->A.tgt)
? 't' /* applies to a ticket */
: 'w' /* applies to a wiki page named d->A.tgt */;
rc = fsl_db_exec(db,
/* REMINDER: fossil(1) uses INSERT here, but that
breaks libfossil crosslinking tests due to a
unique constraint violation on attachid. */
"REPLACE INTO attachment(attachid, mtime, src, target,"
"filename, comment, user) VALUES("
"%"FSL_ID_T_PFMT",%"FSL_JULIAN_T_PFMT","
"%Q,%Q,%Q,"
"%Q,%Q);",
(fsl_id_t)rid, d->D,
d->A.src, d->A.tgt, d->A.name,
(d->C ? d->C : ""), d->U);
if(rc) goto end;
rc = fsl_db_exec(db,
"UPDATE attachment SET isLatest = (mtime=="
"(SELECT max(mtime) FROM attachment"
" WHERE target=%Q AND filename=%Q))"
" WHERE target=%Q AND filename=%Q",
d->A.tgt, d->A.name,
d->A.tgt, d->A.name);
if(rc) goto end;
if('w'==attachedToType){
/* Attachment applies to a wiki page */
if(d->A.src && *d->A.src){
rc = fsl_buffer_appendf(&comment,
"Add attachment \"%h\" "
"to wiki page [%h]",
d->A.name, d->A.tgt);
}else{
rc = fsl_buffer_appendf(&comment,
"Delete attachment \"%h\" "
"from wiki page [%h]",
d->A.name, d->A.tgt);
}
}else{
/* Attachment applies to a ticket */
if(d->A.src && *d->A.src){
rc = fsl_buffer_appendf(&comment,
"Add attachment \"%h\" "
"to ticket [%s|%.10s]",
d->A.name, d->A.tgt, d->A.tgt);
}else{
rc = fsl_buffer_appendf(&comment,
"Delete attachment \"%h\" "
"from ticket [%s|%.10s]",
d->A.name, d->A.tgt, d->A.tgt);
}
}
if(!rc){
rc = fsl_db_exec(db,
"REPLACE INTO event(type,mtime,objid,user,comment)"
"VALUES("
"'%c',%"FSL_JULIAN_T_PFMT",%"FSL_ID_T_PFMT","
"%Q,%B)",
attachedToType, d->D, (fsl_id_t)rid,
d->U, &comment);
}
fsl_buffer_clear(&comment);
if(rc) goto end;
/* Milestone: this particular block is one of the very few of the
ported-over fossil(1) bits which is not significantly longer
than the original implementation :).
*/
}/*ATTACHMENT*/
else if( d->type==FSL_CATYPE_CONTROL){
/*
Create timeline event entry for all tags in this control
construct. Note that we are using a lot of historical code which
hard-codes english-lanuage text and links which only work in
fossil(1). i would prefer to farm this out to a crosslink
callback, and provide a default implementation which more or
less mimics fossil(1).
*/
fsl_buffer comment = fsl_buffer_empty;
fsl_size_t i;
const char *zName;
const char *zValue;
const char *zUuid;
int branchMove = 0;
int const uuidLen = 8;
fsl_card_T const * tag = NULL;
fsl_card_T const * prevTag = NULL;
fsl_list const * li = &d->T;
/**
Reminder to self: fossil(1) has a comment here:
// Next loop expects tags to be sorted on UUID, so sort it.
qsort(p->aTag, p->nTag, sizeof(p->aTag[0]), tag_compare);
That sort plays a role in hook code execution and is needed to
avoid duplicate hook execution in some cases. libfossil
outsources that type of thing to crosslink callbacks, though,
so we won't concern ourselves with it here. We also don't
really want to modify the deck during crosslinking. The only
reason the deck is not const in this routine is because of the
fsl_deck::F::cursor bits inherited from fossil(1), largely
worth its cost except that many routines can no longer be
const. Shame C doesn't have C++'s "mutable" keyword.
That said, sorting by UUID would have a nice side-effect on the
output of grouping tags by the UUID they tag. So far
(201404) such groups of tags have not appeared in the wild
because fossil(1) has no mechanism for creating them.
*/
for( i = 0; !rc && (i < li->used); ++i, prevTag = tag){
char isProp = 0, isAdd = 0, isCancel = 0;
tag = (fsl_card_T const *)li->list[i];
zUuid = tag->uuid;
if(!zUuid /*tag on self*/) continue;
if( i==0 || 0!=fsl_uuidcmp(tag->uuid, prevTag->uuid)){
rc = fsl_buffer_appendf(&comment,
" Edit [%.*s]:", uuidLen, zUuid);
branchMove = 0;
}
if(rc) goto end_ctl;
isProp = FSL_TAGTYPE_PROPAGATING==tag->type;
isAdd = FSL_TAGTYPE_ADD==tag->type;
isCancel = FSL_TAGTYPE_CANCEL==tag->type;
assert(isProp || isAdd || isCancel);
zName = tag->name;
zValue = tag->value;
if( isProp && 0==fsl_strcmp(zName, "branch")){
rc = fsl_buffer_appendf(&comment,
" Move to branch %s"
"[/timeline?r=%h&nd&dp=%.*s | %h].",
zValue, uuidLen, zUuid, zValue);
branchMove = 1;
}else if( isProp && fsl_strcmp(zName, "bgcolor")==0 ){
rc = fsl_buffer_appendf(&comment,
" Change branch background color to \"%h\".", zValue);
}else if( isAdd && fsl_strcmp(zName, "bgcolor")==0 ){
rc = fsl_buffer_appendf(&comment,
" Change background color to \"%h\".", zValue);
}else if( isCancel && fsl_strcmp(zName, "bgcolor")==0 ){
rc = fsl_buffer_appendf(&comment, " Cancel background color.");
}else if( isAdd && fsl_strcmp(zName, "comment")==0 ){
rc = fsl_buffer_appendf(&comment, " Edit check-in comment.");
}else if( isAdd && fsl_strcmp(zName, "user")==0 ){
rc = fsl_buffer_appendf(&comment, " Change user to \"%h\".", zValue);
}else if( isAdd && fsl_strcmp(zName, "date")==0 ){
rc = fsl_buffer_appendf(&comment, " Timestamp %h.", zValue);
}else if( isCancel && memcmp(zName, "sym-",4)==0 ){
if( !branchMove ){
rc = fsl_buffer_appendf(&comment, " Cancel tag %h.", zName+4);
}
}else if( isProp && memcmp(zName, "sym-",4)==0 ){
if( !branchMove ){
rc = fsl_buffer_appendf(&comment, " Add propagating tag \"%h\".", zName+4);
}
}else if( isAdd && memcmp(zName, "sym-",4)==0 ){
rc = fsl_buffer_appendf(&comment, " Add tag \"%h\".", zName+4);
}else if( isCancel && memcmp(zName, "sym-",4)==0 ){
rc = fsl_buffer_appendf(&comment, " Cancel tag \"%h\".", zName+4);
}else if( isAdd && fsl_strcmp(zName, "closed")==0 ){
rc = fsl_buffer_append(&comment, " Marked \"Closed\"", -1);
if( !rc && zValue && *zValue ){
rc = fsl_buffer_appendf(&comment, " with note \"%h\"", zValue);
}
if(!rc) rc = fsl_buffer_append(&comment, ".", 1);
}else if( isCancel && fsl_strcmp(zName, "closed")==0 ){
rc = fsl_buffer_append(&comment, " Removed the \"Closed\" mark", -1);
if( !rc && zValue && *zValue ){
rc = fsl_buffer_appendf(&comment, " with note \"%h\"", zValue);
}
if(!rc) rc = fsl_buffer_append(&comment, ".", 1);
}else {
if( isCancel ){
rc = fsl_buffer_appendf(&comment, " Cancel \"%h\"", zName);
}else if( isAdd ){
rc = fsl_buffer_appendf(&comment, " Add \"%h\"", zName);
}else{
assert(isProp);
rc = fsl_buffer_appendf(&comment, " Add propagating \"%h\"", zName);
}
if(rc) goto end_ctl;
if( zValue && zValue[0] ){
rc = fsl_buffer_appendf(&comment, " with value \"%h\".", zValue);
}else{
rc = fsl_buffer_append(&comment, ".", 1);
}
}
} /* foreach tag loop */
if(!rc){
/* TODO: cached statement */
rc = fsl_db_exec(db,
"REPLACE INTO event"
"(type,mtime,objid,user,comment) "
"VALUES('g',"
"%"FSL_JULIAN_T_PFMT","
"%"FSL_ID_T_PFMT","
"%Q,%Q)",
mtime, (fsl_id_t)rid, d->U,
(comment.used>1)
? (fsl_buffer_cstr(&comment)
+1/*leading space on all entries*/)
: NULL);
}
end_ctl:
fsl_buffer_clear(&comment);
if(rc) goto end;
}/*CONTROL*/
assert(0==rc);
/* Call any crosslink callbacks... */
if(f->xlinkers.list){
fsl_size_t i;
fsl_xlinker * xl = NULL;
for( i = 0; !rc && (i < f->xlinkers.used); ++i ){
xl = f->xlinkers.list+i;
rc = xl->f( f, d, xl->state );
}
if(rc){
assert(xl);
if(!f->error.code){
fsl_cx_err_set(f, rc, "Crosslink callback handler "
"'%s' failed with code %d (%s) for "
"artifact [%.12s].",
xl->name, rc, fsl_rc_cstr(rc),
d->uuid);
}
goto end;
}
}
end:
if(!rc){
rc = fsl_db_transaction_end(db, 0);
}else{
if(db->error.code && !f->error.code){
fsl_cx_uplift_db_error(f,db);
}
fsl_db_transaction_end(db, 1);
}
return rc;
}/*end fsl_deck_crosslink()*/
/**
Return true if z points to the first character after a blank line.
Tolerate either \r\n or \n line endings.
*/
static char fsl_after_blank_line(const char *z){
if( z[-1]!='\n' ) return 0;
if( z[-2]=='\n' ) return 1;
if( z[-2]=='\r' && z[-3]=='\n' ) return 1;
return 0;
}
/**
Verifies that ca points to at least 35 bytes of memory
which hold (at the end) a Z card and its hash value.
Returns 0 if the string does not contain a Z card,
a positive value if it can validate the Z card's hash,
and a negative value on hash mismatch.
*/
static int fsl_mf_verify_Z_card(unsigned char const * ca, fsl_size_t n){
if( n<35 ) return 0;
if( ca[n-35]!='Z' || ca[n-34]!=' ' ) return 0;
else{
unsigned char digest[16];
char hex[FSL_MD5_STRLEN+1];
unsigned char const * zHash = ca+n-FSL_MD5_STRLEN-1;
fsl_md5_cx md5 = fsl_md5_cx_empty;
unsigned char const * zHashEnd =
ca + n -
2 /* 'Z ' */
- FSL_MD5_STRLEN
- 1 /* \n */;
assert( 'Z' == (char)*zHashEnd );
fsl_md5_update(&md5, ca, zHashEnd-ca);
fsl_md5_final(&md5, digest);
fsl_md5_digest_to_base16(digest, hex);
return (0==memcmp(zHash, hex, FSL_MD5_STRLEN))
? 1
: -1;
}
}
void fsl_remove_pgp_signature(unsigned char const **pz, fsl_size_t *pn){
unsigned char const *z = *pz;
fsl_int_t n = (fsl_int_t)*pn;
fsl_int_t i;
if( memcmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)!=0 ) return;
for(i=34; i<n && !fsl_after_blank_line((char const *)(z+i)); i++){}
if( i>=n ) return;
z += i;
n -= i;
*pz = z;
for(i=n-1; i>=0; i--){
if( z[i]=='\n' && memcmp(&z[i],"\n-----BEGIN PGP SIGNATURE-", 25)==0 ){
n = i+1;
break;
}
}
*pn = (fsl_size_t)n;
return;
}
/**
Internal helper for parsing manifests. Holds a source file (memory
range) and gets updated by fsl_mf_next_token() and friends.
*/
struct fsl_src {
/**
First char of the next token.
*/
unsigned char * z;
/**
One-past-the-end of the manifest.
*/
unsigned char * zEnd;
/**
True if z points to the start of a new line.
*/
char atEol;
};
typedef struct fsl_src fsl_src;
static const fsl_src fsl_src_empty = {NULL,NULL,0};
/**
Return a pointer to the next token. The token is zero-terminated.
Return NULL if there are no more tokens on the current line. If
pLen is not NULL and this function returns non-NULL then *pLen is
set to the byte length of the new token.
*/
static unsigned char *fsl_mf_next_token(fsl_src *p, fsl_size_t *pLen){
unsigned char *z;
unsigned char *zStart;
int c;
if( p->atEol ) return NULL;
zStart = z = p->z;
while( (c=(*z))!=' ' && c!='\n' ){ ++z; }
*z = 0;
p->z = &z[1];
p->atEol = c=='\n';
if( pLen ) *pLen = z - zStart;
return zStart;
}
/**
Return the card-type for the next card. Return 0 if there are no
more cards or if we are not at the end of the current card.
*/
static unsigned char mf_next_card(fsl_src *p){
unsigned char c;
if( !p->atEol || p->z>=p->zEnd ) return 0;
c = p->z[0];
if( p->z[1]==' ' ){
p->z += 2;
p->atEol = 0;
}else if( p->z[1]=='\n' ){
p->z += 2;
p->atEol = 1;
}else{
c = 0;
}
return c;
}
/**
Internal helper for fsl_deck_parse(). Expects l to be an array of
26 entries, representing the letters of the alphabet (A-Z), with a
value of 0 if the card was not seen during parsing and a value >0
if it was. Returns the deduced artifact type. Returns
FSL_CATYPE_ANY if the result is ambiguous.
It should guess right for any legal manifests, but it does not go
out of its way to detect incomplete/invalid ones.
*/
static fsl_catype_t fsl_mf_guess_type( int * l ){
#if 0
int i;
assert(!l[26]);
MARKER(("Cards seen during parse:\n"));
for( i = 0; i < 26; ++i ){
if(l[i]) putchar('A'+i);
}
putchar('\n');
#endif
/*
Now look for combinations of cards which will uniquely
identify any syntactical legal combination of cards.
A larger brain than mine could probably come up with a hash of
l[] which could determine this in O(1). But please don't
reimplement this as such unless mere mortals can maintain it -
any performance gain is insignificant in the context of the
underlying SCM/db operations.
*/
#define L(X) l[X-'A']
if(L('M')) return FSL_CATYPE_CLUSTER;
else if(L('E')) return FSL_CATYPE_EVENT;
else if(L('L') || L('W')) return FSL_CATYPE_WIKI;
else if(L('J') || L('K')) return FSL_CATYPE_TICKET;
else if(L('A')) return FSL_CATYPE_ATTACHMENT;
else if(L('B') || L('F') || L('P') || L('Q') || L('R')) return FSL_CATYPE_CHECKIN;
else if(L('D') && L('T') && L('U')) return FSL_CATYPE_CONTROL;
#undef L
return FSL_CATYPE_ANY;
}
int fsl_deck_parse2(fsl_deck * d, fsl_buffer * src, fsl_id_t rid){
#ifdef ERROR
#undef ERROR
#endif
#define ERROR(RC,MSG) do{ rc = (RC); zMsg = (MSG); goto bailout; } while(0)
#define SYNTAX(MSG) ERROR(rc ? rc : FSL_RC_CA_SYNTAX,MSG)
char isRepeat = 0/* , hasSelfRefTag = 0 */;
int rc = 0;
fsl_src x = fsl_src_empty;
char const * zMsg = NULL;
fsl_id_bag * seen;
char cType = 0, cPrevType = 0;
unsigned char * z = src ? src->mem : NULL;
fsl_size_t tokLen = 0;
unsigned char * token;
fsl_size_t n = z ? src->used : 0;
unsigned char * uuid;
fsl_double_t ts;
int cardCount = 0;
fsl_buffer selfHash = fsl_buffer_empty;
fsl_db * db;
fsl_cx * f;
fsl_error * err;
int stealBuf = 0 /* gets incremented if we need to steal src->mem. */;
/*
lettersSeen keeps track of the card letters we have seen so that
we can then relatively quickly figure out what type of manifest we
have parsed without having to inspect the card contents. We use
an int (instead of char) so we can keep track of how many
of each card we've seen.
*/
int lettersSeen[27] = {0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0};
if(!d || !z) return FSL_RC_MISUSE;
/* Every control artifact ends with a '\n' character. Exit early
if that is not the case for this artifact.
*/
f = d->f;
err = f ? &f->error : &d->error;
if(!*z || !n || ( '\n' != z[n-1]) ){
return fsl_error_set(err, FSL_RC_CA_SYNTAX, "%s.",
n ? "Not terminated with \\n"
: "Zero-length input");
}
else if(rid<0){
return fsl_error_set(err, FSL_RC_RANGE,
"Invalid (negative) RID %"FSL_ID_T_PFMT
" for fsl_deck_parse()", (fsl_id_t)rid);
}
/*
Reminder: i'm leaving out v1's manifest cache for the time
being. We'll probably need/want it when rebuild is implemented.
*/
db = f ? fsl_cx_db_repo(f) : NULL
/* We originally needed the db for converting date strings to
doubles for julian (we don't anymore). Since we have it...
we also use it to populate d->B.uuid and d->rid.
*/;
seen = f ? &f->cache.mfSeen : NULL;
if(seen){
if((0==rid) || fsl_id_bag_contains(seen,rid)){
isRepeat = 1;
}else{
isRepeat = 0;
rc = fsl_id_bag_insert(seen, rid);
if(rc){
assert(FSL_RC_OOM==rc);
return rc;
}
}
}
rc = fsl_sha1sum_buffer(src, &selfHash);
if(rc) return rc;
/* legacy: not yet clear if we need this:
if( !isRepeat ) g.parseCnt[0]++;
*/
/*
Strip off the PGP signature if there is one.
Reminder to self: example of signed manifest:
http://fossil-scm.org/index.html/artifact/28987096ac
*/
{
unsigned char const * zz = z;
fsl_remove_pgp_signature(&zz, &n);
z = (unsigned char *)zz;
}
/*
Verify that the first few characters of the artifact look like a
control artifact.
*/
if( n<10 || z[0]<'A' || z[0]>'Z' || z[1]!=' ' ){
ERROR(FSL_RC_CA_SYNTAX, "Content does not look like "
"a control artifact");
}
/* Verify the Z card */
if( fsl_mf_verify_Z_card(z, n) < 0 ){
ERROR(FSL_RC_CONSISTENCY, "Z-card checksum mismatch");
}
/*
Reminder: parsing modifies the input (to simplify the
tokenization/parsing).
A side-effect of our OO API is that we potentially end up
strdup'ing much of this memory into d->CARD. What we could do (and
what i think v1 does?) is instead bypass the OO bits and assign
all the pointers back into this modified copy, which we would then
give over to the manifest (and save tons of mallocs). IFF manifest
parsing becomes prohibitively memory expensive, that would be the
first big optimization to make, but it _would_ have side-effects
and change the way a manifest and its constituent parts are
cleaned up. We might want to make separate input and output
manifest classes, with the OO API for output manifests (created by
code) and input manifests optimized for read-only use and not
duplicating the strings we currently dupe here. If we do that, we
need to change this function to take over ownership of the input
blob's contents.
As of mid-201403, we recycle as much as possible from the source
buffer and take over ownership _if_ we do so.
*/
fsl_deck_clean(d);
fsl_deck_init(f, d, FSL_CATYPE_ANY);
/* Now parse, card by card... */
x.z = z;
x.zEnd = z+n;
x.atEol= 1;
/* Parsing helpers... */
#define TOKEN(DEFOS) tokLen=0; token = fsl_mf_next_token(&x,&tokLen); \
if(token && tokLen && (DEFOS)) fsl_bytes_defossilize(token, &tokLen)
#define TOKEN_EXISTS(MSG_IF_NOT) if(!token){ SYNTAX(MSG_IF_NOT); }(void)0
#define TOKEN_CHECKHEX(LEN,MSG) if(token \
&& ((LEN)!=tokLen || \
!fsl_validate16((char const *)token,(LEN)))){ \
SYNTAX(MSG); }
#define TOKEN_UUID(CARD) TOKEN_CHECKHEX(FSL_UUID_STRLEN,"Malformed UUID in " #CARD "-card")
/**
Reminder: we do not know the type of the manifest at this point,
so all of the fsl_deck_add/set() bits below can't do their
validation. We have to determine at parse-time (or afterwards)
which type of deck it is based on the cards we've seen. We guess
the type as early as possible to enable during-parse validation,
and do a post-parse check for the legality of cards added before
validation became possible.
*/
#define SEEN(CARD) lettersSeen[*#CARD - 'A']
for( cPrevType=1; !rc && (0 < (cType = mf_next_card(&x)));
cPrevType = cType, ++cardCount ){
if(cType<cPrevType){
SYNTAX("Cards are not in strict lexical order");
}
assert(cType>='A' && cType<='Z');
if(cType>='A' && cType<='Z'){
++lettersSeen[cType-'A'];
}else{
SYNTAX("Invalid card name");
}
if(FSL_CATYPE_ANY==d->type){
/* See if we can guess the type now. */
d->type = fsl_mf_guess_type(lettersSeen);
if(FSL_CATYPE_ANY!=d->type){
assert(FSL_CATYPE_INVALID!=d->type);
#if 0
MARKER(("Guessed manifest type on try #%d: %s\n",
cardCount+1, fsl_catype_cstr(d->type)));
#endif
}
}else{
/* If we know the deck type, make sure this card fits. */
if( !fsl_deck_check_type(d, cType) ){
rc = FSL_RC_TYPE;
goto bailout;
}
}
switch(cType){
/*
A <filename> <target> ?<source>?
Identifies an attachment to either a wiki page or a ticket.
<source> is the artifact that is the attachment. <source>
is omitted to delete an attachment. <target> is the name of
a wiki page or ticket to which that attachment is connected.
*/
case 'A':{
unsigned char * name, * src;
if(1<SEEN(A)){
ERROR(FSL_RC_RANGE,"Multiple A-cards");
}
TOKEN(1);
TOKEN_EXISTS("Missing filename for A-card");
name = token;
if(!fsl_is_simple_pathname( (char const *)name, 0 )){
SYNTAX("Invalid filename in A-card");
}
TOKEN(1);
TOKEN_EXISTS("Missing target name in A-card");
uuid = token;
TOKEN(0);
TOKEN_UUID(A);
src = token;
d->A.name = (char *)name;
d->A.tgt = (char *)uuid;
d->A.src = (char *)src;
++stealBuf;
/*rc = fsl_deck_A_set(d, (char const *)name,
(char const *)uuid, (char const *)src);*/
break;
}
/*
B <uuid>
A B-line gives the UUID for the baseline of a delta-manifest.
*/
case 'B':{
if(d->B.uuid){
SYNTAX("Multiple B-cards");
}
TOKEN(0);
TOKEN_UUID(B);
d->B.uuid = (char *)token;
++stealBuf;
/* rc = fsl_deck_B_set(d, (char const *)token); */
break;
}
/*
C <comment>
Comment text is fossil-encoded. There may be no more than
one C line. C lines are required for manifests, are optional
for Events and Attachments, and are disallowed on all other
control files.
*/
case 'C':{
if( d->C ){
SYNTAX("more than one C-card");
}
TOKEN(1);
TOKEN_EXISTS("Missing comment text for C-card");
/* rc = fsl_deck_C_set(d, (char const *)token, (fsl_int_t)tokLen); */
d->C = (char *)token;
++stealBuf;
break;
}
/*
D <timestamp>
The timestamp should be ISO 8601. YYYY-MM-DDtHH:MM:SS
There can be no more than 1 D line. D lines are required
for all control files except for clusters.
*/
case 'D':{
#define TOKEN_DATETIME(LETTER,MEMBER) \
if( d->MEMBER>0.0 ) { SYNTAX("More than one "#LETTER"-card"); } \
TOKEN(0); \
TOKEN_EXISTS("Missing date part of "#LETTER"-card"); \
if(!fsl_str_is_date((char const *)token)){\
SYNTAX("Malformed date part of "#LETTER"-card"); \
} \
if(!fsl_iso8601_to_julian((char const *)token, &ts)){ \
SYNTAX("Cannot parse date from "#LETTER"-card"); \
} (void)0
TOKEN_DATETIME(D,D);
rc = fsl_deck_D_set(d, ts);
break;
}
/*
E <timestamp> <uuid>
An "event" card that contains the timestamp of the event in the
format YYYY-MM-DDtHH:MM:SS and a unique identifier for the event.
The event timestamp is distinct from the D timestamp. The D
timestamp is when the artifact was created whereas the E timestamp
is when the specific event is said to occur.
*/
case 'E':{
TOKEN_DATETIME(E,E.julian);
TOKEN(0);
TOKEN_EXISTS("Missing UUID part of E-card");
TOKEN_UUID(E);
d->E.julian = ts;
d->E.uuid = (char *)token;
++stealBuf;
/* rc = fsl_deck_E_set(d, ts, (char const *)token); */
break;
}
/*
F <filename> ?<uuid>? ?<permissions>? ?<old-name>?
Identifies a file in a manifest. Multiple F lines are
allowed in a manifest. F lines are not allowed in any other
control file. The filename and old-name are fossil-encoded.
In delta manifests, deleted files are denoted by the 1-arg
form. In baseline manifests, deleted files simply are not in
the manifest.
*/
case 'F':{
char * name;
char * perms = NULL;
char * oldName = NULL;
fsl_file_perm_t perm = FSL_FILE_PERM_REGULAR;
fsl_card_F * fc = NULL
/* As a malloc() optimization, rather than add these
F-cards using fsl_deck_F_add(), we will apply the
fsl_card_F::externalStrings optimization, pointing
the various fc->xxx string members back to the
original tokens rather than strdup()'ing them.
*/;
TOKEN(0);
TOKEN_EXISTS("Missing name for F-card");
name = (char *)token;
TOKEN(0);
TOKEN_UUID(F);
uuid = token;
TOKEN(0);
if(token){
perms = (char *)token;
switch(*perms){
case 'w': perm = FSL_FILE_PERM_REGULAR; break;
case 'x': perm = FSL_FILE_PERM_EXE; break;
case 'l': perm = FSL_FILE_PERM_LINK; break;
default:
assert(!"Unmatched perms string character!");
ERROR(FSL_RC_ERROR,"Internal error: unmatched perms string character");
}
TOKEN(0);
if(token) oldName = (char *)token;
}
fc = fsl_malloc( sizeof(fsl_card_F) );
if(!fc){
rc = FSL_RC_OOM;
zMsg = "OOM";
goto bailout;
}
++stealBuf;
*fc = fsl_card_F_empty;
fc->externalStrings = 1;
fc->name = name;
fc->priorName = oldName;
fc->uuid = (char *)uuid;
fc->perm = perm;
rc = fsl_list_append(&d->F.list, fc);
if(rc) fsl_card_F_free(fc);
break;
}
/*
J <name> ?<value>?
Specifies a name value pair for ticket. If the first character
of <name> is "+" then the <value> is appended to any preexisting
value. If <value> is omitted then it is understood to be an
empty string.
*/
case 'J':{
char const * field;
char isAppend = 0;
TOKEN(1);
TOKEN_EXISTS("Missing field name for J-card");
field = (char const *)token;
if('+'==*field){
isAppend = 1;
++field;
}
TOKEN(1);
rc = fsl_deck_J_add(d, isAppend, field,
(char const *)token);
break;
}
/*
K <uuid>
A K-line gives the UUID for the ticket which this control file
is amending.
*/
case 'K':{
if(d->K){
SYNTAX("Multiple K-cards");
}
TOKEN(0);
TOKEN_EXISTS("Missing UUID in K-card");
TOKEN_UUID(K);
d->K = (char*)token;
++stealBuf;
/* rc = fsl_deck_K_set(d, (char const *)token); */
break;
}
/*
L <wikititle>
The wiki page title is fossil-encoded. There may be no more than
one L line.
*/
case 'L':{
if(d->L){
SYNTAX("Multiple L-cards");
}
TOKEN(1);
TOKEN_EXISTS("Missing text for L-card");
d->L = (char*)token;
++stealBuf;
/* rc = fsl_deck_L_set(d, (char const *)token, (fsl_int_t)tokLen); */
break;
}
/*
M <uuid>
An M-line identifies another artifact by its UUID. M-lines
occur in clusters only.
*/
case 'M':{
TOKEN(0);
TOKEN_EXISTS("Missing UUID for M-card");
TOKEN_UUID(M);
++stealBuf;
rc = fsl_list_append(&d->M, token);
break;
}
/*
N <uuid>
An N-line identifies the mimetype of wiki or comment text.
*/
case 'N':{
if(1<SEEN(N)){
ERROR(FSL_RC_RANGE,"Multiple N-cards");
}
TOKEN(0);
TOKEN_EXISTS("Missing UUID on N-card");
++stealBuf;
d->N = (char *)token;
/* rc = fsl_deck_N_set(d, (char const *)token, (fsl_int_t)tokLen); */
break;
}
/*
P <uuid> ...
Specify one or more other artifacts which are the parents of
this artifact. The first parent is the primary parent. All
others are parents by merge.
*/
case 'P':{
if(1<SEEN(P)){
ERROR(FSL_RC_RANGE,"More than one P-card");
}
TOKEN(0);
#if 0
/* The docs all claim that this card does not exist on the first
manifest, but in fact it does exist but has no UUID,
which is invalid per all the P-card docs. Skip this
check (A) for the sake of manifest #1 and (B) because
fossil(1) does it this way.
*/
TOKEN_EXISTS("Missing primary parent UUID for P-card");
#endif
while( token && !rc ){
TOKEN_UUID(P);
/* rc = fsl_deck_P_add(d, (char const *)token); */
++stealBuf;
rc = fsl_list_append(&d->P, token);
if(!rc){
TOKEN(0);
}
}
break;
}
/*
Q (+|-)<uuid> ?<uuid>?
Specify one or a range of checkins that are cherrypicked into
this checkin ("+") or backed out of this checkin ("-").
*/
case 'Q':{
char prefix;
TOKEN(0);
TOKEN_EXISTS("Missing target UUID for Q-card");
prefix = (char)*token;
if('-'!=prefix && '+'!=prefix){
SYNTAX("Malformed target UUID in Q-card");
}
uuid = ++token; --tokLen;
TOKEN_UUID(Q);
TOKEN(0);
if(token){
TOKEN_UUID(Q);
}
rc = fsl_deck_Q_add(d, prefix, (char const *)uuid,
(char const *)token);
break;
}
/*
R <md5sum>
Specify the MD5 checksum over the name and content of all files
in the manifest.
*/
case 'R':{
if(1<SEEN(R)){
ERROR(FSL_RC_RANGE,"More than one R-card");
}
TOKEN(0);
TOKEN_EXISTS("Missing MD5 token in R-card");
TOKEN_CHECKHEX(FSL_MD5_STRLEN,"Malformed MD5 token in R-card");
d->R = (char *)token;
++stealBuf;
/* rc = fsl_deck_R_set(d, (char const *)token); */
break;
}
/*
T (+|*|-)<tagname> <uuid> ?<value>?
Create or cancel a tag or property. The tagname is fossil-encoded.
The first character of the name must be either "+" to create a
singleton tag, "*" to create a propagating tag, or "-" to create
anti-tag that undoes a prior "+" or blocks propagation of of
a "*".
The tag is applied to <uuid>. If <uuid> is "*" then the tag is
applied to the current manifest. If <value> is provided then
the tag is really a property with the given value.
Tags are not allowed in clusters. Multiple T lines are allowed.
*/
case 'T':{
unsigned char * name, * value;
fsl_tag_type tagType = FSL_TAGTYPE_INVALID;
TOKEN(0);
TOKEN_EXISTS("Missing name for T-card");
name = token;
TOKEN(0);
TOKEN_EXISTS("Missing UUID on T-card");
if(FSL_UUID_STRLEN==tokLen){
TOKEN_UUID(T);
/* A valid UUID */
if(FSL_CATYPE_EVENT==d->type){
SYNTAX("Non-self-referential T-card in Event artifact");
}
uuid = token;
}else if( 1==tokLen && '*'==(char)*token ){
/* hasSelfRefTag = 1; */
/* tag for the current artifact */
if((FSL_CATYPE_EVENT==d->type) &&
('+'!=(char)*name)){
SYNTAX("Non-+ T-card in event");
}
uuid = NULL;
}else{
SYNTAX("Malformed UUID in T-card");
}
TOKEN(1);
value = token;
switch(*name){
case '*': tagType = FSL_TAGTYPE_PROPAGATING; break;
case '+': tagType = FSL_TAGTYPE_ADD; break;
case '-': tagType = FSL_TAGTYPE_CANCEL; break;
default: SYNTAX("Malformed tag name");
}
++name /* skip type marker byte */;
/* Potential todo: add the order check from this commit:
http://fossil-scm.org/index.html/info/55cacfcace
*/
rc = fsl_deck_T_add(d, tagType, (fsl_uuid_cstr)uuid,
(char const *)name,
(char const *)value);
break;
}
/*
U ?<login>?
Identify the user who created this control file by their
login. Only one U line is allowed. Prohibited in clusters.
If the user name is omitted, take that to be "anonymous".
*/
case 'U':{
if(d->U) SYNTAX("More than one U-card");
TOKEN(1);
if(token){
/* rc = fsl_deck_U_set( d, (char const *)token, (fsl_int_t)tokLen ); */
++stealBuf;
d->U = (char *)token;
}else{
rc = fsl_deck_U_set( d, "anonymous", 9);
}
break;
}
/*
W <size>
The next <size> bytes of the file contain the text of the wiki
page. There is always an extra \n before the start of the next
record.
*/
case 'W':{
fsl_size_t wlen;
if(d->W.used){
SYNTAX("More than one W-card");
}
TOKEN(0);
TOKEN_EXISTS("Missing size token for W-card");
wlen = fsl_str_to_size((char const *)token);
if((fsl_size_t)-1==wlen){
ERROR(FSL_RC_RANGE,"Wiki size token is invalid");
}
if( (&x.z[wlen+1]) > x.zEnd){
SYNTAX("Not enough content after W-card");
}
rc = fsl_buffer_append(&d->W, x.z, wlen);
if(rc) goto bailout;
x.z += wlen;
if( '\n' != x.z[0] ){
SYNTAX("W-card content not \\n terminated");
}
x.z[0] = 0;
++x.z;
break;
}
/*
Z <md5sum>
MD5 checksum on this control file. The checksum is over all
lines (other than PGP-signature lines) prior to the current
line. This must be the last record.
This card is required for all control file types except for
Manifest. It is not required for manifest only for historical
compatibility reasons.
*/
case 'Z':{
/* We validated the Z card first. We cannot compare against
the original blob now because we've modified it.
*/
goto end;
}
default:
rc = fsl_cx_err_set(f, FSL_RC_CA_SYNTAX,
"Unknown card '%c' in manifest",
cType);
goto bailout;
}/*switch(cType)*/
if(rc) goto bailout;
}/* for-each-card */
#if 1
/* Remove these when we are done porting
resp. we can avoid these unused-var warnings. */
if(isRepeat){}
#endif
end:
if(FSL_CATYPE_ANY==d->type){
rc = fsl_cx_err_set(f, FSL_RC_ERROR,
"Internal error: could not determine type of "
"control artifact we just (successfully!) "
"parsed.");
goto bailout;
}else {
/*
Make sure we didn't pick up any cards which were picked up
before d->type was guessed and are invalid for the post-guessed
type.
*/
int i = 0;
for( ; i < 27; ++i ){
if((lettersSeen[i]>0) && !fsl_card_is_legal(d->type, 'A'+i )){
rc = fsl_cx_err_set(f, FSL_RC_CA_SYNTAX,
"Determined during post-parse processing that "
"the parsed deck (type %s) contains an illegal "
"card type (%c).", fsl_catype_cstr(d->type),
'A'+i);
goto bailout;
}
}
}
assert(FSL_CATYPE_CHECKIN==d->type ||
FSL_CATYPE_CLUSTER==d->type ||
FSL_CATYPE_CONTROL==d->type ||
FSL_CATYPE_WIKI==d->type ||
FSL_CATYPE_TICKET==d->type ||
FSL_CATYPE_ATTACHMENT==d->type ||
FSL_CATYPE_EVENT==d->type);
assert(0==rc);
if(db){
d->rid = fsl_uuid_to_rid(f, fsl_buffer_cstr(&selfHash));
if(d->rid<0){
assert(f->error.code);
goto bailout;
}
/*
We don't care (at this point in time) if it's not found
(0==d->rid)
*/
}
d->uuid = fsl_buffer_str(&selfHash) /* transfer ownership */;
assert(!d->content.mem);
if(stealBuf>0){
/* We stashed something which points to src->mem, so we need to
steal that memory.
*/
d->content = *src;
*src = fsl_buffer_empty;
}
return 0;
bailout:
assert(0 != rc);
fsl_buffer_clear(&selfHash);
if(zMsg){
fsl_error_set(err, rc, "%s", zMsg);
}else if(f){
if(!f->error.code){
if(d->error.code){
fsl_error_move( &d->error, &f->error );
}else if(db && db->error.code){
/* This is "essentially impossible". */
fsl_cx_uplift_db_error(f, db);
}
}
}
return rc;
#undef SEEN
#undef TOKEN_DATETIME
#undef SYNTAX
#undef TOKEN_CHECKHEX
#undef TOKEN_EXISTS
#undef TOKEN_UUID
#undef TOKEN
#undef ERROR
}
int fsl_deck_parse(fsl_deck * d, fsl_buffer * src){
return fsl_deck_parse2(d, src, 0);
}
int fsl_deck_load_rid( fsl_cx * f, fsl_deck * d,
fsl_id_t rid, fsl_catype_t type ){
fsl_buffer buf = fsl_buffer_empty;
int rc;
if(!f || !d) return FSL_RC_CA_SYNTAX;
if(0==rid) rid = f->ckout.rid;
if(rid<0){
return fsl_cx_err_set(f, FSL_RC_RANGE,
"Invalid RID for fsl_deck_load_rid(): "
"%"FSL_ID_T_PFMT, (fsl_id_t)rid);
}
rc = fsl_content_get(f, rid, &buf);
if(!rc){
fsl_deck_clean(d);
fsl_deck_init(f, d, FSL_CATYPE_ANY);
#if 0
/*
If we set d->type=type, the parser can fail more
quickly. However, that failure will bypass our more specific
reporting of the problem (see below). As the type mismatch case
is expected to be fairly rare, we'll leave this out for now, but
it might be worth considering as a small optimization later on.
*/
d->type = type /* may help parsing fail more quickly if
it's not the type we want.*/;
#endif
rc = fsl_deck_parse(d, &buf);
if(!rc){
assert(rid == d->rid);
if( type!=FSL_CATYPE_ANY && d->type!=type ){
rc = fsl_cx_err_set(f, FSL_RC_TYPE,
"RID %"FSL_ID_T_PFMT" is of type %s, "
"but the caller requested type %s.",
(fsl_id_t)rid,
fsl_catype_cstr(d->type),
fsl_catype_cstr(type));
}
}
}
fsl_buffer_clear(&buf);
return rc;
}
int fsl_deck_load_sym( fsl_cx * f, fsl_deck * d,
char const * symbolicName, fsl_catype_t type ){
if(!f || !symbolicName || !d) return FSL_RC_MISUSE;
else{
fsl_id_t vid = 0;
int rc = fsl_sym_to_rid(f, symbolicName, type, &vid);
if(!rc){
assert(vid>0);
rc = fsl_deck_load_rid(f, d, vid, type);
}
return rc;
}
}
static int fsl_deck_baseline_load( fsl_deck * d ){
int rc = 0;
fsl_deck bl = fsl_deck_empty;
fsl_id_t rid;
fsl_cx * f = d ? d->f : NULL;
fsl_db * db = f ? fsl_needs_repo(f) : NULL;
assert(d->f);
assert(d);
if(!d || !d->f) return FSL_RC_MISUSE;
else if(d->B.baseline || !d->B.uuid) return 0 /* nothing to do! */;
else if(!db) return FSL_RC_NOT_A_REPO;
#if 0
else if(d->rid<=0){
return fsl_cx_err_set(f, FSL_RC_RANGE,
"fsl_deck_baseline_load(): "
"fsl_deck::rid is not set.");
}
#endif
rid = fsl_uuid_to_rid(f, d->B.uuid);
if(rid<0){
assert(f->error.code);
return f->error.code;
}
else if(!rid){
if(d->rid>0){
fsl_db_exec(db,
"INSERT OR IGNORE INTO orphan(rid, baseline) "
"VALUES(%"FSL_ID_T_PFMT",%"FSL_ID_T_PFMT")",
(fsl_id_t)d->rid, (fsl_id_t)rid);
}
rc = fsl_cx_err_set(f, FSL_RC_RANGE,
"Could not find/load baseline manifest [%s], "
"parent of manifest rid #%"FSL_ID_T_PFMT".",
d->B.uuid, (fsl_id_t)d->rid);
}else{
rc = fsl_deck_load_rid(f, &bl, rid, FSL_CATYPE_CHECKIN);
if(!rc){
d->B.baseline = fsl_deck_malloc();
if(!d->B.baseline){
fsl_deck_clean(&bl);
rc = FSL_RC_OOM;
}else{
void const * allocStampKludge = d->B.baseline->allocStamp;
*d->B.baseline = bl /* Transfer ownership */;
d->B.baseline->allocStamp = allocStampKludge /* But we need this intact
for deallocation to work */;
assert(f==d->B.baseline->f);
}
}else{
/* bl might be partially populated */
fsl_deck_finalize(&bl);
}
}
return rc;
}
int fsl_deck_baseline_fetch( fsl_deck * d ){
return (d && (d->B.baseline || !d->B.uuid))
? 0
: ((d && d->f)
? fsl_deck_baseline_load(d)
: FSL_RC_MISUSE);
}
int fsl_deck_F_rewind( fsl_deck * d ){
int rc = 0;
assert(d->f);
d->F.cursor = 0;
assert(d->f);
if(!rc && d->B.uuid){
rc = fsl_deck_baseline_fetch(d);
if(!rc){
assert(d->B.baseline);
d = d->B.baseline;
d->F.cursor = 0;
}
}
return rc;
}
int fsl_deck_F_next( fsl_deck * d, fsl_card_F const ** rv ){
assert(d);
assert(d->f);
assert(rv);
#define FCARD(DECK,NDX) ((fsl_card_F const *)(DECK)->F.list.list[NDX])
*rv = NULL;
if(!d->B.baseline){
/* Manifest p is a baseline-manifest. Just scan down the list
of files. */
if(d->B.uuid){
return fsl_cx_err_set(d->f, FSL_RC_MISUSE,
"Deck has a UUID (%.*s) but no baseline "
"loaded. Load the baseline before calling "
"fsl_deck_F_next().",
12, d->B.uuid)
/* We "could" just load the baseline from here. */;
}
if( d->F.cursor < (fsl_int_t)d->F.list.used ){
*rv = FCARD(d, d->F.cursor++);
assert(*rv);
assert((*rv)->uuid && "Baseline manifest has deleted F-card entry!");
}
return 0;
}else{
/* Manifest p is a delta-manifest. Scan the baseline but amend the
file list in the baseline with changes described by p.
*/
fsl_deck *pB = d->B.baseline;
int cmp;
while(1){
if( pB->F.cursor >= (fsl_int_t)pB->F.list.used ){
/* We have used all entries out of the baseline. Return the next
entry from the delta. */
if( d->F.cursor < (fsl_int_t)d->F.list.used ) *rv = FCARD(d, d->F.cursor++);
break;
}else if( d->F.cursor >= (fsl_int_t)d->F.list.used ){
/* We have used all entries from the delta. Return the next
entry from the baseline. */
if( pB->F.cursor < (fsl_int_t)pB->F.list.used ) *rv = FCARD(pB, pB->F.cursor++);
break;
}else if( (cmp = fsl_strcmp(FCARD(pB,pB->F.cursor)->name,
FCARD(d, d->F.cursor)->name)) < 0){
/* The next baseline entry comes before the next delta entry.
So return the baseline entry. */
*rv = FCARD(pB, pB->F.cursor++);
break;
}else if( cmp>0 ){
/* The next delta entry comes before the next baseline
entry so return the delta entry */
*rv = FCARD(d, d->F.cursor++);
break;
}else if( FCARD(d, d->F.cursor)->uuid ){
/* The next delta entry is a replacement for the next baseline
entry. Skip the baseline entry and return the delta entry */
pB->F.cursor++;
*rv = FCARD(d, d->F.cursor++);
break;
}else{
assert(0==cmp);
/*
The next delta entry is a delete of the next baseline entry.
*/
#if 1
/*
20140324: KEEP THIS #if BLOCK for a while in case the #else
impl bites us! This is a fundamental change to a core
moving part of the Manifest machine, and changing it (as is
done in the #else block) may... have unforeseen
side-effects.
*/
/* Skip them both. Repeat the loop to find the next
non-delete entry. */
pB->F.cursor++;
d->F.cursor++;
continue;
#else
/*
Historically, fossil(1) elides all deleted entries in F-card
traversal. libfossil changed that to report them, initially
due to a misunderstanding in how deletions are recorded in
baseline manifests[1], but also because it is "sometimes"
interesting to know about them during card traversal.
Probably not as often as initially thought, but there we
are. Eliding them altogether just seems kinda wrong.
However, _almost_ all code which uses this routine skips
over them.
But... the other bits in this while loop will happily return
deleted entries, so why shouldn't we?
[1] = i _thought_, due to a mis-interpretation of the Fossil
File Format docs, that baselines recorded deletions the same
way as deltas, but they don't (they simply do not include an
F-card for deleted entries). Thus reporting of deleted
entries leads to an unfortunate inconsistency in that we do
_not_ report them a baseline, and cannot without also
traversing its parent version. It seems that fossil(1) also
internally has a minor incosistency, in that as long as a
repo has no delta manifests (fossil(1) still has none as of
20140324), the repo actually has no way of knowing if a
given blob was ever "rm'd" from the repo. It can only know
that once deltas appear. Hmm. That's never come up before.
*/
pB->F.cursor++;
*rv = FCARD(d, d->F.cursor++);
break;
#endif
}
}
return 0;
}
#undef FCARD
}
int fsl_deck_save( fsl_deck * d, char isPrivate ){
int rc;
fsl_cx * f = d ? d->f : NULL;
fsl_db * db = f ? fsl_needs_repo(f) : NULL;
fsl_buffer buf = fsl_buffer_empty;
fsl_id_t newRid = 0;
char const oldPrivate = f ? f->cache.markPrivate : 0;
char const isNew = d && !d->uuid;
if(!f || !d ) return FSL_RC_MISUSE;
else if(!db) return FSL_RC_NOT_A_REPO;
else if( (d->rid>0 && !d->uuid) ||
(d->rid<=0 && d->uuid)){
return fsl_cx_err_set(f, FSL_RC_MISUSE,
"The input deck must have either rid _and_ UUID "
"or neither of rid/UUID. A mixture is ambiguous.");
}
rc = fsl_deck_unshuffle(d,
(FSL_CX_F_CALC_R_CARD & f->flags)
? ((d->F.list.used && !d->R) ? 1 : 0)
: 0);
if(rc) return rc;
rc = fsl_deck_output(d, fsl_output_f_buffer, &buf, &f->error);
if(rc) return rc;
rc = fsl_db_transaction_begin(db);
if(rc){
fsl_buffer_clear(&buf);
return rc;
}
/* Starting here, don't return, use (goto end) instead. */
f->cache.markPrivate = isPrivate;
rc = fsl_content_put_ex(f, &buf, d->uuid, 0,
0U, isPrivate, &newRid);
if(rc) goto end;
assert(newRid>0);
rc = fsl_sha1sum_buffer( &buf, &buf );
if(rc) goto end;
/* We need d->uuid and d->rid for crosslinking purposes, but will
unset them on error (if we set them) because their values will no
longer be in the db after rollback...
*/
#if 0
/* practice shows that this is not needed/desired. There are cases
where round-tripping a manifest results in a 1-millisecond
difference in the D-card (Julian Day/double), which changes the
has but otherwise has no difference on the meaning. That said,
the way we use decks means that we really don't care (for
purposes of this function) what their initial UUID is except for
the purpose of fsl_content_put_ex(), above.
*/
if(d->uuid){
/* Compare original and resulting hashes. */
if(0 != fsl_uuidcmp(fsl_buffer_cstr(&buf), d->uuid)){
rc = fsl_cx_err_set(f, FSL_RC_CONSISTENCY,
"Input deck's original UUID and resulting UUID "
"do not match: [%s] vs [%b]",
d->uuid, &buf);
goto end;
}
/* ??? assert(d->rid==newRid); */
d->rid = newRid;
}else
#endif
{
fsl_free(d->uuid);
d->uuid = fsl_buffer_str(&buf) /* transfer ownership */;
buf = fsl_buffer_empty;
d->rid = newRid;
}
#if 0
/* Something to consider: if d is new and has a parent, deltify the
parent. The branch operation does this, but it is not yet clear
whether that is a general pattern for manifests.
*/
if(isNew && d->P.used){
fsl_id_t pid;
assert(FSL_CATYPE_CHECKIN == d->type);
pid = fsl_uuid_to_rid(f, (char const *)d->P.list[0]);
if(pid>0){
rc = fsl_content_deltify(f, pid, d->rid, 0);
if(rc) goto end;
}
}
#endif
if(isNew && (FSL_CATYPE_WIKI==d->type)){
/* Analog to v1's wiki.c:wiki_put(): */
/*
MISSING:
v1's wiki.c:wiki_put() handles the moderation bits.
*/
if(d->P.used){
fsl_id_t pid = fsl_deck_P_get_id(d, 0);
assert(pid>0);
if(pid<0){
assert(f->error.code);
rc = f->error.code;
goto end;
}else if(!pid){
if(!f->error.code){
rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND,
"Did not find matching RID "
"for P-card[0] (%s).",
(char const *)d->P.list[0]);
}
goto end;
}
rc = fsl_content_deltify(f, pid, d->rid, 0);
if(rc) goto end;
}
rc = fsl_db_exec_multi(db,
"INSERT OR IGNORE INTO unsent "
"VALUES(%"FSL_ID_T_PFMT");"
"INSERT OR IGNORE INTO unclustered "
"VALUES(%"FSL_ID_T_PFMT");",
d->rid, d->rid);
if(rc){
fsl_cx_uplift_db_error(f, db);
goto end;
}
}
rc = fsl_deck_crosslink(d);
end:
f->cache.markPrivate = oldPrivate;
if(!rc) rc = fsl_db_transaction_end( db, 0);
else fsl_db_transaction_end(db, 1);
if(rc){
if(isNew){
fsl_free(d->uuid);
d->uuid = NULL;
d->rid = 0;
}
if(!f->error.code){
if(d->error.code){
rc = fsl_cx_err_set_e(f, &d->error);
}else if(db->error.code){
rc = fsl_cx_uplift_db_error(f, db);
}
}
}
fsl_buffer_clear(&buf);
return rc;
}
#undef MARKER
#undef AGE_FUDGE_WINDOW
#undef AGE_ADJUST_INCREMENT