Fossil SCM
The /search page now covers wiki and check-in comments. And the formatting of snippets is improved. The search is still done by full-scan but the infrastructure is coming into place to handle the search using an index.
Commit
8e02c26ad2baf8553290376aa7ec1e7e7a715b5d
Parent
48e1e18304a2ec1…
2 files changed
+153
-15
+10
-7
+153
-15
| --- src/search.c | ||
| +++ src/search.c | ||
| @@ -411,10 +411,40 @@ | ||
| 411 | 411 | } |
| 412 | 412 | }else{ |
| 413 | 413 | sqlite3_result_int(context, score); |
| 414 | 414 | } |
| 415 | 415 | } |
| 416 | + | |
| 417 | +/* | |
| 418 | +** This is an SQLite function that computes the searchable text. | |
| 419 | +** It is a wrapper around the search_stext() routine. See the | |
| 420 | +** search_stext() routine for further detail. | |
| 421 | +*/ | |
| 422 | +static void search_stext_sqlfunc( | |
| 423 | + sqlite3_context *context, | |
| 424 | + int argc, | |
| 425 | + sqlite3_value **argv | |
| 426 | +){ | |
| 427 | + Blob txt; | |
| 428 | + const char *zType = (const char*)sqlite3_value_text(argv[0]); | |
| 429 | + const char *zArg1 = (const char*)sqlite3_value_text(argv[1]); | |
| 430 | + const char *zArg2 = (const char*)sqlite3_value_text(argv[2]); | |
| 431 | + search_stext(zType[0], zArg1, zArg2, &txt); | |
| 432 | + sqlite3_result_text(context, blob_materialize(&txt), -1, fossil_free); | |
| 433 | +} | |
| 434 | + | |
| 435 | +/* | |
| 436 | +** Encode a string for use as a query parameter in a URL | |
| 437 | +*/ | |
| 438 | +static void search_urlencode_sqlfunc( | |
| 439 | + sqlite3_context *context, | |
| 440 | + int argc, | |
| 441 | + sqlite3_value **argv | |
| 442 | +){ | |
| 443 | + char *z = mprintf("%T",sqlite3_value_text(argv[0])); | |
| 444 | + sqlite3_result_text(context, z, -1, fossil_free); | |
| 445 | +} | |
| 416 | 446 | |
| 417 | 447 | /* |
| 418 | 448 | ** Register the "score()" SQL function to score its input text |
| 419 | 449 | ** using the given Search object. Once this function is registered, |
| 420 | 450 | ** do not delete the Search object. |
| @@ -424,10 +454,14 @@ | ||
| 424 | 454 | search_score_sqlfunc, 0, 0); |
| 425 | 455 | sqlite3_create_function(db, "snippet", -1, SQLITE_UTF8, &gSearch, |
| 426 | 456 | search_score_sqlfunc, 0, 0); |
| 427 | 457 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 428 | 458 | search_init_sqlfunc, 0, 0); |
| 459 | + sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0, | |
| 460 | + search_stext_sqlfunc, 0, 0); | |
| 461 | + sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0, | |
| 462 | + search_urlencode_sqlfunc, 0, 0); | |
| 429 | 463 | } |
| 430 | 464 | |
| 431 | 465 | /* |
| 432 | 466 | ** Testing the search function. |
| 433 | 467 | ** |
| @@ -522,10 +556,11 @@ | ||
| 522 | 556 | ** * Search wiki |
| 523 | 557 | */ |
| 524 | 558 | void search_page(void){ |
| 525 | 559 | const char *zPattern = PD("s",""); |
| 526 | 560 | Stmt q; |
| 561 | + const char *zSrchEnable = "dwc"; | |
| 527 | 562 | |
| 528 | 563 | login_check_credentials(); |
| 529 | 564 | if( !g.perm.Read ){ login_needed(); return; } |
| 530 | 565 | style_header("Search"); |
| 531 | 566 | @ <form method="GET" action="search"><center> |
| @@ -537,30 +572,72 @@ | ||
| 537 | 572 | search_sql_setup(g.db); |
| 538 | 573 | add_content_sql_commands(g.db); |
| 539 | 574 | search_init(zPattern, "<b>", "</b>", " ... ", |
| 540 | 575 | SRCHFLG_STATIC|SRCHFLG_HTML|SRCHFLG_SCORE); |
| 541 | 576 | db_multi_exec( |
| 542 | - "CREATE VIRTUAL TABLE temp.foci USING files_of_checkin;" | |
| 543 | - "CREATE TEMP TABLE x(fn TEXT,url TEXT,snip TEXT);" | |
| 544 | - "INSERT INTO x(fn,url,snip)" | |
| 545 | - " SELECT filename, printf('%R/doc/trunk/%%s',filename)," | |
| 546 | - " snippet(content(uuid))" | |
| 547 | - " FROM foci" | |
| 548 | - " WHERE checkinID=symbolic_name_to_rid('trunk')" | |
| 549 | - " AND (filename GLOB '*.wiki' OR" | |
| 550 | - " filename GLOB '*.md' OR" | |
| 551 | - " filename GLOB '*.txt' OR" | |
| 552 | - " filename GLOB '*.html');" | |
| 577 | + "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" | |
| 578 | + "CREATE TEMP TABLE x(label TEXT,url TEXT,date TEXT,snip TEXT);" | |
| 553 | 579 | ); |
| 554 | - db_prepare(&q, "SELECT url, substr(snip,8)" | |
| 580 | + if( strchr(zSrchEnable, 'd') ){ | |
| 581 | + db_multi_exec( | |
| 582 | + "INSERT INTO x(label,url,date,snip)" | |
| 583 | + " SELECT printf('Document: %%s',foci.filename)," | |
| 584 | + " printf('%R/doc/trunk/%%s',foci.filename)," | |
| 585 | + " (SELECT datetime(event.mtime) FROM event" | |
| 586 | + " WHERE objid=symbolic_name_to_rid('trunk'))," | |
| 587 | + " snippet(stext('d',blob.rid,foci.filename))" | |
| 588 | + " FROM foci CROSS JOIN blob" | |
| 589 | + " WHERE checkinID=symbolic_name_to_rid('trunk')" | |
| 590 | + " AND blob.uuid=foci.uuid" | |
| 591 | + " AND (filename GLOB '*.wiki' OR" | |
| 592 | + " filename GLOB '*.md' OR" | |
| 593 | + " filename GLOB '*.txt' OR" | |
| 594 | + " filename GLOB '*.html');" | |
| 595 | + ); | |
| 596 | + } | |
| 597 | + if( strchr(zSrchEnable, 'w') ){ | |
| 598 | + db_multi_exec( | |
| 599 | + "WITH wiki(name,rid,mtime) AS (" | |
| 600 | + " SELECT substr(tagname,6), tagxref.rid, max(tagxref.mtime)" | |
| 601 | + " FROM tag, tagxref" | |
| 602 | + " WHERE tag.tagname GLOB 'wiki-*'" | |
| 603 | + " AND tagxref.tagid=tag.tagid" | |
| 604 | + " GROUP BY 1" | |
| 605 | + ")" | |
| 606 | + "INSERT INTO x(label,url,date,snip)" | |
| 607 | + " SELECT printf('Wiki: %%s',name)," | |
| 608 | + " printf('%R/wiki?name=%%s',urlencode(name))," | |
| 609 | + " datetime(mtime)," | |
| 610 | + " snippet(stext('w',rid,name))" | |
| 611 | + " FROM wiki;" | |
| 612 | + ); | |
| 613 | + } | |
| 614 | + if( strchr(zSrchEnable, 'c') ){ | |
| 615 | + db_multi_exec( | |
| 616 | + "WITH ckin(uuid,rid,mtime) AS (" | |
| 617 | + " SELECT blob.uuid, event.objid, event.mtime" | |
| 618 | + " FROM event, blob" | |
| 619 | + " WHERE event.type='ci'" | |
| 620 | + " AND blob.rid=event.objid" | |
| 621 | + ")" | |
| 622 | + "INSERT INTO x(label,url,date,snip)" | |
| 623 | + " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," | |
| 624 | + " printf('%R/timeline?c=%%s&n=8&y=ci',uuid)," | |
| 625 | + " datetime(mtime)," | |
| 626 | + " snippet(stext('c',rid,NULL))" | |
| 627 | + " FROM ckin;" | |
| 628 | + ); | |
| 629 | + } | |
| 630 | + db_prepare(&q, "SELECT url, substr(snip,9), label" | |
| 555 | 631 | " FROM x WHERE snip IS NOT NULL" |
| 556 | - " ORDER BY substr(snip,1,8) DESC, fn;"); | |
| 632 | + " ORDER BY substr(snip,1,9) DESC, date DESC;"); | |
| 557 | 633 | @ <ol> |
| 558 | 634 | while( db_step(&q)==SQLITE_ROW ){ |
| 559 | 635 | const char *zUrl = db_column_text(&q, 0); |
| 560 | 636 | const char *zSnippet = db_column_text(&q, 1); |
| 561 | - @ <li><p>%s(href("%s",zUrl))%h(zUrl)</a><br>%s(zSnippet)</li> | |
| 637 | + const char *zLabel = db_column_text(&q, 2); | |
| 638 | + @ <li><p>%s(href("%s",zUrl))%h(zLabel)</a><br>%s(zSnippet)</li> | |
| 562 | 639 | } |
| 563 | 640 | db_finalize(&q); |
| 564 | 641 | @ </ol> |
| 565 | 642 | } |
| 566 | 643 | style_footer(); |
| @@ -639,21 +716,82 @@ | ||
| 639 | 716 | pOut); |
| 640 | 717 | blob_reset(&wiki); |
| 641 | 718 | manifest_destroy(pWiki); |
| 642 | 719 | break; |
| 643 | 720 | } |
| 721 | + case 'c': { /* Ckeckin: zArg1: RID of the checkin. zArg2: Not used */ | |
| 722 | + int rid = atoi(zArg1); | |
| 723 | + static Stmt q; | |
| 724 | + db_static_prepare(&q, | |
| 725 | + "SELECT coalesce(ecomment,comment)" | |
| 726 | + " ||' (user: '||coalesce(euser,user,'?')" | |
| 727 | + " ||', tags: '||" | |
| 728 | + " (SELECT group_concat(substr(tag.tagname,5),',')" | |
| 729 | + " FROM tag, tagxref" | |
| 730 | + " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" | |
| 731 | + " AND tagxref.rid=event.objid AND tagxref.tagtype>0)" | |
| 732 | + " ||')'" | |
| 733 | + " FROM event WHERE objid=:x AND type='ci'"); | |
| 734 | + db_bind_int(&q, ":x", rid); | |
| 735 | + if( db_step(&q)==SQLITE_ROW ){ | |
| 736 | + db_column_blob(&q, 0, pOut); | |
| 737 | + blob_append(pOut, "\n", 1); | |
| 738 | + } | |
| 739 | + db_reset(&q); | |
| 740 | + break; | |
| 741 | + } | |
| 644 | 742 | } |
| 645 | 743 | } |
| 744 | + | |
| 745 | +/* | |
| 746 | +** The arguments cType,zArg1,zArg2 define an object that can be searched | |
| 747 | +** for. Return a URL (relative to the root of the Fossil project) that | |
| 748 | +** will jump to that document. | |
| 749 | +** | |
| 750 | +** Space to hold the returned string is obtained from mprintf() and should | |
| 751 | +** be freed by the caller using fossil_free() or the equivalent. | |
| 752 | +*/ | |
| 753 | +char *search_url( | |
| 754 | + char cType, /* Type of document */ | |
| 755 | + const char *zArg1, /* First parameter */ | |
| 756 | + const char *zArg2 /* Second parameter */ | |
| 757 | +){ | |
| 758 | + char *zUrl = 0; | |
| 759 | + switch( cType ){ | |
| 760 | + case 'd': { /* Doc. zArg1: RID of the file. zArg2: Filename */ | |
| 761 | + case 's': /* Source. zArg1: RID of the file. zArg2: Filename */ | |
| 762 | + zUrl = db_text(0, | |
| 763 | + "SELECT printf('/doc/%%s%%s', substr(blob.uuid,20), %Q)" | |
| 764 | + " FROM mlink, blob" | |
| 765 | + " WHERE mlink.fid=%d AND mlink.mid=blob.rid", | |
| 766 | + zArg2, atoi(zArg1)); | |
| 767 | + break; | |
| 768 | + } | |
| 769 | + case 'w': { /* Wiki. zArg1: RID of the page. zArg2: Page name */ | |
| 770 | + char *zId = db_text(0, "SELECT uuid FROM blob WHERE rid=%d",atoi(zArg1)); | |
| 771 | + zUrl = mprintf("/wiki?id=%z&name=%t", zId, zArg2); | |
| 772 | + break; | |
| 773 | + } | |
| 774 | + case 'c': { /* Ckeckin: zArg1: RID of the checkin. zArg2: Not used */ | |
| 775 | + char *zId = db_text(0, "SELECT uuid FROM blob WHERE rid=%d",atoi(zArg1)); | |
| 776 | + zUrl = mprintf("/info/%z", zId); | |
| 777 | + break; | |
| 778 | + } | |
| 779 | + } | |
| 780 | + return zUrl; | |
| 781 | +} | |
| 646 | 782 | |
| 647 | 783 | /* |
| 648 | 784 | ** COMMAND: test-search-stext |
| 649 | 785 | ** |
| 650 | 786 | ** Usage: fossil test-search-stext TYPE ARG1 ARG2 |
| 651 | 787 | */ |
| 652 | 788 | void test_search_stext(void){ |
| 653 | 789 | Blob out; |
| 790 | + char *zUrl; | |
| 654 | 791 | db_find_and_open_repository(0,0); |
| 655 | 792 | if( g.argc!=5 ) usage("TYPE ARG1 ARG2"); |
| 656 | 793 | search_stext(g.argv[2][0], g.argv[3], g.argv[4], &out); |
| 657 | - fossil_print("%s",blob_str(&out)); | |
| 794 | + zUrl = search_url(g.argv[2][0], g.argv[3], g.argv[4]); | |
| 795 | + fossil_print("%s\n%z\n",blob_str(&out),zUrl); | |
| 658 | 796 | blob_reset(&out); |
| 659 | 797 | } |
| 660 | 798 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -411,10 +411,40 @@ | |
| 411 | } |
| 412 | }else{ |
| 413 | sqlite3_result_int(context, score); |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | /* |
| 418 | ** Register the "score()" SQL function to score its input text |
| 419 | ** using the given Search object. Once this function is registered, |
| 420 | ** do not delete the Search object. |
| @@ -424,10 +454,14 @@ | |
| 424 | search_score_sqlfunc, 0, 0); |
| 425 | sqlite3_create_function(db, "snippet", -1, SQLITE_UTF8, &gSearch, |
| 426 | search_score_sqlfunc, 0, 0); |
| 427 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 428 | search_init_sqlfunc, 0, 0); |
| 429 | } |
| 430 | |
| 431 | /* |
| 432 | ** Testing the search function. |
| 433 | ** |
| @@ -522,10 +556,11 @@ | |
| 522 | ** * Search wiki |
| 523 | */ |
| 524 | void search_page(void){ |
| 525 | const char *zPattern = PD("s",""); |
| 526 | Stmt q; |
| 527 | |
| 528 | login_check_credentials(); |
| 529 | if( !g.perm.Read ){ login_needed(); return; } |
| 530 | style_header("Search"); |
| 531 | @ <form method="GET" action="search"><center> |
| @@ -537,30 +572,72 @@ | |
| 537 | search_sql_setup(g.db); |
| 538 | add_content_sql_commands(g.db); |
| 539 | search_init(zPattern, "<b>", "</b>", " ... ", |
| 540 | SRCHFLG_STATIC|SRCHFLG_HTML|SRCHFLG_SCORE); |
| 541 | db_multi_exec( |
| 542 | "CREATE VIRTUAL TABLE temp.foci USING files_of_checkin;" |
| 543 | "CREATE TEMP TABLE x(fn TEXT,url TEXT,snip TEXT);" |
| 544 | "INSERT INTO x(fn,url,snip)" |
| 545 | " SELECT filename, printf('%R/doc/trunk/%%s',filename)," |
| 546 | " snippet(content(uuid))" |
| 547 | " FROM foci" |
| 548 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| 549 | " AND (filename GLOB '*.wiki' OR" |
| 550 | " filename GLOB '*.md' OR" |
| 551 | " filename GLOB '*.txt' OR" |
| 552 | " filename GLOB '*.html');" |
| 553 | ); |
| 554 | db_prepare(&q, "SELECT url, substr(snip,8)" |
| 555 | " FROM x WHERE snip IS NOT NULL" |
| 556 | " ORDER BY substr(snip,1,8) DESC, fn;"); |
| 557 | @ <ol> |
| 558 | while( db_step(&q)==SQLITE_ROW ){ |
| 559 | const char *zUrl = db_column_text(&q, 0); |
| 560 | const char *zSnippet = db_column_text(&q, 1); |
| 561 | @ <li><p>%s(href("%s",zUrl))%h(zUrl)</a><br>%s(zSnippet)</li> |
| 562 | } |
| 563 | db_finalize(&q); |
| 564 | @ </ol> |
| 565 | } |
| 566 | style_footer(); |
| @@ -639,21 +716,82 @@ | |
| 639 | pOut); |
| 640 | blob_reset(&wiki); |
| 641 | manifest_destroy(pWiki); |
| 642 | break; |
| 643 | } |
| 644 | } |
| 645 | } |
| 646 | |
| 647 | /* |
| 648 | ** COMMAND: test-search-stext |
| 649 | ** |
| 650 | ** Usage: fossil test-search-stext TYPE ARG1 ARG2 |
| 651 | */ |
| 652 | void test_search_stext(void){ |
| 653 | Blob out; |
| 654 | db_find_and_open_repository(0,0); |
| 655 | if( g.argc!=5 ) usage("TYPE ARG1 ARG2"); |
| 656 | search_stext(g.argv[2][0], g.argv[3], g.argv[4], &out); |
| 657 | fossil_print("%s",blob_str(&out)); |
| 658 | blob_reset(&out); |
| 659 | } |
| 660 |
| --- src/search.c | |
| +++ src/search.c | |
| @@ -411,10 +411,40 @@ | |
| 411 | } |
| 412 | }else{ |
| 413 | sqlite3_result_int(context, score); |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | /* |
| 418 | ** This is an SQLite function that computes the searchable text. |
| 419 | ** It is a wrapper around the search_stext() routine. See the |
| 420 | ** search_stext() routine for further detail. |
| 421 | */ |
| 422 | static void search_stext_sqlfunc( |
| 423 | sqlite3_context *context, |
| 424 | int argc, |
| 425 | sqlite3_value **argv |
| 426 | ){ |
| 427 | Blob txt; |
| 428 | const char *zType = (const char*)sqlite3_value_text(argv[0]); |
| 429 | const char *zArg1 = (const char*)sqlite3_value_text(argv[1]); |
| 430 | const char *zArg2 = (const char*)sqlite3_value_text(argv[2]); |
| 431 | search_stext(zType[0], zArg1, zArg2, &txt); |
| 432 | sqlite3_result_text(context, blob_materialize(&txt), -1, fossil_free); |
| 433 | } |
| 434 | |
| 435 | /* |
| 436 | ** Encode a string for use as a query parameter in a URL |
| 437 | */ |
| 438 | static void search_urlencode_sqlfunc( |
| 439 | sqlite3_context *context, |
| 440 | int argc, |
| 441 | sqlite3_value **argv |
| 442 | ){ |
| 443 | char *z = mprintf("%T",sqlite3_value_text(argv[0])); |
| 444 | sqlite3_result_text(context, z, -1, fossil_free); |
| 445 | } |
| 446 | |
| 447 | /* |
| 448 | ** Register the "score()" SQL function to score its input text |
| 449 | ** using the given Search object. Once this function is registered, |
| 450 | ** do not delete the Search object. |
| @@ -424,10 +454,14 @@ | |
| 454 | search_score_sqlfunc, 0, 0); |
| 455 | sqlite3_create_function(db, "snippet", -1, SQLITE_UTF8, &gSearch, |
| 456 | search_score_sqlfunc, 0, 0); |
| 457 | sqlite3_create_function(db, "search_init", -1, SQLITE_UTF8, 0, |
| 458 | search_init_sqlfunc, 0, 0); |
| 459 | sqlite3_create_function(db, "stext", 3, SQLITE_UTF8, 0, |
| 460 | search_stext_sqlfunc, 0, 0); |
| 461 | sqlite3_create_function(db, "urlencode", 1, SQLITE_UTF8, 0, |
| 462 | search_urlencode_sqlfunc, 0, 0); |
| 463 | } |
| 464 | |
| 465 | /* |
| 466 | ** Testing the search function. |
| 467 | ** |
| @@ -522,10 +556,11 @@ | |
| 556 | ** * Search wiki |
| 557 | */ |
| 558 | void search_page(void){ |
| 559 | const char *zPattern = PD("s",""); |
| 560 | Stmt q; |
| 561 | const char *zSrchEnable = "dwc"; |
| 562 | |
| 563 | login_check_credentials(); |
| 564 | if( !g.perm.Read ){ login_needed(); return; } |
| 565 | style_header("Search"); |
| 566 | @ <form method="GET" action="search"><center> |
| @@ -537,30 +572,72 @@ | |
| 572 | search_sql_setup(g.db); |
| 573 | add_content_sql_commands(g.db); |
| 574 | search_init(zPattern, "<b>", "</b>", " ... ", |
| 575 | SRCHFLG_STATIC|SRCHFLG_HTML|SRCHFLG_SCORE); |
| 576 | db_multi_exec( |
| 577 | "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" |
| 578 | "CREATE TEMP TABLE x(label TEXT,url TEXT,date TEXT,snip TEXT);" |
| 579 | ); |
| 580 | if( strchr(zSrchEnable, 'd') ){ |
| 581 | db_multi_exec( |
| 582 | "INSERT INTO x(label,url,date,snip)" |
| 583 | " SELECT printf('Document: %%s',foci.filename)," |
| 584 | " printf('%R/doc/trunk/%%s',foci.filename)," |
| 585 | " (SELECT datetime(event.mtime) FROM event" |
| 586 | " WHERE objid=symbolic_name_to_rid('trunk'))," |
| 587 | " snippet(stext('d',blob.rid,foci.filename))" |
| 588 | " FROM foci CROSS JOIN blob" |
| 589 | " WHERE checkinID=symbolic_name_to_rid('trunk')" |
| 590 | " AND blob.uuid=foci.uuid" |
| 591 | " AND (filename GLOB '*.wiki' OR" |
| 592 | " filename GLOB '*.md' OR" |
| 593 | " filename GLOB '*.txt' OR" |
| 594 | " filename GLOB '*.html');" |
| 595 | ); |
| 596 | } |
| 597 | if( strchr(zSrchEnable, 'w') ){ |
| 598 | db_multi_exec( |
| 599 | "WITH wiki(name,rid,mtime) AS (" |
| 600 | " SELECT substr(tagname,6), tagxref.rid, max(tagxref.mtime)" |
| 601 | " FROM tag, tagxref" |
| 602 | " WHERE tag.tagname GLOB 'wiki-*'" |
| 603 | " AND tagxref.tagid=tag.tagid" |
| 604 | " GROUP BY 1" |
| 605 | ")" |
| 606 | "INSERT INTO x(label,url,date,snip)" |
| 607 | " SELECT printf('Wiki: %%s',name)," |
| 608 | " printf('%R/wiki?name=%%s',urlencode(name))," |
| 609 | " datetime(mtime)," |
| 610 | " snippet(stext('w',rid,name))" |
| 611 | " FROM wiki;" |
| 612 | ); |
| 613 | } |
| 614 | if( strchr(zSrchEnable, 'c') ){ |
| 615 | db_multi_exec( |
| 616 | "WITH ckin(uuid,rid,mtime) AS (" |
| 617 | " SELECT blob.uuid, event.objid, event.mtime" |
| 618 | " FROM event, blob" |
| 619 | " WHERE event.type='ci'" |
| 620 | " AND blob.rid=event.objid" |
| 621 | ")" |
| 622 | "INSERT INTO x(label,url,date,snip)" |
| 623 | " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," |
| 624 | " printf('%R/timeline?c=%%s&n=8&y=ci',uuid)," |
| 625 | " datetime(mtime)," |
| 626 | " snippet(stext('c',rid,NULL))" |
| 627 | " FROM ckin;" |
| 628 | ); |
| 629 | } |
| 630 | db_prepare(&q, "SELECT url, substr(snip,9), label" |
| 631 | " FROM x WHERE snip IS NOT NULL" |
| 632 | " ORDER BY substr(snip,1,9) DESC, date DESC;"); |
| 633 | @ <ol> |
| 634 | while( db_step(&q)==SQLITE_ROW ){ |
| 635 | const char *zUrl = db_column_text(&q, 0); |
| 636 | const char *zSnippet = db_column_text(&q, 1); |
| 637 | const char *zLabel = db_column_text(&q, 2); |
| 638 | @ <li><p>%s(href("%s",zUrl))%h(zLabel)</a><br>%s(zSnippet)</li> |
| 639 | } |
| 640 | db_finalize(&q); |
| 641 | @ </ol> |
| 642 | } |
| 643 | style_footer(); |
| @@ -639,21 +716,82 @@ | |
| 716 | pOut); |
| 717 | blob_reset(&wiki); |
| 718 | manifest_destroy(pWiki); |
| 719 | break; |
| 720 | } |
| 721 | case 'c': { /* Ckeckin: zArg1: RID of the checkin. zArg2: Not used */ |
| 722 | int rid = atoi(zArg1); |
| 723 | static Stmt q; |
| 724 | db_static_prepare(&q, |
| 725 | "SELECT coalesce(ecomment,comment)" |
| 726 | " ||' (user: '||coalesce(euser,user,'?')" |
| 727 | " ||', tags: '||" |
| 728 | " (SELECT group_concat(substr(tag.tagname,5),',')" |
| 729 | " FROM tag, tagxref" |
| 730 | " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" |
| 731 | " AND tagxref.rid=event.objid AND tagxref.tagtype>0)" |
| 732 | " ||')'" |
| 733 | " FROM event WHERE objid=:x AND type='ci'"); |
| 734 | db_bind_int(&q, ":x", rid); |
| 735 | if( db_step(&q)==SQLITE_ROW ){ |
| 736 | db_column_blob(&q, 0, pOut); |
| 737 | blob_append(pOut, "\n", 1); |
| 738 | } |
| 739 | db_reset(&q); |
| 740 | break; |
| 741 | } |
| 742 | } |
| 743 | } |
| 744 | |
| 745 | /* |
| 746 | ** The arguments cType,zArg1,zArg2 define an object that can be searched |
| 747 | ** for. Return a URL (relative to the root of the Fossil project) that |
| 748 | ** will jump to that document. |
| 749 | ** |
| 750 | ** Space to hold the returned string is obtained from mprintf() and should |
| 751 | ** be freed by the caller using fossil_free() or the equivalent. |
| 752 | */ |
| 753 | char *search_url( |
| 754 | char cType, /* Type of document */ |
| 755 | const char *zArg1, /* First parameter */ |
| 756 | const char *zArg2 /* Second parameter */ |
| 757 | ){ |
| 758 | char *zUrl = 0; |
| 759 | switch( cType ){ |
| 760 | case 'd': { /* Doc. zArg1: RID of the file. zArg2: Filename */ |
| 761 | case 's': /* Source. zArg1: RID of the file. zArg2: Filename */ |
| 762 | zUrl = db_text(0, |
| 763 | "SELECT printf('/doc/%%s%%s', substr(blob.uuid,20), %Q)" |
| 764 | " FROM mlink, blob" |
| 765 | " WHERE mlink.fid=%d AND mlink.mid=blob.rid", |
| 766 | zArg2, atoi(zArg1)); |
| 767 | break; |
| 768 | } |
| 769 | case 'w': { /* Wiki. zArg1: RID of the page. zArg2: Page name */ |
| 770 | char *zId = db_text(0, "SELECT uuid FROM blob WHERE rid=%d",atoi(zArg1)); |
| 771 | zUrl = mprintf("/wiki?id=%z&name=%t", zId, zArg2); |
| 772 | break; |
| 773 | } |
| 774 | case 'c': { /* Ckeckin: zArg1: RID of the checkin. zArg2: Not used */ |
| 775 | char *zId = db_text(0, "SELECT uuid FROM blob WHERE rid=%d",atoi(zArg1)); |
| 776 | zUrl = mprintf("/info/%z", zId); |
| 777 | break; |
| 778 | } |
| 779 | } |
| 780 | return zUrl; |
| 781 | } |
| 782 | |
| 783 | /* |
| 784 | ** COMMAND: test-search-stext |
| 785 | ** |
| 786 | ** Usage: fossil test-search-stext TYPE ARG1 ARG2 |
| 787 | */ |
| 788 | void test_search_stext(void){ |
| 789 | Blob out; |
| 790 | char *zUrl; |
| 791 | db_find_and_open_repository(0,0); |
| 792 | if( g.argc!=5 ) usage("TYPE ARG1 ARG2"); |
| 793 | search_stext(g.argv[2][0], g.argv[3], g.argv[4], &out); |
| 794 | zUrl = search_url(g.argv[2][0], g.argv[3], g.argv[4]); |
| 795 | fossil_print("%s\n%z\n",blob_str(&out),zUrl); |
| 796 | blob_reset(&out); |
| 797 | } |
| 798 |
+10
-7
| --- src/wiki.c | ||
| +++ src/wiki.c | ||
| @@ -264,17 +264,20 @@ | ||
| 264 | 264 | if( isSandbox ){ |
| 265 | 265 | zBody = db_get("sandbox",zBody); |
| 266 | 266 | zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki"); |
| 267 | 267 | rid = 0; |
| 268 | 268 | }else{ |
| 269 | - zTag = mprintf("wiki-%s", zPageName); | |
| 270 | - rid = db_int(0, | |
| 271 | - "SELECT rid FROM tagxref" | |
| 272 | - " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" | |
| 273 | - " ORDER BY mtime DESC", zTag | |
| 274 | - ); | |
| 275 | - free(zTag); | |
| 269 | + const char *zUuid = P("id"); | |
| 270 | + if( zUuid==0 || (rid = symbolic_name_to_rid(zUuid,"w"))==0 ){ | |
| 271 | + zTag = mprintf("wiki-%s", zPageName); | |
| 272 | + rid = db_int(0, | |
| 273 | + "SELECT rid FROM tagxref" | |
| 274 | + " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" | |
| 275 | + " ORDER BY mtime DESC", zTag | |
| 276 | + ); | |
| 277 | + free(zTag); | |
| 278 | + } | |
| 276 | 279 | pWiki = manifest_get(rid, CFTYPE_WIKI, 0); |
| 277 | 280 | if( pWiki ){ |
| 278 | 281 | zBody = pWiki->zWiki; |
| 279 | 282 | zMimetype = pWiki->zMimetype; |
| 280 | 283 | } |
| 281 | 284 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -264,17 +264,20 @@ | |
| 264 | if( isSandbox ){ |
| 265 | zBody = db_get("sandbox",zBody); |
| 266 | zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki"); |
| 267 | rid = 0; |
| 268 | }else{ |
| 269 | zTag = mprintf("wiki-%s", zPageName); |
| 270 | rid = db_int(0, |
| 271 | "SELECT rid FROM tagxref" |
| 272 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" |
| 273 | " ORDER BY mtime DESC", zTag |
| 274 | ); |
| 275 | free(zTag); |
| 276 | pWiki = manifest_get(rid, CFTYPE_WIKI, 0); |
| 277 | if( pWiki ){ |
| 278 | zBody = pWiki->zWiki; |
| 279 | zMimetype = pWiki->zMimetype; |
| 280 | } |
| 281 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -264,17 +264,20 @@ | |
| 264 | if( isSandbox ){ |
| 265 | zBody = db_get("sandbox",zBody); |
| 266 | zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki"); |
| 267 | rid = 0; |
| 268 | }else{ |
| 269 | const char *zUuid = P("id"); |
| 270 | if( zUuid==0 || (rid = symbolic_name_to_rid(zUuid,"w"))==0 ){ |
| 271 | zTag = mprintf("wiki-%s", zPageName); |
| 272 | rid = db_int(0, |
| 273 | "SELECT rid FROM tagxref" |
| 274 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" |
| 275 | " ORDER BY mtime DESC", zTag |
| 276 | ); |
| 277 | free(zTag); |
| 278 | } |
| 279 | pWiki = manifest_get(rid, CFTYPE_WIKI, 0); |
| 280 | if( pWiki ){ |
| 281 | zBody = pWiki->zWiki; |
| 282 | zMimetype = pWiki->zMimetype; |
| 283 | } |
| 284 |