Index: src/branch.c ================================================================== --- src/branch.c +++ src/branch.c @@ -323,11 +323,12 @@ @ WHERE plink.pid=event.objid @ AND tagxref.rid=plink.cid @ AND tagxref.tagid=(SELECT tagid FROM tag WHERE tagname='branch') @ AND tagtype>0), @ count(*), -@ (SELECT uuid FROM blob WHERE rid=tagxref.rid) +@ (SELECT uuid FROM blob WHERE rid=tagxref.rid), +@ event.bgcolor @ FROM tagxref, tag, event @ WHERE tagxref.tagid=tag.tagid @ AND tagxref.tagtype>0 @ AND tag.tagname='branch' @ AND event.objid=tagxref.rid @@ -344,14 +345,16 @@ ** if there are no query parameters. */ static void new_brlist_page(void){ Stmt q; double rNow; + int show_colors = PB("colors"); login_check_credentials(); if( !g.perm.Read ){ login_needed(g.anon.Read); return; } style_header("Branches"); style_adunit_config(ADUNIT_RIGHT_OK); + style_submenu_binary("colors", "Show branch colors", "No branch colors", 0); login_anonymous_available(); db_prepare(&q, brlistQuery/*works-like:""*/); rNow = db_double(0.0, "SELECT julianday('now')"); @
@@ -367,14 +370,26 @@ double rMtime = db_column_double(&q, 1); int isClosed = db_column_int(&q, 2); const char *zMergeTo = db_column_text(&q, 3); int nCkin = db_column_int(&q, 4); const char *zLastCkin = db_column_text(&q, 5); + const char *zBgClr = db_column_text(&q, 6); char *zAge = human_readable_age(rNow - rMtime); sqlite3_int64 iMtime = (sqlite3_int64)(rMtime*86400.0); if( zMergeTo && zMergeTo[0]==0 ) zMergeTo = 0; - @ + if( zBgClr == 0 ){ + if( zBranch==0 || strcmp(zBranch,"trunk")==0 ){ + zBgClr = 0; + }else{ + zBgClr = hash_color(zBranch); + } + } + if( show_colors ){ + @ + }else{ + @ + } @ @ @ fossil_free(zAge); @ Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -870,31 +870,113 @@ db_tolocal_function, 0, 0); sqlite3_create_function(db, "fromLocal", 0, SQLITE_UTF8, 0, db_fromlocal_function, 0, 0); } +#if USE_SEE +/* +** This is a pointer to the saved database encryption key string. +*/ +static char *zSavedKey = 0; + +/* +** This is the size of the saved database encryption key, in bytes. +*/ +size_t savedKeySize = 0; + +/* +** This function returns the saved database encryption key -OR- zero if +** no database encryption key is saved. +*/ +static char *db_get_saved_encryption_key(){ + return zSavedKey; +} + +/* +** This function arranges for the database encryption key to be securely +** saved in non-pagable memory (on platforms where this is possible). +*/ +static void db_save_encryption_key( + Blob *pKey +){ + void *p = NULL; + size_t n = 0; + size_t pageSize = 0; + size_t blobSize = 0; + + blobSize = blob_size(pKey); + if( blobSize==0 ) return; + fossil_get_page_size(&pageSize); + assert( pageSize>0 ); + if( blobSize>pageSize ){ + fossil_fatal("key blob too large: %u versus %u", blobSize, pageSize); + } + p = fossil_secure_alloc_page(&n); + assert( p!=NULL ); + assert( n==pageSize ); + assert( n>=blobSize ); + memcpy(p, blob_str(pKey), blobSize); + zSavedKey = p; + savedKeySize = n; +} + +/* +** This function arranges for the saved database encryption key to be +** securely zeroed, unlocked (if necessary), and freed. +*/ +void db_unsave_encryption_key(){ + fossil_secure_free_page(zSavedKey, savedKeySize); + zSavedKey = NULL; + savedKeySize = 0; +} + +/* +** This function sets the saved database encryption key to the specified +** string value, allocating or freeing the underlying memory if needed. +*/ +void db_set_saved_encryption_key( + Blob *pKey +){ + if( zSavedKey!=NULL ){ + size_t blobSize = blob_size(pKey); + if( blobSize==0 ){ + db_unsave_encryption_key(); + }else{ + if( blobSize>savedKeySize ){ + fossil_fatal("key blob too large: %u versus %u", + blobSize, savedKeySize); + } + fossil_secure_zero(zSavedKey, savedKeySize); + memcpy(zSavedKey, blob_str(pKey), blobSize); + } + }else{ + db_save_encryption_key(pKey); + } +} +#endif /* USE_SEE */ + /* ** If the database file zDbFile has a name that suggests that it is -** encrypted, then prompt for the encryption key and return it in the -** blob *pKey. Or, if the encryption key has previously been requested, -** just return a copy of the previous result. +** encrypted, then prompt for the database encryption key and return it +** in the blob *pKey. Or, if the encryption key has previously been +** requested, just return a copy of the previous result. The blob in +** *pKey must be initialized. */ -static void db_encryption_key( +static void db_maybe_obtain_encryption_key( const char *zDbFile, /* Name of the database file */ Blob *pKey /* Put the encryption key here */ ){ - blob_init(pKey, 0, 0); #if USE_SEE if( sqlite3_strglob("*.efossil", zDbFile)==0 ){ - static char *zSavedKey = 0; - if( zSavedKey ){ - blob_set(pKey, zSavedKey); + char *zKey = db_get_saved_encryption_key(); + if( zKey ){ + blob_set(pKey, zKey); }else{ char *zPrompt = mprintf("\rencryption key for '%s': ", zDbFile); prompt_for_password(zPrompt, pKey, 0); fossil_free(zPrompt); - zSavedKey = fossil_strdup(blob_str(pKey)); + db_set_saved_encryption_key(pKey); } } #endif } @@ -915,14 +997,16 @@ g.zVfsName ); if( rc!=SQLITE_OK ){ db_err("[%s]: %s", zDbName, sqlite3_errmsg(db)); } - db_encryption_key(zDbName, &key); + blob_init(&key, 0, 0); + db_maybe_obtain_encryption_key(zDbName, &key); if( blob_size(&key)>0 ){ char *zCmd = sqlite3_mprintf("PRAGMA key(%Q)", blob_str(&key)); sqlite3_exec(db, zCmd, 0, 0, 0); + fossil_secure_zero(zCmd, strlen(zCmd)); sqlite3_free(zCmd); } blob_reset(&key); sqlite3_busy_timeout(db, 5000); sqlite3_wal_autocheckpoint(db, 1); /* Set to checkpoint frequently */ @@ -955,14 +1039,19 @@ /* ** zDbName is the name of a database file. Attach zDbName using ** the name zLabel. */ void db_attach(const char *zDbName, const char *zLabel){ + char *zCmd; Blob key; - db_encryption_key(zDbName, &key); - db_multi_exec("ATTACH DATABASE %Q AS %Q KEY %Q", - zDbName, zLabel, blob_str(&key)); + blob_init(&key, 0, 0); + db_maybe_obtain_encryption_key(zDbName, &key); + zCmd = sqlite3_mprintf("ATTACH DATABASE %Q AS %Q KEY %Q", + zDbName, zLabel, blob_str(&key)); + db_multi_exec(zCmd /*works-like:""*/); + fossil_secure_zero(zCmd, strlen(zCmd)); + sqlite3_free(zCmd); blob_reset(&key); } /* ** Change the schema name of the "main" database to zLabel. Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -115,10 +115,34 @@ int nFrom; /* Number of lines in aFrom[] */ DLine *aTo; /* File on right side of the diff */ int nTo; /* Number of lines in aTo[] */ int (*same_fn)(const DLine*,const DLine*); /* comparison function */ }; + +/* +** Count the number of lines in the input string. Include the last line +** in the count even if it lacks the \n terminator. If an empty string +** is specified, the number of lines is zero. For the purposes of this +** function, a string is considered empty if it contains no characters +** -OR- it contains only NUL characters. +*/ +static int count_lines( + const char *z, + int n, + int *pnLine +){ + int nLine; + const char *zNL, *z2; + for(nLine=0, z2=z; (zNL = strchr(z2,'\n'))!=0; z2=zNL+1, nLine++){} + if( z2[0]!='\0' ){ + nLine++; + do{ z2++; }while( z2[0]!='\0' ); + } + if( n!=(int)(z2-z) ) return 0; + if( pnLine ) *pnLine = nLine; + return 1; +} /* ** Return an array of DLine objects containing a pointer to the ** start of each line and a hash of that line. The lower ** bits of the hash store the length of each line. @@ -140,32 +164,26 @@ u64 diffFlags ){ int nLine, i, k, nn, s, x; unsigned int h, h2; DLine *a; - const char *zNL, *z2; - - /* Count the number of lines in the input file. Include the last line - ** in the count even if it lacks the \n terminator - */ - for(nLine=0, z2=z; (zNL = strchr(z2,'\n'))!=0; z2=zNL+1, nLine++){} - if( z2[0]!=0 ){ - nLine++; - do{ z2++; }while( z2[0] ); - } - if( n!=(int)(z2-z) ) return 0; - + const char *zNL; + + if( count_lines(z, n, &nLine)==0 ){ + return 0; + } + assert( nLine>0 || z[0]=='\0' ); a = fossil_malloc( sizeof(a[0])*nLine ); memset(a, 0, sizeof(a[0])*nLine); if( nLine==0 ){ *pnLine = 0; return a; } i = 0; do{ zNL = strchr(z,'\n'); - if( zNL==0 ) zNL = z+strlen(z); + if( zNL==0 ) zNL = z+n; nn = (int)(zNL - z); if( nn>LENGTH_MASK ){ fossil_free(a); return 0; } @@ -181,14 +199,15 @@ } if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){ int numws = 0; while( sname is dynamically allocated and is owned by the caller upon return. */ -int create_mark(int rid, struct mark_t *mark){ +int create_mark(int rid, struct mark_t *mark, unsigned int *unused_mark){ char sid[13]; char *zUuid = rid_to_uuid(rid); - if(!zUuid){ + if( !zUuid ){ fossil_trace("Undefined rid=%d\n", rid); return -1; } mark->rid = rid; - sqlite3_snprintf(sizeof(sid), sid, ":%d", COMMITMARK(rid)); + sqlite3_snprintf(sizeof(sid), sid, ":%d", *unused_mark); + *unused_mark += 1; mark->name = fossil_strdup(sid); sqlite3_snprintf(sizeof(mark->uuid), mark->uuid, "%s", zUuid); free(zUuid); insert_commit_xref(mark->rid, mark->name, mark->uuid); return 0; @@ -156,19 +161,22 @@ /* ** mark_name_from_rid() ** Find the mark associated with the given rid. Mark names always start ** with ':', and are pulled from the 'xmark' temporary table. -** This function returns NULL if the rid does not exist in the 'xmark' table. -** Otherwise, it returns the name of the mark, which is dynamically allocated -** and is owned by the caller of this function. +** If the given rid doesn't have a mark associated with it yet, one is +** created with a value of *unused_mark. +** *unused_mark functions exactly as in create_mark(). +** This function returns NULL if the rid does not have an associated UUID, +** (i.e. is not valid). Otherwise, it returns the name of the mark, which is +** dynamically allocated and is owned by the caller of this function. */ -char * mark_name_from_rid(int rid){ +char * mark_name_from_rid(int rid, unsigned int *unused_mark){ char *zMark = db_text(0, "SELECT tname FROM xmark WHERE trid=%d", rid); - if(zMark==NULL){ + if( zMark==NULL ){ struct mark_t mark; - if(create_mark(rid, &mark)==0){ + if( create_mark(rid, &mark, unused_mark)==0 ){ zMark = mark.name; }else{ return NULL; } } @@ -185,43 +193,52 @@ ** database. Otherwise, 0 is returned. ** mark->name is dynamically allocated, and owned by the caller. */ int parse_mark(char *line, struct mark_t *mark){ char *cur_tok; + char type_; cur_tok = strtok(line, " \t"); - if(!cur_tok||strlen(cur_tok)<2){ + if( !cur_tok || strlen(cur_tok)<2 ){ return -1; } mark->rid = atoi(&cur_tok[1]); - if(cur_tok[0]!='c'){ + type_ = cur_tok[0]; + if( type_!='c' && type_!='b' ){ /* This is probably a blob mark */ mark->name = NULL; return 0; } cur_tok = strtok(NULL, " \t"); - if(!cur_tok){ + if( !cur_tok ){ /* This mark was generated by an older version of Fossil and doesn't ** include the mark name and uuid. create_mark() will name the new mark ** exactly as it was when exported to git, so that we should have a ** valid mapping from git sha1<->mark name<->fossil sha1. */ - return create_mark(mark->rid, mark); + unsigned int mid; + if( type_=='c' ){ + mid = COMMITMARK(mark->rid); + } + else{ + mid = BLOBMARK(mark->rid); + } + return create_mark(mark->rid, mark, &mid); }else{ mark->name = fossil_strdup(cur_tok); } cur_tok = strtok(NULL, "\n"); - if(!cur_tok||strlen(cur_tok)!=40){ + if( !cur_tok || strlen(cur_tok)!=40 ){ free(mark->name); fossil_trace("Invalid SHA-1 in marks file: %s\n", cur_tok); return -1; }else{ sqlite3_snprintf(sizeof(mark->uuid), mark->uuid, "%s", cur_tok); } /* make sure that rid corresponds to UUID */ - if(fast_uuid_to_rid(mark->uuid)!=mark->rid){ + if( fast_uuid_to_rid(mark->uuid)!=mark->rid ){ free(mark->name); fossil_trace("Non-existent SHA-1 in marks file: %s\n", mark->uuid); return -1; } @@ -233,40 +250,66 @@ /* ** import_marks() ** Import the marks specified in file 'f' into the 'xmark' table. ** If 'blobs' is non-null, insert all blob marks into it. ** If 'vers' is non-null, insert all commit marks into it. +** If 'unused_marks' is non-null, upon return of this function, all values +** x >= *unused_marks are free to use as marks, i.e. they do not clash with +** any marks appearing in the marks file. ** Each line in the file must be at most 100 characters in length. This ** seems like a reasonable maximum for a 40-character uuid, and 1-13 ** character rid. ** The function returns -1 if any of the lines in file 'f' are malformed, ** or the rid/uuid information doesn't match what is in the repository ** database. Otherwise, 0 is returned. */ -int import_marks(FILE* f, Bag *blobs, Bag *vers){ +int import_marks(FILE* f, Bag *blobs, Bag *vers, unsigned int *unused_mark){ char line[101]; while(fgets(line, sizeof(line), f)){ struct mark_t mark; - if(strlen(line)==100&&line[99]!='\n'){ + if( strlen(line)==100 && line[99]!='\n' ){ /* line too long */ return -1; } if( parse_mark(line, &mark)<0 ){ return -1; }else if( line[0]=='b' ){ - /* Don't import blob marks into 'xmark' table--git doesn't use them, - ** so they need to be left free for git to reuse. */ - if(blobs!=NULL){ + if( blobs!=NULL ){ bag_insert(blobs, mark.rid); } - }else if( vers!=NULL ){ - bag_insert(vers, mark.rid); + }else{ + if( vers!=NULL ){ + bag_insert(vers, mark.rid); + } + } + if( unused_mark!=NULL ){ + unsigned int mid = atoi(mark.name + 1); + if( mid>=*unused_mark ){ + *unused_mark = mid + 1; + } } free(mark.name); } return 0; } + +void export_mark(FILE* f, int rid, char obj_type) +{ + unsigned int z = 0; + char *zUuid = rid_to_uuid(rid); + char *zMark; + if( zUuid==NULL ){ + fossil_trace("No uuid matching rid=%d when exporting marks\n", rid); + return; + } + /* Since rid is already in the 'xmark' table, the value of z won't be + ** used, but pass in a valid pointer just to be safe. */ + zMark = mark_name_from_rid(rid, &z); + fprintf(f, "%c%d %s %s\n", obj_type, rid, zMark, zUuid); + free(zMark); + free(zUuid); +} /* ** If 'blobs' is non-null, it must point to a Bag of blob rids to be ** written to disk. Blob rids are written as 'b'. ** If 'vers' is non-null, it must point to a Bag of commit rids to be @@ -275,32 +318,24 @@ ** This function does not fail, but may produce errors if a uuid cannot ** be found for an rid in 'vers'. */ void export_marks(FILE* f, Bag *blobs, Bag *vers){ int rid; + if( blobs!=NULL ){ rid = bag_first(blobs); - if(rid!=0){ + if( rid!=0 ){ do{ - fprintf(f, "b%d\n", rid); - }while((rid = bag_next(blobs, rid))!=0); + export_mark(f, rid, 'b'); + }while( (rid = bag_next(blobs, rid))!=0 ); } } if( vers!=NULL ){ rid = bag_first(vers); if( rid!=0 ){ do{ - char *zUuid = rid_to_uuid(rid); - char *zMark; - if(zUuid==NULL){ - fossil_trace("No uuid matching rid=%d when exporting marks\n", rid); - continue; - } - zMark = mark_name_from_rid(rid); - fprintf(f, "c%d %s %s\n", rid, zMark, zUuid); - free(zMark); - free(zUuid); + export_mark(f, rid, 'c'); }while( (rid = bag_next(vers, rid))!=0 ); } } } @@ -336,10 +371,11 @@ */ void export_cmd(void){ Stmt q, q2, q3; int i; Bag blobs, vers; + unsigned int unused_mark = 1; const char *markfile_in; const char *markfile_out; bag_init(&blobs); bag_init(&vers); @@ -362,25 +398,25 @@ f = fossil_fopen(markfile_in, "r"); if( f==0 ){ fossil_fatal("cannot open %s for reading", markfile_in); } - if(import_marks(f, &blobs, &vers)<0){ + if( import_marks(f, &blobs, &vers, &unused_mark)<0 ){ fossil_fatal("error importing marks from file: %s", markfile_in); } db_prepare(&qb, "INSERT OR IGNORE INTO oldblob VALUES (:rid)"); db_prepare(&qc, "INSERT OR IGNORE INTO oldcommit VALUES (:rid)"); rid = bag_first(&blobs); - if(rid!=0){ + if( rid!=0 ){ do{ db_bind_int(&qb, ":rid", rid); db_step(&qb); db_reset(&qb); }while((rid = bag_next(&blobs, rid))!=0); } rid = bag_first(&vers); - if(rid!=0){ + if( rid!=0 ){ do{ db_bind_int(&qc, ":rid", rid); db_step(&qc); db_reset(&qc); }while((rid = bag_next(&vers, rid))!=0); @@ -416,15 +452,18 @@ while( db_step(&q)==SQLITE_ROW ){ int rid = db_column_int(&q, 0); Blob content; while( !bag_find(&blobs, rid) ){ + char *zMark; content_get(rid, &content); db_bind_int(&q2, ":rid", rid); db_step(&q2); db_reset(&q2); - printf("blob\nmark :%d\ndata %d\n", BLOBMARK(rid), blob_size(&content)); + zMark = mark_name_from_rid(rid, &unused_mark); + printf("blob\nmark %s\ndata %d\n", zMark, blob_size(&content)); + free(zMark); bag_insert(&blobs, rid); fwrite(blob_buffer(&content), 1, blob_size(&content), stdout); printf("\n"); blob_reset(&content); @@ -470,11 +509,11 @@ if( zBranch==0 ) zBranch = "trunk"; zBr = mprintf("%s", zBranch); for(i=0; zBr[i]; i++){ if( !fossil_isalnum(zBr[i]) ) zBr[i] = '_'; } - zMark = mark_name_from_rid(ckinId); + zMark = mark_name_from_rid(ckinId, &unused_mark); printf("commit refs/heads/%s\nmark %s\n", zBr, zMark); free(zMark); free(zBr); printf("committer"); print_person(zUser); @@ -487,21 +526,21 @@ " AND pid IN (SELECT objid FROM event)", ckinId ); if( db_step(&q3) == SQLITE_ROW ){ int pid = db_column_int(&q3, 0); - zMark = mark_name_from_rid(pid); + zMark = mark_name_from_rid(pid, &unused_mark); printf("from %s\n", zMark); free(zMark); db_prepare(&q4, "SELECT pid FROM plink" " WHERE cid=%d AND NOT isprim" " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)" " ORDER BY pid", ckinId); while( db_step(&q4)==SQLITE_ROW ){ - zMark = mark_name_from_rid(db_column_int(&q4, 0)); + zMark = mark_name_from_rid(db_column_int(&q4, 0), &unused_mark); printf("merge %s\n", zMark); free(zMark); } db_finalize(&q4); }else{ @@ -516,20 +555,22 @@ ); while( db_step(&q4)==SQLITE_ROW ){ const char *zName = db_column_text(&q4,0); int zNew = db_column_int(&q4,1); int mPerm = db_column_int(&q4,2); - if( zNew==0) + if( zNew==0 ){ printf("D %s\n", zName); - else if( bag_find(&blobs, zNew) ) { + }else if( bag_find(&blobs, zNew) ){ + zMark = mark_name_from_rid(zNew, &unused_mark); const char *zPerm; switch( mPerm ){ case PERM_LNK: zPerm = "120000"; break; case PERM_EXE: zPerm = "100755"; break; default: zPerm = "100644"; break; } - printf("M %s :%d %s\n", zPerm, BLOBMARK(zNew), zName); + printf("M %s %s %s\n", zPerm, zMark, zName); + free(zMark); } } db_finalize(&q4); db_finalize(&q3); printf("\n"); @@ -547,20 +588,22 @@ ); while( db_step(&q)==SQLITE_ROW ){ const char *zTagname = db_column_text(&q, 0); char *zEncoded = 0; int rid = db_column_int(&q, 1); + char *zMark = mark_name_from_rid(rid, &unused_mark); const char *zSecSince1970 = db_column_text(&q, 2); int i; if( rid==0 || !bag_find(&vers, rid) ) continue; zTagname += 4; zEncoded = mprintf("%s", zTagname); for(i=0; zEncoded[i]; i++){ if( !fossil_isalnum(zEncoded[i]) ) zEncoded[i] = '_'; } printf("tag %s\n", zEncoded); - printf("from :%d\n", COMMITMARK(rid)); + printf("from %s\n", zMark); + free(zMark); printf("tagger %s +0000\n", zSecSince1970); printf("data 0\n"); fossil_free(zEncoded); } db_finalize(&q); @@ -570,12 +613,12 @@ f = fossil_fopen(markfile_out, "w"); if( f == 0 ){ fossil_fatal("cannot open %s for writing", markfile_out); } export_marks(f, &blobs, &vers); - if( ferror(f)!=0 || fclose(f)!=0 ) { + if( ferror(f)!=0 || fclose(f)!=0 ){ fossil_fatal("error while writing %s", markfile_out); } } bag_clear(&blobs); bag_clear(&vers); } Index: src/fusefs.c ================================================================== --- src/fusefs.c +++ src/fusefs.c @@ -20,20 +20,20 @@ ** ** This module is a mostly a no-op unless compiled with -DFOSSIL_HAVE_FUSEFS. ** The FOSSIL_HAVE_FUSEFS should be omitted on systems that lack support for ** the Fuse Filesystem, of course. */ +#ifdef FOSSIL_HAVE_FUSEFS #include "config.h" #include #include #include #include #include #include #include #include "fusefs.h" -#ifdef FOSSIL_HAVE_FUSEFS #define FUSE_USE_VERSION 26 #include /* @@ -283,11 +283,10 @@ static struct fuse_operations fusefs_methods = { .getattr = fusefs_getattr, .readdir = fusefs_readdir, .read = fusefs_read, }; -#endif /* FOSSIL_HAVE_FUSEFS */ /* ** COMMAND: fusefs ** ** Usage: %fossil fusefs [--debug] DIRECTORY @@ -315,13 +314,10 @@ ** After stopping the "fossil fusefs" command, it might also be necessary ** to run "fusermount -u DIRECTORY" to reset the FuseFS before using it ** again. */ void fusefs_cmd(void){ -#ifndef FOSSIL_HAVE_FUSEFS - fossil_fatal("this build of fossil does not support the fuse filesystem"); -#else char *zMountPoint; char *azNewArgv[5]; int doDebug = find_option("debug","d",0)!=0; db_find_and_open_repository(0,0); @@ -339,7 +335,7 @@ azNewArgv[4] = 0; g.localOpen = 0; /* Prevent tags like "current" and "prev" */ fuse_main(4, azNewArgv, &fusefs_methods, NULL); fusefs_reset(); fusefs_clear_path(); -#endif } +#endif /* FOSSIL_HAVE_FUSEFS */ Index: src/import.c ================================================================== --- src/import.c +++ src/import.c @@ -1770,11 +1770,11 @@ if( markfile_in ){ FILE *f = fossil_fopen(markfile_in, "r"); if( !f ){ fossil_fatal("cannot open %s for reading", markfile_in); } - if(import_marks(f, &blobs, NULL)<0){ + if( import_marks(f, &blobs, NULL, NULL)<0 ){ fossil_fatal("error importing marks from file: %s", markfile_in); } fclose(f); } @@ -1795,13 +1795,13 @@ db_prepare(&q_marks, "SELECT DISTINCT trid FROM xmark"); while( db_step(&q_marks)==SQLITE_ROW ){ rid = db_column_int(&q_marks, 0); if( db_int(0, "SELECT count(objid) FROM event" " WHERE objid=%d AND type='ci'", rid)==0 ){ - if( bag_find(&blobs, rid)==0 ){ - bag_insert(&blobs, rid); - } + /* Blob marks exported by git aren't saved between runs, so they need + ** to be left free for git to re-use in the future. + */ }else{ bag_insert(&vers, rid); } } db_finalize(&q_marks); Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -299,10 +299,22 @@ /* ** atexit() handler which frees up "some" of the resources ** used by fossil. */ static void fossil_atexit(void) { +#if USE_SEE + /* + ** Zero, unlock, and free the saved database encryption key now. + */ + db_unsave_encryption_key(); +#endif +#if defined(_WIN32) || defined(__BIONIC__) + /* + ** Free the secure getpass() buffer now. + */ + freepass(); +#endif #if defined(_WIN32) && !defined(_WIN64) && defined(FOSSIL_ENABLE_TCL) && \ defined(USE_TCL_STUBS) /* ** If Tcl is compiled on Windows using the latest MinGW, Fossil can crash ** when exiting while a stubs-enabled Tcl is still loaded. This is due to Index: src/style.c ================================================================== --- src/style.c +++ src/style.c @@ -400,10 +400,11 @@ if( g.thTrace ) Th_Trace("BEGIN_HEADER
\n", -1); /* Generate the header up through the main menu */ Th_Store("project_name", db_get("project-name","Unnamed Fossil Project")); + Th_Store("project_description", db_get("project-description","")); Th_Store("title", zTitle); Th_Store("baseurl", g.zBaseURL); Th_Store("secureurl", login_wants_https_redirect()? g.zHttpsURL: g.zBaseURL); Th_Store("home", g.zTop); Th_Store("index_page", db_get("index-page","/home")); Index: src/th_main.c ================================================================== --- src/th_main.c +++ src/th_main.c @@ -1317,10 +1317,92 @@ }else{ Th_SetResult(interp, "repository unavailable", -1); return TH_ERROR; } } + +/* +** TH1 command: unversioned content FILENAME +** +** Attempts to locate the specified unversioned file and return its contents. +** An error is generated if the repository is not open or the unversioned file +** cannot be found. +*/ +static int unversionedContentCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + if( argc!=3 ){ + return Th_WrongNumArgs(interp, "unversioned content FILENAME"); + } + if( Th_IsRepositoryOpen() ){ + Blob content; + if( unversioned_content(argv[2], &content)==0 ){ + Th_SetResult(interp, blob_str(&content), blob_size(&content)); + blob_reset(&content); + return TH_OK; + }else{ + return TH_ERROR; + } + }else{ + Th_SetResult(interp, "repository unavailable", -1); + return TH_ERROR; + } +} + +/* +** TH1 command: unversioned list +** +** Returns a list of the names of all unversioned files held in the local +** repository. An error is generated if the repository is not open. +*/ +static int unversionedListCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + if( argc!=2 ){ + return Th_WrongNumArgs(interp, "unversioned list"); + } + if( Th_IsRepositoryOpen() ){ + Stmt q; + char *zList = 0; + int nList = 0; + db_prepare(&q, "SELECT name FROM unversioned WHERE hash IS NOT NULL" + " ORDER BY name"); + while( db_step(&q)==SQLITE_ROW ){ + Th_ListAppend(interp, &zList, &nList, db_column_text(&q,0), -1); + } + db_finalize(&q); + Th_SetResult(interp, zList, nList); + Th_Free(interp, zList); + return TH_OK; + }else{ + Th_SetResult(interp, "repository unavailable", -1); + return TH_ERROR; + } +} + +static int unversionedCmd( + Th_Interp *interp, + void *p, + int argc, + const char **argv, + int *argl +){ + static const Th_SubCommand aSub[] = { + { "content", unversionedContentCmd }, + { "list", unversionedListCmd }, + { 0, 0 } + }; + return Th_CallSubCommand(interp, p, argc, argv, argl, aSub); +} #ifdef _WIN32 # include #else # include @@ -1886,10 +1968,11 @@ {"styleHeader", styleHeaderCmd, 0}, {"styleFooter", styleFooterCmd, 0}, {"tclReady", tclReadyCmd, 0}, {"trace", traceCmd, 0}, {"stime", stimeCmd, 0}, + {"unversioned", unversionedCmd, 0}, {"utime", utimeCmd, 0}, {"verifyCsrf", verifyCsrfCmd, 0}, {"wiki", wikiCmd, (void*)&aFlags[0]}, {0, 0, 0} }; Index: src/user.c ================================================================== --- src/user.c +++ src/user.c @@ -19,14 +19,10 @@ ** querying information about users. */ #include "config.h" #include "user.h" -#if defined(_WIN32) -#include -#endif - /* ** Strip leading and trailing space from a string and add the string ** onto the end of a blob. */ static void strip_string(Blob *pBlob, char *z){ @@ -43,53 +39,74 @@ } blob_append(pBlob, z, -1); } #if defined(_WIN32) || defined(__BIONIC__) -#ifdef __MINGW32__ +#ifdef _WIN32 #include #endif + /* -** getpass for Windows and Android +** getpass() for Windows and Android. */ +static char *zPwdBuffer = 0; +static size_t nPwdBuffer = 0; + static char *getpass(const char *prompt){ - static char pwd[64]; + char *zPwd; + size_t nPwd; size_t i; + if( zPwdBuffer==0 ){ + zPwdBuffer = fossil_secure_alloc_page(&nPwdBuffer); + assert( zPwdBuffer ); + }else{ + fossil_secure_zero(zPwdBuffer, nPwdBuffer); + } + zPwd = zPwdBuffer; + nPwd = nPwdBuffer; fputs(prompt,stderr); fflush(stderr); - for(i=0; i0 ); + for(i=0; i0 && (pwd[i]==8 || pwd[i]==127)){ + else if(i>0 && (zPwd[i]==8 || zPwd[i]==127)){ i -= 2; continue; } /* CTRL-C */ - else if(pwd[i]==3) { + else if(zPwd[i]==3) { i=0; break; } /* ESC */ - else if(pwd[i]==27){ + else if(zPwd[i]==27){ i=0; break; } else{ fputc('*',stderr); } } - pwd[i]='\0'; + zPwd[i]='\0'; fputs("\n", stderr); - return pwd; + assert( zPwd==zPwdBuffer ); + return zPwd; +} +void freepass(){ + if( !zPwdBuffer ) return; + assert( nPwdBuffer>0 ); + fossil_secure_free_page(zPwdBuffer, nPwdBuffer); } #endif #if defined(_WIN32) || defined(WIN32) # include Index: src/util.c ================================================================== --- src/util.c +++ src/util.c @@ -55,10 +55,67 @@ } void *fossil_realloc(void *p, size_t n){ p = realloc(p, n); if( p==0 ) fossil_panic("out of memory"); return p; +} +void fossil_secure_zero(void *p, size_t n){ + volatile unsigned char *vp = (volatile unsigned char *)p; + size_t i; + + if( p==0 ) return; + assert( n>0 ); + if( n==0 ) return; + for(i=0; i0 ); + assert( pageSize%2==0 ); +#if defined(_WIN32) + p = VirtualAlloc(NULL, pageSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE); + if( p==NULL ){ + fossil_fatal("VirtualAlloc failed: %lu\n", GetLastError()); + } + if( !VirtualLock(p, pageSize) ){ + fossil_fatal("VirtualLock failed: %lu\n", GetLastError()); + } +#else + p = fossil_malloc(pageSize); +#endif + fossil_secure_zero(p, pageSize); + if( pN ) *pN = pageSize; + return p; +} +void fossil_secure_free_page(void *p, size_t n){ + if( !p ) return; + assert( n>0 ); + fossil_secure_zero(p, n); +#if defined(_WIN32) + if( !VirtualUnlock(p, n) ){ + fossil_fatal("VirtualUnlock failed: %lu\n", GetLastError()); + } + if( !VirtualFree(p, 0, MEM_RELEASE) ){ + fossil_fatal("VirtualFree failed: %lu\n", GetLastError()); + } +#else + fossil_free(p); +#endif } /* ** This function implements a cross-platform "system()" interface. */ ADDED test/diff.test Index: test/diff.test ================================================================== --- /dev/null +++ test/diff.test @@ -0,0 +1,116 @@ +# +# Copyright (c) 2016 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/ +# +############################################################################ +# +# Tests for the diff command. +# + +require_no_open_checkout + +test_setup; set rootDir [file normalize [pwd]] + +################################### +# Tests of binary file detection. # +################################### + +file mkdir .fossil-settings +write_file [file join .fossil-settings binary-glob] "*" + +write_file file0.dat ""; # no content. +write_file file1.dat "test file 1 (one line no term)." +write_file file2.dat "test file 2 (NUL character).\0" +write_file file3.dat "test file 3 (long line).[string repeat x 16384]" +write_file file4.dat "test file 4 (long line).[string repeat y 16384]\ntwo" +write_file file5.dat "[string repeat z 16384]\ntest file 5 (long line)." + +fossil add $rootDir +fossil commit -m "c1" + +############################################################################### + +fossil ls +test diff-ls-1 {[normalize_result] eq \ +"file0.dat\nfile1.dat\nfile2.dat\nfile3.dat\nfile4.dat\nfile5.dat"} + +############################################################################### + +write_file file0.dat "\0" +fossil diff file0.dat + +test diff-file0-1 {[normalize_result] eq {Index: file0.dat +================================================================== +--- file0.dat ++++ file0.dat +cannot compute difference between binary files}} + +############################################################################### + +write_file file1.dat [string repeat z 16384] +fossil diff file1.dat + +test diff-file1-1 {[normalize_result] eq {Index: file1.dat +================================================================== +--- file1.dat ++++ file1.dat +cannot compute difference between binary files}} + +############################################################################### + +write_file file2.dat "test file 2 (no NUL character)." +fossil diff file2.dat + +test diff-file2-1 {[normalize_result] eq {Index: file2.dat +================================================================== +--- file2.dat ++++ file2.dat +cannot compute difference between binary files}} + +############################################################################### + +write_file file3.dat "test file 3 (not a long line)." +fossil diff file3.dat + +test diff-file3-1 {[normalize_result] eq {Index: file3.dat +================================================================== +--- file3.dat ++++ file3.dat +cannot compute difference between binary files}} + +############################################################################### + +write_file file4.dat "test file 4 (not a long line).\ntwo" +fossil diff file4.dat + +test diff-file4-1 {[normalize_result] eq {Index: file4.dat +================================================================== +--- file4.dat ++++ file4.dat +cannot compute difference between binary files}} + +############################################################################### + +write_file file5.dat "[string repeat 0 16]\ntest file 5 (not a long line)." +fossil diff file5.dat + +test diff-file5-1 {[normalize_result] eq {Index: file5.dat +================================================================== +--- file5.dat ++++ file5.dat +cannot compute difference between binary files}} + +############################################################################### + +test_cleanup Index: test/th1.test ================================================================== --- test/th1.test +++ test/th1.test @@ -1038,11 +1038,11 @@ error expr for getParameter glob_match globalState hascap hasfeature\ html htmlize http httpize if info insertCsrf lindex linecount list\ llength lsearch markdown proc puts query randhex redirect regexp\ reinitialize rename render repository return searchable set\ setParameter setting stime string styleFooter styleHeader tclReady\ - trace unset uplevel upvar utime verifyCsrf wiki} + trace unset unversioned uplevel upvar utime verifyCsrf wiki} set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe} if {$th1Tcl} { test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]} } else { test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]} @@ -1562,9 +1562,45 @@ } fossil test-th-source $th1FileName test th1-source-1 {$RESULT eq {TH_RETURN: 0 1 2 3 4 5 6 7 8 9}} file delete $th1FileName + +############################################################################### + +# +# TODO: Modify the result of this test if the list of unversioned files +# changes. +# +run_in_checkout { + fossil test-th-eval --open-config "unversioned list" +} + +test th1-unversioned-1 {[normalize_result] eq \ +{build-icons/linux.gif build-icons/linux64.gif build-icons/mac.gif\ +build-icons/openbsd.gif build-icons/src.gif build-icons/win32.gif\ +download.html download/fossil-linux-x86-1.32.zip\ +download/fossil-linux-x86-1.33.zip download/fossil-linux-x86-1.34.zip\ +download/fossil-linux-x86-1.35.zip download/fossil-macosx-x86-1.32.zip\ +download/fossil-macosx-x86-1.33.zip download/fossil-macosx-x86-1.34.zip\ +download/fossil-macosx-x86-1.35.zip download/fossil-openbsd-x86-1.32.zip\ +download/fossil-openbsd-x86-1.33.zip download/fossil-openbsd-x86-1.34.tar.gz\ +download/fossil-openbsd-x86-1.35.tar.gz download/fossil-src-1.32.tar.gz\ +download/fossil-src-1.33.tar.gz download/fossil-src-1.34.tar.gz\ +download/fossil-src-1.35.tar.gz download/fossil-w32-1.32.zip\ +download/fossil-w32-1.33.zip download/fossil-w32-1.34.zip\ +download/fossil-w32-1.35.zip download/releasenotes-1.32.html\ +download/releasenotes-1.33.html download/releasenotes-1.34.html\ +download/releasenotes-1.35.html index.wiki}} + +############################################################################### + +run_in_checkout { + fossil test-th-eval --open-config \ + {string length [unversioned content build-icons/src.gif]} +} + +test th1-unversioned-2 {$RESULT eq {4592}} ############################################################################### test_cleanup Index: www/customskin.md ================================================================== --- www/customskin.md +++ www/customskin.md @@ -144,10 +144,14 @@ respository settings and the specific page being generated. * **project_name** - The project_name variable is filled with the name of the project as configured under the Admin/Configuration menu. + + * **project_description** - The project_description variable is + filled with the description of the project as configured under + the Admin/Configuration menu. * **title** - The title variable holds the title of the page being generated. The title variable is special in that it is deleted after Index: www/inout.wiki ================================================================== --- www/inout.wiki +++ www/inout.wiki @@ -49,15 +49,54 @@ format that Fossil will generate. However, future versions of Fossil might add the ability to generate other VCS interchange formats, and so for compatibility, the use of the --git option recommended. -An anonymous user sends this comment: - -
-The main Fossil branch is called "trunk", while the main git branch is -called "master". After you've exported your FOSSIL repo to git, you won't -see any files and gitk will complain about a missing "HEAD". You can -resolve this problem by merging "trunk" with "master" -(first verify using git status that you are on the "master" branch): -git merge trunk -
+

Bidirectional Synchronization

+Fossil also has the ability to synchronize with a Git repository via repeated +imports and/or exports. To do this, it uses marks files to store a record of +artifacts which are known by both Git and Fossil to exist at a given point in +time. + +To illustrate, consider the example of a remote Fossil repository that a +user wants to import into a local Git repository. First, the user would clone +the remote repository and import it into a new Git repository: + +
+fossil clone /path/to/remote/repo.fossil repo.fossil
+mkdir repo
+cd repo
+fossil open ../repo.fossil
+mkdir ../repo.git
+cd ../repo.git
+git init .
+fossil export --git --export-marks ../repo/fossil.marks  \
+       ../repo.fossil | git fast-import                  \
+       --export-marks=../repo/git.marks
+
+ +Once the import has completed, the user would need to git checkout +trunk. At any point after this, new changes can be imported from the +remote Fossil repository: + +
+cd ../repo
+fossil pull
+cd ../repo.git
+fossil export --git --import-marks ../repo/fossil.marks  \
+       --export-marks ../repo/fossil.marks               \
+       ../repo.fossil | git fast-import                  \
+       --import-marks=../repo/git.marks                  \
+       --export-marks=../repo/git.marks
+
+ +Changes in the Git repository can be exported to the Fossil repository and then +pushed to the remote: + +
+git fast-export --import-marks=../repo/git.marks                  \
+    --export-marks=../repo/git.marks --all | fossil import --git  \
+    --incremental --import-marks ../repo/fossil.marks             \
+    --export-marks ../repo/fossil.marks ../repo.fossil
+cd ../repo
+fossil push
+
Index: www/th1.md ================================================================== --- www/th1.md +++ www/th1.md @@ -174,10 +174,12 @@ * tclInvoke * tclIsSafe * tclMakeSafe * tclReady * trace + * unversioned content + * unversioned list * utime * verifyCsrf * wiki Each of the commands above is documented by a block comment above their @@ -610,10 +612,27 @@ ------------------------------------- * trace STRING Generates a TH1 trace message if TH1 tracing is enabled. + +TH1 unversioned content Command +----------------------------------------------------------------- + + * unversioned content FILENAME + +Attempts to locate the specified unversioned file and return its contents. +An error is generated if the repository is not open or the unversioned file +cannot be found. + +TH1 unversioned list Command +----------------------------------------------------------- + + * unversioned list + +Returns a list of the names of all unversioned files held in the local +repository. An error is generated if the repository is not open. TH1 utime Command ------------------------------------- * utime
%z(href("%R/timeline?n=100&r=%T",zBranch))%h(zBranch)%s(zAge)%d(nCkin)%s(isClosed?"closed":"")