Index: src/diff.c ================================================================== --- src/diff.c +++ src/diff.c @@ -40,10 +40,11 @@ #define DIFF_NOOPT (((u64)0x01)<<32) /* Suppress optimizations (debug) */ #define DIFF_INVERT (((u64)0x02)<<32) /* Invert the diff (debug) */ #define DIFF_CONTEXT_EX (((u64)0x04)<<32) /* Use context even if zero */ #define DIFF_NOTTOOBIG (((u64)0x08)<<32) /* Only display if not too big */ #define DIFF_STRIP_EOLCR (((u64)0x10)<<32) /* Strip trailing CR */ +#define DIFF_NO_ERRMSG (((u64)0x20)<<32) /* Do not generate error messages */ /* ** These error messages are shared in multiple locations. They are defined ** here for consistency. */ @@ -1804,10 +1805,12 @@ /* ** Append the error message to pOut. */ void diff_errmsg(Blob *pOut, const char *msg, int diffFlags){ + if( pOut==0 ) return; + if( diffFlags & DIFF_NO_ERRMSG ) return; if( diffFlags & DIFF_HTML ){ blob_appendf(pOut, "

%s

", msg); }else{ blob_append(pOut, msg, -1); } @@ -1858,23 +1861,21 @@ c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob), &c.nTo, diffFlags); if( c.aFrom==0 || c.aTo==0 ){ fossil_free(c.aFrom); fossil_free(c.aTo); - if( pOut ){ - diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, diffFlags); - } + diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, diffFlags); return 0; } /* Compute the difference */ diff_all(&c); if( ignoreWs && c.nEdit==6 && c.aEdit[1]==0 && c.aEdit[2]==0 ){ fossil_free(c.aFrom); fossil_free(c.aTo); fossil_free(c.aEdit); - if( pOut ) diff_errmsg(pOut, DIFF_WHITESPACE_ONLY, diffFlags); + diff_errmsg(pOut, DIFF_WHITESPACE_ONLY, diffFlags); return 0; } if( (diffFlags & DIFF_NOTTOOBIG)!=0 ){ int i, m, n; int *a = c.aEdit; @@ -1882,11 +1883,11 @@ for(i=m=n=0; i10000 ){ fossil_free(c.aFrom); fossil_free(c.aTo); fossil_free(c.aEdit); - if( pOut ) diff_errmsg(pOut, DIFF_TOO_MANY_CHANGES, diffFlags); + diff_errmsg(pOut, DIFF_TOO_MANY_CHANGES, diffFlags); return 0; } } if( (diffFlags & DIFF_NOOPT)==0 ){ diff_optimize(&c); Index: src/diffcmd.c ================================================================== --- src/diffcmd.c +++ src/diffcmd.c @@ -927,5 +927,82 @@ if( zFrom==0 || zTo==0 ) fossil_redirect_home(); cgi_set_content_type("text/plain"); diff_two_versions(zFrom, zTo, 0, 0, 0, DIFF_VERBOSE, 0); } + +/* +** Compute a string (into pOut) that is a diff of check-in "rid" and its +** primary parent. The diff has no context lines and is designed to support +** full-text search. +*/ +void diff_for_search(int rid, Blob *pOut){ + Manifest *pFrom, *pTo; + ManifestFile *pFromFile, *pToFile; + const u64 diffFlags = DIFF_NO_ERRMSG | DIFF_IGNORE_ALLWS | DIFF_CONTEXT_EX; + + pTo = manifest_get(rid, CFTYPE_MANIFEST, 0); + if( pTo==0 ) return; + if( pTo->nParent==0 ){ + manifest_destroy(pTo); + return; + } + pFrom = manifest_get_by_name(pTo->azParent[0], 0); + manifest_file_rewind(pFrom); + manifest_file_rewind(pTo); + pFromFile = manifest_file_next(pFrom,0); + pToFile = manifest_file_next(pTo,0); + + while( pFromFile || pToFile ){ + int cmp; + if( pFromFile==0 ){ + cmp = +1; + }else if( pToFile==0 ){ + cmp = -1; + }else{ + cmp = fossil_strcmp(pFromFile->zName, pToFile->zName); + } + if( cmp<0 ){ + blob_appendf(pOut, "DELETED %s\n", pFromFile->zName); + pFromFile = manifest_file_next(pFrom,0); + }else if( cmp>0 ){ + blob_appendf(pOut, "ADDED %s\n", pToFile->zName); + pToFile = manifest_file_next(pTo,0); + }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){ + /* No changes */ + pFromFile = manifest_file_next(pFrom,0); + pToFile = manifest_file_next(pTo,0); + }else{ + Blob fileA, fileB; + blob_appendf(pOut, "CHANGED %s\n", pToFile->zName); + rid = uuid_to_rid(pFromFile->zUuid, 0); + content_get(rid, &fileA); + rid = uuid_to_rid(pToFile->zUuid, 0); + content_get(rid, &fileB); + text_diff(&fileA, &fileB, pOut, 0, diffFlags); + blob_reset(&fileA); + blob_reset(&fileB); + pFromFile = manifest_file_next(pFrom,0); + pToFile = manifest_file_next(pTo,0); + } + } + manifest_destroy(pFrom); + manifest_destroy(pTo); +} + +/* +** Implement the diff(RID) SQL function that returns a text diff for +** check-in RID. +*/ +void diff_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int rid; + Blob out; + rid = sqlite3_value_int(argv[0]); + blob_init(&out, 0, 0); + diff_for_search(rid, &out); + sqlite3_result_text(context, blob_str(&out), blob_size(&out), SQLITE_TRANSIENT); + blob_reset(&out); +} Index: src/search.c ================================================================== --- src/search.c +++ src/search.c @@ -510,10 +510,12 @@ search_title_sqlfunc, 0, 0); sqlite3_create_function(db, "body", 3, SQLITE_UTF8, 0, search_body_sqlfunc, 0, 0); sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0, search_urlencode_sqlfunc, 0, 0); + sqlite3_create_function(db, "diff", 1, SQLITE_UTF8, 0, + diff_sqlfunc, 0, 0); } /* ** Testing the search function. ** @@ -600,11 +602,12 @@ /* What to search for */ #define SRCH_CKIN 0x0001 /* Search over check-in comments */ #define SRCH_DOC 0x0002 /* Search over embedded documents */ #define SRCH_TKT 0x0004 /* Search over tickets */ #define SRCH_WIKI 0x0008 /* Search over wiki */ -#define SRCH_ALL 0x000f /* Search over everything */ +#define SRCH_DIFF 0x0010 /* Search check-in diffs */ +#define SRCH_ALL 0x001f /* Search over everything */ #endif /* ** Remove bits from srchFlags which are disallowed by either the ** current server configuration or by user permissions. @@ -615,13 +618,14 @@ static const struct { unsigned m; const char *zKey; } aSetng[] = { { SRCH_CKIN, "search-ci" }, { SRCH_DOC, "search-doc" }, { SRCH_TKT, "search-tkt" }, { SRCH_WIKI, "search-wiki" }, + { SRCH_DIFF, "search-diff" }, }; int i; - if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC); + if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC|SRCH_DIFF); if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT); if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI); for(i=0; i @@ -1063,10 +1070,11 @@ ** y=TYPE What to search. ** c -> check-ins ** d -> documentation ** t -> tickets ** w -> wiki +** x -> diff ** all -> everything */ void search_page(void){ login_check_credentials(); style_header("Search"); @@ -1169,10 +1177,11 @@ ** ** cType: d Embedded documentation ** w Wiki page ** c Check-in comment ** t Ticket text +** x Check-in diffs ** ** rid The RID of an artifact that defines the object ** being searched. ** ** zName Name of the object being searched. @@ -1182,10 +1191,11 @@ int rid, /* BLOB.RID or TAG.TAGID value for document */ const char *zName, /* Auxiliary information */ Blob *pOut /* OUT: Initialize to the search text */ ){ blob_init(pOut, 0, 0); + /* printf("stext(%c%d)\n", cType, rid); fflush(stdout); */ switch( cType ){ case 'd': { /* Documents */ Blob doc; content_get(rid, &doc); blob_to_utf8_no_bom(&doc, 0); @@ -1201,10 +1211,14 @@ get_stext_by_mimetype(&wiki, wiki_filter_mimetypes(pWiki->zMimetype), pOut); blob_reset(&wiki); manifest_destroy(pWiki); break; + } + case 'x': { /* Check-in diff */ + diff_for_search(rid, pOut); + break; } case 'c': { /* Check-in Comments */ static Stmt q; static int isPlainText = -1; db_static_prepare(&q, @@ -1411,10 +1425,14 @@ search_sql_setup(g.db); db_multi_exec( "INSERT OR IGNORE INTO ftsdocs(type,rid,idxed)" " SELECT 'c', objid, 0 FROM event WHERE type='ci';" ); + db_multi_exec( + "INSERT OR IGNORE INTO ftsdocs(type,rid,idxed)" + " SELECT 'x', objid, 0 FROM event WHERE type='ci';" + ); db_multi_exec( "WITH latest_wiki(rid,name,mtime) AS (" " SELECT tagxref.rid, substr(tag.tagname,6), max(tagxref.mtime)" " FROM tag, tagxref" " WHERE tag.tagname GLOB 'wiki-*'" @@ -1545,10 +1563,32 @@ " WHERE ftsdocs.type='c' AND NOT ftsdocs.idxed" " AND event.objid=ftsdocs.rid" " AND blob.rid=ftsdocs.rid" ); } + +/* +** Deal with all of the unindexed 'x' terms in FTSDOCS +*/ +static void search_update_diff_index(void){ + db_multi_exec( + "INSERT INTO ftsidx(docid,title,body)" + " SELECT rowid, '', body('x',rid,NULL) FROM ftsdocs" + " WHERE type='x' AND NOT idxed;" + ); + db_multi_exec( + "REPLACE INTO ftsdocs(rowid,idxed,type,rid,name,label,url,mtime)" + " SELECT ftsdocs.rowid, 1, 'x', ftsdocs.rid, NULL," + " printf('Check-in [%%.16s] on %%s',blob.uuid,datetime(event.mtime))," + " printf('/info/%%.20s',blob.uuid)," + " event.mtime" + " FROM ftsdocs, event, blob" + " WHERE ftsdocs.type='x' AND NOT ftsdocs.idxed" + " AND event.objid=ftsdocs.rid" + " AND blob.rid=ftsdocs.rid" + ); +} /* ** Deal with all of the unindexed 't' terms in FTSDOCS */ static void search_update_ticket_index(void){ @@ -1604,10 +1644,13 @@ search_sql_setup(g.db); if( srchFlags & (SRCH_CKIN|SRCH_DOC) ){ search_update_doc_index(); search_update_checkin_index(); } + if( srchFlags & SRCH_DIFF ){ + search_update_diff_index(); + } if( srchFlags & SRCH_TKT ){ search_update_ticket_index(); } if( srchFlags & SRCH_WIKI ){ search_update_wiki_index(); @@ -1637,14 +1680,14 @@ ** reindex Rebuild the search index. This is a no-op if ** index search is disabled ** ** index (on|off) Turn the search index on or off ** -** enable cdtw Enable various kinds of search. c=Check-ins, -** d=Documents, t=Tickets, w=Wiki. +** enable cdtwx Enable various kinds of search. c=Check-ins, +** d=Documents, t=Tickets, w=Wiki, x=Diffs. ** -** disable cdtw Disable various kinds of search +** disable cdtwx Disable various kinds of search ** ** stemmer (on|off) Turn the Porter stemmer on or off for indexed ** search. (Unindexed search is never stemmed.) ** ** The current search settings are displayed after any changes are applied. @@ -1657,14 +1700,15 @@ { 3, "disable" }, { 4, "enable" }, { 5, "stemmer" }, }; static const struct { char *zSetting; char *zName; char *zSw; } aSetng[] = { - { "search-ckin", "check-in search:", "c" }, - { "search-doc", "document search:", "d" }, - { "search-tkt", "ticket search:", "t" }, - { "search-wiki", "wiki search:", "w" }, + { "search-ckin", "check-in comment search:", "c" }, + { "search-diff", "check-in diff search:", "x" }, + { "search-doc", "document search:", "d" }, + { "search-tkt", "ticket search:", "t" }, + { "search-wiki", "wiki search:", "w" }, }; char *zSubCmd = 0; int i, j, n; int iCmd = 0; int iAction = 0; @@ -1683,10 +1727,12 @@ zSubCmd, blob_str(&all)); return; } iCmd = aCmd[i].iCmd; } + login_set_capabilities("s",0); + g.zTop = ""; if( iCmd==1 ){ if( search_index_exists() ) iAction = 2; } if( iCmd==2 ){ if( g.argc<3 ) usage("index (on|off)"); @@ -1719,19 +1765,19 @@ search_rebuild_index(); } /* Always show the status before ending */ for(i=0; iWhen searching documents, use the versions of the files found at the @ type of the "Document Branch" branch. Recommended value: "trunk". @ Document search is disabled if blank. @
onoff_attribute("Search Check-in Comments", "search-ci", "sc", 0, 0); + @
+ onoff_attribute("Search Check-in Diffs","search-diff", "sx", 0, 0); @
onoff_attribute("Search Documents", "search-doc", "sd", 0, 0); @
onoff_attribute("Search Tickets", "search-tkt", "st", 0, 0); @