Index: src/blob.c ================================================================== --- src/blob.c +++ src/blob.c @@ -920,11 +920,11 @@ */ int blob_uncompress(Blob *pIn, Blob *pOut){ unsigned int nOut; unsigned char *inBuf; unsigned int nIn = blob_size(pIn); - Blob temp; + Blob temp = empty_blob; int rc; unsigned long int nOut2; if( nIn<=4 ){ return 0; } Index: src/content.c ================================================================== --- src/content.c +++ src/content.c @@ -95,11 +95,11 @@ p = &contentCache.a[contentCache.n++]; p->rid = rid; p->age = contentCache.nextAge++; contentCache.szTotal += blob_size(pBlob); p->content = *pBlob; - blob_zero(pBlob); + *pBlob = empty_blob; bag_insert(&contentCache.inCache, rid); } /* ** Clear the content cache. @@ -259,11 +259,11 @@ }else{ int n = 1; int nAlloc = 10; int *a = 0; int mx; - Blob delta, next; + Blob delta = empty_blob, next = empty_blob; a = fossil_malloc( sizeof(a[0])*nAlloc ); a[0] = rid; a[1] = nextRid; n = 1; Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -211,11 +211,11 @@ ** one is processed. All statements beyond the first are silently ignored. */ int db_vprepare(Stmt *pStmt, int errOk, const char *zFormat, va_list ap){ int rc; char *zSql; - blob_zero(&pStmt->sql); + pStmt->sql = empty_blob; blob_vappendf(&pStmt->sql, zFormat, ap); va_end(ap); zSql = blob_str(&pStmt->sql); nPrepare++; rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt->pStmt, 0); Index: src/deltacmd.c ================================================================== --- src/deltacmd.c +++ src/deltacmd.c @@ -77,11 +77,11 @@ ** ** Return the length of the target. Return -1 if there is an error. */ int blob_delta_apply(Blob *pOriginal, Blob *pDelta, Blob *pTarget){ int len, n; - Blob out; + Blob out = empty_blob; n = delta_output_size(blob_buffer(pDelta), blob_size(pDelta)); blob_zero(&out); if( n<0 ) return -1; blob_resize(&out, n); Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -619,10 +619,24 @@ /************************************************************************** ** The basic difference engine is above. What follows is the annotation ** engine. Both are in the same file since they share many components. */ +/* +** Linked list of strings, labels used in the annotator code. +** The elements of the list and the pointed string (str) +** will be freed once they become totally unreferenced +** (nref == 0). +*/ +struct Label +{ + struct Label *prev; /* previous element */ + struct Label *next; /* next element */ + char *str; /* The label string */ + int nref; /* Number of references to the string */ +}; + /* ** The status of an annotation operation is recorded by an instance ** of the following structure. */ typedef struct Annotator Annotator; @@ -630,29 +644,31 @@ DContext c; /* The diff-engine context */ struct AnnLine { /* Lines of the original files... */ const char *z; /* The text of the line */ short int n; /* Number of bytes (omitting trailing space and \n) */ short int iLevel; /* Level at which tag was set */ - const char *zSrc; /* Tag showing origin of this line */ + struct Label *zSrc; /* Tag showing origin of this line */ } *aOrig; int nOrig; /* Number of elements in aOrig[] */ int nNoSrc; /* Number of entries where aOrig[].zSrc==NULL */ int iLevel; /* Current level */ int nVers; /* Number of versions analyzed */ - char **azVers; /* Names of versions analyzed */ + struct Label **azVers; /* Names of versions analyzed */ + Blob toAnnotate; + struct Label *firstLabel; }; /* ** Initialize the annotation process by specifying the file that is ** to be annotated. The annotator takes control of the input Blob and ** will release it when it is finished with it. */ -static int annotation_start(Annotator *p, Blob *pInput){ +static int annotation_start(Annotator *p){ int i; - memset(p, 0, sizeof(*p)); - p->c.aTo = break_into_lines(blob_str(pInput), blob_size(pInput),&p->c.nTo,1); + p->c.aTo = break_into_lines(blob_str(&p->toAnnotate), + blob_size(&p->toAnnotate),&p->c.nTo,1); if( p->c.aTo==0 ){ return 1; } p->aOrig = fossil_malloc( sizeof(p->aOrig[0])*p->c.nTo ); for(i=0; ic.nTo; i++){ @@ -669,20 +685,21 @@ ** being annotated. Do another step of the annotation. Return true ** if additional annotation is required. zPName is the tag to insert ** on each line of the file being annotated that was contributed by ** pParent. Memory to hold zPName is leaked. */ -static int annotation_step(Annotator *p, Blob *pParent, char *zPName){ +static int annotation_step(Annotator *p, Blob *pParent, struct Label *zPName){ int i, j; int lnTo; int iPrevLevel; int iThisLevel; /* Prepare the parent file to be diffed */ p->c.aFrom = break_into_lines(blob_str(pParent), blob_size(pParent), &p->c.nFrom, 1); if( p->c.aFrom==0 ){ + free(p->c.aFrom); return 1; } /* Compute the differences going from pParent to the file being ** annotated. */ @@ -696,11 +713,24 @@ iThisLevel = p->iLevel; for(i=lnTo=0; ic.nEdit; i+=3){ struct AnnLine *x = &p->aOrig[lnTo]; for(j=0; jc.aEdit[i]; j++, lnTo++, x++){ if( x->zSrc==0 || x->iLevel==iPrevLevel ){ + if (x->zSrc!=0) + { + if(--x->zSrc->nref == 0) + { + free(x->zSrc->str); + if (x->zSrc->prev) + x->zSrc->prev->next = x->zSrc->next; + if (x->zSrc->next) + x->zSrc->next->prev = x->zSrc->prev; + free(x->zSrc); + } + } x->zSrc = zPName; + ++zPName->nref; x->iLevel = iThisLevel; } } lnTo += p->c.aEdit[i+2]; } @@ -711,11 +741,11 @@ p->c.nEdit = 0; p->c.nEditAlloc = 0; /* Clear out the from file */ free(p->c.aFrom); - blob_zero(pParent); + blob_reset(pParent); /* Return no errors */ return 0; } @@ -722,32 +752,49 @@ /* ** COMMAND: test-annotate-step */ void test_annotate_step_cmd(void){ - Blob orig, b; + Blob b = empty_blob; Annotator x; int i; if( g.argc<4 ) usage("RID1 RID2 ..."); db_must_be_within_tree(); - blob_zero(&b); - content_get(name_to_rid(g.argv[2]), &orig); - if( annotation_start(&x, &orig) ){ + memset(&x, 0, sizeof(x)); + x.toAnnotate = empty_blob; + content_get(name_to_rid(g.argv[2]), &x.toAnnotate); + if( annotation_start(&x) ){ fossil_fatal("binary file"); } for(i=3; istr = g.argv[i-1]; + l->nref = 0; + l->next = x.firstLabel; + if (x.firstLabel) + x.firstLabel->prev = l; + x.firstLabel = l; + if( annotation_step(&x, &b, l) ){ fossil_fatal("binary file"); } } for(i=0; istr; if( zSrc==0 ) zSrc = g.argv[g.argc-1]; fossil_print("%10s: %.*s\n", zSrc, x.aOrig[i].n, x.aOrig[i].z); + } + while(x.firstLabel) { + struct Label *l; + l = x.firstLabel->next; + assert(x.firstLabel->nref > 0); + free(x.firstLabel->str); + free(x.firstLabel); + x.firstLabel = l; } } /* Annotation flags */ #define ANN_FILE_VERS 0x001 /* Show file version rather than commit version */ @@ -763,28 +810,28 @@ int mid, /* Use the version of the file in this check-in */ int webLabel, /* Use web-style annotations if true */ int iLimit, /* Limit the number of levels if greater than zero */ int annFlags /* Flags to alter the annotation */ ){ - Blob toAnnotate; /* Text of the final (mid) version of the file */ - Blob step; /* Text of previous revision */ + Blob step = empty_blob; /* Text of previous revision */ int rid; /* Artifact ID of the file being annotated */ - char *zLabel; /* Label to apply to a line */ Stmt q; /* Query returning all ancestor versions */ /* Initialize the annotation */ rid = db_int(0, "SELECT fid FROM mlink WHERE mid=%d AND fnid=%d",mid,fnid); if( rid==0 ){ fossil_panic("file #%d is unchanged in manifest #%d", fnid, mid); } - if( !content_get(rid, &toAnnotate) ){ + memset(p, 0, sizeof(*p)); + p->toAnnotate = empty_blob; + if( !content_get(rid, &p->toAnnotate) ){ fossil_panic("unable to retrieve content of artifact #%d", rid); } db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)"); if( iLimit<=0 ) iLimit = 1000000000; compute_direct_ancestors(mid, iLimit); - annotation_start(p, &toAnnotate); + annotation_start(p); db_prepare(&q, "SELECT mlink.fid," " (SELECT uuid FROM blob WHERE rid=mlink.%s)," " date(event.mtime), " @@ -802,26 +849,42 @@ while( db_step(&q)==SQLITE_ROW ){ int pid = db_column_int(&q, 0); const char *zUuid = db_column_text(&q, 1); const char *zDate = db_column_text(&q, 2); const char *zUser = db_column_text(&q, 3); + struct Label *l = fossil_malloc(sizeof(*l)); + l->nref = 0; + l->next = p->firstLabel; + l->prev = 0; + if (p->firstLabel) + p->firstLabel->prev = l; if( webLabel ){ - zLabel = mprintf( + l->str = mprintf( "%.10s %s %9.9s", g.zTop, zUuid, zUuid, zDate, zUser ); }else{ - zLabel = mprintf("%.10s %s %9.9s", zUuid, zDate, zUser); + l->str = mprintf("%.10s %s %9.9s", zUuid, zDate, zUser); } + p->firstLabel = l; p->nVers++; p->azVers = fossil_realloc(p->azVers, p->nVers*sizeof(p->azVers[0]) ); - p->azVers[p->nVers-1] = zLabel; + p->azVers[p->nVers-1] = l; content_get(pid, &step); - annotation_step(p, &step, zLabel); + annotation_step(p, &step, l); + if (l->nref == 0) + { + free(l->str); + p->firstLabel = l->next; + if (l->next) + l->next->prev = 0; + free(l); + } blob_reset(&step); } db_finalize(&q); + free(p->c.aTo); } /* ** WEBPAGE: annotate ** @@ -853,23 +916,35 @@ if( P("log") ){ int i; @

Versions analyzed:

@
    for(i=0; i%s(ann.azVers[i]) + @
  1. %s(ann.azVers[i]->str)
  2. } @
@
@

Annotation:

} @
   for(i=0; istr): %h(ann.aOrig[i].z)
   }
   @ 
style_footer(); + + free(ann.azVers); + free(ann.aOrig); + blob_reset(&ann.toAnnotate); + while(ann.firstLabel) { + struct Label *l; + l = ann.firstLabel->next; + assert(ann.firstLabel->nref > 0); + free(ann.firstLabel->str); + free(ann.firstLabel); + ann.firstLabel = l; + } } /* ** COMMAND: annotate ** @@ -885,11 +960,11 @@ */ void annotate_cmd(void){ int fnid; /* Filename ID */ int fid; /* File instance ID */ int mid; /* Manifest where file was checked in */ - Blob treename; /* FILENAME translated to canonical form */ + Blob treename = empty_blob; /* FILENAME translated to canonical form */ char *zFilename; /* Cannonical filename */ Annotator ann; /* The annotation of the file */ int i; /* Loop counter */ const char *zLimit; /* The value to the --limit option */ int iLimit; /* How far back in time to look */ @@ -914,22 +989,34 @@ } fid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFilename); if( fid==0 ){ fossil_fatal("not part of current checkout: %s", zFilename); } + blob_reset(&treename); mid = db_int(0, "SELECT mid FROM mlink WHERE fid=%d AND fnid=%d", fid, fnid); if( mid==0 ){ fossil_panic("unable to find manifest"); } if( fileVers ) annFlags |= ANN_FILE_VERS; annotate_file(&ann, fnid, mid, 0, iLimit, annFlags); if( showLog ){ for(i=0; istr); } printf("---------------------------------------------------\n"); } for(i=0; istr, ann.aOrig[i].n, ann.aOrig[i].z); + } + free(ann.azVers); + free(ann.aOrig); + blob_reset(&ann.toAnnotate); + while(ann.firstLabel) { + struct Label *l; + l = ann.firstLabel->next; + assert(ann.firstLabel->nref > 0); + free(ann.firstLabel->str); + free(ann.firstLabel); + ann.firstLabel = l; } } Index: src/diffcmd.c ================================================================== --- src/diffcmd.c +++ src/diffcmd.c @@ -183,14 +183,14 @@ /* ** Do a diff against a single file named in zFileTreeName from version zFrom ** against the same file on disk. */ static void diff_one_against_disk( - const char *zFrom, /* Name of file */ + const char *zFrom, /* Version to difference from */ const char *zDiffCmd, /* Use this "diff" command */ int ignoreEolWs, /* Ignore whitespace changes at end of lines */ - const char *zFileTreeName + const char *zFileTreeName /* Name of file */ ){ Blob fname; Blob content; int isLink; file_tree_name(zFileTreeName, &fname, 1); @@ -225,11 +225,11 @@ vid = db_lget_int("checkout", 0); vfile_check_signature(vid, 1, 0); blob_zero(&sql); db_begin_transaction(); if( zFrom ){ - int rid = name_to_typed_rid(zFrom, "ci"); + int rid = extended_ci_name_to_rid(zFrom); if( !is_a_version(rid) ){ fossil_fatal("no such check-in: %s", zFrom); } load_vfile_from_rid(rid); blob_appendf(&sql, Index: src/file.c ================================================================== --- src/file.c +++ src/file.c @@ -757,11 +757,11 @@ ** ** The root of the tree is defined by the g.zLocalRoot variable. */ int file_tree_name(const char *zOrigName, Blob *pOut, int errFatal){ int n; - Blob full; + Blob full = empty_blob; int nFull; char *zFull; blob_zero(pOut); db_must_be_within_tree(); Index: src/manifest.c ================================================================== --- src/manifest.c +++ src/manifest.c @@ -938,11 +938,11 @@ */ Manifest *manifest_get_by_name(const char *zName, int *pRid){ int rid; Manifest *p; - rid = name_to_typed_rid(zName, "ci"); + rid = extended_ci_name_to_rid(zName); if( !is_a_version(rid) ){ fossil_fatal("no such checkin: %s", zName); } if( pRid ) *pRid = rid; p = manifest_get(rid, CFTYPE_MANIFEST); Index: src/merge.c ================================================================== --- src/merge.c +++ src/merge.c @@ -328,10 +328,14 @@ ); idv = db_last_insert_rowid(); db_multi_exec("UPDATE fv SET idv=%d WHERE rowid=%d", idv, rowid); zName = db_column_text(&q, 2); fossil_print("ADDED %s\n", zName); + if ( file_wd_isfile_or_link(zName) ) { + fossil_print("***** The extra file %s has been overwritten\n", zName); + nConflict++; + } if( !nochangeFlag ){ undo_save(zName); vfile_to_disk(0, idm, 0, 0); } } @@ -497,11 +501,12 @@ /* Report on conflicts */ if( nConflict && !nochangeFlag ){ fossil_warning( - "WARNING: merge conflicts - see messages above for details.\n"); + "WARNING: %d merge conflicts - see messages above for details.\n", + nConflict); } /* ** Clean up the mid and pid VFILE entries. Then commit the changes. */ Index: src/name.c ================================================================== --- src/name.c +++ src/name.c @@ -387,7 +387,70 @@ return 0; }else{ rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &name); blob_reset(&name); } + return rid; +} + + +/* +** Similar to name_to_typed_rid(zName, "ci"), +** but it accepts more variants for the name. The additional variants are: +** +** checkout The current checkout +** parent The parent of the current checkout +** pivot:id1:id2 The pivot between id1 and id2 +** +** It should allow easier naming of checkins, both in 'diff' and 'update' +** commands for example. +*/ +int extended_ci_name_to_rid(const char *zName){ + int rid; + + rid = db_lget_int("checkout", 0); + + if( fossil_strcmp(zName, "checkout")==0 ){ + rid = db_lget_int("checkout", 0); + } + else if( fossil_strcmp(zName, "parent")==0 ){ + int cid; + cid = db_lget_int("checkout", 0); + if (cid == 0) + fossil_fatal("cannot find current checkout version"); + rid = db_int(0, "SELECT pid FROM plink WHERE cid=%d", cid); + if (rid == 0) + fossil_fatal("cannot find the parent of the current checkout version"); + } + else if( strlen(zName) > 6 && memcmp(zName, "pivot:", 6)==0 ){ + /* This conflicts with 'tag:', but I don't know a better char than : */ + const char *zPair = zName + 6; + char *zIdName; + int rid1, rid2; + char *zPair2 = strdup(zPair); /* Just for constness and strtok */ + + zIdName = strtok(zPair2,":"); + + if (!zIdName) + fossil_fatal("Cannot parse pivot#checkin1#checkin2"); + rid1 = name_to_typed_rid(zIdName, "ci"); + if (rid1 == 0) + fossil_fatal("Cannot find the check-in %s", zIdName); + + zIdName = strtok(NULL,":"); + rid2 = name_to_typed_rid(zIdName, "ci"); + + pivot_set_primary(rid1); + pivot_set_secondary(rid2); + rid = pivot_find(); + + if (rid == 0) + fossil_fatal("Cannot find the pivot of %s", zName); + + free(zPair2); + } + else{ + rid = name_to_typed_rid(zName, "ci"); + } + return rid; } Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -131,11 +131,11 @@ /* If VERSION is "latest", then use the same algorithm to find the ** target as if VERSION were omitted and the --latest flag is present. */ latestFlag = 1; }else{ - tid = name_to_typed_rid(g.argv[2],"ci"); + tid = extended_ci_name_to_rid(g.argv[2]); if( tid==0 ){ fossil_fatal("no such version: %s", g.argv[2]); }else if( !is_a_version(tid) ){ fossil_fatal("no such version: %s", g.argv[2]); } @@ -360,10 +360,14 @@ fossil_print("CONFLICT %s\n", zName); nConflict++; }else if( idt>0 && idv==0 ){ /* File added in the target. */ fossil_print("ADD %s\n", zName); + if ( file_wd_isfile_or_link(zName) ) { + fossil_print("***** The extra file %s has been overwritten\n", zName); + nConflict++; + } undo_save(zName); if( !nochangeFlag ) vfile_to_disk(0, idt, 0, 0); }else if( idt>0 && idv>0 && ridt!=ridv && chnged==0 ){ /* The file is unedited. Change it to the target version */ undo_save(zName); @@ -553,11 +557,11 @@ Manifest *pManifest; ManifestFile *pFile; int rid=0; if( revision ){ - rid = name_to_typed_rid(revision,"ci"); + rid = extended_ci_name_to_rid(revision); }else{ rid = db_lget_int("checkout", 0); } if( !is_a_version(rid) ){ if( errCode>0 ) return errCode; Index: test/merge5.test ================================================================== --- test/merge5.test +++ test/merge5.test @@ -38,14 +38,18 @@ } } catch {exec $::fossilexe info} res puts res=$res -if {![regexp {not within an open checkout} $res]} { +if {![regexp {use --repository} $res]} { puts stderr "Cannot run this test within an open checkout" return } +# +# Fossil will write data on $HOME, running 'fossil open' here. +# We need not to clutter the $HOME of the test caller. +set env(HOME) [pwd] # Construct a test repository # exec sqlite3 m5.fossil <$testdir/${testfile}_repo.sql fossil rebuild m5.fossil Index: test/merge_renames.test ================================================================== --- test/merge_renames.test +++ test/merge_renames.test @@ -7,10 +7,16 @@ puts res=$res if {![regexp {use --repository} $res]} { puts stderr "Cannot run this test within an open checkout" return } + + +# Fossil will write data on $HOME, running 'fossil new' here. +# We need not to clutter the $HOME of the test caller. +set env(HOME) [pwd] + ###################################### # Test 1 # # Reported: Ticket [554f44ee74e3d] # ###################################### Index: www/changes.wiki ================================================================== --- www/changes.wiki +++ www/changes.wiki @@ -1,7 +1,22 @@ Change Log +

Changes For Version 1.20 (upcoming)

+ * Added side-by-side diffs in HTML interface + * Fixed annotate to show "more relevant" versions of lines in some cases. + * Timeline now shows tag changes (requires rebuild) + * Added support for symlinks. + * New command: ticket history + * Several SSL improvements. Disabled SSLv2 in HTTPS client. + * Added -R REPOFILE support to several more CLI commands. + * Updated sqlite3 to 3.7.9. + * Generated tarfiles now have constant timestamps, to avoid them having a new checksum each time they are generated. + * A number of minor HTML-related tweaks and fixes. + * Added --args FILENAME global CLI argument to import arbitrary CLI arguments from a file (e.g. long file lists). + * Fixed significant memory leak in annotation of files with long histories. + * Added warnings when a merge operation overwrites local copies (UNDO is available, but previously this condition normally went silently unnoticed). +

Changes For Version 1.19 (2011-09-02)

* Added a ./configure script based on autosetup. * Added the "[/help/winsrv | fossil winsrv]" command for creating a Fossil service on windows systems.