/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* Copyright (c) 2014 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 contains routines related to working with the filesystem. */ #include "fossil-scm/fossil-internal.h" #include "fossil-scm/fossil-path.h" #include /* Only for debugging */ #include #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) static const fsl_path_node fsl_path_node_empty = {0,0,0,0,0,{0},0}; const fsl_path fsl_path_empty = fsl_path_empty_m; void fsl_path_clear(fsl_path *path){ fsl_path_node * p; while(path->pAll){ p = path->pAll; path->pAll = p->pAll; fsl_free(p); } fsl_id_bag_clear(&path->seen); *path = fsl_path_empty; } fsl_path_node * fsl_path_first(fsl_path *p){ return p->pStart; } fsl_path_node * fsl_path_last(fsl_path *p){ return p->pEnd; } int fsl_path_length(fsl_path const * p){ return p->nStep; } fsl_path_node * fsl_path_next(fsl_path_node *p){ return p->u.pTo; } fsl_path_node * fsl_path_midpoint(fsl_path * path){ if( path->nStep<2 ) return 0; else{ fsl_path_node *p; int i; int const max = path->nStep/2; for(p=path->pEnd, i=0; p && ipFrom, i++){} return p; } } /** Reconstructs path from path->pStart to path->pEnd, reversing its order by fiddling with the u->pTo fields. Unfortunately does not reverse after the initial creation/reversal :/. */ void fsl_path_reverse(fsl_path * path); void fsl_path_reverse(fsl_path * path){ fsl_path_node *p; assert( path->pEnd!=0 ); for(p=path->pEnd; p && p->pFrom; p = p->pFrom){ p->pFrom->u.pTo = p; } path->pEnd->u.pTo = 0; assert( p==path->pStart ); } /** Adds a new node to path and returns it. Returns 0 on allocation error. path must not be 0. rid must be greater than 0. pFrom may be 0. If pFrom is not 0 then isParent must be true if pFrom is a parent of rid. On success, sets the returned node as path->pCurrent, sets its pFrom to the given pFrom, and sets rc->u.pPeer to the prior path->pCurrent value. */ static fsl_path_node * fsl_path_new_node(fsl_path * path, fsl_id_t rid, fsl_path_node * pFrom, char isParent){ fsl_path_node * rc = 0; assert(path); assert(rid>0); if(0 != fsl_id_bag_insert(&path->seen, rid)) return 0; rc = (fsl_path_node*)fsl_malloc(sizeof(fsl_path_node)); if(!rc){ fsl_id_bag_remove(&path->seen, rid); return 0; } *rc = fsl_path_node_empty; rc->rid = rid; rc->fromIsParent = pFrom ? isParent : 0; rc->pFrom = pFrom; rc->u.pPeer = path->pCurrent; path->pCurrent = rc; rc->pAll = path->pAll; path->pAll = rc; return rc; } int fsl_path_shortest( fsl_cx * f, fsl_path * path, fsl_id_t iFrom, fsl_id_t iTo, char directOnly, char oneWayOnly ){ fsl_stmt s = fsl_stmt_empty; fsl_db * db = fsl_needs_repo(f); int rc = 0; fsl_path_node * pPrev; fsl_path_node * p; assert(db); if(!db) return FSL_RC_NOT_A_REPO; else if(iFrom<=0){ return fsl_cx_err_set(f, FSL_RC_RANGE, "Invalid 'from' RID: %d", (int)iFrom); }else if(iTo<=0){ /* Possible TODO: if iTo==0, use... what? Checkout? Tip of current checkout branch? Trunk? The multitude of options make it impossible to decide :/. */ return fsl_cx_err_set(f, FSL_RC_RANGE, "Invalid 'to' RID: %d", (int)iTo); } fsl_path_clear(path); path->pStart = fsl_path_new_node(path, iFrom, 0, 0); if(!path->pStart){ return fsl_cx_err_set(f, FSL_RC_OOM, 0); } if( iTo == iFrom ){ path->pEnd = path->pStart; return 0; } if( oneWayOnly ){ if(directOnly){ rc = fsl_db_prepare(db, &s, "SELECT cid, 1 FROM plink WHERE pid=?1 AND isprim"); }else{ rc = fsl_db_prepare(db, &s, "SELECT cid, 1 FROM plink WHERE pid=?1"); } }else if( directOnly ){ rc = fsl_db_prepare(db, &s, "SELECT cid, 1 FROM plink WHERE pid=?1 AND isprim " "UNION ALL " "SELECT pid, 0 FROM plink WHERE cid=?1 AND isprim"); }else{ rc = fsl_db_prepare(db, &s, "SELECT cid, 1 FROM plink WHERE pid=?1 " "UNION ALL " "SELECT pid, 0 FROM plink WHERE cid=?1"); } if(rc){ fsl_cx_uplift_db_error(f, db); assert(f->error.code); goto end; } while(path->pCurrent){ ++path->nStep; pPrev = path->pCurrent; path->pCurrent = 0; while( pPrev ){ rc = fsl_stmt_bind_id(&s, 1, pPrev->rid); assert(0==rc); while( FSL_RC_STEP_ROW == fsl_stmt_step(&s) ){ fsl_id_t const cid = fsl_stmt_g_id(&s, 0); int const isParent = fsl_stmt_g_int32(&s, 1); assert((cid>0) && "fsl_id_bag_find() asserts this."); if( fsl_id_bag_contains(&path->seen, cid) ) continue; p = fsl_path_new_node(path, cid, pPrev, isParent ? 1 : 0); if(!p){ rc = fsl_cx_err_set(f, FSL_RC_OOM, 0); goto end; } if( cid == iTo ){ fsl_stmt_finalize(&s); path->pEnd = p; fsl_path_reverse( path ); return 0; } } fsl_stmt_reset(&s); pPrev = pPrev->u.pPeer; } } end: fsl_stmt_finalize(&s); fsl_path_clear(path); return rc; } #undef MARKER