Index: f-apps/Makefile.in ================================================================== --- f-apps/Makefile.in +++ f-apps/Makefile.in @@ -39,10 +39,11 @@ f-_scratchpad \ f-_template \ f-acat \ f-add \ f-adiff \ + f-annotate \ f-aparse \ f-ci \ f-co \ f-config \ f-delta \ ADDED f-apps/f-annotate.c Index: f-apps/f-annotate.c ================================================================== --- /dev/null +++ f-apps/f-annotate.c @@ -0,0 +1,129 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* + Copyright 2013-2021 The Libfossil Authors, see LICENSES/BSD-2-Clause.txt + + SPDX-License-Identifier: BSD-2-Clause-FreeBSD + SPDX-FileCopyrightText: 2021 The Libfossil Authors + SPDX-ArtifactOfProjectName: Libfossil + SPDX-FileType: Code +*/ +/** + This is a template application for libfossil fcli client apps, with + commentary explaining how to do various common things with the + API. Copy/paste this and modify it to suit. +*/ +#include "fossil-scm/fossil-cli.h" +#include "fossil-scm/fossil-internal.h" + +// Only for testing/debugging.. +#define MARKER(pfexp) \ + do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ + printf pfexp; \ + } while(0) + +// Global app state. +struct App_ { + fsl_annotate_opt opt; +} App = { +fsl_annotate_opt_empty_m +}; + +int main(int argc, const char * const * argv ){ + bool ignoreAllSpace = false; + bool ignoreEOLSpace = false; + char const * zRevision = 0; + char const * zOrigin = 0; + fsl_buffer fnamebuf = fsl_buffer_empty; + + /** + Set up flag handling, which is used for processing + basic CLI flags and generating --help text output. + */ + const fcli_cliflag FCliFlags[] = { + // FCLI_FLAG_xxx macros are convenience forms for initializing + // these members... + FCLI_FLAG_BOOL("p","praise",&App.opt.praise, + "Use praise/blame mode."), + FCLI_FLAG_BOOL("f","file-versions",&App.opt.fileVersions, + "List file blob versions instead of checkin versions."), + FCLI_FLAG_BOOL("w","ignore-all-space",&ignoreAllSpace, + "Ignore all whitespace changes."), + FCLI_FLAG_BOOL("Z","ignore-trailing-space",&ignoreEOLSpace, + "Ignore end-of-line whitespace."), + FCLI_FLAG("r","revision", "string",&zRevision, + "Checkin containing input file (default=checkout)"), + FCLI_FLAG("o","origin", "string", &zOrigin, + "Origin Starting checkin version (default root of " + "the tree)"), + FCLI_FLAG("f","file", "filename",&App.opt.filename, + "Repo-relative file to annote. May be provided as the " + "first non-flag argument."), + FCLI_FLAG_I32("n","limit", "int>=0",(int32_t*)&App.opt.limit, + "Checkin containing input file (default=checkout)"), + FCLI_FLAG_BOOL(NULL,"versions", &App.opt.dumpVersions, + "Start output with a list of all analyzed versions."), + fcli_cliflag_empty_m // list MUST end with this (or equivalent) + }; + const fcli_help_info FCliHelp = { + "Outputs an annoted listing of a file's contents.", + "[options] FILENAME", + NULL // optional callback which outputs app-specific help + }; + fcli.cliFlags = FCliFlags; + fcli.appHelp = &FCliHelp; + + int rc = fcli_setup(argc, argv); + if(rc) goto end; + + fsl_cx * const f = fcli_cx(); + + if(!App.opt.filename){ + App.opt.filename = fcli_next_arg(true); + if(!App.opt.filename){ + rc = fcli_err_set(FSL_RC_MISUSE, + "Missing required filename argument. Try --help."); + goto end; + } + } + if(fsl_cx_db_ckout(f) + && 0==fsl_stat(App.opt.filename, NULL, false)){ + // We're in a checkout and the file exists. Canonicalize + // the filename relative to the repo root... + rc = fsl_ckout_filename_check(f, true, App.opt.filename, + &fnamebuf); + if(rc) goto end; + App.opt.filename = fsl_buffer_cstr(&fnamebuf); + } + + if(zRevision){ + rc = fsl_sym_to_rid(f, zRevision, FSL_SATYPE_CHECKIN, + &App.opt.versionRid); + if(rc) goto end; + }else{ + fsl_ckout_version_info(f, &App.opt.versionRid, &zRevision); + if(!zRevision){ + rc = fcli_err_set(FSL_RC_MISUSE, + "Cannot determine --revision value."); + goto end; + } + } + if(zOrigin){ + rc = fsl_sym_to_rid(f, zOrigin, FSL_SATYPE_CHECKIN, + &App.opt.originRid); + if(rc) goto end; + } + if((rc=fcli_has_unused_args(false))) goto end; + if(App.opt.limit<0) App.opt.limit = 0; + if(ignoreAllSpace) App.opt.spacePolicy = 1; + else if(ignoreEOLSpace) App.opt.spacePolicy = -1; + App.opt.out = fsl_output_f_FILE; + App.opt.outState = stdout; + rc = fsl_annotate(f, &App.opt); + + end: + fsl_buffer_clear(&fnamebuf); + return fcli_end_of_main(rc) + /* Will report any pending error state and return either + EXIT_SUCCESS or EXIT_FAILURE. */; +} Index: include/fossil-scm/fossil-checkout.h ================================================================== --- include/fossil-scm/fossil-checkout.h +++ include/fossil-scm/fossil-checkout.h @@ -46,12 +46,12 @@ Corner case: a new repo with no checkins has an RID of 0 and a UUID of NULL. That does not happen with fossil-generated repositories, as those always "seed" the database with an initial commit artifact containing no files. */ -FSL_EXPORT void fsl_ckout_version_info(fsl_cx *f, fsl_id_t * rid, - fsl_uuid_cstr * uuid ); +FSL_EXPORT void fsl_ckout_version_info(fsl_cx * const f, fsl_id_t * const rid, + fsl_uuid_cstr * const uuid ); /** Given a fsl_cx with an opened checkout, and a filename, this function canonicalizes zOrigName to a form suitable for use as an in-repo filename, _appending_ the results to pOut. If pOut is @@ -247,12 +247,12 @@ committed using fsl_ckout_unmanage(). @see fsl_ckout_unmanage() @see fsl_reserved_fn_check() */ -FSL_EXPORT int fsl_ckout_manage( fsl_cx * f, - fsl_ckout_manage_opt * opt ); +FSL_EXPORT int fsl_ckout_manage( fsl_cx * const f, + fsl_ckout_manage_opt * const opt ); /** Callback type for use with fsl_ckout_unmanage(). It is called by the removal process, immediately after a file is "removed" Index: include/fossil-scm/fossil-core.h ================================================================== --- include/fossil-scm/fossil-core.h +++ include/fossil-scm/fossil-core.h @@ -1794,21 +1794,21 @@ Returns the same as passing fsl_cx_db() to fsl_db_transaction_level(), or 0 if f has no db opened. @see fsl_cx_db() */ -FSL_EXPORT int fsl_cx_transaction_level(fsl_cx * f); +FSL_EXPORT int fsl_cx_transaction_level(fsl_cx * const f); /** Returns the same as passing fsl_cx_db() to fsl_db_transaction_begin(). */ -FSL_EXPORT int fsl_cx_transaction_begin(fsl_cx * f); +FSL_EXPORT int fsl_cx_transaction_begin(fsl_cx * const f); /** Returns the same as passing fsl_cx_db() to fsl_db_transaction_end(). */ -FSL_EXPORT int fsl_cx_transaction_end(fsl_cx * f, bool doRollback); +FSL_EXPORT int fsl_cx_transaction_end(fsl_cx * const f, bool doRollback); /** Installs or (if f is NULL) uninstalls a confirmation callback for use by operations on f which require user confirmation. The exact implications of *not* installing a confirmer depend on the Index: include/fossil-scm/fossil-repo.h ================================================================== --- include/fossil-scm/fossil-repo.h +++ include/fossil-scm/fossil-repo.h @@ -1821,17 +1821,19 @@ @see fsl_content_blob() @see fsl_content_size() */ -FSL_EXPORT int fsl_content_get( fsl_cx * f, fsl_id_t blobRid, fsl_buffer * tgt ); +FSL_EXPORT int fsl_content_get( fsl_cx * const f, fsl_id_t blobRid, + fsl_buffer * const tgt ); /** Uses fsl_sym_to_rid() to convert sym to a record ID, then passes that to fsl_content_get(). Returns 0 on success. */ -FSL_EXPORT int fsl_content_get_sym( fsl_cx * f, char const * sym, fsl_buffer * tgt ); +FSL_EXPORT int fsl_content_get_sym( fsl_cx * const f, char const * sym, + fsl_buffer * const tgt ); /** Returns true if the given rid is marked as PRIVATE in f's current repository. Returns false (0) on error or if the content is not marked as private. @@ -2901,12 +2903,12 @@ of the specified type. In order to resolve arbitrary UUIDs, e.g. those of arbitrary blob content, type needs to be FSL_SATYPE_ANY. */ -FSL_EXPORT int fsl_sym_to_rid( fsl_cx * f, char const * sym, fsl_satype_e type, - fsl_id_t * rv ); +FSL_EXPORT int fsl_sym_to_rid( fsl_cx * const f, char const * sym, + fsl_satype_e type, fsl_id_t * rv ); /** Similar to fsl_sym_to_rid() but on success it returns a UUID string by assigning it to *rv (if rv is not NULL). If rid is not NULL then on success the db record ID corresponding to the returned UUID is @@ -3258,11 +3260,17 @@ fsl_buffer * const manifest, fsl_buffer * const manifestUuid, fsl_buffer * const manifestTags ); /** - UNDER CONSTRUCTION. Configuration for use with fsl_annotate(). + Configuration for use with fsl_annotate(). + + This structure holds options for the "annotate" operation and its + close cousin, "blame" a.k.a. "praise." Annotation takes a given + file version and builds a line-by-line history, showing when each + line was last modified. The "blame" a.k.a. "praise" option includes + *who* modified that line. */ struct fsl_annotate_opt { /** The repository-root-relative NUL-terminated filename to annotate. */ @@ -3281,31 +3289,45 @@ and originRid. */ fsl_id_t originRid; /** The maximum number of versions to search through. + + Note that fossil(1) offers the ability to limit the calculation + based on processing time, e.g. to 1500ms. We may or may not add + that in this library. */ - unsigned int limit; + uint32_t limit; /** - 0 = do not ignore any spaces. - <0 = ignore trailing end-of-line spaces. - >1 = ignore all spaces */ - int spacePolicy; + int16_t spacePolicy; /** - If true, include the name of the user for which each - change is attributed (noting that merges show whoever - merged the change, which may differ from the original - committer. If false, show only version information. + If true, include the name of the user for which each change is + attributed (noting that merges show whoever merged the change, + which may differ from the original committer, and amended user + names will be used over those in the initial commit). If false, + show only version information. This option is alternately known as "blame". + + For reasons lost to history, blame/praise mode does not include + line numbers. That may change in the future. */ bool praise; /** Output file blob versions, instead of checkin versions. */ bool fileVersions; + + /** + If true, annotation output will start with a list of all + versions analyzed by the annotation process. + */ + bool dumpVersions; /** The output channel for the resulting annotation. */ fsl_output_f out; /** @@ -3320,20 +3342,22 @@ const-copy initialization. */ #define fsl_annotate_opt_empty_m {\ NULL/*filename*/, \ 0/*versionRid*/,0/*originRid*/, \ 0U/*limit*/, 0/*spacePolicy*/, \ - false/*praise*/, \ - NULL/*out*/, NULL/*outState*/ \ + false/*praise*/, false/*fileVersions*/, \ + false/*dumpVersions*/, \ + NULL/*out*/, NULL/*outState*/ \ } /** Initialized-with-defaults fsl_annotate_opt structure, intended for non-const copy initialization. */ extern const fsl_annotate_opt fsl_annotate_opt_empty; /** - UNDER CONSTRUCTION, not yet implemented. + UNDER CONSTRUCTION. Not yet known to be fully functional or + bug-free. Runs an "annotation" of an SCM-controled file and sends the results to opt->out(). Returns 0 on success. On error, returns one of: @@ -3349,14 +3373,17 @@ - FSL_RC_PHANTOM if a phantom blob is encountered while trying to annotate. opt->out() may return arbitrary non-0 result codes, in which case the returned code is propagated to the caller of this function. + + Results are undefined if either argument is invalid or opt->out is + NULL. */ FSL_EXPORT int fsl_annotate( fsl_cx * const f, fsl_annotate_opt const * const opt ); #if defined(__cplusplus) } /*extern "C"*/ #endif #endif /* ORG_FOSSIL_SCM_FSL_REPO_H_INCLUDED */ Index: include/fossil-scm/fossil-util.h ================================================================== --- include/fossil-scm/fossil-util.h +++ include/fossil-scm/fossil-util.h @@ -3323,11 +3323,11 @@ */ struct fsl_dline { /** The text of the line. Owned by higher-level code. */ const char *z; /** Hash of the line. Lower X bits are the length. */ - unsigned int h; + uint64_t h; /** Indent of the line. Only !=0 with certain options */ unsigned short indent; /** number of bytes */ unsigned short n; /** 1+(Index of next line with same the same hash) */ Index: include/fossil-scm/fossil-vpath.h ================================================================== --- include/fossil-scm/fossil-vpath.h +++ include/fossil-scm/fossil-vpath.h @@ -204,10 +204,24 @@ FSL_EXPORT int fsl_vpath_shortest_store_in_ancestor(fsl_cx * const f, fsl_id_t iFrom, fsl_id_t iTo, uint32_t *pSteps); +/** + Computes a list of direct (non-merge) ancestors of the given + checkin RID and stores it in the TEMP table [ancestor], which it + creates if needed or clears if it currently exists. + + The [ancestor] schema is described in + fsl_vpath_shortest_store_in_ancestor(). The [generation] value of + the record corresponding to rid is 1, increasing by 1 for each + generation back in the history. + + Returns 0 on success, FSL_RC_NOT_A_REPO if f has no repo db opened, + and any number of lower-level result codes if something goes wrong. +*/ +FSL_EXPORT int fsl_compute_direct_ancestors(fsl_cx * const f, fsl_id_t rid); /** Reconstructs path from path->pStart to path->pEnd, reversing its order by fiddling with the u->pTo fields. Index: src/annotate.c ================================================================== --- src/annotate.c +++ src/annotate.c @@ -13,10 +13,11 @@ /************************************************************************ This file implements the annoate/blame/praise-related APIs. */ #include "fossil-scm/fossil-internal.h" #include "fossil-scm/fossil-vpath.h" +#include "fossil-scm/fossil-checkout.h" #include /* Only for debugging */ #include #define MARKER(pfexp) \ @@ -32,75 +33,88 @@ ** of the following structure. */ typedef struct Annotator Annotator; struct Annotator { fsl_diff_cx c; /* The diff-engine context */ + fsl_buffer headVersion;/*starting version of the content*/ struct AnnLine { /* Lines of the original files... */ - const char *z; /* The text of the line */ + const char *z; /* The text of the line. Points into + this->headVersion. */ short int n; /* Number of bytes (omitting trailing \n) */ short int iVers; /* Level at which tag was set */ } *aOrig; - int nOrig; /* Number of elements in aOrig[] */ - int nVers; /* Number of versions analyzed */ - int bMoreToDo; /* True if the limit was reached */ + unsigned int nOrig;/* Number of elements in aOrig[] */ + unsigned int nVers;/* Number of versions analyzed */ + bool bMoreToDo; /* True if the limit was reached */ fsl_id_t origId; /* RID for the zOrigin version */ fsl_id_t showId; /* RID for the version being analyzed */ struct AnnVers { - const char *zFUuid; /* File being analyzed */ - const char *zMUuid; /* Check-in containing the file */ - const char *zDate; /* Date of the check-in */ - const char *zBgColor; /* Suggested background color */ - const char *zUser; /* Name of user who did the check-in */ - unsigned cnt; /* Number of lines contributed by this check-in */ + char *zFUuid; /* File being analyzed */ + char *zMUuid; /* Check-in containing the file */ + char *zDate; /* Date of the check-in */ + char *zUser; /* Name of user who did the check-in */ } *aVers; /* For each check-in analyzed */ - char **azVers; /* Names of versions analyzed */ + unsigned int naVers; /* # of entries allocated in this->aVers */ }; -TO_BE_STATIC const Annotator Annotator_empty = { +static const Annotator Annotator_empty = { fsl_diff_cx_empty_m, +fsl_buffer_empty_m/*headVersion*/, NULL/*aOrig*/, -0/*nOrig*/, 0/*nVers*/, -0/*bMoreToDo*/, +0U/*nOrig*/, 0U/*nVers*/, +false/*bMoreToDo*/, 0/*origId*/, 0/*showId*/, -NULL/*aVerse*/, -NULL/*azVers*/ +NULL/*aVers*/, +0U/*naVerse*/ }; -TO_BE_STATIC void fsl__annotator_clean(Annotator * const a){ +static void fsl__annotator_clean(Annotator * const a){ + unsigned i; fsl__diff_cx_clean(&a->c); - //fsl_free(...); + for(i = 0; i < a->nVers; ++i){ + fsl_free(a->aVers[i].zFUuid); + fsl_free(a->aVers[i].zMUuid); + fsl_free(a->aVers[i].zDate); + fsl_free(a->aVers[i].zUser); + } + fsl_free(a->aVers); + fsl_free(a->aOrig); + fsl_buffer_clear(&a->headVersion); } static uint64_t fsl__annotate_opt_difflags(fsl_annotate_opt const * const opt){ - uint64_t diffFlags = 0; + uint64_t diffFlags = FSL_DIFF2_STRIP_EOLCR; if(opt->spacePolicy>0) diffFlags |= FSL_DIFF2_IGNORE_ALLWS; else if(opt->spacePolicy<0) diffFlags |= FSL_DIFF2_IGNORE_EOLWS; return diffFlags; } /** Initializes the annocation process by populating `a` from - pInput. `a` must have already been cleanly initialized via copying - from Annotator_empty. Returns 0 on success, else: + a->toAnnote, which must have been previously populated. `a` must + have already been cleanly initialized via copying from + Annotator_empty and a->headVersion populated. Returns 0 on success, + else: - FSL_RC_RANGE if pInput is empty. - FSL_RC_OOM on OOM. Regardless of success or failure, `a` must eventually be passed to fsl__annotator_clean() to free up any resources. */ -TO_BE_STATIC int fsl__annotation_start(Annotator * const a, fsl_buffer * const pInput, - fsl_annotate_opt const * const opt){ +static int fsl__annotation_start(Annotator * const a, + fsl_annotate_opt const * const opt){ int rc; uint64_t const diffFlags = fsl__annotate_opt_difflags(opt); if(opt->spacePolicy>0){ a->c.cmpLine = fsl_dline_cmp_ignore_ws; }else{ assert(fsl_dline_cmp == a->c.cmpLine); } - rc = fsl_break_into_dlines(fsl_buffer_cstr(pInput), (fsl_int_t)pInput->used, + rc = fsl_break_into_dlines(fsl_buffer_cstr(&a->headVersion), + (fsl_int_t)a->headVersion.used, (uint32_t*)&a->c.nTo, &a->c.aTo, diffFlags); if(rc) goto end; if(!a->c.nTo){ rc = FSL_RC_RANGE; goto end; @@ -108,32 +122,31 @@ a->aOrig = fsl_malloc( (fsl_size_t)(sizeof(a->aOrig[0]) * a->c.nTo) ); if(!a->aOrig){ rc = FSL_RC_OOM; goto end; } - a->nOrig = a->c.nTo; for(int i = 0; i < a->c.nTo; ++i){ a->aOrig[i].z = a->c.aTo[i].z; a->aOrig[i].n = a->c.aTo[i].n; a->aOrig[i].iVers = -1; } + a->nOrig = (unsigned)a->c.nTo; end: return rc; } /** The input pParent is the next most recent ancestor of the file being annotated. Do another step of the annotation. On success return 0 and, if additional annotation is required, assign *doMore - to true. + (if not NULL) to true. */ -TO_BE_STATIC int fsl__annotation_step( - Annotator *a, +static int fsl__annotation_step( + Annotator * const a, fsl_buffer const *pParent, int iVers, - fsl_annotate_opt const * const opt, - bool * doMore + fsl_annotate_opt const * const opt ){ int i, j, rc; int lnTo; uint64_t const diffFlags = fsl__annotate_opt_difflags(opt); @@ -141,15 +154,14 @@ rc = fsl_break_into_dlines(fsl_buffer_cstr(pParent), (fsl_int_t)pParent->used, (uint32_t*)&a->c.nFrom, &a->c.aFrom, diffFlags); if(rc) goto end; - if( a->c.aFrom==0 ){ - *doMore = true; + else if( a->c.aFrom==0 ){ return 0; } - + //MARKER(("Line #1: %.*s\n", (int)a->c.aFrom[0].n, a->c.aFrom[0].z)); /* Compute the differences going from pParent to the file being ** annotated. */ rc = fsl__diff_all(&a->c); if(rc) goto end; @@ -158,27 +170,28 @@ */ for(i=lnTo=0; ic.nEdit; i+=3){ int const nCopy = a->c.aEdit[i]; int const nIns = a->c.aEdit[i+2]; lnTo += nCopy; - for(j=0; jaOrig[lnTo].iVers<0 ){ a->aOrig[lnTo].iVers = iVers; } } } - /* Clear out the diff results */ + /* Clear out the diff results except for c.aTo, as that's pointed to + by a->aOrig.*/ fsl_free(a->c.aEdit); a->c.aEdit = 0; a->c.nEdit = 0; a->c.nEditAlloc = 0; /* Clear out the from file */ fsl_free(a->c.aFrom); a->c.aFrom = 0; - + a->c.nFrom = 0; end: return rc; } /* MISSING(?) fossil(1) converts the diff inputs into utf8 with no @@ -185,27 +198,228 @@ BOM. Whether we really want to do that here or rely on the caller to is up for debate. If we do it here, we have to make the inputs non-const, which seems "wrong" for a library API. */ #define blob_to_utf8_no_bom(A,B) (void)0 +static int fsl__annotate_file(fsl_cx * const f, + Annotator * const a, + fsl_annotate_opt const * const opt){ + int rc = FSL_RC_NYI; + fsl_buffer step = fsl_buffer_empty /*previous revision*/; + fsl_id_t cid = 0, fnid = 0; // , rid = 0; + fsl_stmt q = fsl_stmt_empty; + bool openedTransaction = false; + fsl_db * const db = fsl_needs_repo(f); + + if(!db) return FSL_RC_NOT_A_REPO; + rc = fsl_cx_transaction_begin(f); + if(rc) goto dberr; + openedTransaction = true; + + fnid = fsl_db_g_id(db, 0, + "SELECT fnid FROM filename WHERE name=%Q %s", + opt->filename, fsl_cx_filename_collation(f)); + if(0==fnid){ + rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND, + "File not found in repository: %s", + opt->filename); + goto end; + } + if(opt->versionRid>0){ + cid = opt->versionRid; + }else{ + fsl_ckout_version_info(f, &cid, NULL); + if(cid<=0){ + rc = fsl_cx_err_set(f, FSL_RC_NOT_A_CKOUT, + "Cannot determine version RID to " + "annotate from."); + goto end; + } + } + if(opt->originRid>0){ + rc = fsl_vpath_shortest_store_in_ancestor(f, cid, opt->originRid, NULL); + }else{ + rc = fsl_compute_direct_ancestors(f, cid); + } + if(rc) goto end; + + rc = fsl_db_prepare(db, &q, + "SELECT DISTINCT" + " (SELECT uuid FROM blob WHERE rid=mlink.fid)," + " (SELECT uuid FROM blob WHERE rid=mlink.mid)," + " date(event.mtime)," + " coalesce(event.euser,event.user)," + " mlink.fid" + " FROM mlink, event, ancestor" + " WHERE mlink.fnid=%" FSL_ID_T_PFMT + " AND ancestor.rid=mlink.mid" + " AND event.objid=mlink.mid" + " AND mlink.mid!=mlink.pid" + " ORDER BY ancestor.generation;", + fnid + ); + if(rc) goto dberr; + + while(FSL_RC_STEP_ROW==fsl_stmt_step(&q)){ + if(a->nVers>=3){ + /*Process at least 3 rows before imposing any limit. + Note that we do not impose a time-based limit here like + fossil does, but may want to add that at some point.*/ + if(opt->limit>0 && a->nVers>=opt->limit){ + a->bMoreToDo = true; + break; + } + } + char * zTmp = 0; + char const * zCol = 0; + fsl_size_t nCol = 0; + fsl_id_t const rid = fsl_stmt_g_id(&q, 4); + if(0==a->nVers){ + rc = fsl_content_get(f, rid, &a->headVersion); + if(rc) goto end; + blob_to_utf8_no_bom(&a->headVersion,0); + rc = fsl__annotation_start(a, opt); + if(rc) goto end; + a->bMoreToDo = opt->originRid>0; + a->origId = opt->originRid; + a->showId = cid; + assert(0==a->nVers); + assert(NULL==a->aVers); + } + if(a->naVers==a->nVers){ + unsigned int const n = a->naVers ? a->naVers*3/2 : 10; + void * const x = fsl_realloc(a->aVers, n*sizeof(a->aVers[0])); + if(NULL==x){ + rc = FSL_RC_OOM; + goto end; + } + a->aVers = x; + a->naVers = n; + } +#define AnnStr(COL,FLD) zCol = fsl_stmt_g_text(&q, COL, &nCol); \ + zTmp = fsl_strndup(zCol, nCol); \ + if(!zTmp){ rc = FSL_RC_OOM; goto end; } \ + a->aVers[a->nVers].FLD = zTmp + AnnStr(0,zFUuid); + AnnStr(1,zMUuid); + AnnStr(2,zDate); + AnnStr(3,zUser); +#undef AnnStr + if( a->nVers>0 ){ + rc = fsl_content_get(f, rid, &step); + if(!rc){ + rc = fsl__annotation_step(a, &step, a->nVers-1, opt); + } + fsl_buffer_reuse(&step); + if(rc) goto end; + } + ++a->nVers; + } + + assert(0==rc); + if(0==a->nVers){ + if(opt->versionRid>0){ + rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND, + "File [%s] does not exist " + "in checkin RID %" FSL_ID_T_PFMT, + opt->filename, opt->versionRid); + }else{ + rc = fsl_cx_err_set(f, FSL_RC_NOT_FOUND, + "No history found for file: %s", + opt->filename); + } + } + + end: + fsl_buffer_clear(&step); + fsl_stmt_finalize(&q); + if(openedTransaction) fsl_cx_transaction_end(f, rc!=0); + return rc; + dberr: + assert(openedTransaction); + assert(rc!=0); + fsl_stmt_finalize(&q); + fsl_buffer_clear(&step); + rc = fsl_cx_uplift_db_error2(f, db, rc); + if(openedTransaction) fsl_cx_transaction_end(f, rc!=0); + return rc; +} + +/*TO_BE_STATIC int fann__out(fsl_annotate_opt const *const o, + char const *z, fsl_size_t n){ + return o->out(o->outState, z, n); + }*/ + +static int fann__outf(fsl_annotate_opt const * const o, + char const *fmt, ...){ + int rc = 0; + //MARKER(("fmt=%s\n", fmt)); + va_list va; + va_start(va,fmt); + rc = fsl_appendfv(o->out, o->outState, fmt, va); + va_end(va); + //MARKER(("after appendfv\n")); + return rc; +} int fsl_annotate( fsl_cx * const f, fsl_annotate_opt const * const opt ){ int rc; - if(!opt->out) return FSL_RC_MISUSE; - if(f||opt){/*unused*/} - - rc = fsl_cx_err_set(f, FSL_RC_NYI, "%s() is not yet implemented.", - __func__); - goto end; - - /* TODO: - - Port over fossil(1) diff.c:annotate_file(). - */ + Annotator ann = Annotator_empty; + unsigned int i; + assert(opt->out); + + rc = fsl__annotate_file(f, &ann, opt); + if(rc) goto end; + + int const szHash = 10 + /*# of hash bytes to show. TODO: move this into fsl_annotate_opt. */; + if(opt->dumpVersions){ + struct AnnVers *av; + for(av = ann.aVers, i = 0; + 0==rc && i < ann.nVers; ++i, ++av){ + rc = fann__outf(opt, "version %3u: %s %.*s file %.*s\n", + i+1, av->zDate, szHash, av->zMUuid, + szHash, av->zFUuid); + } + if(!rc) rc = fann__outf(opt, "%.*c\n", 60, '-'); + if(rc) goto end; + } + + for(i = 0; 0==rc && ipraise){ + if(iVers>=0){ + struct AnnVers * const av = &ann.aVers[iVers]; + rc = fann__outf(opt, "%.*s %s %13.13s: %.*s\n", + szHash, + opt->fileVersions ? av->zFUuid : av->zMUuid, + av->zDate, av->zUser, n, z); + }else{ + rc = fann__outf(opt, "%*s %.*s\n", szHash+26, "", n, z); + } + }else{ + if(iVers>=0){ + struct AnnVers * const av = &ann.aVers[iVers]; + rc = fann__outf(opt, "%.*s %s %5u: %.*s\n", + szHash, + opt->fileVersions ? av->zFUuid : av->zMUuid, + av->zDate, i+1, n, z); + }else{ + rc = fann__outf(opt, "%*s %5u: %.*s\n", + szHash+11, "", i+1, n, z); + } + } + } end: + fsl__annotator_clean(&ann); return rc; } #undef MARKER #undef TO_BE_STATIC #undef blob_to_utf8_no_bom Index: src/checkout.c ================================================================== --- src/checkout.c +++ src/checkout.c @@ -416,11 +416,11 @@ "%s/%s", dst->absoluteDir, dst->entryName); if(!rc) rc = co_add_one(cas, true); return rc; } -int fsl_ckout_manage( fsl_cx * f, fsl_ckout_manage_opt * opt_ ){ +int fsl_ckout_manage( fsl_cx * const f, fsl_ckout_manage_opt * const opt_ ){ int rc = 0; CoAddState cas = CoAddState_empty; fsl_ckout_manage_opt opt; if(!f) return FSL_RC_MISUSE; else if(!fsl_needs_ckout(f)) return FSL_RC_NOT_A_CKOUT; Index: src/content.c ================================================================== --- src/content.c +++ src/content.c @@ -147,12 +147,13 @@ } return rc==FSL_RC_STEP_ROW ? true : false; } -int fsl_content_get( fsl_cx * f, fsl_id_t rid, fsl_buffer * tgt ){ - fsl_db * db = fsl_cx_db_repo(f); +int fsl_content_get( fsl_cx * const f, fsl_id_t rid, + fsl_buffer * const tgt ){ + fsl_db * const db = fsl_cx_db_repo(f); if(!tgt) return FSL_RC_MISUSE; else if(rid<=0){ return fsl_cx_err_set(f, FSL_RC_RANGE, "RID %"FSL_ID_T_PFMT" is out of range.", rid); @@ -313,11 +314,12 @@ } return rc; } } -int fsl_content_get_sym( fsl_cx * f, char const * sym, fsl_buffer * tgt ){ +int fsl_content_get_sym( fsl_cx * const f, char const * sym, + fsl_buffer * const tgt ){ int rc; fsl_db * db = f ? fsl_needs_repo(f) : NULL; fsl_id_t rid = 0; if(!f || !sym || !tgt) return FSL_RC_MISUSE; else if(!db) return FSL_RC_NOT_A_REPO; Index: src/cx.c ================================================================== --- src/cx.c +++ src/cx.c @@ -1281,11 +1281,12 @@ rc = fsl_ckout_manifest_write(f, mode, mode, mode, 0); } return rc; } -void fsl_ckout_version_info(fsl_cx *f, fsl_id_t * rid, fsl_uuid_cstr * uuid ){ +void fsl_ckout_version_info(fsl_cx * const f, fsl_id_t * const rid, + fsl_uuid_cstr * const uuid ){ if(uuid) *uuid = f->ckout.uuid; if(rid) *rid = f->ckout.rid>=0 ? f->ckout.rid : 0; } int fsl_ckout_db_search( char const * dirName, bool checkParentDirs, @@ -1811,22 +1812,24 @@ fsl_hashpolicy_e fsl_cx_hash_policy_get(fsl_cx const*f){ return f->cxConfig.hashPolicy; } -int fsl_cx_transaction_level(fsl_cx * f){ +int fsl_cx_transaction_level(fsl_cx * const f){ return f->dbMain ? fsl_db_transaction_level(f->dbMain) : 0; } -int fsl_cx_transaction_begin(fsl_cx * f){ - return fsl_db_transaction_begin(f->dbMain); +int fsl_cx_transaction_begin(fsl_cx * const f){ + int const rc = fsl_db_transaction_begin(f->dbMain); + return rc ? fsl_cx_uplift_db_error2(f, f->dbMain, rc) : 0; } -int fsl_cx_transaction_end(fsl_cx * f, bool doRollback){ - return fsl_db_transaction_end(f->dbMain, doRollback); +int fsl_cx_transaction_end(fsl_cx * const f, bool doRollback){ + int const rc = fsl_db_transaction_end(f->dbMain, doRollback); + return rc ? fsl_cx_uplift_db_error2(f, f->dbMain, rc) : 0; } void fsl_cx_confirmer(fsl_cx * f, fsl_confirmer const * newConfirmer, fsl_confirmer * prevConfirmer){ Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -52,11 +52,11 @@ #define ANN_FILE_ANCEST (((u64)0x40)<<32) /* Prefer check-ins in the ANCESTOR table */ /** Maximum length of a line in a text file, in bytes. (2**13 = 8192 bytes) */ -#define LENGTH_MASK_SZ 13 +#define LENGTH_MASK_SZ 15 #define LENGTH_MASK ((1<bestScore ){ bestScore = score; iSXb = iSX; iSYb = iSY; iEXb = iEX; @@ -412,27 +411,36 @@ iSYp = iSY; iEXp = iEX; iEYp = iEY; } } - if( iSXb==iEXb && (int64_t)(iE1-iS1)*(iE2-iS2)<400 ){ + if( +#if 0 + 1 + /* CANNOT EXPLAIN why we get different diff results than fossil + unless we use fsl_diff_optimal_lcs() despite using, insofar as + i can tell, the same inputs. There is some aspect i'm + overlooking. */ +#else + iSXb==iEXb && (sqlite3_int64)(iE1-iS1)*(iE2-iS2)<400 +#endif + ){ /* If no common sequence is found using the hashing heuristic and ** the input is not too big, use the expensive exact solution */ fsl__diff_optimal_lcs(p, iS1, iE1, iS2, iE2, piSX, piEX, piSY, piEY); }else{ *piSX = iSXb; *piSY = iSYb; *piEX = iEXb; *piEY = iEYb; } - } void fsl__dump_triples(fsl_diff_cx const * const p, char const * zFile, int ln ){ - // Compare this with (fossil xdiff --raw) on the same inputs + // Compare this with (fossil xdiff --raw) on the same inputs fprintf(stderr,"%s:%d: Compare this with (fossil xdiff --raw) on the same inputs:\n", zFile, ln); for(int i = 0; p->aEdit[i] || p->aEdit[i+1] || p->aEdit[i+2]; i+=3){ printf(" copy %6d delete %6d insert %6d\n", p->aEdit[i], p->aEdit[i+1], p->aEdit[i+2]); @@ -516,12 +524,10 @@ return appendTriple(p, 0, iE1-iS1, 0); } /* Find the longest matching segment between the two sequences */ fsl__diff_lcs(p, iS1, iE1, iS2, iE2, &iSX, &iEX, &iSY, &iEY); - //MARKER(("L p->nFrom = %d, p->nTo=%d, p->nEdit=%d\n", p->nFrom, p->nTo,p->nEdit)); - if( iEX>iSX ){ /* A common segment has been found. Recursively diff either side of the matching segment */ rc = diff_step(p, iS1, iSX, iS2, iSY); if(!rc){ @@ -542,35 +548,29 @@ int mnE, iS, iE1, iE2; int rc = 0; /* Carve off the common header and footer */ iE1 = p->nFrom; iE2 = p->nTo; - //MARKER(("A p->nFrom = %d, p->nTo=%d, p->nEdit=%d\n", p->nFrom, p->nTo,p->nEdit)); while( iE1>0 && iE2>0 && p->cmpLine(&p->aFrom[iE1-1], &p->aTo[iE2-1])==0 ){ iE1--; iE2--; } - //MARKER(("iE1=%d, iE2=%d\n", iE1, iE2)); mnE = iE1cmpLine(&p->aFrom[iS],&p->aTo[iS])==0; iS++){} - //MARKER(("B p->nFrom = %d, p->nTo=%d, p->nEdit=%d, iS=%d\n", p->nFrom, p->nTo,p->nEdit, iS)); /* do the difference */ if( iS>0 ){ rc = appendTriple(p, iS, 0, 0); if(rc) return rc; } - //MARKER(("B2 p->nFrom = %d, p->nTo=%d, p->nEdit=%d, iS=%d\n", p->nFrom, p->nTo,p->nEdit, iS)); rc = diff_step(p, iS, iE1, iS, iE2); //fsl__dump_triples(p, __FILE__, __LINE__); - //MARKER(("B3 p->nFrom = %d, p->nTo=%d, p->nEdit=%d, iS=%d\n", p->nFrom, p->nTo,p->nEdit, iS)); if(rc) return rc; else if( iE1nFrom ){ rc = appendTriple(p, p->nFrom - iE1, 0, 0); if(rc) return rc; } - //MARKER(("C p->nFrom = %d, p->nTo=%d, p->nEdit=%d\n", p->nFrom, p->nTo,p->nEdit)); /* Terminate the COPY/DELETE/INSERT triples with three zeros */ rc = fsl__diff_expand_edit(p, p->nEdit+3); if(0==rc){ if(p->aEdit ){ p->aEdit[p->nEdit++] = 0; @@ -577,11 +577,10 @@ p->aEdit[p->nEdit++] = 0; p->aEdit[p->nEdit++] = 0; //fsl__dump_triples(p, __FILE__, __LINE__); } } - //MARKER(("D p->nFrom = %d, p->nTo=%d, p->nEdit=%d\n", p->nFrom, p->nTo,p->nEdit)); return rc; } /* @@ -1862,11 +1861,10 @@ } fsl_buffer_clear(&unesc); return rc; } - /** @internal Performs a text diff on two buffers, either streaming the output to the 3rd argument or returning the results as an array of copy/delete/insert triples via the final argument. @@ -2032,11 +2030,10 @@ #undef DIFF_NOOPT #undef DIFF_INVERT #undef DIFF_CONTEXT_EX #undef DIFF_NOTTOOBIG #undef DIFF_STRIP_EOLCR -#undef minInt #undef SBS_LNA #undef SBS_TXTA #undef SBS_MKR #undef SBS_LNB #undef SBS_TXTB Index: src/diff2.c ================================================================== --- src/diff2.c +++ src/diff2.c @@ -165,17 +165,17 @@ *pOut = a; return 0; } int fsl_dline_cmp(const fsl_dline * const pA, - const fsl_dline * const pB){ + const fsl_dline * const pB){ if( pA->h!=pB->h ) return 1; return memcmp(pA->z,pB->z, pA->h&LENGTH_MASK); } int fsl_dline_cmp_ignore_ws(const fsl_dline * const pA, - const fsl_dline * const pB){ + const fsl_dline * const pB){ unsigned short a = pA->indent, b = pB->indent; if( pA->h==pB->h ){ while( an || bn ){ if( an && bn && pA->z[a++] != pB->z[b++] ) return 1; while( an && fsl_isspace(pA->z[a])) ++a; @@ -511,11 +511,11 @@ } /* ** Minimum of two values */ -static int minInt(int a, int b){ return abest ) best = k; } } score = (best>=avg) ? 0 : (avg - best)*100/avg; @@ -1120,11 +1120,10 @@ } /* Compute the difference */ rc = fsl__diff_all(&c); if(rc) goto end; - if( ignoreWs && c.nEdit==6 && c.aEdit[1]==0 && c.aEdit[2]==0 ){ rc = FSL_RC_DIFF_WS_ONLY; goto end; } if( (cfg->diffFlags & FSL_DIFF2_NOTTOOBIG)!=0 ){ @@ -1201,16 +1200,10 @@ } } return rc; } -static int fdb__fsl_output_f( void * state, void const * src, - fsl_size_t n ){ - fsl_diff_builder * const b = (fsl_diff_builder *)state; - return b->cfg->out(b->cfg->outState, src, n); -} - static int fdb__out(fsl_diff_builder *const b, char const *z, fsl_size_t n){ return b->cfg->out(b->cfg->outState, z, n); } static int fdb__outf(fsl_diff_builder * const b, @@ -1217,11 +1210,11 @@ char const *fmt, ...){ int rc = 0; va_list va; assert(b->cfg->out); va_start(va,fmt); - rc = fsl_appendfv(fdb__fsl_output_f, b, fmt, va); + rc = fsl_appendfv(b->cfg->out, b->cfg->outState, fmt, va); va_end(va); return rc; } Index: src/repo.c ================================================================== --- src/repo.c +++ src/repo.c @@ -223,12 +223,12 @@ oom: fsl_cx_err_set(f, FSL_RC_OOM, NULL); return -1; } -int fsl_sym_to_rid( fsl_cx * f, char const * sym, fsl_satype_e type, - fsl_id_t * rv ){ +int fsl_sym_to_rid( fsl_cx * const f, char const * sym, + fsl_satype_e type, fsl_id_t * rv ){ fsl_id_t rid = 0; fsl_id_t vid; fsl_size_t symLen; /* fsl_int_t i; */ fsl_db * dbR = fsl_cx_db_repo(f); Index: src/vpath.c ================================================================== --- src/vpath.c +++ src/vpath.c @@ -199,10 +199,48 @@ end: fsl_stmt_finalize(&s); fsl_vpath_clear(path); return rc; } + +/** + Creates, if needed, the [ancestor] table, else clears its + contents. Returns + */ +static int fsl__init_ancestor(fsl_cx * const f){ + fsl_db * const db = fsl_cx_db_repo(f); + int rc; + if(db){ + rc = fsl_db_exec_multi(db, + "CREATE TEMP TABLE IF NOT EXISTS ancestor(" + " rid INT UNIQUE," + " generation INTEGER PRIMARY KEY" + ");" + "DELETE FROM TEMP.ancestor;"); + }else{ + rc = fsl_cx_err_set(f, FSL_RC_NOT_A_REPO, + "Cannot compute ancestors without an " + "opened repository."); + } + return rc ? fsl_cx_uplift_db_error2(f, db, rc) : 0; +} + +int fsl_compute_direct_ancestors(fsl_cx * const f, fsl_id_t rid){ + int rc = fsl__init_ancestor(f); + fsl_db * const db = rc ? NULL : fsl_needs_repo(f); + if(rc) return rc; + assert(db); + return fsl_db_exec_multi(db, + "WITH RECURSIVE g(x,i) AS (" + " VALUES(%" FSL_ID_T_PFMT ",1)" + " UNION ALL" + " SELECT plink.pid, g.i+1 FROM plink, g" + " WHERE plink.cid=g.x AND plink.isprim)" + "INSERT INTO ancestor(rid,generation) SELECT x,i FROM g;", + rid + ); +} int fsl_vpath_shortest_store_in_ancestor(fsl_cx * const f, fsl_id_t iFrom, fsl_id_t iTo, uint32_t *pSteps){ @@ -213,17 +251,12 @@ fsl_vpath_node * node; int32_t gen = 0; if(!db) return FSL_RC_NOT_A_REPO; rc = fsl_vpath_shortest(f, &path, iFrom, iTo, true, false); if(rc) goto end; - rc = fsl_db_exec_multi(db, - "CREATE TEMP TABLE IF NOT EXISTS ancestor(" - " rid INT UNIQUE," - " generation INTEGER PRIMARY KEY" - ");" - "DELETE FROM TEMP.ancestor;"); - if(rc) goto dberr; + rc = fsl__init_ancestor(f); + if(rc) goto end; rc = fsl_db_prepare(db, &ins, "INSERT INTO TEMP.ancestor(rid, generation) " "VALUES(?,?)"); if(rc) goto dberr; for(node = fsl_vpath_first(&path);