Fossil SCM
Make some auxiliary Th1 variables available within skins' header/footer. For /doc, /uv and /wiki pages put into "artifact_hashsum" variable (and also into its page-specific variant) a full identifier of the requested file or wiki. For /wiki pages also provide "wiki_editor" and "wiki_timestamp" variables. There is an old forum [forum:/forumtread/436c618d309917|thread on the topic] and another thread on [forum:/forumthread/48e2ae955f09|a little bit related] issue.
Commit
80e062e1a7fea350fae19c47762d3e7263038327ffbc1aee7073f1da5f5f817c
Parent
61132cefacba2ea…
2 files changed
+28
-6
+12
+28
-6
| --- src/doc.c | ||
| +++ src/doc.c | ||
| @@ -910,19 +910,20 @@ | ||
| 910 | 910 | */ |
| 911 | 911 | void doc_page(void){ |
| 912 | 912 | const char *zName = 0; /* Argument to the /doc page */ |
| 913 | 913 | const char *zOrigName = "?"; /* Original document name */ |
| 914 | 914 | const char *zMime; /* Document MIME type */ |
| 915 | - char *zCheckin = "tip"; /* The check-in holding the document */ | |
| 916 | - char *zPathSuffix = ""; /* Text to append to g.zPath */ | |
| 915 | + const char *zCheckin = "tip"; /* The check-in holding the document */ | |
| 916 | + const char *zPathSuffix = ""; /* Text to append to g.zPath */ | |
| 917 | 917 | int vid = 0; /* Artifact of check-in */ |
| 918 | 918 | int rid = 0; /* Artifact of file */ |
| 919 | 919 | int i; /* Loop counter */ |
| 920 | 920 | Blob filebody; /* Content of the documentation file */ |
| 921 | 921 | Blob title; /* Document title */ |
| 922 | + Blob filehash = empty_blob; /* Hashsum of the document's source */ | |
| 922 | 923 | int nMiss = (-1); /* Failed attempts to find the document */ |
| 923 | - int isUV = g.zPath[0]=='u'; /* True for /uv. False for /doc */ | |
| 924 | + const int isUV = g.zPath[0]=='u'; /* True for /uv. False for /doc */ | |
| 924 | 925 | const char *zDfltTitle; |
| 925 | 926 | static const char *const azSuffix[] = { |
| 926 | 927 | "index.html", "index.wiki", "index.md" |
| 927 | 928 | #ifdef FOSSIL_ENABLE_TH1_DOCS |
| 928 | 929 | , "index.th1" |
| @@ -986,20 +987,23 @@ | ||
| 986 | 987 | } |
| 987 | 988 | } |
| 988 | 989 | if( isUV ){ |
| 989 | 990 | if( db_table_exists("repository","unversioned") ){ |
| 990 | 991 | rid = unversioned_content(zName, &filebody); |
| 991 | - if( rid==1 ){ | |
| 992 | + if( rid==1 ){ /* found by name */ | |
| 992 | 993 | Stmt q; |
| 993 | 994 | db_prepare(&q, "SELECT hash, mtime FROM unversioned" |
| 994 | 995 | " WHERE name=%Q", zName); |
| 995 | 996 | if( db_step(&q)==SQLITE_ROW ){ |
| 996 | - etag_check(ETAG_HASH, db_column_text(&q,0)); | |
| 997 | + const char* hash = db_column_text(&q,0); | |
| 998 | + blob_set_dynamic( &filehash, fossil_strdup( hash )); | |
| 999 | + etag_check(ETAG_HASH,hash); | |
| 997 | 1000 | etag_last_modified(db_column_int64(&q,1)); |
| 998 | 1001 | } |
| 999 | 1002 | db_finalize(&q); |
| 1000 | - }else if( rid==2 ){ | |
| 1003 | + }else if( rid==2 ){ /* found by hash */ | |
| 1004 | + blob_set_dynamic( &filehash, fossil_strdup( zName )); | |
| 1001 | 1005 | zName = db_text(zName, |
| 1002 | 1006 | "SELECT name FROM unversioned WHERE hash=%Q", zName); |
| 1003 | 1007 | g.isConst = 1; |
| 1004 | 1008 | } |
| 1005 | 1009 | zDfltTitle = zName; |
| @@ -1012,15 +1016,21 @@ | ||
| 1012 | 1016 | db_must_be_within_tree(); |
| 1013 | 1017 | zFullpath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 1014 | 1018 | if( file_isfile(zFullpath, RepoFILE) |
| 1015 | 1019 | && blob_read_from_file(&filebody, zFullpath, RepoFILE)>0 ){ |
| 1016 | 1020 | rid = 1; /* Fake RID just to get the loop to end */ |
| 1021 | + if(filebody.nUsed <= 256*1024){/* don't hash big files */ | |
| 1022 | + hname_hash( &filebody, 0, &filehash ); | |
| 1023 | + } | |
| 1017 | 1024 | } |
| 1018 | 1025 | fossil_free(zFullpath); |
| 1019 | 1026 | }else{ |
| 1020 | 1027 | vid = symbolic_name_to_rid(zCheckin, "ci"); |
| 1021 | 1028 | rid = vid>0 ? doc_load_content(vid, zName, &filebody) : 0; |
| 1029 | + if( rid ){ | |
| 1030 | + blob_set_dynamic( &filehash, rid_to_uuid(rid) ); | |
| 1031 | + } | |
| 1022 | 1032 | } |
| 1023 | 1033 | } |
| 1024 | 1034 | g.zPath = mprintf("%s/%s", g.zPath, zPathSuffix); |
| 1025 | 1035 | if( rid==0 ) goto doc_not_found; |
| 1026 | 1036 | blob_to_utf8_no_bom(&filebody, 0); |
| @@ -1031,11 +1041,23 @@ | ||
| 1031 | 1041 | zMime = nMiss==0 ? P("mimetype") : 0; |
| 1032 | 1042 | if( zMime==0 ){ |
| 1033 | 1043 | zMime = mimetype_from_name(zName); |
| 1034 | 1044 | } |
| 1035 | 1045 | Th_Store("doc_name", zName); |
| 1046 | + if( !blob_is_reset(&filehash) ){ | |
| 1047 | + Th_Store( "artifact_hashsum", blob_str(&filehash)); | |
| 1048 | + Th_Store( isUV ? "uv_hashsum" : "doc_hashsum", blob_str(&filehash)); | |
| 1049 | + blob_reset( &filehash ); | |
| 1050 | + } | |
| 1036 | 1051 | if( vid ){ |
| 1052 | + /* FIXME: the following two Th1 variables seem misleading because | |
| 1053 | + ** 1) variables' names imply a document while their | |
| 1054 | + ** values correspond to a check-in that is being served, | |
| 1055 | + ** 2) truncation and mangling of `uuid` seems very unhelpful, | |
| 1056 | + ** 3) the date's meaning seems ambiguous | |
| 1057 | + ** (expecially if check-in has been amended) | |
| 1058 | + */ | |
| 1037 | 1059 | Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'" |
| 1038 | 1060 | " FROM blob WHERE rid=%d", vid)); |
| 1039 | 1061 | Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" |
| 1040 | 1062 | " WHERE objid=%d AND type='ci'", vid)); |
| 1041 | 1063 | } |
| 1042 | 1064 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -910,19 +910,20 @@ | |
| 910 | */ |
| 911 | void doc_page(void){ |
| 912 | const char *zName = 0; /* Argument to the /doc page */ |
| 913 | const char *zOrigName = "?"; /* Original document name */ |
| 914 | const char *zMime; /* Document MIME type */ |
| 915 | char *zCheckin = "tip"; /* The check-in holding the document */ |
| 916 | char *zPathSuffix = ""; /* Text to append to g.zPath */ |
| 917 | int vid = 0; /* Artifact of check-in */ |
| 918 | int rid = 0; /* Artifact of file */ |
| 919 | int i; /* Loop counter */ |
| 920 | Blob filebody; /* Content of the documentation file */ |
| 921 | Blob title; /* Document title */ |
| 922 | int nMiss = (-1); /* Failed attempts to find the document */ |
| 923 | int isUV = g.zPath[0]=='u'; /* True for /uv. False for /doc */ |
| 924 | const char *zDfltTitle; |
| 925 | static const char *const azSuffix[] = { |
| 926 | "index.html", "index.wiki", "index.md" |
| 927 | #ifdef FOSSIL_ENABLE_TH1_DOCS |
| 928 | , "index.th1" |
| @@ -986,20 +987,23 @@ | |
| 986 | } |
| 987 | } |
| 988 | if( isUV ){ |
| 989 | if( db_table_exists("repository","unversioned") ){ |
| 990 | rid = unversioned_content(zName, &filebody); |
| 991 | if( rid==1 ){ |
| 992 | Stmt q; |
| 993 | db_prepare(&q, "SELECT hash, mtime FROM unversioned" |
| 994 | " WHERE name=%Q", zName); |
| 995 | if( db_step(&q)==SQLITE_ROW ){ |
| 996 | etag_check(ETAG_HASH, db_column_text(&q,0)); |
| 997 | etag_last_modified(db_column_int64(&q,1)); |
| 998 | } |
| 999 | db_finalize(&q); |
| 1000 | }else if( rid==2 ){ |
| 1001 | zName = db_text(zName, |
| 1002 | "SELECT name FROM unversioned WHERE hash=%Q", zName); |
| 1003 | g.isConst = 1; |
| 1004 | } |
| 1005 | zDfltTitle = zName; |
| @@ -1012,15 +1016,21 @@ | |
| 1012 | db_must_be_within_tree(); |
| 1013 | zFullpath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 1014 | if( file_isfile(zFullpath, RepoFILE) |
| 1015 | && blob_read_from_file(&filebody, zFullpath, RepoFILE)>0 ){ |
| 1016 | rid = 1; /* Fake RID just to get the loop to end */ |
| 1017 | } |
| 1018 | fossil_free(zFullpath); |
| 1019 | }else{ |
| 1020 | vid = symbolic_name_to_rid(zCheckin, "ci"); |
| 1021 | rid = vid>0 ? doc_load_content(vid, zName, &filebody) : 0; |
| 1022 | } |
| 1023 | } |
| 1024 | g.zPath = mprintf("%s/%s", g.zPath, zPathSuffix); |
| 1025 | if( rid==0 ) goto doc_not_found; |
| 1026 | blob_to_utf8_no_bom(&filebody, 0); |
| @@ -1031,11 +1041,23 @@ | |
| 1031 | zMime = nMiss==0 ? P("mimetype") : 0; |
| 1032 | if( zMime==0 ){ |
| 1033 | zMime = mimetype_from_name(zName); |
| 1034 | } |
| 1035 | Th_Store("doc_name", zName); |
| 1036 | if( vid ){ |
| 1037 | Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'" |
| 1038 | " FROM blob WHERE rid=%d", vid)); |
| 1039 | Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" |
| 1040 | " WHERE objid=%d AND type='ci'", vid)); |
| 1041 | } |
| 1042 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -910,19 +910,20 @@ | |
| 910 | */ |
| 911 | void doc_page(void){ |
| 912 | const char *zName = 0; /* Argument to the /doc page */ |
| 913 | const char *zOrigName = "?"; /* Original document name */ |
| 914 | const char *zMime; /* Document MIME type */ |
| 915 | const char *zCheckin = "tip"; /* The check-in holding the document */ |
| 916 | const char *zPathSuffix = ""; /* Text to append to g.zPath */ |
| 917 | int vid = 0; /* Artifact of check-in */ |
| 918 | int rid = 0; /* Artifact of file */ |
| 919 | int i; /* Loop counter */ |
| 920 | Blob filebody; /* Content of the documentation file */ |
| 921 | Blob title; /* Document title */ |
| 922 | Blob filehash = empty_blob; /* Hashsum of the document's source */ |
| 923 | int nMiss = (-1); /* Failed attempts to find the document */ |
| 924 | const int isUV = g.zPath[0]=='u'; /* True for /uv. False for /doc */ |
| 925 | const char *zDfltTitle; |
| 926 | static const char *const azSuffix[] = { |
| 927 | "index.html", "index.wiki", "index.md" |
| 928 | #ifdef FOSSIL_ENABLE_TH1_DOCS |
| 929 | , "index.th1" |
| @@ -986,20 +987,23 @@ | |
| 987 | } |
| 988 | } |
| 989 | if( isUV ){ |
| 990 | if( db_table_exists("repository","unversioned") ){ |
| 991 | rid = unversioned_content(zName, &filebody); |
| 992 | if( rid==1 ){ /* found by name */ |
| 993 | Stmt q; |
| 994 | db_prepare(&q, "SELECT hash, mtime FROM unversioned" |
| 995 | " WHERE name=%Q", zName); |
| 996 | if( db_step(&q)==SQLITE_ROW ){ |
| 997 | const char* hash = db_column_text(&q,0); |
| 998 | blob_set_dynamic( &filehash, fossil_strdup( hash )); |
| 999 | etag_check(ETAG_HASH,hash); |
| 1000 | etag_last_modified(db_column_int64(&q,1)); |
| 1001 | } |
| 1002 | db_finalize(&q); |
| 1003 | }else if( rid==2 ){ /* found by hash */ |
| 1004 | blob_set_dynamic( &filehash, fossil_strdup( zName )); |
| 1005 | zName = db_text(zName, |
| 1006 | "SELECT name FROM unversioned WHERE hash=%Q", zName); |
| 1007 | g.isConst = 1; |
| 1008 | } |
| 1009 | zDfltTitle = zName; |
| @@ -1012,15 +1016,21 @@ | |
| 1016 | db_must_be_within_tree(); |
| 1017 | zFullpath = mprintf("%s/%s", g.zLocalRoot, zName); |
| 1018 | if( file_isfile(zFullpath, RepoFILE) |
| 1019 | && blob_read_from_file(&filebody, zFullpath, RepoFILE)>0 ){ |
| 1020 | rid = 1; /* Fake RID just to get the loop to end */ |
| 1021 | if(filebody.nUsed <= 256*1024){/* don't hash big files */ |
| 1022 | hname_hash( &filebody, 0, &filehash ); |
| 1023 | } |
| 1024 | } |
| 1025 | fossil_free(zFullpath); |
| 1026 | }else{ |
| 1027 | vid = symbolic_name_to_rid(zCheckin, "ci"); |
| 1028 | rid = vid>0 ? doc_load_content(vid, zName, &filebody) : 0; |
| 1029 | if( rid ){ |
| 1030 | blob_set_dynamic( &filehash, rid_to_uuid(rid) ); |
| 1031 | } |
| 1032 | } |
| 1033 | } |
| 1034 | g.zPath = mprintf("%s/%s", g.zPath, zPathSuffix); |
| 1035 | if( rid==0 ) goto doc_not_found; |
| 1036 | blob_to_utf8_no_bom(&filebody, 0); |
| @@ -1031,11 +1041,23 @@ | |
| 1041 | zMime = nMiss==0 ? P("mimetype") : 0; |
| 1042 | if( zMime==0 ){ |
| 1043 | zMime = mimetype_from_name(zName); |
| 1044 | } |
| 1045 | Th_Store("doc_name", zName); |
| 1046 | if( !blob_is_reset(&filehash) ){ |
| 1047 | Th_Store( "artifact_hashsum", blob_str(&filehash)); |
| 1048 | Th_Store( isUV ? "uv_hashsum" : "doc_hashsum", blob_str(&filehash)); |
| 1049 | blob_reset( &filehash ); |
| 1050 | } |
| 1051 | if( vid ){ |
| 1052 | /* FIXME: the following two Th1 variables seem misleading because |
| 1053 | ** 1) variables' names imply a document while their |
| 1054 | ** values correspond to a check-in that is being served, |
| 1055 | ** 2) truncation and mangling of `uuid` seems very unhelpful, |
| 1056 | ** 3) the date's meaning seems ambiguous |
| 1057 | ** (expecially if check-in has been amended) |
| 1058 | */ |
| 1059 | Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'" |
| 1060 | " FROM blob WHERE rid=%d", vid)); |
| 1061 | Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event" |
| 1062 | " WHERE objid=%d AND type='ci'", vid)); |
| 1063 | } |
| 1064 |
+12
| --- src/wiki.c | ||
| +++ src/wiki.c | ||
| @@ -543,10 +543,11 @@ | ||
| 543 | 543 | unsigned submenuFlags = W_HELP; |
| 544 | 544 | Blob wiki; |
| 545 | 545 | Manifest *pWiki = 0; |
| 546 | 546 | const char *zPageName; |
| 547 | 547 | const char *zMimetype = 0; |
| 548 | + const char *zHashsum = 0; /* of the wiki's corresponding artifact */ | |
| 548 | 549 | int isPopup = P("popup")!=0; |
| 549 | 550 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| 550 | 551 | int noSubmenu = P("nsm")!=0; |
| 551 | 552 | |
| 552 | 553 | login_check_credentials(); |
| @@ -580,10 +581,11 @@ | ||
| 580 | 581 | } |
| 581 | 582 | pWiki = manifest_get(rid, CFTYPE_WIKI, 0); |
| 582 | 583 | if( pWiki ){ |
| 583 | 584 | zBody = pWiki->zWiki; |
| 584 | 585 | zMimetype = pWiki->zMimetype; |
| 586 | + zHashsum = rid_to_uuid(rid); | |
| 585 | 587 | } |
| 586 | 588 | } |
| 587 | 589 | zMimetype = wiki_filter_mimetypes(zMimetype); |
| 588 | 590 | if( !g.isHome && !noSubmenu ){ |
| 589 | 591 | if( ((rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki)) |
| @@ -597,10 +599,20 @@ | ||
| 597 | 599 | style_submenu_element("History", "%R/whistory?name=%T", zPageName); |
| 598 | 600 | style_submenu_parametric("wiki",7); |
| 599 | 601 | } |
| 600 | 602 | } |
| 601 | 603 | if( !isPopup ){ |
| 604 | + char *zDate = db_text(0,"SELECT strftime('%%Y-%%m-%%d %%H:%%M'," | |
| 605 | + "'%.17g',toLocal())",pWiki->rDate); | |
| 606 | + Th_Store("wiki_timestamp",zDate); | |
| 607 | + fossil_free( zDate ); | |
| 608 | + Th_Store("wiki_editor",pWiki->zUser); | |
| 609 | + if( zHashsum ){ | |
| 610 | + Th_Store("artifact_hashsum",zHashsum); | |
| 611 | + Th_Store("wiki_hashsum",zHashsum); | |
| 612 | + fossil_free( (char*)zHashsum ); | |
| 613 | + } | |
| 602 | 614 | style_set_current_page("%T?name=%T", g.zPath, zPageName); |
| 603 | 615 | wiki_page_header(WIKITYPE_UNKNOWN, zPageName, ""); |
| 604 | 616 | if( !noSubmenu ){ |
| 605 | 617 | wiki_standard_submenu(submenuFlags); |
| 606 | 618 | } |
| 607 | 619 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -543,10 +543,11 @@ | |
| 543 | unsigned submenuFlags = W_HELP; |
| 544 | Blob wiki; |
| 545 | Manifest *pWiki = 0; |
| 546 | const char *zPageName; |
| 547 | const char *zMimetype = 0; |
| 548 | int isPopup = P("popup")!=0; |
| 549 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| 550 | int noSubmenu = P("nsm")!=0; |
| 551 | |
| 552 | login_check_credentials(); |
| @@ -580,10 +581,11 @@ | |
| 580 | } |
| 581 | pWiki = manifest_get(rid, CFTYPE_WIKI, 0); |
| 582 | if( pWiki ){ |
| 583 | zBody = pWiki->zWiki; |
| 584 | zMimetype = pWiki->zMimetype; |
| 585 | } |
| 586 | } |
| 587 | zMimetype = wiki_filter_mimetypes(zMimetype); |
| 588 | if( !g.isHome && !noSubmenu ){ |
| 589 | if( ((rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki)) |
| @@ -597,10 +599,20 @@ | |
| 597 | style_submenu_element("History", "%R/whistory?name=%T", zPageName); |
| 598 | style_submenu_parametric("wiki",7); |
| 599 | } |
| 600 | } |
| 601 | if( !isPopup ){ |
| 602 | style_set_current_page("%T?name=%T", g.zPath, zPageName); |
| 603 | wiki_page_header(WIKITYPE_UNKNOWN, zPageName, ""); |
| 604 | if( !noSubmenu ){ |
| 605 | wiki_standard_submenu(submenuFlags); |
| 606 | } |
| 607 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -543,10 +543,11 @@ | |
| 543 | unsigned submenuFlags = W_HELP; |
| 544 | Blob wiki; |
| 545 | Manifest *pWiki = 0; |
| 546 | const char *zPageName; |
| 547 | const char *zMimetype = 0; |
| 548 | const char *zHashsum = 0; /* of the wiki's corresponding artifact */ |
| 549 | int isPopup = P("popup")!=0; |
| 550 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| 551 | int noSubmenu = P("nsm")!=0; |
| 552 | |
| 553 | login_check_credentials(); |
| @@ -580,10 +581,11 @@ | |
| 581 | } |
| 582 | pWiki = manifest_get(rid, CFTYPE_WIKI, 0); |
| 583 | if( pWiki ){ |
| 584 | zBody = pWiki->zWiki; |
| 585 | zMimetype = pWiki->zMimetype; |
| 586 | zHashsum = rid_to_uuid(rid); |
| 587 | } |
| 588 | } |
| 589 | zMimetype = wiki_filter_mimetypes(zMimetype); |
| 590 | if( !g.isHome && !noSubmenu ){ |
| 591 | if( ((rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki)) |
| @@ -597,10 +599,20 @@ | |
| 599 | style_submenu_element("History", "%R/whistory?name=%T", zPageName); |
| 600 | style_submenu_parametric("wiki",7); |
| 601 | } |
| 602 | } |
| 603 | if( !isPopup ){ |
| 604 | char *zDate = db_text(0,"SELECT strftime('%%Y-%%m-%%d %%H:%%M'," |
| 605 | "'%.17g',toLocal())",pWiki->rDate); |
| 606 | Th_Store("wiki_timestamp",zDate); |
| 607 | fossil_free( zDate ); |
| 608 | Th_Store("wiki_editor",pWiki->zUser); |
| 609 | if( zHashsum ){ |
| 610 | Th_Store("artifact_hashsum",zHashsum); |
| 611 | Th_Store("wiki_hashsum",zHashsum); |
| 612 | fossil_free( (char*)zHashsum ); |
| 613 | } |
| 614 | style_set_current_page("%T?name=%T", g.zPath, zPageName); |
| 615 | wiki_page_header(WIKITYPE_UNKNOWN, zPageName, ""); |
| 616 | if( !noSubmenu ){ |
| 617 | wiki_standard_submenu(submenuFlags); |
| 618 | } |
| 619 |