Fossil SCM
Land the timeline-enhance-2025 branch. The "Simple" timeline view. Improved access to tarballs and ZIPs. See items 3 and 4 in the merged [/doc/7b7cbd325350/www/changes.wiki|www/changes.wiki] for more detail.
Commit
6ce705b8dcfeca4f83c95a0a0a566e8912449d32a64abd2cec0ee83ba1c51a2f
Parent
ee445feef78a265…
15 files changed
+22
-30
+6
+7
-27
+2
-2
+3
+14
-7
+4
-4
+13
-24
+48
-19
+73
-40
+2
-2
+350
-76
+79
-46
+1
-9
+34
-2
+22
-30
| --- src/branch.c | ||
| +++ src/branch.c | ||
| @@ -843,10 +843,11 @@ | ||
| 843 | 843 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 844 | 844 | style_set_current_feature("branch"); |
| 845 | 845 | style_header("Branches"); |
| 846 | 846 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 847 | 847 | style_submenu_checkbox("colors", "Use Branch Colors", 0, 0); |
| 848 | + | |
| 848 | 849 | login_anonymous_available(); |
| 849 | 850 | |
| 850 | 851 | brlist_create_temp_table(); |
| 851 | 852 | db_prepare(&q, "SELECT * FROM tmp_brlist ORDER BY mtime DESC"); |
| 852 | 853 | rNow = db_double(0.0, "SELECT julianday('now')"); |
| @@ -1025,34 +1026,30 @@ | ||
| 1025 | 1026 | Stmt *pQuery, /* Current row of the timeline query */ |
| 1026 | 1027 | int tmFlags, /* Flags to www_print_timeline() */ |
| 1027 | 1028 | const char *zThisUser, /* Suppress links to this user */ |
| 1028 | 1029 | const char *zThisTag /* Suppress links to this tag */ |
| 1029 | 1030 | ){ |
| 1030 | - int rid = db_column_int(pQuery, 0); | |
| 1031 | - Stmt q; | |
| 1031 | + int rid; | |
| 1032 | + int tmFlagsNew; | |
| 1033 | + char *zBrName; | |
| 1034 | + | |
| 1035 | + if( (tmFlags & TIMELINE_INLINE)!=0 ){ | |
| 1036 | + tmFlagsNew = (tmFlags & ~TIMELINE_VIEWS) | TIMELINE_MODERN; | |
| 1037 | + cgi_printf("("); | |
| 1038 | + }else{ | |
| 1039 | + tmFlagsNew = tmFlags; | |
| 1040 | + } | |
| 1041 | + timeline_extra(pQuery,tmFlagsNew,zThisUser,zThisTag); | |
| 1042 | + | |
| 1032 | 1043 | if( !g.perm.Hyperlink ) return; |
| 1033 | - db_prepare(&q, | |
| 1034 | - "SELECT substr(tagname,5) FROM tagxref, tag" | |
| 1035 | - " WHERE tagxref.rid=%d" | |
| 1036 | - " AND tagxref.tagid=tag.tagid" | |
| 1037 | - " AND tagxref.tagtype>0" | |
| 1038 | - " AND tag.tagname GLOB 'sym-*'", | |
| 1039 | - rid | |
| 1040 | - ); | |
| 1041 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 1042 | - const char *zTagName = db_column_text(&q, 0); | |
| 1043 | -#define OLD_STYLE 1 | |
| 1044 | -#if OLD_STYLE | |
| 1045 | - @ %z(href("%R/timeline?r=%T",zTagName))[timeline]</a> | |
| 1046 | -#else | |
| 1047 | - char *zBrName = branch_of_rid(rid); | |
| 1048 | - @ <strong>%h(zBrName)</strong><br>\ | |
| 1049 | - @ %z(href("%R/timeline?r=%T",zTagName))<button>timeline</button></a> | |
| 1050 | - fossil_free(zBrName); | |
| 1051 | -#endif | |
| 1052 | - } | |
| 1053 | - db_finalize(&q); | |
| 1044 | + rid = db_column_int(pQuery,0); | |
| 1045 | + zBrName = branch_of_rid(rid); | |
| 1046 | + @ branch: <span class='timelineHash'>\ | |
| 1047 | + @ %z(href("%R/timeline?r=%T",zBrName))%h(zBrName)</a></span> | |
| 1048 | + if( (tmFlags & TIMELINE_INLINE)!=0 ){ | |
| 1049 | + cgi_printf(")"); | |
| 1050 | + } | |
| 1054 | 1051 | } |
| 1055 | 1052 | |
| 1056 | 1053 | /* |
| 1057 | 1054 | ** WEBPAGE: brtimeline |
| 1058 | 1055 | ** |
| @@ -1070,21 +1067,19 @@ | ||
| 1070 | 1067 | int fNoHidden = PB("nohidden")!=0; /* The "nohidden" query parameter */ |
| 1071 | 1068 | int fOnlyHidden = PB("onlyhidden")!=0; /* The "onlyhidden" query parameter */ |
| 1072 | 1069 | |
| 1073 | 1070 | login_check_credentials(); |
| 1074 | 1071 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1072 | + if( robot_restrict("timelineX") ) return; | |
| 1075 | 1073 | |
| 1076 | 1074 | style_set_current_feature("branch"); |
| 1077 | 1075 | style_header("Branches"); |
| 1078 | 1076 | style_submenu_element("Branch List", "brlist"); |
| 1079 | 1077 | login_anonymous_available(); |
| 1080 | -#if OLD_STYLE | |
| 1081 | 1078 | timeline_ss_submenu(); |
| 1082 | -#endif | |
| 1083 | 1079 | cgi_check_for_malice(); |
| 1084 | - @ <h2>First check-in for every branch, starting with the most recent | |
| 1085 | - @ and going backwards in time.</h2> | |
| 1080 | + @ <h2>The initial check-in for each branch:</h2> | |
| 1086 | 1081 | blob_append(&sql, timeline_query_for_www(), -1); |
| 1087 | 1082 | blob_append_sql(&sql, |
| 1088 | 1083 | "AND blob.rid IN (SELECT rid FROM tagxref" |
| 1089 | 1084 | " WHERE tagtype>0 AND tagid=%d AND srcid!=0)", TAG_BRANCH); |
| 1090 | 1085 | if( fNoHidden || fOnlyHidden ){ |
| @@ -1097,13 +1092,10 @@ | ||
| 1097 | 1092 | db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql)); |
| 1098 | 1093 | blob_reset(&sql); |
| 1099 | 1094 | /* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too |
| 1100 | 1095 | ** many descenders to (off-screen) parents. */ |
| 1101 | 1096 | tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL; |
| 1102 | -#if !OLD_STYLE | |
| 1103 | - tmFlags |= TIMELINE_COLUMNAR; | |
| 1104 | -#endif | |
| 1105 | 1097 | if( PB("ubg")!=0 ){ |
| 1106 | 1098 | tmFlags |= TIMELINE_UCOLOR; |
| 1107 | 1099 | }else{ |
| 1108 | 1100 | tmFlags |= TIMELINE_BRCOLOR; |
| 1109 | 1101 | } |
| 1110 | 1102 |
| --- src/branch.c | |
| +++ src/branch.c | |
| @@ -843,10 +843,11 @@ | |
| 843 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 844 | style_set_current_feature("branch"); |
| 845 | style_header("Branches"); |
| 846 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 847 | style_submenu_checkbox("colors", "Use Branch Colors", 0, 0); |
| 848 | login_anonymous_available(); |
| 849 | |
| 850 | brlist_create_temp_table(); |
| 851 | db_prepare(&q, "SELECT * FROM tmp_brlist ORDER BY mtime DESC"); |
| 852 | rNow = db_double(0.0, "SELECT julianday('now')"); |
| @@ -1025,34 +1026,30 @@ | |
| 1025 | Stmt *pQuery, /* Current row of the timeline query */ |
| 1026 | int tmFlags, /* Flags to www_print_timeline() */ |
| 1027 | const char *zThisUser, /* Suppress links to this user */ |
| 1028 | const char *zThisTag /* Suppress links to this tag */ |
| 1029 | ){ |
| 1030 | int rid = db_column_int(pQuery, 0); |
| 1031 | Stmt q; |
| 1032 | if( !g.perm.Hyperlink ) return; |
| 1033 | db_prepare(&q, |
| 1034 | "SELECT substr(tagname,5) FROM tagxref, tag" |
| 1035 | " WHERE tagxref.rid=%d" |
| 1036 | " AND tagxref.tagid=tag.tagid" |
| 1037 | " AND tagxref.tagtype>0" |
| 1038 | " AND tag.tagname GLOB 'sym-*'", |
| 1039 | rid |
| 1040 | ); |
| 1041 | while( db_step(&q)==SQLITE_ROW ){ |
| 1042 | const char *zTagName = db_column_text(&q, 0); |
| 1043 | #define OLD_STYLE 1 |
| 1044 | #if OLD_STYLE |
| 1045 | @ %z(href("%R/timeline?r=%T",zTagName))[timeline]</a> |
| 1046 | #else |
| 1047 | char *zBrName = branch_of_rid(rid); |
| 1048 | @ <strong>%h(zBrName)</strong><br>\ |
| 1049 | @ %z(href("%R/timeline?r=%T",zTagName))<button>timeline</button></a> |
| 1050 | fossil_free(zBrName); |
| 1051 | #endif |
| 1052 | } |
| 1053 | db_finalize(&q); |
| 1054 | } |
| 1055 | |
| 1056 | /* |
| 1057 | ** WEBPAGE: brtimeline |
| 1058 | ** |
| @@ -1070,21 +1067,19 @@ | |
| 1070 | int fNoHidden = PB("nohidden")!=0; /* The "nohidden" query parameter */ |
| 1071 | int fOnlyHidden = PB("onlyhidden")!=0; /* The "onlyhidden" query parameter */ |
| 1072 | |
| 1073 | login_check_credentials(); |
| 1074 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1075 | |
| 1076 | style_set_current_feature("branch"); |
| 1077 | style_header("Branches"); |
| 1078 | style_submenu_element("Branch List", "brlist"); |
| 1079 | login_anonymous_available(); |
| 1080 | #if OLD_STYLE |
| 1081 | timeline_ss_submenu(); |
| 1082 | #endif |
| 1083 | cgi_check_for_malice(); |
| 1084 | @ <h2>First check-in for every branch, starting with the most recent |
| 1085 | @ and going backwards in time.</h2> |
| 1086 | blob_append(&sql, timeline_query_for_www(), -1); |
| 1087 | blob_append_sql(&sql, |
| 1088 | "AND blob.rid IN (SELECT rid FROM tagxref" |
| 1089 | " WHERE tagtype>0 AND tagid=%d AND srcid!=0)", TAG_BRANCH); |
| 1090 | if( fNoHidden || fOnlyHidden ){ |
| @@ -1097,13 +1092,10 @@ | |
| 1097 | db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql)); |
| 1098 | blob_reset(&sql); |
| 1099 | /* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too |
| 1100 | ** many descenders to (off-screen) parents. */ |
| 1101 | tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL; |
| 1102 | #if !OLD_STYLE |
| 1103 | tmFlags |= TIMELINE_COLUMNAR; |
| 1104 | #endif |
| 1105 | if( PB("ubg")!=0 ){ |
| 1106 | tmFlags |= TIMELINE_UCOLOR; |
| 1107 | }else{ |
| 1108 | tmFlags |= TIMELINE_BRCOLOR; |
| 1109 | } |
| 1110 |
| --- src/branch.c | |
| +++ src/branch.c | |
| @@ -843,10 +843,11 @@ | |
| 843 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 844 | style_set_current_feature("branch"); |
| 845 | style_header("Branches"); |
| 846 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 847 | style_submenu_checkbox("colors", "Use Branch Colors", 0, 0); |
| 848 | |
| 849 | login_anonymous_available(); |
| 850 | |
| 851 | brlist_create_temp_table(); |
| 852 | db_prepare(&q, "SELECT * FROM tmp_brlist ORDER BY mtime DESC"); |
| 853 | rNow = db_double(0.0, "SELECT julianday('now')"); |
| @@ -1025,34 +1026,30 @@ | |
| 1026 | Stmt *pQuery, /* Current row of the timeline query */ |
| 1027 | int tmFlags, /* Flags to www_print_timeline() */ |
| 1028 | const char *zThisUser, /* Suppress links to this user */ |
| 1029 | const char *zThisTag /* Suppress links to this tag */ |
| 1030 | ){ |
| 1031 | int rid; |
| 1032 | int tmFlagsNew; |
| 1033 | char *zBrName; |
| 1034 | |
| 1035 | if( (tmFlags & TIMELINE_INLINE)!=0 ){ |
| 1036 | tmFlagsNew = (tmFlags & ~TIMELINE_VIEWS) | TIMELINE_MODERN; |
| 1037 | cgi_printf("("); |
| 1038 | }else{ |
| 1039 | tmFlagsNew = tmFlags; |
| 1040 | } |
| 1041 | timeline_extra(pQuery,tmFlagsNew,zThisUser,zThisTag); |
| 1042 | |
| 1043 | if( !g.perm.Hyperlink ) return; |
| 1044 | rid = db_column_int(pQuery,0); |
| 1045 | zBrName = branch_of_rid(rid); |
| 1046 | @ branch: <span class='timelineHash'>\ |
| 1047 | @ %z(href("%R/timeline?r=%T",zBrName))%h(zBrName)</a></span> |
| 1048 | if( (tmFlags & TIMELINE_INLINE)!=0 ){ |
| 1049 | cgi_printf(")"); |
| 1050 | } |
| 1051 | } |
| 1052 | |
| 1053 | /* |
| 1054 | ** WEBPAGE: brtimeline |
| 1055 | ** |
| @@ -1070,21 +1067,19 @@ | |
| 1067 | int fNoHidden = PB("nohidden")!=0; /* The "nohidden" query parameter */ |
| 1068 | int fOnlyHidden = PB("onlyhidden")!=0; /* The "onlyhidden" query parameter */ |
| 1069 | |
| 1070 | login_check_credentials(); |
| 1071 | if( !g.perm.Read ){ login_needed(g.anon.Read); return; } |
| 1072 | if( robot_restrict("timelineX") ) return; |
| 1073 | |
| 1074 | style_set_current_feature("branch"); |
| 1075 | style_header("Branches"); |
| 1076 | style_submenu_element("Branch List", "brlist"); |
| 1077 | login_anonymous_available(); |
| 1078 | timeline_ss_submenu(); |
| 1079 | cgi_check_for_malice(); |
| 1080 | @ <h2>The initial check-in for each branch:</h2> |
| 1081 | blob_append(&sql, timeline_query_for_www(), -1); |
| 1082 | blob_append_sql(&sql, |
| 1083 | "AND blob.rid IN (SELECT rid FROM tagxref" |
| 1084 | " WHERE tagtype>0 AND tagid=%d AND srcid!=0)", TAG_BRANCH); |
| 1085 | if( fNoHidden || fOnlyHidden ){ |
| @@ -1097,13 +1092,10 @@ | |
| 1092 | db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql)); |
| 1093 | blob_reset(&sql); |
| 1094 | /* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too |
| 1095 | ** many descenders to (off-screen) parents. */ |
| 1096 | tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL; |
| 1097 | if( PB("ubg")!=0 ){ |
| 1098 | tmFlags |= TIMELINE_UCOLOR; |
| 1099 | }else{ |
| 1100 | tmFlags |= TIMELINE_BRCOLOR; |
| 1101 | } |
| 1102 |
+6
| --- src/browse.c | ||
| +++ src/browse.c | ||
| @@ -229,10 +229,13 @@ | ||
| 229 | 229 | }else{ |
| 230 | 230 | zMatch = ""; |
| 231 | 231 | } |
| 232 | 232 | style_header("%s", zHeader); |
| 233 | 233 | fossil_free(zHeader); |
| 234 | + if( rid && zD==0 && zMatch[0]==0 && g.perm.Zip ){ | |
| 235 | + style_submenu_element("Download","%R/rchvdwnld/%!S",zUuid); | |
| 236 | + } | |
| 234 | 237 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 235 | 238 | sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, |
| 236 | 239 | pathelementFunc, 0, 0); |
| 237 | 240 | url_initialize(&sURI, "dir"); |
| 238 | 241 | cgi_check_for_malice(); |
| @@ -814,10 +817,13 @@ | ||
| 814 | 817 | style_submenu_element("File Ages", "%R/fileage?name=%T", zCI); |
| 815 | 818 | } |
| 816 | 819 | } |
| 817 | 820 | style_submenu_element("Flat-View", "%s", |
| 818 | 821 | url_render(&sURI, "type", "flat", 0, 0)); |
| 822 | + if( rid && zD==0 && zRE==0 && !showDirOnly && g.perm.Zip ){ | |
| 823 | + style_submenu_element("Download","%R/rchvdwnld/%!S", zUuid); | |
| 824 | + } | |
| 819 | 825 | |
| 820 | 826 | /* Compute the file hierarchy. |
| 821 | 827 | */ |
| 822 | 828 | if( zCI ){ |
| 823 | 829 | Stmt q; |
| 824 | 830 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -229,10 +229,13 @@ | |
| 229 | }else{ |
| 230 | zMatch = ""; |
| 231 | } |
| 232 | style_header("%s", zHeader); |
| 233 | fossil_free(zHeader); |
| 234 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 235 | sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, |
| 236 | pathelementFunc, 0, 0); |
| 237 | url_initialize(&sURI, "dir"); |
| 238 | cgi_check_for_malice(); |
| @@ -814,10 +817,13 @@ | |
| 814 | style_submenu_element("File Ages", "%R/fileage?name=%T", zCI); |
| 815 | } |
| 816 | } |
| 817 | style_submenu_element("Flat-View", "%s", |
| 818 | url_render(&sURI, "type", "flat", 0, 0)); |
| 819 | |
| 820 | /* Compute the file hierarchy. |
| 821 | */ |
| 822 | if( zCI ){ |
| 823 | Stmt q; |
| 824 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -229,10 +229,13 @@ | |
| 229 | }else{ |
| 230 | zMatch = ""; |
| 231 | } |
| 232 | style_header("%s", zHeader); |
| 233 | fossil_free(zHeader); |
| 234 | if( rid && zD==0 && zMatch[0]==0 && g.perm.Zip ){ |
| 235 | style_submenu_element("Download","%R/rchvdwnld/%!S",zUuid); |
| 236 | } |
| 237 | style_adunit_config(ADUNIT_RIGHT_OK); |
| 238 | sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, |
| 239 | pathelementFunc, 0, 0); |
| 240 | url_initialize(&sURI, "dir"); |
| 241 | cgi_check_for_malice(); |
| @@ -814,10 +817,13 @@ | |
| 817 | style_submenu_element("File Ages", "%R/fileage?name=%T", zCI); |
| 818 | } |
| 819 | } |
| 820 | style_submenu_element("Flat-View", "%s", |
| 821 | url_render(&sURI, "type", "flat", 0, 0)); |
| 822 | if( rid && zD==0 && zRE==0 && !showDirOnly && g.perm.Zip ){ |
| 823 | style_submenu_element("Download","%R/rchvdwnld/%!S", zUuid); |
| 824 | } |
| 825 | |
| 826 | /* Compute the file hierarchy. |
| 827 | */ |
| 828 | if( zCI ){ |
| 829 | Stmt q; |
| 830 |
+7
-27
| --- src/clone.c | ||
| +++ src/clone.c | ||
| @@ -405,40 +405,18 @@ | ||
| 405 | 405 | db_protect_pop(); |
| 406 | 406 | } |
| 407 | 407 | } |
| 408 | 408 | |
| 409 | 409 | /* |
| 410 | -** WEBPAGE: download | |
| 410 | +** WEBPAGE: howtoclone | |
| 411 | 411 | ** |
| 412 | -** Provide a simple page that enables newbies to download the latest tarball or | |
| 413 | -** ZIP archive, and provides instructions on how to clone. | |
| 412 | +** Provide instructions on how to clone this repository. | |
| 414 | 413 | */ |
| 415 | -void download_page(void){ | |
| 414 | +void howtoclone_page(void){ | |
| 416 | 415 | login_check_credentials(); |
| 417 | 416 | cgi_check_for_malice(); |
| 418 | - style_header("Download Page"); | |
| 419 | - if( !g.perm.Zip ){ | |
| 420 | - @ <p>Bummer. You do not have permission to download. | |
| 421 | - if( g.zLogin==0 || g.zLogin[0]==0 ){ | |
| 422 | - @ Maybe it would work better if you | |
| 423 | - @ %z(href("%R/login"))logged in</a>. | |
| 424 | - }else{ | |
| 425 | - @ Contact the site administrator and ask them to give | |
| 426 | - @ you "Download Zip" privileges. | |
| 427 | - } | |
| 428 | - }else{ | |
| 429 | - const char *zDLTag = db_get("download-tag","trunk"); | |
| 430 | - const char *zNm = db_get("short-project-name","download"); | |
| 431 | - char *zUrl = href("%R/zip/%t/%t.zip", zDLTag, zNm); | |
| 432 | - @ <p>ZIP Archive: %z(zUrl)%h(zNm).zip</a> | |
| 433 | - zUrl = href("%R/tarball/%t/%t.tar.gz", zDLTag, zNm); | |
| 434 | - @ <p>Tarball: %z(zUrl)%h(zNm).tar.gz</a> | |
| 435 | - if( g.zLogin!=0 ){ | |
| 436 | - zUrl = href("%R/sqlar/%t/%t.sqlar", zDLTag, zNm); | |
| 437 | - @ <p>SQLite Archive: %z(zUrl)%h(zNm).sqlar</a> | |
| 438 | - } | |
| 439 | - } | |
| 417 | + style_header("How To Clone This Repository"); | |
| 440 | 418 | if( !g.perm.Clone ){ |
| 441 | 419 | @ <p>You are not authorized to clone this repository. |
| 442 | 420 | if( g.zLogin==0 || g.zLogin[0]==0 ){ |
| 443 | 421 | @ Maybe you would be able to clone if you |
| 444 | 422 | @ %z(href("%R/login"))logged in</a>. |
| @@ -446,12 +424,14 @@ | ||
| 446 | 424 | @ Contact the site administrator and ask them to give |
| 447 | 425 | @ you "Clone" privileges in order to clone. |
| 448 | 426 | } |
| 449 | 427 | }else{ |
| 450 | 428 | const char *zNm = db_get("short-project-name","clone"); |
| 451 | - @ <p>Clone the repository using this command: | |
| 429 | + @ <p>Clone this repository by running a command like the following: | |
| 452 | 430 | @ <blockquote><pre> |
| 453 | 431 | @ fossil clone %s(g.zBaseURL) %h(zNm).fossil |
| 454 | 432 | @ </pre></blockquote> |
| 433 | + @ <p>Do a web search for "fossil clone" or similar to find additional | |
| 434 | + @ information about using a cloned Fossil repository. | |
| 455 | 435 | } |
| 456 | 436 | style_finish_page(); |
| 457 | 437 | } |
| 458 | 438 |
| --- src/clone.c | |
| +++ src/clone.c | |
| @@ -405,40 +405,18 @@ | |
| 405 | db_protect_pop(); |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | /* |
| 410 | ** WEBPAGE: download |
| 411 | ** |
| 412 | ** Provide a simple page that enables newbies to download the latest tarball or |
| 413 | ** ZIP archive, and provides instructions on how to clone. |
| 414 | */ |
| 415 | void download_page(void){ |
| 416 | login_check_credentials(); |
| 417 | cgi_check_for_malice(); |
| 418 | style_header("Download Page"); |
| 419 | if( !g.perm.Zip ){ |
| 420 | @ <p>Bummer. You do not have permission to download. |
| 421 | if( g.zLogin==0 || g.zLogin[0]==0 ){ |
| 422 | @ Maybe it would work better if you |
| 423 | @ %z(href("%R/login"))logged in</a>. |
| 424 | }else{ |
| 425 | @ Contact the site administrator and ask them to give |
| 426 | @ you "Download Zip" privileges. |
| 427 | } |
| 428 | }else{ |
| 429 | const char *zDLTag = db_get("download-tag","trunk"); |
| 430 | const char *zNm = db_get("short-project-name","download"); |
| 431 | char *zUrl = href("%R/zip/%t/%t.zip", zDLTag, zNm); |
| 432 | @ <p>ZIP Archive: %z(zUrl)%h(zNm).zip</a> |
| 433 | zUrl = href("%R/tarball/%t/%t.tar.gz", zDLTag, zNm); |
| 434 | @ <p>Tarball: %z(zUrl)%h(zNm).tar.gz</a> |
| 435 | if( g.zLogin!=0 ){ |
| 436 | zUrl = href("%R/sqlar/%t/%t.sqlar", zDLTag, zNm); |
| 437 | @ <p>SQLite Archive: %z(zUrl)%h(zNm).sqlar</a> |
| 438 | } |
| 439 | } |
| 440 | if( !g.perm.Clone ){ |
| 441 | @ <p>You are not authorized to clone this repository. |
| 442 | if( g.zLogin==0 || g.zLogin[0]==0 ){ |
| 443 | @ Maybe you would be able to clone if you |
| 444 | @ %z(href("%R/login"))logged in</a>. |
| @@ -446,12 +424,14 @@ | |
| 446 | @ Contact the site administrator and ask them to give |
| 447 | @ you "Clone" privileges in order to clone. |
| 448 | } |
| 449 | }else{ |
| 450 | const char *zNm = db_get("short-project-name","clone"); |
| 451 | @ <p>Clone the repository using this command: |
| 452 | @ <blockquote><pre> |
| 453 | @ fossil clone %s(g.zBaseURL) %h(zNm).fossil |
| 454 | @ </pre></blockquote> |
| 455 | } |
| 456 | style_finish_page(); |
| 457 | } |
| 458 |
| --- src/clone.c | |
| +++ src/clone.c | |
| @@ -405,40 +405,18 @@ | |
| 405 | db_protect_pop(); |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | /* |
| 410 | ** WEBPAGE: howtoclone |
| 411 | ** |
| 412 | ** Provide instructions on how to clone this repository. |
| 413 | */ |
| 414 | void howtoclone_page(void){ |
| 415 | login_check_credentials(); |
| 416 | cgi_check_for_malice(); |
| 417 | style_header("How To Clone This Repository"); |
| 418 | if( !g.perm.Clone ){ |
| 419 | @ <p>You are not authorized to clone this repository. |
| 420 | if( g.zLogin==0 || g.zLogin[0]==0 ){ |
| 421 | @ Maybe you would be able to clone if you |
| 422 | @ %z(href("%R/login"))logged in</a>. |
| @@ -446,12 +424,14 @@ | |
| 424 | @ Contact the site administrator and ask them to give |
| 425 | @ you "Clone" privileges in order to clone. |
| 426 | } |
| 427 | }else{ |
| 428 | const char *zNm = db_get("short-project-name","clone"); |
| 429 | @ <p>Clone this repository by running a command like the following: |
| 430 | @ <blockquote><pre> |
| 431 | @ fossil clone %s(g.zBaseURL) %h(zNm).fossil |
| 432 | @ </pre></blockquote> |
| 433 | @ <p>Do a web search for "fossil clone" or similar to find additional |
| 434 | @ information about using a cloned Fossil repository. |
| 435 | } |
| 436 | style_finish_page(); |
| 437 | } |
| 438 |
M
src/db.c
+2
-2
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -1228,12 +1228,12 @@ | ||
| 1228 | 1228 | } |
| 1229 | 1229 | |
| 1230 | 1230 | /* |
| 1231 | 1231 | ** Execute a query. Return the first column of the first row |
| 1232 | 1232 | ** of the result set as a string. Space to hold the string is |
| 1233 | -** obtained from malloc(). If the result set is empty, return | |
| 1234 | -** zDefault instead. | |
| 1233 | +** obtained from fossil_strdup() and should be freed using fossil_free(). | |
| 1234 | +** If the result set is empty, return a copy of zDefault instead. | |
| 1235 | 1235 | */ |
| 1236 | 1236 | char *db_text(const char *zDefault, const char *zSql, ...){ |
| 1237 | 1237 | va_list ap; |
| 1238 | 1238 | Stmt s; |
| 1239 | 1239 | char *z; |
| 1240 | 1240 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -1228,12 +1228,12 @@ | |
| 1228 | } |
| 1229 | |
| 1230 | /* |
| 1231 | ** Execute a query. Return the first column of the first row |
| 1232 | ** of the result set as a string. Space to hold the string is |
| 1233 | ** obtained from malloc(). If the result set is empty, return |
| 1234 | ** zDefault instead. |
| 1235 | */ |
| 1236 | char *db_text(const char *zDefault, const char *zSql, ...){ |
| 1237 | va_list ap; |
| 1238 | Stmt s; |
| 1239 | char *z; |
| 1240 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -1228,12 +1228,12 @@ | |
| 1228 | } |
| 1229 | |
| 1230 | /* |
| 1231 | ** Execute a query. Return the first column of the first row |
| 1232 | ** of the result set as a string. Space to hold the string is |
| 1233 | ** obtained from fossil_strdup() and should be freed using fossil_free(). |
| 1234 | ** If the result set is empty, return a copy of zDefault instead. |
| 1235 | */ |
| 1236 | char *db_text(const char *zDefault, const char *zSql, ...){ |
| 1237 | va_list ap; |
| 1238 | Stmt s; |
| 1239 | char *z; |
| 1240 |
+3
| --- src/default.css | ||
| +++ src/default.css | ||
| @@ -57,10 +57,13 @@ | ||
| 57 | 57 | border-width: 0; |
| 58 | 58 | } |
| 59 | 59 | span.timelineLeaf { |
| 60 | 60 | font-weight: bold; |
| 61 | 61 | } |
| 62 | +span.timelineHash { | |
| 63 | + font-weight: bold; | |
| 64 | +} | |
| 62 | 65 | span.timelineHistDsp { |
| 63 | 66 | font-weight: bold; |
| 64 | 67 | } |
| 65 | 68 | td.timelineTime { |
| 66 | 69 | vertical-align: top; |
| 67 | 70 |
| --- src/default.css | |
| +++ src/default.css | |
| @@ -57,10 +57,13 @@ | |
| 57 | border-width: 0; |
| 58 | } |
| 59 | span.timelineLeaf { |
| 60 | font-weight: bold; |
| 61 | } |
| 62 | span.timelineHistDsp { |
| 63 | font-weight: bold; |
| 64 | } |
| 65 | td.timelineTime { |
| 66 | vertical-align: top; |
| 67 |
| --- src/default.css | |
| +++ src/default.css | |
| @@ -57,10 +57,13 @@ | |
| 57 | border-width: 0; |
| 58 | } |
| 59 | span.timelineLeaf { |
| 60 | font-weight: bold; |
| 61 | } |
| 62 | span.timelineHash { |
| 63 | font-weight: bold; |
| 64 | } |
| 65 | span.timelineHistDsp { |
| 66 | font-weight: bold; |
| 67 | } |
| 68 | td.timelineTime { |
| 69 | vertical-align: top; |
| 70 |
+14
-7
| --- src/finfo.c | ||
| +++ src/finfo.c | ||
| @@ -394,10 +394,12 @@ | ||
| 394 | 394 | tmFlags = timeline_ss_submenu(); |
| 395 | 395 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 396 | 396 | zStyle = "Columnar"; |
| 397 | 397 | }else if( tmFlags & TIMELINE_COMPACT ){ |
| 398 | 398 | zStyle = "Compact"; |
| 399 | + }else if( tmFlags & TIMELINE_SIMPLE ){ | |
| 400 | + zStyle = "Simple"; | |
| 399 | 401 | }else if( tmFlags & TIMELINE_VERBOSE ){ |
| 400 | 402 | zStyle = "Verbose"; |
| 401 | 403 | }else if( tmFlags & TIMELINE_CLASSIC ){ |
| 402 | 404 | zStyle = "Classic"; |
| 403 | 405 | }else{ |
| @@ -731,14 +733,14 @@ | ||
| 731 | 733 | } |
| 732 | 734 | if( tmFlags & TIMELINE_COMPACT ){ |
| 733 | 735 | cgi_printf("<span class='clutter' id='detail-%d'>",frid); |
| 734 | 736 | } |
| 735 | 737 | cgi_printf("<span class='timeline%sDetail'>", zStyle); |
| 736 | - if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ) cgi_printf("("); | |
| 738 | + if( tmFlags & TIMELINE_INLINE ) cgi_printf("("); | |
| 737 | 739 | if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){ |
| 738 | 740 | @ file: %z(href("%R/file?name=%T&ci=%!S",zFName,zCkin))\ |
| 739 | - @ [%S(zUuid)]</a> | |
| 741 | + @ %S(zUuid)</a> | |
| 740 | 742 | if( fShowId ){ |
| 741 | 743 | int srcId = delta_source_rid(frid); |
| 742 | 744 | if( srcId>0 ){ |
| 743 | 745 | @ id: %z(href("%R/deltachain/%d",frid))\ |
| 744 | 746 | @ %d(frid)←%d(srcId)</a> |
| @@ -745,20 +747,25 @@ | ||
| 745 | 747 | }else{ |
| 746 | 748 | @ id: %z(href("%R/deltachain/%d",frid))%d(frid)</a> |
| 747 | 749 | } |
| 748 | 750 | } |
| 749 | 751 | } |
| 752 | + if( tmFlags & TIMELINE_SIMPLE ){ | |
| 753 | + @ <span class='timelineEllipsis' data-id='%d(frid)' \ | |
| 754 | + @ id='ellipsis-%d(frid)'>...</span> | |
| 755 | + @ <span class='clutter' id='detail-%d(frid)'> | |
| 756 | + } | |
| 750 | 757 | @ check-in: \ |
| 751 | 758 | hyperlink_to_version(zCkin); |
| 752 | 759 | if( fShowId ){ |
| 753 | 760 | @ (%d(fmid)) |
| 754 | 761 | } |
| 755 | 762 | @ user: \ |
| 756 | 763 | hyperlink_to_user(zUser, zDate, ","); |
| 757 | 764 | @ branch: %z(href("%R/timeline?t=%T",zBr))%h(zBr)</a>, |
| 758 | - if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ){ | |
| 759 | - @ size: %d(szFile)) | |
| 765 | + if( tmFlags & TIMELINE_INLINE ){ | |
| 766 | + @ size: %d(szFile) | |
| 760 | 767 | }else{ |
| 761 | 768 | @ size: %d(szFile) |
| 762 | 769 | } |
| 763 | 770 | if( g.perm.Hyperlink && zUuid ){ |
| 764 | 771 | const char *z = zFName; |
| @@ -793,14 +800,14 @@ | ||
| 793 | 800 | zAncLink = href("%R/finfo?name=%T&from=%!S&debug=1",zFName,zCkin); |
| 794 | 801 | @ %z(zAncLink)[ancestry]</a> |
| 795 | 802 | } |
| 796 | 803 | tag_private_status(frid); |
| 797 | 804 | /* End timelineDetail */ |
| 798 | - if( tmFlags & TIMELINE_COMPACT ){ | |
| 799 | - @ </span></span> | |
| 805 | + if( tmFlags & (TIMELINE_COMPACT|TIMELINE_SIMPLE) ){ | |
| 806 | + @ </span>)</span> | |
| 800 | 807 | }else{ |
| 801 | - @ </span> | |
| 808 | + @ )</span> | |
| 802 | 809 | } |
| 803 | 810 | @ </td></tr> |
| 804 | 811 | } |
| 805 | 812 | db_finalize(&q); |
| 806 | 813 | db_finalize(&qparent); |
| 807 | 814 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -394,10 +394,12 @@ | |
| 394 | tmFlags = timeline_ss_submenu(); |
| 395 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 396 | zStyle = "Columnar"; |
| 397 | }else if( tmFlags & TIMELINE_COMPACT ){ |
| 398 | zStyle = "Compact"; |
| 399 | }else if( tmFlags & TIMELINE_VERBOSE ){ |
| 400 | zStyle = "Verbose"; |
| 401 | }else if( tmFlags & TIMELINE_CLASSIC ){ |
| 402 | zStyle = "Classic"; |
| 403 | }else{ |
| @@ -731,14 +733,14 @@ | |
| 731 | } |
| 732 | if( tmFlags & TIMELINE_COMPACT ){ |
| 733 | cgi_printf("<span class='clutter' id='detail-%d'>",frid); |
| 734 | } |
| 735 | cgi_printf("<span class='timeline%sDetail'>", zStyle); |
| 736 | if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ) cgi_printf("("); |
| 737 | if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){ |
| 738 | @ file: %z(href("%R/file?name=%T&ci=%!S",zFName,zCkin))\ |
| 739 | @ [%S(zUuid)]</a> |
| 740 | if( fShowId ){ |
| 741 | int srcId = delta_source_rid(frid); |
| 742 | if( srcId>0 ){ |
| 743 | @ id: %z(href("%R/deltachain/%d",frid))\ |
| 744 | @ %d(frid)←%d(srcId)</a> |
| @@ -745,20 +747,25 @@ | |
| 745 | }else{ |
| 746 | @ id: %z(href("%R/deltachain/%d",frid))%d(frid)</a> |
| 747 | } |
| 748 | } |
| 749 | } |
| 750 | @ check-in: \ |
| 751 | hyperlink_to_version(zCkin); |
| 752 | if( fShowId ){ |
| 753 | @ (%d(fmid)) |
| 754 | } |
| 755 | @ user: \ |
| 756 | hyperlink_to_user(zUser, zDate, ","); |
| 757 | @ branch: %z(href("%R/timeline?t=%T",zBr))%h(zBr)</a>, |
| 758 | if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ){ |
| 759 | @ size: %d(szFile)) |
| 760 | }else{ |
| 761 | @ size: %d(szFile) |
| 762 | } |
| 763 | if( g.perm.Hyperlink && zUuid ){ |
| 764 | const char *z = zFName; |
| @@ -793,14 +800,14 @@ | |
| 793 | zAncLink = href("%R/finfo?name=%T&from=%!S&debug=1",zFName,zCkin); |
| 794 | @ %z(zAncLink)[ancestry]</a> |
| 795 | } |
| 796 | tag_private_status(frid); |
| 797 | /* End timelineDetail */ |
| 798 | if( tmFlags & TIMELINE_COMPACT ){ |
| 799 | @ </span></span> |
| 800 | }else{ |
| 801 | @ </span> |
| 802 | } |
| 803 | @ </td></tr> |
| 804 | } |
| 805 | db_finalize(&q); |
| 806 | db_finalize(&qparent); |
| 807 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -394,10 +394,12 @@ | |
| 394 | tmFlags = timeline_ss_submenu(); |
| 395 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 396 | zStyle = "Columnar"; |
| 397 | }else if( tmFlags & TIMELINE_COMPACT ){ |
| 398 | zStyle = "Compact"; |
| 399 | }else if( tmFlags & TIMELINE_SIMPLE ){ |
| 400 | zStyle = "Simple"; |
| 401 | }else if( tmFlags & TIMELINE_VERBOSE ){ |
| 402 | zStyle = "Verbose"; |
| 403 | }else if( tmFlags & TIMELINE_CLASSIC ){ |
| 404 | zStyle = "Classic"; |
| 405 | }else{ |
| @@ -731,14 +733,14 @@ | |
| 733 | } |
| 734 | if( tmFlags & TIMELINE_COMPACT ){ |
| 735 | cgi_printf("<span class='clutter' id='detail-%d'>",frid); |
| 736 | } |
| 737 | cgi_printf("<span class='timeline%sDetail'>", zStyle); |
| 738 | if( tmFlags & TIMELINE_INLINE ) cgi_printf("("); |
| 739 | if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){ |
| 740 | @ file: %z(href("%R/file?name=%T&ci=%!S",zFName,zCkin))\ |
| 741 | @ %S(zUuid)</a> |
| 742 | if( fShowId ){ |
| 743 | int srcId = delta_source_rid(frid); |
| 744 | if( srcId>0 ){ |
| 745 | @ id: %z(href("%R/deltachain/%d",frid))\ |
| 746 | @ %d(frid)←%d(srcId)</a> |
| @@ -745,20 +747,25 @@ | |
| 747 | }else{ |
| 748 | @ id: %z(href("%R/deltachain/%d",frid))%d(frid)</a> |
| 749 | } |
| 750 | } |
| 751 | } |
| 752 | if( tmFlags & TIMELINE_SIMPLE ){ |
| 753 | @ <span class='timelineEllipsis' data-id='%d(frid)' \ |
| 754 | @ id='ellipsis-%d(frid)'>...</span> |
| 755 | @ <span class='clutter' id='detail-%d(frid)'> |
| 756 | } |
| 757 | @ check-in: \ |
| 758 | hyperlink_to_version(zCkin); |
| 759 | if( fShowId ){ |
| 760 | @ (%d(fmid)) |
| 761 | } |
| 762 | @ user: \ |
| 763 | hyperlink_to_user(zUser, zDate, ","); |
| 764 | @ branch: %z(href("%R/timeline?t=%T",zBr))%h(zBr)</a>, |
| 765 | if( tmFlags & TIMELINE_INLINE ){ |
| 766 | @ size: %d(szFile) |
| 767 | }else{ |
| 768 | @ size: %d(szFile) |
| 769 | } |
| 770 | if( g.perm.Hyperlink && zUuid ){ |
| 771 | const char *z = zFName; |
| @@ -793,14 +800,14 @@ | |
| 800 | zAncLink = href("%R/finfo?name=%T&from=%!S&debug=1",zFName,zCkin); |
| 801 | @ %z(zAncLink)[ancestry]</a> |
| 802 | } |
| 803 | tag_private_status(frid); |
| 804 | /* End timelineDetail */ |
| 805 | if( tmFlags & (TIMELINE_COMPACT|TIMELINE_SIMPLE) ){ |
| 806 | @ </span>)</span> |
| 807 | }else{ |
| 808 | @ )</span> |
| 809 | } |
| 810 | @ </td></tr> |
| 811 | } |
| 812 | db_finalize(&q); |
| 813 | db_finalize(&qparent); |
| 814 |
+4
-4
| --- src/graph.js | ||
| +++ src/graph.js | ||
| @@ -726,15 +726,15 @@ | ||
| 726 | 726 | function toggleDetail(){ |
| 727 | 727 | var id = parseInt(this.getAttribute('data-id')) |
| 728 | 728 | var x = document.getElementById("detail-"+id); |
| 729 | 729 | if( x.style.display=="inline" ){ |
| 730 | 730 | x.style.display="none"; |
| 731 | - changeDisplayById("ellipsis-"+id,"inline"); | |
| 731 | + document.getElementById("ellipsis-"+id).textContent = "..."; | |
| 732 | 732 | changeDisplayById("links-"+id,"none"); |
| 733 | 733 | }else{ |
| 734 | 734 | x.style.display="inline"; |
| 735 | - changeDisplayById("ellipsis-"+id,"none"); | |
| 735 | + document.getElementById("ellipsis-"+id).textContent = "←"; | |
| 736 | 736 | changeDisplayById("links-"+id,"inline"); |
| 737 | 737 | } |
| 738 | 738 | checkHeight(); |
| 739 | 739 | } |
| 740 | 740 | function scrollToSelected(){ |
| @@ -764,12 +764,12 @@ | ||
| 764 | 764 | } |
| 765 | 765 | if( tx.scrollToSelect ){ |
| 766 | 766 | scrollToSelected(); |
| 767 | 767 | } |
| 768 | 768 | |
| 769 | - /* Set the onclick= attributes for elements of the "Compact" display | |
| 770 | - ** mode so that clicking turns the details on and off. | |
| 769 | + /* Set the onclick= attributes for elements of the "Compact" and | |
| 770 | + ** "Simple" views so that clicking turns the details on and off. | |
| 771 | 771 | */ |
| 772 | 772 | var lx = topObj.getElementsByClassName('timelineEllipsis'); |
| 773 | 773 | var i; |
| 774 | 774 | for(i=0; i<lx.length; i++){ |
| 775 | 775 | if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail; |
| 776 | 776 |
| --- src/graph.js | |
| +++ src/graph.js | |
| @@ -726,15 +726,15 @@ | |
| 726 | function toggleDetail(){ |
| 727 | var id = parseInt(this.getAttribute('data-id')) |
| 728 | var x = document.getElementById("detail-"+id); |
| 729 | if( x.style.display=="inline" ){ |
| 730 | x.style.display="none"; |
| 731 | changeDisplayById("ellipsis-"+id,"inline"); |
| 732 | changeDisplayById("links-"+id,"none"); |
| 733 | }else{ |
| 734 | x.style.display="inline"; |
| 735 | changeDisplayById("ellipsis-"+id,"none"); |
| 736 | changeDisplayById("links-"+id,"inline"); |
| 737 | } |
| 738 | checkHeight(); |
| 739 | } |
| 740 | function scrollToSelected(){ |
| @@ -764,12 +764,12 @@ | |
| 764 | } |
| 765 | if( tx.scrollToSelect ){ |
| 766 | scrollToSelected(); |
| 767 | } |
| 768 | |
| 769 | /* Set the onclick= attributes for elements of the "Compact" display |
| 770 | ** mode so that clicking turns the details on and off. |
| 771 | */ |
| 772 | var lx = topObj.getElementsByClassName('timelineEllipsis'); |
| 773 | var i; |
| 774 | for(i=0; i<lx.length; i++){ |
| 775 | if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail; |
| 776 |
| --- src/graph.js | |
| +++ src/graph.js | |
| @@ -726,15 +726,15 @@ | |
| 726 | function toggleDetail(){ |
| 727 | var id = parseInt(this.getAttribute('data-id')) |
| 728 | var x = document.getElementById("detail-"+id); |
| 729 | if( x.style.display=="inline" ){ |
| 730 | x.style.display="none"; |
| 731 | document.getElementById("ellipsis-"+id).textContent = "..."; |
| 732 | changeDisplayById("links-"+id,"none"); |
| 733 | }else{ |
| 734 | x.style.display="inline"; |
| 735 | document.getElementById("ellipsis-"+id).textContent = "←"; |
| 736 | changeDisplayById("links-"+id,"inline"); |
| 737 | } |
| 738 | checkHeight(); |
| 739 | } |
| 740 | function scrollToSelected(){ |
| @@ -764,12 +764,12 @@ | |
| 764 | } |
| 765 | if( tx.scrollToSelect ){ |
| 766 | scrollToSelected(); |
| 767 | } |
| 768 | |
| 769 | /* Set the onclick= attributes for elements of the "Compact" and |
| 770 | ** "Simple" views so that clicking turns the details on and off. |
| 771 | */ |
| 772 | var lx = topObj.getElementsByClassName('timelineEllipsis'); |
| 773 | var i; |
| 774 | for(i=0; i<lx.length; i++){ |
| 775 | if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail; |
| 776 |
+13
-24
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -993,34 +993,23 @@ | ||
| 993 | 993 | @ <tr><th>Comment:</th><td class="infoComment">\ |
| 994 | 994 | @ %!W(zEComment?zEComment:zComment)</td></tr> |
| 995 | 995 | |
| 996 | 996 | /* The Download: line */ |
| 997 | 997 | if( g.perm.Zip ){ |
| 998 | - char *zPJ = db_get("short-project-name", 0); | |
| 999 | - char *zUrl; | |
| 1000 | - Blob projName; | |
| 1001 | - int jj; | |
| 1002 | - if( zPJ==0 ) zPJ = db_get("project-name", "unnamed"); | |
| 1003 | - blob_zero(&projName); | |
| 1004 | - blob_append(&projName, zPJ, -1); | |
| 1005 | - blob_trim(&projName); | |
| 1006 | - zPJ = blob_str(&projName); | |
| 1007 | - for(jj=0; zPJ[jj]; jj++){ | |
| 1008 | - if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){ | |
| 1009 | - zPJ[jj] = '_'; | |
| 1010 | - } | |
| 1011 | - } | |
| 1012 | - zUrl = mprintf("%R/tarball/%S/%t-%S.tar.gz", zUuid, zPJ, zUuid); | |
| 1013 | 998 | @ <tr><th>Downloads:</th><td> |
| 1014 | - @ %z(href("%s",zUrl))Tarball</a> | |
| 1015 | - @ | %z(href("%R/zip/%S/%t-%S.zip",zUuid, zPJ,zUuid))ZIP archive</a> | |
| 1016 | - if( g.zLogin!=0 ){ | |
| 1017 | - @ | %z(href("%R/sqlar/%S/%t-%S.sqlar",zUuid,zPJ,zUuid))\ | |
| 1018 | - @ SQL archive</a></td></tr> | |
| 1019 | - } | |
| 1020 | - fossil_free(zUrl); | |
| 1021 | - blob_reset(&projName); | |
| 999 | + if( robot_would_be_restricted("download") ){ | |
| 1000 | + @ See separate %z(href("%R/rchvdwnld/%!S",zUuid))download page</a> | |
| 1001 | + }else{ | |
| 1002 | + char *zBase = archive_base_name(rid); | |
| 1003 | + @ %z(href("%R/tarball/%s.tar.gz",zBase))Tarball</a> | |
| 1004 | + @ | %z(href("%R/zip/%s.zip",zBase))ZIP archive</a> | |
| 1005 | + if( g.zLogin!=0 ){ | |
| 1006 | + @ | %z(href("%R/sqlar/%s.sqlar",zBase))\ | |
| 1007 | + @ SQL archive</a></td></tr> | |
| 1008 | + } | |
| 1009 | + fossil_free(zBase); | |
| 1010 | + } | |
| 1022 | 1011 | } |
| 1023 | 1012 | |
| 1024 | 1013 | @ <tr><th>Timelines:</th><td> |
| 1025 | 1014 | @ %z(href("%R/timeline?f=%!S&unhide",zUuid))family</a> |
| 1026 | 1015 | if( zParent ){ |
| @@ -1950,11 +1939,11 @@ | ||
| 1950 | 1939 | int dflt; |
| 1951 | 1940 | int res; |
| 1952 | 1941 | int isBot; |
| 1953 | 1942 | static char zDflt[2] |
| 1954 | 1943 | /*static b/c cookie_link_parameter() does not copy it!*/; |
| 1955 | - if( client_might_be_a_robot() && robot_restrict_has_tag("diff") ){ | |
| 1944 | + if( robot_would_be_restricted("diff") ){ | |
| 1956 | 1945 | dflt = 0; |
| 1957 | 1946 | isBot = 1; |
| 1958 | 1947 | }else{ |
| 1959 | 1948 | dflt = db_get_int("preferred-diff-type",-99); |
| 1960 | 1949 | if( dflt<=0 ) dflt = user_agent_is_likely_mobile() ? 1 : 2; |
| 1961 | 1950 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -993,34 +993,23 @@ | |
| 993 | @ <tr><th>Comment:</th><td class="infoComment">\ |
| 994 | @ %!W(zEComment?zEComment:zComment)</td></tr> |
| 995 | |
| 996 | /* The Download: line */ |
| 997 | if( g.perm.Zip ){ |
| 998 | char *zPJ = db_get("short-project-name", 0); |
| 999 | char *zUrl; |
| 1000 | Blob projName; |
| 1001 | int jj; |
| 1002 | if( zPJ==0 ) zPJ = db_get("project-name", "unnamed"); |
| 1003 | blob_zero(&projName); |
| 1004 | blob_append(&projName, zPJ, -1); |
| 1005 | blob_trim(&projName); |
| 1006 | zPJ = blob_str(&projName); |
| 1007 | for(jj=0; zPJ[jj]; jj++){ |
| 1008 | if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){ |
| 1009 | zPJ[jj] = '_'; |
| 1010 | } |
| 1011 | } |
| 1012 | zUrl = mprintf("%R/tarball/%S/%t-%S.tar.gz", zUuid, zPJ, zUuid); |
| 1013 | @ <tr><th>Downloads:</th><td> |
| 1014 | @ %z(href("%s",zUrl))Tarball</a> |
| 1015 | @ | %z(href("%R/zip/%S/%t-%S.zip",zUuid, zPJ,zUuid))ZIP archive</a> |
| 1016 | if( g.zLogin!=0 ){ |
| 1017 | @ | %z(href("%R/sqlar/%S/%t-%S.sqlar",zUuid,zPJ,zUuid))\ |
| 1018 | @ SQL archive</a></td></tr> |
| 1019 | } |
| 1020 | fossil_free(zUrl); |
| 1021 | blob_reset(&projName); |
| 1022 | } |
| 1023 | |
| 1024 | @ <tr><th>Timelines:</th><td> |
| 1025 | @ %z(href("%R/timeline?f=%!S&unhide",zUuid))family</a> |
| 1026 | if( zParent ){ |
| @@ -1950,11 +1939,11 @@ | |
| 1950 | int dflt; |
| 1951 | int res; |
| 1952 | int isBot; |
| 1953 | static char zDflt[2] |
| 1954 | /*static b/c cookie_link_parameter() does not copy it!*/; |
| 1955 | if( client_might_be_a_robot() && robot_restrict_has_tag("diff") ){ |
| 1956 | dflt = 0; |
| 1957 | isBot = 1; |
| 1958 | }else{ |
| 1959 | dflt = db_get_int("preferred-diff-type",-99); |
| 1960 | if( dflt<=0 ) dflt = user_agent_is_likely_mobile() ? 1 : 2; |
| 1961 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -993,34 +993,23 @@ | |
| 993 | @ <tr><th>Comment:</th><td class="infoComment">\ |
| 994 | @ %!W(zEComment?zEComment:zComment)</td></tr> |
| 995 | |
| 996 | /* The Download: line */ |
| 997 | if( g.perm.Zip ){ |
| 998 | @ <tr><th>Downloads:</th><td> |
| 999 | if( robot_would_be_restricted("download") ){ |
| 1000 | @ See separate %z(href("%R/rchvdwnld/%!S",zUuid))download page</a> |
| 1001 | }else{ |
| 1002 | char *zBase = archive_base_name(rid); |
| 1003 | @ %z(href("%R/tarball/%s.tar.gz",zBase))Tarball</a> |
| 1004 | @ | %z(href("%R/zip/%s.zip",zBase))ZIP archive</a> |
| 1005 | if( g.zLogin!=0 ){ |
| 1006 | @ | %z(href("%R/sqlar/%s.sqlar",zBase))\ |
| 1007 | @ SQL archive</a></td></tr> |
| 1008 | } |
| 1009 | fossil_free(zBase); |
| 1010 | } |
| 1011 | } |
| 1012 | |
| 1013 | @ <tr><th>Timelines:</th><td> |
| 1014 | @ %z(href("%R/timeline?f=%!S&unhide",zUuid))family</a> |
| 1015 | if( zParent ){ |
| @@ -1950,11 +1939,11 @@ | |
| 1939 | int dflt; |
| 1940 | int res; |
| 1941 | int isBot; |
| 1942 | static char zDflt[2] |
| 1943 | /*static b/c cookie_link_parameter() does not copy it!*/; |
| 1944 | if( robot_would_be_restricted("diff") ){ |
| 1945 | dflt = 0; |
| 1946 | isBot = 1; |
| 1947 | }else{ |
| 1948 | dflt = db_get_int("preferred-diff-type",-99); |
| 1949 | if( dflt<=0 ) dflt = user_agent_is_likely_mobile() ? 1 : 2; |
| 1950 |
+48
-19
| --- src/robot.c | ||
| +++ src/robot.c | ||
| @@ -263,19 +263,19 @@ | ||
| 263 | 263 | ** The VALUE of this setting is a list of GLOB patterns that match |
| 264 | 264 | ** pages for which complex HTTP requests from unauthenticated clients |
| 265 | 265 | ** should be disallowed. "Unauthenticated" means the user is "nobody". |
| 266 | 266 | ** The recommended value for this setting is: |
| 267 | 267 | ** |
| 268 | -** timelineX,diff,annotate,fileage,file,finfo,reports | |
| 268 | +** timelineX,diff,annotate,fileage,file,finfo,reports,tree,download,hexdump | |
| 269 | 269 | ** |
| 270 | 270 | ** The "diff" tag covers all diffing pages such as /vdiff, /fdiff, and |
| 271 | 271 | ** /vpatch. The "annotate" tag also covers /blame and /praise. "zip" |
| 272 | -** also covers /tarball and /sqlar. If a tag has an "X" character appended, | |
| 272 | +** also covers /tarball and /sqlar. If a tag has an "X" character appended | |
| 273 | 273 | ** then it only applies if query parameters are such that the page is |
| 274 | 274 | ** particularly difficult to compute. In all other case, the tag should |
| 275 | -** exactly match the page name. Useful "X" tags include "timelineX" | |
| 276 | -** and "zipX". See the robot-zip-leaf and robot-zip-tag settings | |
| 275 | +** exactly match the page name. Useful "X" tags include "timelineX" and | |
| 276 | +** "zipX". See the [[robot-zip-leaf]] and [[robot-zip-tag]] settings | |
| 277 | 277 | ** for additional controls associated with the "zipX" restriction. |
| 278 | 278 | ** |
| 279 | 279 | ** Change this setting "off" to disable all robot restrictions. |
| 280 | 280 | */ |
| 281 | 281 | /* |
| @@ -299,36 +299,37 @@ | ||
| 299 | 299 | /* |
| 300 | 300 | ** SETTING: robot-zip-leaf boolean |
| 301 | 301 | ** |
| 302 | 302 | ** If this setting is true, the robots are allowed to download tarballs, |
| 303 | 303 | ** ZIP-archives, and SQL-archives even though "zipX" is found in |
| 304 | -** the robot-restrict setting as long as the specific check-in being | |
| 304 | +** the [[robot-restrict]] setting as long as the specific check-in being | |
| 305 | 305 | ** downloaded is a leaf check-in. |
| 306 | 306 | */ |
| 307 | 307 | /* |
| 308 | 308 | ** SETTING: robot-zip-tag width=40 block-text |
| 309 | 309 | ** |
| 310 | 310 | ** If this setting is a list of GLOB patterns matching tags, |
| 311 | 311 | ** then robots are allowed to download tarballs, ZIP-archives, and |
| 312 | -** SQL-archives even though "zipX" appears in robot-restrict, as long as | |
| 312 | +** SQL-archives even though "zipX" appears in [[robot-restrict]], as long as | |
| 313 | 313 | ** the specific check-in being downloaded has a tags that matches |
| 314 | 314 | ** the GLOB list of this setting. Recommended value: |
| 315 | 315 | ** "release,robot-access". |
| 316 | 316 | */ |
| 317 | 317 | |
| 318 | 318 | /* |
| 319 | 319 | ** Return the default restriction GLOB |
| 320 | 320 | */ |
| 321 | 321 | const char *robot_restrict_default(void){ |
| 322 | - return "timelineX,diff,annotate,fileage,file,finfo,reports"; | |
| 322 | + return "timelineX,diff,annotate,fileage,file,finfo,reports," | |
| 323 | + "tree,hexdump,download"; | |
| 323 | 324 | } |
| 324 | 325 | |
| 325 | 326 | /* |
| 326 | 327 | ** Return true if zTag matches one of the tags in the robot-restrict |
| 327 | 328 | ** setting. |
| 328 | 329 | */ |
| 329 | -int robot_restrict_has_tag(const char *zTag){ | |
| 330 | +static int robot_restrict_has_tag(const char *zTag){ | |
| 330 | 331 | static const char *zGlob = 0; |
| 331 | 332 | if( zGlob==0 ){ |
| 332 | 333 | zGlob = db_get("robot-restrict",robot_restrict_default()); |
| 333 | 334 | if( zGlob==0 ) zGlob = ""; |
| 334 | 335 | } |
| @@ -408,32 +409,60 @@ | ||
| 408 | 409 | fossil_free(zRequest); |
| 409 | 410 | return bMatch; |
| 410 | 411 | } |
| 411 | 412 | |
| 412 | 413 | /* |
| 413 | -** Check to see if the page named in the argument is on the | |
| 414 | -** robot-restrict list. If it is on the list and if the user | |
| 415 | -** is "nobody" then bring up a captcha to test to make sure that | |
| 416 | -** client is not a robot. | |
| 414 | +** Return true if one or more of the conditions below are true. | |
| 415 | +** Return false if all of the following are false: | |
| 416 | +** | |
| 417 | +** * The zTag is on the robot-restrict list | |
| 418 | +** | |
| 419 | +** * The client that submitted the HTTP request might be | |
| 420 | +** a robot | |
| 421 | +** | |
| 422 | +** * The Request URI does not match any of the exceptions | |
| 423 | +** in the robot-exception setting. | |
| 424 | +** | |
| 425 | +** In other words, return true if a call to robot_restrict() would | |
| 426 | +** return true and false if a call to robot_restrict() would return | |
| 427 | +** false. | |
| 417 | 428 | ** |
| 418 | -** This routine returns true if a captcha was rendered and if subsequent | |
| 419 | -** page generation should be aborted. It returns false if the page | |
| 420 | -** should not be restricted and should be rendered normally. | |
| 429 | +** The difference between this routine an robot_restrict() is that | |
| 430 | +** this routine does not generate a proof-of-work captcha. This | |
| 431 | +** routine does not change the HTTP reply in any way. It simply | |
| 432 | +** returns true or false. | |
| 421 | 433 | */ |
| 422 | -int robot_restrict(const char *zTag){ | |
| 434 | +int robot_would_be_restricted(const char *zTag){ | |
| 423 | 435 | if( robot.resultCache==KNOWN_NOT_ROBOT ) return 0; |
| 424 | 436 | if( !robot_restrict_has_tag(zTag) ) return 0; |
| 425 | 437 | if( !client_might_be_a_robot() ) return 0; |
| 426 | 438 | if( robot_exception() ){ |
| 427 | 439 | robot.resultCache = KNOWN_NOT_ROBOT; |
| 428 | 440 | return 0; |
| 429 | 441 | } |
| 430 | - | |
| 431 | - /* Generate the proof-of-work captcha */ | |
| 432 | - ask_for_proof_that_client_is_not_robot(); | |
| 433 | 442 | return 1; |
| 434 | 443 | } |
| 444 | + | |
| 445 | +/* | |
| 446 | +** Check to see if the page named in the argument is on the | |
| 447 | +** robot-restrict list. If it is on the list and if the user | |
| 448 | +** is might be a robot, then bring up a captcha to test to make | |
| 449 | +** sure that client is not a robot. | |
| 450 | +** | |
| 451 | +** This routine returns true if a captcha was rendered and if subsequent | |
| 452 | +** page generation should be aborted. It returns false if the page | |
| 453 | +** should not be restricted and should be rendered normally. | |
| 454 | +*/ | |
| 455 | +int robot_restrict(const char *zTag){ | |
| 456 | + if( robot_would_be_restricted(zTag) ){ | |
| 457 | + /* Generate the proof-of-work captcha */ | |
| 458 | + ask_for_proof_that_client_is_not_robot(); | |
| 459 | + return 1; | |
| 460 | + }else{ | |
| 461 | + return 0; | |
| 462 | + } | |
| 463 | +} | |
| 435 | 464 | |
| 436 | 465 | /* |
| 437 | 466 | ** Check to see if a robot is allowed to download a tarball, ZIP archive, |
| 438 | 467 | ** or SQL Archive for a particular check-in identified by the "rid" |
| 439 | 468 | ** argument. Return true to block the download. Return false to |
| 440 | 469 |
| --- src/robot.c | |
| +++ src/robot.c | |
| @@ -263,19 +263,19 @@ | |
| 263 | ** The VALUE of this setting is a list of GLOB patterns that match |
| 264 | ** pages for which complex HTTP requests from unauthenticated clients |
| 265 | ** should be disallowed. "Unauthenticated" means the user is "nobody". |
| 266 | ** The recommended value for this setting is: |
| 267 | ** |
| 268 | ** timelineX,diff,annotate,fileage,file,finfo,reports |
| 269 | ** |
| 270 | ** The "diff" tag covers all diffing pages such as /vdiff, /fdiff, and |
| 271 | ** /vpatch. The "annotate" tag also covers /blame and /praise. "zip" |
| 272 | ** also covers /tarball and /sqlar. If a tag has an "X" character appended, |
| 273 | ** then it only applies if query parameters are such that the page is |
| 274 | ** particularly difficult to compute. In all other case, the tag should |
| 275 | ** exactly match the page name. Useful "X" tags include "timelineX" |
| 276 | ** and "zipX". See the robot-zip-leaf and robot-zip-tag settings |
| 277 | ** for additional controls associated with the "zipX" restriction. |
| 278 | ** |
| 279 | ** Change this setting "off" to disable all robot restrictions. |
| 280 | */ |
| 281 | /* |
| @@ -299,36 +299,37 @@ | |
| 299 | /* |
| 300 | ** SETTING: robot-zip-leaf boolean |
| 301 | ** |
| 302 | ** If this setting is true, the robots are allowed to download tarballs, |
| 303 | ** ZIP-archives, and SQL-archives even though "zipX" is found in |
| 304 | ** the robot-restrict setting as long as the specific check-in being |
| 305 | ** downloaded is a leaf check-in. |
| 306 | */ |
| 307 | /* |
| 308 | ** SETTING: robot-zip-tag width=40 block-text |
| 309 | ** |
| 310 | ** If this setting is a list of GLOB patterns matching tags, |
| 311 | ** then robots are allowed to download tarballs, ZIP-archives, and |
| 312 | ** SQL-archives even though "zipX" appears in robot-restrict, as long as |
| 313 | ** the specific check-in being downloaded has a tags that matches |
| 314 | ** the GLOB list of this setting. Recommended value: |
| 315 | ** "release,robot-access". |
| 316 | */ |
| 317 | |
| 318 | /* |
| 319 | ** Return the default restriction GLOB |
| 320 | */ |
| 321 | const char *robot_restrict_default(void){ |
| 322 | return "timelineX,diff,annotate,fileage,file,finfo,reports"; |
| 323 | } |
| 324 | |
| 325 | /* |
| 326 | ** Return true if zTag matches one of the tags in the robot-restrict |
| 327 | ** setting. |
| 328 | */ |
| 329 | int robot_restrict_has_tag(const char *zTag){ |
| 330 | static const char *zGlob = 0; |
| 331 | if( zGlob==0 ){ |
| 332 | zGlob = db_get("robot-restrict",robot_restrict_default()); |
| 333 | if( zGlob==0 ) zGlob = ""; |
| 334 | } |
| @@ -408,32 +409,60 @@ | |
| 408 | fossil_free(zRequest); |
| 409 | return bMatch; |
| 410 | } |
| 411 | |
| 412 | /* |
| 413 | ** Check to see if the page named in the argument is on the |
| 414 | ** robot-restrict list. If it is on the list and if the user |
| 415 | ** is "nobody" then bring up a captcha to test to make sure that |
| 416 | ** client is not a robot. |
| 417 | ** |
| 418 | ** This routine returns true if a captcha was rendered and if subsequent |
| 419 | ** page generation should be aborted. It returns false if the page |
| 420 | ** should not be restricted and should be rendered normally. |
| 421 | */ |
| 422 | int robot_restrict(const char *zTag){ |
| 423 | if( robot.resultCache==KNOWN_NOT_ROBOT ) return 0; |
| 424 | if( !robot_restrict_has_tag(zTag) ) return 0; |
| 425 | if( !client_might_be_a_robot() ) return 0; |
| 426 | if( robot_exception() ){ |
| 427 | robot.resultCache = KNOWN_NOT_ROBOT; |
| 428 | return 0; |
| 429 | } |
| 430 | |
| 431 | /* Generate the proof-of-work captcha */ |
| 432 | ask_for_proof_that_client_is_not_robot(); |
| 433 | return 1; |
| 434 | } |
| 435 | |
| 436 | /* |
| 437 | ** Check to see if a robot is allowed to download a tarball, ZIP archive, |
| 438 | ** or SQL Archive for a particular check-in identified by the "rid" |
| 439 | ** argument. Return true to block the download. Return false to |
| 440 |
| --- src/robot.c | |
| +++ src/robot.c | |
| @@ -263,19 +263,19 @@ | |
| 263 | ** The VALUE of this setting is a list of GLOB patterns that match |
| 264 | ** pages for which complex HTTP requests from unauthenticated clients |
| 265 | ** should be disallowed. "Unauthenticated" means the user is "nobody". |
| 266 | ** The recommended value for this setting is: |
| 267 | ** |
| 268 | ** timelineX,diff,annotate,fileage,file,finfo,reports,tree,download,hexdump |
| 269 | ** |
| 270 | ** The "diff" tag covers all diffing pages such as /vdiff, /fdiff, and |
| 271 | ** /vpatch. The "annotate" tag also covers /blame and /praise. "zip" |
| 272 | ** also covers /tarball and /sqlar. If a tag has an "X" character appended |
| 273 | ** then it only applies if query parameters are such that the page is |
| 274 | ** particularly difficult to compute. In all other case, the tag should |
| 275 | ** exactly match the page name. Useful "X" tags include "timelineX" and |
| 276 | ** "zipX". See the [[robot-zip-leaf]] and [[robot-zip-tag]] settings |
| 277 | ** for additional controls associated with the "zipX" restriction. |
| 278 | ** |
| 279 | ** Change this setting "off" to disable all robot restrictions. |
| 280 | */ |
| 281 | /* |
| @@ -299,36 +299,37 @@ | |
| 299 | /* |
| 300 | ** SETTING: robot-zip-leaf boolean |
| 301 | ** |
| 302 | ** If this setting is true, the robots are allowed to download tarballs, |
| 303 | ** ZIP-archives, and SQL-archives even though "zipX" is found in |
| 304 | ** the [[robot-restrict]] setting as long as the specific check-in being |
| 305 | ** downloaded is a leaf check-in. |
| 306 | */ |
| 307 | /* |
| 308 | ** SETTING: robot-zip-tag width=40 block-text |
| 309 | ** |
| 310 | ** If this setting is a list of GLOB patterns matching tags, |
| 311 | ** then robots are allowed to download tarballs, ZIP-archives, and |
| 312 | ** SQL-archives even though "zipX" appears in [[robot-restrict]], as long as |
| 313 | ** the specific check-in being downloaded has a tags that matches |
| 314 | ** the GLOB list of this setting. Recommended value: |
| 315 | ** "release,robot-access". |
| 316 | */ |
| 317 | |
| 318 | /* |
| 319 | ** Return the default restriction GLOB |
| 320 | */ |
| 321 | const char *robot_restrict_default(void){ |
| 322 | return "timelineX,diff,annotate,fileage,file,finfo,reports," |
| 323 | "tree,hexdump,download"; |
| 324 | } |
| 325 | |
| 326 | /* |
| 327 | ** Return true if zTag matches one of the tags in the robot-restrict |
| 328 | ** setting. |
| 329 | */ |
| 330 | static int robot_restrict_has_tag(const char *zTag){ |
| 331 | static const char *zGlob = 0; |
| 332 | if( zGlob==0 ){ |
| 333 | zGlob = db_get("robot-restrict",robot_restrict_default()); |
| 334 | if( zGlob==0 ) zGlob = ""; |
| 335 | } |
| @@ -408,32 +409,60 @@ | |
| 409 | fossil_free(zRequest); |
| 410 | return bMatch; |
| 411 | } |
| 412 | |
| 413 | /* |
| 414 | ** Return true if one or more of the conditions below are true. |
| 415 | ** Return false if all of the following are false: |
| 416 | ** |
| 417 | ** * The zTag is on the robot-restrict list |
| 418 | ** |
| 419 | ** * The client that submitted the HTTP request might be |
| 420 | ** a robot |
| 421 | ** |
| 422 | ** * The Request URI does not match any of the exceptions |
| 423 | ** in the robot-exception setting. |
| 424 | ** |
| 425 | ** In other words, return true if a call to robot_restrict() would |
| 426 | ** return true and false if a call to robot_restrict() would return |
| 427 | ** false. |
| 428 | ** |
| 429 | ** The difference between this routine an robot_restrict() is that |
| 430 | ** this routine does not generate a proof-of-work captcha. This |
| 431 | ** routine does not change the HTTP reply in any way. It simply |
| 432 | ** returns true or false. |
| 433 | */ |
| 434 | int robot_would_be_restricted(const char *zTag){ |
| 435 | if( robot.resultCache==KNOWN_NOT_ROBOT ) return 0; |
| 436 | if( !robot_restrict_has_tag(zTag) ) return 0; |
| 437 | if( !client_might_be_a_robot() ) return 0; |
| 438 | if( robot_exception() ){ |
| 439 | robot.resultCache = KNOWN_NOT_ROBOT; |
| 440 | return 0; |
| 441 | } |
| 442 | return 1; |
| 443 | } |
| 444 | |
| 445 | /* |
| 446 | ** Check to see if the page named in the argument is on the |
| 447 | ** robot-restrict list. If it is on the list and if the user |
| 448 | ** is might be a robot, then bring up a captcha to test to make |
| 449 | ** sure that client is not a robot. |
| 450 | ** |
| 451 | ** This routine returns true if a captcha was rendered and if subsequent |
| 452 | ** page generation should be aborted. It returns false if the page |
| 453 | ** should not be restricted and should be rendered normally. |
| 454 | */ |
| 455 | int robot_restrict(const char *zTag){ |
| 456 | if( robot_would_be_restricted(zTag) ){ |
| 457 | /* Generate the proof-of-work captcha */ |
| 458 | ask_for_proof_that_client_is_not_robot(); |
| 459 | return 1; |
| 460 | }else{ |
| 461 | return 0; |
| 462 | } |
| 463 | } |
| 464 | |
| 465 | /* |
| 466 | ** Check to see if a robot is allowed to download a tarball, ZIP archive, |
| 467 | ** or SQL Archive for a particular check-in identified by the "rid" |
| 468 | ** argument. Return true to block the download. Return false to |
| 469 |
+73
-40
| --- src/setup.c | ||
| +++ src/setup.c | ||
| @@ -118,10 +118,12 @@ | ||
| 118 | 118 | setup_menu_entry("Settings", "setup_settings", |
| 119 | 119 | "Web interface to the \"fossil settings\" command"); |
| 120 | 120 | } |
| 121 | 121 | setup_menu_entry("Timeline", "setup_timeline", |
| 122 | 122 | "Timeline display preferences"); |
| 123 | + setup_menu_entry("Tarballs and ZIPs", "setup_download", | |
| 124 | + "Preferences for auto-generated tarballs and ZIP files"); | |
| 123 | 125 | if( setup_user ){ |
| 124 | 126 | setup_menu_entry("Login-Group", "setup_login_group", |
| 125 | 127 | "Manage single sign-on between this repository and others" |
| 126 | 128 | " on the same server"); |
| 127 | 129 | setup_menu_entry("Tickets", "tktsetup", |
| @@ -140,12 +142,14 @@ | ||
| 140 | 142 | setup_menu_entry("URL Aliases", "waliassetup", |
| 141 | 143 | "Configure URL aliases"); |
| 142 | 144 | if( setup_user ){ |
| 143 | 145 | setup_menu_entry("Notification", "setup_notification", |
| 144 | 146 | "Automatic notifications of changes via outbound email"); |
| 147 | +#if 0 /* Disabled for now. Does this even work? */ | |
| 145 | 148 | setup_menu_entry("Transfers", "xfersetup", |
| 146 | 149 | "Configure the transfer system for this repository"); |
| 150 | +#endif | |
| 147 | 151 | } |
| 148 | 152 | setup_menu_entry("Skins", "setup_skin_admin", |
| 149 | 153 | "Select and/or modify the web interface \"skins\""); |
| 150 | 154 | setup_menu_entry("Moderation", "setup_modreq", |
| 151 | 155 | "Enable/Disable requiring moderator approval of Wiki and/or Ticket" |
| @@ -488,14 +492,15 @@ | ||
| 488 | 492 | @ <p> |
| 489 | 493 | @    <tt>%h(robot_restrict_default())</tt> |
| 490 | 494 | @ <p> |
| 491 | 495 | @ The "diff" tag covers all diffing pages such as /vdiff, /fdiff, and |
| 492 | 496 | @ /vpatch. The "annotate" tag covers /annotate and also /blame and |
| 493 | - @ /praise. The "zip" covers itself and also /tarball and /sqlar. If a | |
| 494 | - @ tag has an "X" character appended, then it only applies if query | |
| 495 | - @ parameters are such that the page is expensive and/or unusual. | |
| 496 | - @ In all other case, the tag should exactly match the page name. | |
| 497 | + @ /praise. The "zip" covers itself and also /tarball and /sqlar. | |
| 498 | + @ If a tag has an "X" character appended (ex: "timelineX") then it only | |
| 499 | + @ applies if query parameters are such that the page is expensive | |
| 500 | + @ and/or unusual. In all other case, the tag should exactly match | |
| 501 | + @ the page name. | |
| 497 | 502 | @ |
| 498 | 503 | @ To disable robot restrictions, change this setting to "off". |
| 499 | 504 | @ (Property: robot-restrict) |
| 500 | 505 | @ <br> |
| 501 | 506 | textarea_attribute("", 2, 80, |
| @@ -985,10 +990,15 @@ | ||
| 985 | 990 | "1", "HH:MM:SS", |
| 986 | 991 | "2", "YYYY-MM-DD HH:MM", |
| 987 | 992 | "3", "YYMMDD HH:MM", |
| 988 | 993 | "4", "(off)" |
| 989 | 994 | }; |
| 995 | + static const char *const azLeafMark[] = { | |
| 996 | + "0", "No", | |
| 997 | + "1", "Yes", | |
| 998 | + "2", "Yes - with emphasis", | |
| 999 | + }; | |
| 990 | 1000 | login_check_credentials(); |
| 991 | 1001 | if( !g.perm.Admin ){ |
| 992 | 1002 | login_needed(0); |
| 993 | 1003 | return; |
| 994 | 1004 | } |
| @@ -1073,10 +1083,16 @@ | ||
| 1073 | 1083 | @ in a separate box (using CSS class "timelineDate") whenever the date |
| 1074 | 1084 | @ changes. With the "YYYY-MM-DD HH:MM" and "YYMMDD ..." formats, |
| 1075 | 1085 | @ the complete date and time is shown on every timeline entry using the |
| 1076 | 1086 | @ CSS class "timelineTime". (Property: "timeline-date-format")</p> |
| 1077 | 1087 | |
| 1088 | + @ <hr> | |
| 1089 | + multiple_choice_attribute("Leaf Markings", "timeline-mark-leaves", | |
| 1090 | + "tml", "1", count(azLeafMark)/2, azLeafMark); | |
| 1091 | + @ <p>Should timeline entries for leaf check-ins be identified in the | |
| 1092 | + @ detail section. (Property: "timeline-mark-leaves")</p> | |
| 1093 | + | |
| 1078 | 1094 | @ <hr> |
| 1079 | 1095 | entry_attribute("Max timeline comment length", 6, |
| 1080 | 1096 | "timeline-max-comment", "tmc", "0", 0); |
| 1081 | 1097 | @ <p>The maximum length of a comment to be displayed in a timeline. |
| 1082 | 1098 | @ "0" there is no length limit. |
| @@ -1326,28 +1342,10 @@ | ||
| 1326 | 1342 | @ Omit the trailing "/". |
| 1327 | 1343 | @ If this repo will not be set up as a persistent server and will not |
| 1328 | 1344 | @ be sending email alerts, then leave this entry blank. |
| 1329 | 1345 | @ Suggested value: "%h(g.zBaseURL)" |
| 1330 | 1346 | @ (Property: "email-url")</p> |
| 1331 | - @ <hr> | |
| 1332 | - entry_attribute("Tarball and ZIP-archive Prefix", 20, "short-project-name", | |
| 1333 | - "spn", "", 0); | |
| 1334 | - @ <p>This is used as a prefix on the names of generated tarballs and | |
| 1335 | - @ ZIP archive. For best results, keep this prefix brief and avoid special | |
| 1336 | - @ characters such as "/" and "\". | |
| 1337 | - @ If no tarball prefix is specified, then the full Project Name above is used. | |
| 1338 | - @ (Property: "short-project-name") | |
| 1339 | - @ </p> | |
| 1340 | - @ <hr> | |
| 1341 | - entry_attribute("Download Tag", 20, "download-tag", "dlt", "trunk", 0); | |
| 1342 | - @ <p>The <a href='%R/download'>/download</a> page is designed to provide | |
| 1343 | - @ a convenient place for newbies | |
| 1344 | - @ to download a ZIP archive or a tarball of the project. By default, | |
| 1345 | - @ the latest trunk check-in is downloaded. Change this tag to something | |
| 1346 | - @ else (ex: release) to alter the behavior of the /download page. | |
| 1347 | - @ (Property: "download-tag") | |
| 1348 | - @ </p> | |
| 1349 | 1347 | @ <hr> |
| 1350 | 1348 | entry_attribute("Index Page", 60, "index-page", "idxpg", "/home", 0); |
| 1351 | 1349 | @ <p>Enter the pathname of the page to display when the "Home" menu |
| 1352 | 1350 | @ option is selected and when no pathname is |
| 1353 | 1351 | @ specified in the URL. For example, if you visit the url:</p> |
| @@ -1426,30 +1424,65 @@ | ||
| 1426 | 1424 | @ (Property: sitemap-extra) |
| 1427 | 1425 | @ <p> |
| 1428 | 1426 | textarea_attribute("Custom Sitemap Entries", 8, 80, |
| 1429 | 1427 | "sitemap-extra", "smextra", "", 0); |
| 1430 | 1428 | @ <hr> |
| 1431 | - @ <p>Configuration for the <a href="%R/tarlist">/tarlist</a> page. | |
| 1432 | - @ The value is a TCL list divided into pairs. | |
| 1429 | + @ <p><input type="submit" name="submit" value="Apply Changes"></p> | |
| 1430 | + @ </div></form> | |
| 1431 | + db_end_transaction(0); | |
| 1432 | + style_finish_page(); | |
| 1433 | +} | |
| 1434 | + | |
| 1435 | +/* | |
| 1436 | +** WEBPAGE: setup_download | |
| 1437 | +** | |
| 1438 | +** The "Admin/Download" page. Requires Setup privilege. | |
| 1439 | +*/ | |
| 1440 | +void setup_download(void){ | |
| 1441 | + login_check_credentials(); | |
| 1442 | + if( !g.perm.Setup ){ | |
| 1443 | + login_needed(0); | |
| 1444 | + return; | |
| 1445 | + } | |
| 1446 | + | |
| 1447 | + style_set_current_feature("setup"); | |
| 1448 | + style_header("Tarball and ZIP Downloads"); | |
| 1449 | + db_begin_transaction(); | |
| 1450 | + @ <form action="%R/setup_download" method="post"><div> | |
| 1451 | + login_insert_csrf_secret(); | |
| 1452 | + @ <input type="submit" name="submit" value="Apply Changes"></p> | |
| 1453 | + @ <hr> | |
| 1454 | + entry_attribute("Tarball and ZIP Name Prefix", 20, "short-project-name", | |
| 1455 | + "spn", "", 0); | |
| 1456 | + @ <p>This is used as a prefix for the names of generated tarballs and | |
| 1457 | + @ ZIP archive. Keep this prefix brief and use only lower-case ASCII | |
| 1458 | + @ characters, digits, "_", "-" in the name. If this setting is blank, | |
| 1459 | + @ then the full <a href='%R/help/project-name'>project-name</a> setting | |
| 1460 | + @ is used instead. | |
| 1461 | + @ (Property: "short-project-name") | |
| 1462 | + @ </p> | |
| 1463 | + @ <hr> | |
| 1464 | + @ <p><b>Configuration for the <a href="%R/download">/download</a> page.</b> | |
| 1465 | + @ <p>The value is a TCL list divided into groups of four tokens: | |
| 1433 | 1466 | @ <ol> |
| 1434 | - @ <li> The first term of each pair is an integer (N). | |
| 1435 | - @ <li> The second term of each pair is a glob pattern (PATTERN). | |
| 1467 | + @ <li> Maximum number of matches (COUNT). | |
| 1468 | + @ <li> Tag to match using glob (TAG). | |
| 1469 | + @ <li> Maximum age of check-ins to match (MAX_AGE). | |
| 1470 | + @ <li> Comment to apply to matches (COMMENT). | |
| 1436 | 1471 | @ </ol> |
| 1437 | - @ For each pair, the most recent N check-ins that have a tag that | |
| 1438 | - @ matches PATTERN are included in on the /tarlist page. The special | |
| 1439 | - @ pattern of "OPEN-LEAF" matches all open leaf check-ins. Example: | |
| 1440 | - @ <blockquote><tt>1 trunk 3 release 5 OPEN-LEAF</tt></blockquote> | |
| 1441 | - @ The example pattern above shows the union of the most recent trunk | |
| 1442 | - @ check-in, the 5 most recent open leaf check-ins, and the 3 most | |
| 1443 | - @ recent check-ins tagged with "release". | |
| 1444 | - @ <p> | |
| 1445 | - @ The /tarlist page is omitted from the <a href="%R/sitemap">/sitemap</a> | |
| 1446 | - @ if the first token is "0". The default value is "1 trunk". | |
| 1447 | - @ (Property: suggested-tarlist) | |
| 1448 | - @ <p> | |
| 1449 | - textarea_attribute("Check-ins To Show On /tarlist", 2, 80, | |
| 1450 | - "suggested-tarlist", "sgtrlst", "", 0); | |
| 1472 | + @ Each 4-tuple will match zero or more check-ins. The /download page | |
| 1473 | + @ displays the union of matches from all 4-tuples. | |
| 1474 | + @ See the <a href="%R/help/suggested-downloads">suggested-downloads</a> | |
| 1475 | + @ setting documentation for further detail. | |
| 1476 | + @ <p> | |
| 1477 | + @ The /download page is omitted from the <a href="%R/sitemap">/sitemap</a> | |
| 1478 | + @ if the first token is "0" or "off" or "no". The default value | |
| 1479 | + @ for this setting is "off". | |
| 1480 | + @ (Property: <a href="%R/help/suggested-downloads">suggested-downloads</a>) | |
| 1481 | + @ <p> | |
| 1482 | + textarea_attribute("", 4, 80, | |
| 1483 | + "suggested-downloads", "sgtrlst", "off", 0); | |
| 1451 | 1484 | @ <hr> |
| 1452 | 1485 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 1453 | 1486 | @ </div></form> |
| 1454 | 1487 | db_end_transaction(0); |
| 1455 | 1488 | style_finish_page(); |
| 1456 | 1489 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -118,10 +118,12 @@ | |
| 118 | setup_menu_entry("Settings", "setup_settings", |
| 119 | "Web interface to the \"fossil settings\" command"); |
| 120 | } |
| 121 | setup_menu_entry("Timeline", "setup_timeline", |
| 122 | "Timeline display preferences"); |
| 123 | if( setup_user ){ |
| 124 | setup_menu_entry("Login-Group", "setup_login_group", |
| 125 | "Manage single sign-on between this repository and others" |
| 126 | " on the same server"); |
| 127 | setup_menu_entry("Tickets", "tktsetup", |
| @@ -140,12 +142,14 @@ | |
| 140 | setup_menu_entry("URL Aliases", "waliassetup", |
| 141 | "Configure URL aliases"); |
| 142 | if( setup_user ){ |
| 143 | setup_menu_entry("Notification", "setup_notification", |
| 144 | "Automatic notifications of changes via outbound email"); |
| 145 | setup_menu_entry("Transfers", "xfersetup", |
| 146 | "Configure the transfer system for this repository"); |
| 147 | } |
| 148 | setup_menu_entry("Skins", "setup_skin_admin", |
| 149 | "Select and/or modify the web interface \"skins\""); |
| 150 | setup_menu_entry("Moderation", "setup_modreq", |
| 151 | "Enable/Disable requiring moderator approval of Wiki and/or Ticket" |
| @@ -488,14 +492,15 @@ | |
| 488 | @ <p> |
| 489 | @    <tt>%h(robot_restrict_default())</tt> |
| 490 | @ <p> |
| 491 | @ The "diff" tag covers all diffing pages such as /vdiff, /fdiff, and |
| 492 | @ /vpatch. The "annotate" tag covers /annotate and also /blame and |
| 493 | @ /praise. The "zip" covers itself and also /tarball and /sqlar. If a |
| 494 | @ tag has an "X" character appended, then it only applies if query |
| 495 | @ parameters are such that the page is expensive and/or unusual. |
| 496 | @ In all other case, the tag should exactly match the page name. |
| 497 | @ |
| 498 | @ To disable robot restrictions, change this setting to "off". |
| 499 | @ (Property: robot-restrict) |
| 500 | @ <br> |
| 501 | textarea_attribute("", 2, 80, |
| @@ -985,10 +990,15 @@ | |
| 985 | "1", "HH:MM:SS", |
| 986 | "2", "YYYY-MM-DD HH:MM", |
| 987 | "3", "YYMMDD HH:MM", |
| 988 | "4", "(off)" |
| 989 | }; |
| 990 | login_check_credentials(); |
| 991 | if( !g.perm.Admin ){ |
| 992 | login_needed(0); |
| 993 | return; |
| 994 | } |
| @@ -1073,10 +1083,16 @@ | |
| 1073 | @ in a separate box (using CSS class "timelineDate") whenever the date |
| 1074 | @ changes. With the "YYYY-MM-DD HH:MM" and "YYMMDD ..." formats, |
| 1075 | @ the complete date and time is shown on every timeline entry using the |
| 1076 | @ CSS class "timelineTime". (Property: "timeline-date-format")</p> |
| 1077 | |
| 1078 | @ <hr> |
| 1079 | entry_attribute("Max timeline comment length", 6, |
| 1080 | "timeline-max-comment", "tmc", "0", 0); |
| 1081 | @ <p>The maximum length of a comment to be displayed in a timeline. |
| 1082 | @ "0" there is no length limit. |
| @@ -1326,28 +1342,10 @@ | |
| 1326 | @ Omit the trailing "/". |
| 1327 | @ If this repo will not be set up as a persistent server and will not |
| 1328 | @ be sending email alerts, then leave this entry blank. |
| 1329 | @ Suggested value: "%h(g.zBaseURL)" |
| 1330 | @ (Property: "email-url")</p> |
| 1331 | @ <hr> |
| 1332 | entry_attribute("Tarball and ZIP-archive Prefix", 20, "short-project-name", |
| 1333 | "spn", "", 0); |
| 1334 | @ <p>This is used as a prefix on the names of generated tarballs and |
| 1335 | @ ZIP archive. For best results, keep this prefix brief and avoid special |
| 1336 | @ characters such as "/" and "\". |
| 1337 | @ If no tarball prefix is specified, then the full Project Name above is used. |
| 1338 | @ (Property: "short-project-name") |
| 1339 | @ </p> |
| 1340 | @ <hr> |
| 1341 | entry_attribute("Download Tag", 20, "download-tag", "dlt", "trunk", 0); |
| 1342 | @ <p>The <a href='%R/download'>/download</a> page is designed to provide |
| 1343 | @ a convenient place for newbies |
| 1344 | @ to download a ZIP archive or a tarball of the project. By default, |
| 1345 | @ the latest trunk check-in is downloaded. Change this tag to something |
| 1346 | @ else (ex: release) to alter the behavior of the /download page. |
| 1347 | @ (Property: "download-tag") |
| 1348 | @ </p> |
| 1349 | @ <hr> |
| 1350 | entry_attribute("Index Page", 60, "index-page", "idxpg", "/home", 0); |
| 1351 | @ <p>Enter the pathname of the page to display when the "Home" menu |
| 1352 | @ option is selected and when no pathname is |
| 1353 | @ specified in the URL. For example, if you visit the url:</p> |
| @@ -1426,30 +1424,65 @@ | |
| 1426 | @ (Property: sitemap-extra) |
| 1427 | @ <p> |
| 1428 | textarea_attribute("Custom Sitemap Entries", 8, 80, |
| 1429 | "sitemap-extra", "smextra", "", 0); |
| 1430 | @ <hr> |
| 1431 | @ <p>Configuration for the <a href="%R/tarlist">/tarlist</a> page. |
| 1432 | @ The value is a TCL list divided into pairs. |
| 1433 | @ <ol> |
| 1434 | @ <li> The first term of each pair is an integer (N). |
| 1435 | @ <li> The second term of each pair is a glob pattern (PATTERN). |
| 1436 | @ </ol> |
| 1437 | @ For each pair, the most recent N check-ins that have a tag that |
| 1438 | @ matches PATTERN are included in on the /tarlist page. The special |
| 1439 | @ pattern of "OPEN-LEAF" matches all open leaf check-ins. Example: |
| 1440 | @ <blockquote><tt>1 trunk 3 release 5 OPEN-LEAF</tt></blockquote> |
| 1441 | @ The example pattern above shows the union of the most recent trunk |
| 1442 | @ check-in, the 5 most recent open leaf check-ins, and the 3 most |
| 1443 | @ recent check-ins tagged with "release". |
| 1444 | @ <p> |
| 1445 | @ The /tarlist page is omitted from the <a href="%R/sitemap">/sitemap</a> |
| 1446 | @ if the first token is "0". The default value is "1 trunk". |
| 1447 | @ (Property: suggested-tarlist) |
| 1448 | @ <p> |
| 1449 | textarea_attribute("Check-ins To Show On /tarlist", 2, 80, |
| 1450 | "suggested-tarlist", "sgtrlst", "", 0); |
| 1451 | @ <hr> |
| 1452 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 1453 | @ </div></form> |
| 1454 | db_end_transaction(0); |
| 1455 | style_finish_page(); |
| 1456 |
| --- src/setup.c | |
| +++ src/setup.c | |
| @@ -118,10 +118,12 @@ | |
| 118 | setup_menu_entry("Settings", "setup_settings", |
| 119 | "Web interface to the \"fossil settings\" command"); |
| 120 | } |
| 121 | setup_menu_entry("Timeline", "setup_timeline", |
| 122 | "Timeline display preferences"); |
| 123 | setup_menu_entry("Tarballs and ZIPs", "setup_download", |
| 124 | "Preferences for auto-generated tarballs and ZIP files"); |
| 125 | if( setup_user ){ |
| 126 | setup_menu_entry("Login-Group", "setup_login_group", |
| 127 | "Manage single sign-on between this repository and others" |
| 128 | " on the same server"); |
| 129 | setup_menu_entry("Tickets", "tktsetup", |
| @@ -140,12 +142,14 @@ | |
| 142 | setup_menu_entry("URL Aliases", "waliassetup", |
| 143 | "Configure URL aliases"); |
| 144 | if( setup_user ){ |
| 145 | setup_menu_entry("Notification", "setup_notification", |
| 146 | "Automatic notifications of changes via outbound email"); |
| 147 | #if 0 /* Disabled for now. Does this even work? */ |
| 148 | setup_menu_entry("Transfers", "xfersetup", |
| 149 | "Configure the transfer system for this repository"); |
| 150 | #endif |
| 151 | } |
| 152 | setup_menu_entry("Skins", "setup_skin_admin", |
| 153 | "Select and/or modify the web interface \"skins\""); |
| 154 | setup_menu_entry("Moderation", "setup_modreq", |
| 155 | "Enable/Disable requiring moderator approval of Wiki and/or Ticket" |
| @@ -488,14 +492,15 @@ | |
| 492 | @ <p> |
| 493 | @    <tt>%h(robot_restrict_default())</tt> |
| 494 | @ <p> |
| 495 | @ The "diff" tag covers all diffing pages such as /vdiff, /fdiff, and |
| 496 | @ /vpatch. The "annotate" tag covers /annotate and also /blame and |
| 497 | @ /praise. The "zip" covers itself and also /tarball and /sqlar. |
| 498 | @ If a tag has an "X" character appended (ex: "timelineX") then it only |
| 499 | @ applies if query parameters are such that the page is expensive |
| 500 | @ and/or unusual. In all other case, the tag should exactly match |
| 501 | @ the page name. |
| 502 | @ |
| 503 | @ To disable robot restrictions, change this setting to "off". |
| 504 | @ (Property: robot-restrict) |
| 505 | @ <br> |
| 506 | textarea_attribute("", 2, 80, |
| @@ -985,10 +990,15 @@ | |
| 990 | "1", "HH:MM:SS", |
| 991 | "2", "YYYY-MM-DD HH:MM", |
| 992 | "3", "YYMMDD HH:MM", |
| 993 | "4", "(off)" |
| 994 | }; |
| 995 | static const char *const azLeafMark[] = { |
| 996 | "0", "No", |
| 997 | "1", "Yes", |
| 998 | "2", "Yes - with emphasis", |
| 999 | }; |
| 1000 | login_check_credentials(); |
| 1001 | if( !g.perm.Admin ){ |
| 1002 | login_needed(0); |
| 1003 | return; |
| 1004 | } |
| @@ -1073,10 +1083,16 @@ | |
| 1083 | @ in a separate box (using CSS class "timelineDate") whenever the date |
| 1084 | @ changes. With the "YYYY-MM-DD HH:MM" and "YYMMDD ..." formats, |
| 1085 | @ the complete date and time is shown on every timeline entry using the |
| 1086 | @ CSS class "timelineTime". (Property: "timeline-date-format")</p> |
| 1087 | |
| 1088 | @ <hr> |
| 1089 | multiple_choice_attribute("Leaf Markings", "timeline-mark-leaves", |
| 1090 | "tml", "1", count(azLeafMark)/2, azLeafMark); |
| 1091 | @ <p>Should timeline entries for leaf check-ins be identified in the |
| 1092 | @ detail section. (Property: "timeline-mark-leaves")</p> |
| 1093 | |
| 1094 | @ <hr> |
| 1095 | entry_attribute("Max timeline comment length", 6, |
| 1096 | "timeline-max-comment", "tmc", "0", 0); |
| 1097 | @ <p>The maximum length of a comment to be displayed in a timeline. |
| 1098 | @ "0" there is no length limit. |
| @@ -1326,28 +1342,10 @@ | |
| 1342 | @ Omit the trailing "/". |
| 1343 | @ If this repo will not be set up as a persistent server and will not |
| 1344 | @ be sending email alerts, then leave this entry blank. |
| 1345 | @ Suggested value: "%h(g.zBaseURL)" |
| 1346 | @ (Property: "email-url")</p> |
| 1347 | @ <hr> |
| 1348 | entry_attribute("Index Page", 60, "index-page", "idxpg", "/home", 0); |
| 1349 | @ <p>Enter the pathname of the page to display when the "Home" menu |
| 1350 | @ option is selected and when no pathname is |
| 1351 | @ specified in the URL. For example, if you visit the url:</p> |
| @@ -1426,30 +1424,65 @@ | |
| 1424 | @ (Property: sitemap-extra) |
| 1425 | @ <p> |
| 1426 | textarea_attribute("Custom Sitemap Entries", 8, 80, |
| 1427 | "sitemap-extra", "smextra", "", 0); |
| 1428 | @ <hr> |
| 1429 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 1430 | @ </div></form> |
| 1431 | db_end_transaction(0); |
| 1432 | style_finish_page(); |
| 1433 | } |
| 1434 | |
| 1435 | /* |
| 1436 | ** WEBPAGE: setup_download |
| 1437 | ** |
| 1438 | ** The "Admin/Download" page. Requires Setup privilege. |
| 1439 | */ |
| 1440 | void setup_download(void){ |
| 1441 | login_check_credentials(); |
| 1442 | if( !g.perm.Setup ){ |
| 1443 | login_needed(0); |
| 1444 | return; |
| 1445 | } |
| 1446 | |
| 1447 | style_set_current_feature("setup"); |
| 1448 | style_header("Tarball and ZIP Downloads"); |
| 1449 | db_begin_transaction(); |
| 1450 | @ <form action="%R/setup_download" method="post"><div> |
| 1451 | login_insert_csrf_secret(); |
| 1452 | @ <input type="submit" name="submit" value="Apply Changes"></p> |
| 1453 | @ <hr> |
| 1454 | entry_attribute("Tarball and ZIP Name Prefix", 20, "short-project-name", |
| 1455 | "spn", "", 0); |
| 1456 | @ <p>This is used as a prefix for the names of generated tarballs and |
| 1457 | @ ZIP archive. Keep this prefix brief and use only lower-case ASCII |
| 1458 | @ characters, digits, "_", "-" in the name. If this setting is blank, |
| 1459 | @ then the full <a href='%R/help/project-name'>project-name</a> setting |
| 1460 | @ is used instead. |
| 1461 | @ (Property: "short-project-name") |
| 1462 | @ </p> |
| 1463 | @ <hr> |
| 1464 | @ <p><b>Configuration for the <a href="%R/download">/download</a> page.</b> |
| 1465 | @ <p>The value is a TCL list divided into groups of four tokens: |
| 1466 | @ <ol> |
| 1467 | @ <li> Maximum number of matches (COUNT). |
| 1468 | @ <li> Tag to match using glob (TAG). |
| 1469 | @ <li> Maximum age of check-ins to match (MAX_AGE). |
| 1470 | @ <li> Comment to apply to matches (COMMENT). |
| 1471 | @ </ol> |
| 1472 | @ Each 4-tuple will match zero or more check-ins. The /download page |
| 1473 | @ displays the union of matches from all 4-tuples. |
| 1474 | @ See the <a href="%R/help/suggested-downloads">suggested-downloads</a> |
| 1475 | @ setting documentation for further detail. |
| 1476 | @ <p> |
| 1477 | @ The /download page is omitted from the <a href="%R/sitemap">/sitemap</a> |
| 1478 | @ if the first token is "0" or "off" or "no". The default value |
| 1479 | @ for this setting is "off". |
| 1480 | @ (Property: <a href="%R/help/suggested-downloads">suggested-downloads</a>) |
| 1481 | @ <p> |
| 1482 | textarea_attribute("", 4, 80, |
| 1483 | "suggested-downloads", "sgtrlst", "off", 0); |
| 1484 | @ <hr> |
| 1485 | @ <p><input type="submit" name="submit" value="Apply Changes"></p> |
| 1486 | @ </div></form> |
| 1487 | db_end_transaction(0); |
| 1488 | style_finish_page(); |
| 1489 |
+2
-2
| --- src/sitemap.c | ||
| +++ src/sitemap.c | ||
| @@ -133,12 +133,12 @@ | ||
| 133 | 133 | if( g.perm.Write && zEditGlob[0]!=0 ){ |
| 134 | 134 | @ <li>%z(href("%R/fileedit"))On-line File Editor</li> |
| 135 | 135 | } |
| 136 | 136 | @ </ul> |
| 137 | 137 | } |
| 138 | - if( g.perm.Zip && db_get_boolean("suggested-tarlist",1)!=0 ){ | |
| 139 | - @ <li>%z(href("%R/tarlist"))Tarballs and ZIPs</a> | |
| 138 | + if( g.perm.Zip && db_get_boolean("suggested-downloads",0)!=0 ){ | |
| 139 | + @ <li>%z(href("%R/download"))Tarballs and ZIPs</a> | |
| 140 | 140 | } |
| 141 | 141 | if( g.perm.Read ){ |
| 142 | 142 | @ <li>%z(href("%R/timeline"))Project Timeline</a> |
| 143 | 143 | @ <ul> |
| 144 | 144 | @ <li>%z(href("%R/reports"))Activity Reports</a></li> |
| 145 | 145 |
| --- src/sitemap.c | |
| +++ src/sitemap.c | |
| @@ -133,12 +133,12 @@ | |
| 133 | if( g.perm.Write && zEditGlob[0]!=0 ){ |
| 134 | @ <li>%z(href("%R/fileedit"))On-line File Editor</li> |
| 135 | } |
| 136 | @ </ul> |
| 137 | } |
| 138 | if( g.perm.Zip && db_get_boolean("suggested-tarlist",1)!=0 ){ |
| 139 | @ <li>%z(href("%R/tarlist"))Tarballs and ZIPs</a> |
| 140 | } |
| 141 | if( g.perm.Read ){ |
| 142 | @ <li>%z(href("%R/timeline"))Project Timeline</a> |
| 143 | @ <ul> |
| 144 | @ <li>%z(href("%R/reports"))Activity Reports</a></li> |
| 145 |
| --- src/sitemap.c | |
| +++ src/sitemap.c | |
| @@ -133,12 +133,12 @@ | |
| 133 | if( g.perm.Write && zEditGlob[0]!=0 ){ |
| 134 | @ <li>%z(href("%R/fileedit"))On-line File Editor</li> |
| 135 | } |
| 136 | @ </ul> |
| 137 | } |
| 138 | if( g.perm.Zip && db_get_boolean("suggested-downloads",0)!=0 ){ |
| 139 | @ <li>%z(href("%R/download"))Tarballs and ZIPs</a> |
| 140 | } |
| 141 | if( g.perm.Read ){ |
| 142 | @ <li>%z(href("%R/timeline"))Project Timeline</a> |
| 143 | @ <ul> |
| 144 | @ <li>%z(href("%R/reports"))Activity Reports</a></li> |
| 145 |
+350
-76
| --- src/tar.c | ||
| +++ src/tar.c | ||
| @@ -31,10 +31,65 @@ | ||
| 31 | 31 | char *zPrevDir; /* Name of directory for previous entry */ |
| 32 | 32 | int nPrevDirAlloc; /* size of zPrevDir */ |
| 33 | 33 | Blob pax; /* PAX data */ |
| 34 | 34 | } tball; |
| 35 | 35 | |
| 36 | +/* | |
| 37 | +** Convert a string so that it contains only lower-case ASCII, digits, | |
| 38 | +** "_" and "-". Changes are made in-place. | |
| 39 | +*/ | |
| 40 | +static void sanitize_name(char *zName){ | |
| 41 | + int i; | |
| 42 | + char c; | |
| 43 | + for(i=0; (c = zName[i])!=0; i++){ | |
| 44 | + if( fossil_isupper(c) ){ | |
| 45 | + zName[i] = fossil_tolower(c); | |
| 46 | + }else if( !fossil_isalnum(c) && c!='_' && c!='-' ){ | |
| 47 | + if( c<=0x7f ){ | |
| 48 | + zName[i] = '_'; | |
| 49 | + }else{ | |
| 50 | + /* 123456789 123456789 123456 */ | |
| 51 | + zName[i] = "abcdefghijklmnopqrstuvwxyz"[(unsigned)c%26]; | |
| 52 | + } | |
| 53 | + } | |
| 54 | + } | |
| 55 | +} | |
| 56 | + | |
| 57 | +/* | |
| 58 | +** Compute a sensible base-name for an archive file (tarball, ZIP, or SQLAR) | |
| 59 | +** based on the rid of the check-in contained in that file. | |
| 60 | +** | |
| 61 | +** PROJECTNAME-DATETIME-HASHPREFIX | |
| 62 | +** | |
| 63 | +** So that the name will be safe to use as a URL or a filename on any system, | |
| 64 | +** the name is only allowed to contain lower-case ASCII alphabetics, | |
| 65 | +** digits, '_' and '-'. Upper-case ASCII is converted to lower-case. All | |
| 66 | +** other bytes are mapped into a lower-case alphabetic. | |
| 67 | +** | |
| 68 | +** The value returned is obtained from mprintf() or fossil_strdup() and should | |
| 69 | +** be released by the caller using fossil_free(). | |
| 70 | +*/ | |
| 71 | +char *archive_base_name(int rid){ | |
| 72 | + char *zPrefix; | |
| 73 | + char *zName; | |
| 74 | + zPrefix = db_get("short-project-name",0); | |
| 75 | + if( zPrefix==0 || zPrefix[0]==0 ){ | |
| 76 | + zPrefix = db_get("project-name","unnamed"); | |
| 77 | + } | |
| 78 | + zName = db_text(0, | |
| 79 | + "SELECT %Q||" | |
| 80 | + " strftime('-%%Y%%m%%d%%H%%M%%S-',event.mtime)||" | |
| 81 | + " substr(blob.uuid,1,10)" | |
| 82 | + " FROM blob, event LEFT JOIN config" | |
| 83 | + " WHERE blob.rid=%d" | |
| 84 | + " AND event.objid=%d" | |
| 85 | + " AND config.name='project-name'", | |
| 86 | + zPrefix, rid, rid); | |
| 87 | + fossil_free(zPrefix); | |
| 88 | + sanitize_name(zName); | |
| 89 | + return zName; | |
| 90 | +} | |
| 36 | 91 | |
| 37 | 92 | /* |
| 38 | 93 | ** field lengths of 'ustar' name and prefix fields. |
| 39 | 94 | */ |
| 40 | 95 | #define USTAR_NAME_LEN 100 |
| @@ -653,19 +708,11 @@ | ||
| 653 | 708 | if( fossil_strcmp("/dev/null",zOut)==0 || fossil_strcmp("",zOut)==0 ){ |
| 654 | 709 | zOut = 0; |
| 655 | 710 | } |
| 656 | 711 | |
| 657 | 712 | if( zName==0 ){ |
| 658 | - zName = db_text("default-name", | |
| 659 | - "SELECT replace(%Q,' ','_') " | |
| 660 | - " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) " | |
| 661 | - " || substr(blob.uuid, 1, 10)" | |
| 662 | - " FROM event, blob" | |
| 663 | - " WHERE event.objid=%d" | |
| 664 | - " AND blob.rid=%d", | |
| 665 | - db_get("project-name", "unnamed"), rid, rid | |
| 666 | - ); | |
| 713 | + zName = archive_base_name(rid); | |
| 667 | 714 | } |
| 668 | 715 | tarball_of_checkin(rid, zOut ? &tarball : 0, |
| 669 | 716 | zName, pInclude, pExclude, listFlag); |
| 670 | 717 | glob_free(pInclude); |
| 671 | 718 | glob_free(pExclude); |
| @@ -673,27 +720,76 @@ | ||
| 673 | 720 | if( zOut ){ |
| 674 | 721 | blob_write_to_file(&tarball, zOut); |
| 675 | 722 | blob_reset(&tarball); |
| 676 | 723 | } |
| 677 | 724 | } |
| 725 | + | |
| 726 | +/* | |
| 727 | +** This is a helper routine for tar_uuid_from_name(). It handles | |
| 728 | +** the case where *pzName contains no "/" character. Check for | |
| 729 | +** format (3). Return the hash if the name matches format (3), | |
| 730 | +** or return NULL if it does not. | |
| 731 | +*/ | |
| 732 | +static char *format_three_parser(const char *zName){ | |
| 733 | + int iDot = 0; /* Index in zName[] of the first '.' */ | |
| 734 | + int iDash1 = 0; /* Index in zName[] of the '-' before the timestamp */ | |
| 735 | + int iDash2 = 0; /* Index in zName[] of the '-' between timestamp and hash */ | |
| 736 | + int nHash; /* Size of the hash */ | |
| 737 | + char *zHash; /* A copy of the hash value */ | |
| 738 | + char *zDate; /* Copy of the timestamp */ | |
| 739 | + char *zUuid; /* Final result */ | |
| 740 | + int i; /* Loop query */ | |
| 741 | + Stmt q; /* Query to verify that hash and timestamp agree */ | |
| 742 | + | |
| 743 | + for(i=0; zName[i]; i++){ | |
| 744 | + char c = zName[i]; | |
| 745 | + if( c=='.' ){ iDot = i; break; } | |
| 746 | + if( c=='-' ){ iDash1 = iDash2; iDash2 = i; } | |
| 747 | + if( !fossil_isalnum(c) && c!='_' && c!='-' ){ break; } | |
| 748 | + } | |
| 749 | + if( iDot==0 ) return 0; | |
| 750 | + if( iDash1==0 ) return 0; | |
| 751 | + nHash = iDot - iDash2 - 1; | |
| 752 | + if( nHash<8 ) return 0; /* HASH value too short */ | |
| 753 | + if( (iDash2 - iDash1)!=15 ) return 0; /* Wrong timestamp size */ | |
| 754 | + zHash = fossil_strndup(&zName[iDash2+1], nHash); | |
| 755 | + zDate = fossil_strndup(&zName[iDash1+1], 14); | |
| 756 | + db_prepare(&q, | |
| 757 | + "SELECT blob.uuid" | |
| 758 | + " FROM blob JOIN event ON event.objid=blob.rid" | |
| 759 | + " WHERE blob.uuid GLOB '%q*'" | |
| 760 | + " AND strftime('%%Y%%m%%d%%H%%M%%S',event.mtime)='%q'", | |
| 761 | + zHash, zDate | |
| 762 | + ); | |
| 763 | + fossil_free(zHash); | |
| 764 | + fossil_free(zDate); | |
| 765 | + if( db_step(&q)==SQLITE_ROW ){ | |
| 766 | + zUuid = fossil_strdup(db_column_text(&q,0)); | |
| 767 | + }else{ | |
| 768 | + zUuid = 0; | |
| 769 | + } | |
| 770 | + db_finalize(&q); | |
| 771 | + return zUuid; | |
| 772 | +} | |
| 678 | 773 | |
| 679 | 774 | /* |
| 680 | 775 | ** Check to see if the input string is of one of the following |
| 681 | 776 | ** two the forms: |
| 682 | 777 | ** |
| 683 | 778 | ** check-in-name/filename.ext (1) |
| 684 | -** tag-name/check-in-name/filename.txt (2) | |
| 779 | +** tag-name/check-in-name/filename.ext (2) | |
| 780 | +** project-datetime-hash.ext (3) | |
| 685 | 781 | ** |
| 686 | 782 | ** In other words, check to see if the input string contains either |
| 687 | 783 | ** a check-in name or a tag-name and a check-in name separated by |
| 688 | -** a slash. There must be either 1 or 2 "/" characters. In the | |
| 784 | +** a slash. There must be between 0 or 2 "/" characters. In the | |
| 689 | 785 | ** second form, tag-name must be an individual tag (not a branch-tag) |
| 690 | 786 | ** that is found on the check-in identified by the check-in-name. |
| 691 | 787 | ** |
| 692 | 788 | ** If the condition is true, then: |
| 693 | 789 | ** |
| 694 | -** * Make *pzName point to the fielname suffix only | |
| 790 | +** * Make *pzName point to the filename suffix only | |
| 695 | 791 | ** * return a copy of the check-in name in memory from mprintf(). |
| 696 | 792 | ** |
| 697 | 793 | ** If the condition is false, leave *pzName unchanged and return either |
| 698 | 794 | ** NULL or an empty string. Normally NULL is returned, however an |
| 699 | 795 | ** empty string is returned for format (2) if check-in-name does not |
| @@ -705,10 +801,19 @@ | ||
| 705 | 801 | ** |
| 706 | 802 | ** Such URLs will pass through most anti-robot filters because of the |
| 707 | 803 | ** "/tarball/release" prefix will match the suggested "robot-exception" |
| 708 | 804 | ** pattern and can still refer to an historic release rather than just |
| 709 | 805 | ** the most recent release. |
| 806 | +** | |
| 807 | +** Format (3) is designed to allow URLs like this: | |
| 808 | +** | |
| 809 | +** /tarball/fossil-20251018193920-d6c9aee97df.tar.gz | |
| 810 | +** | |
| 811 | +** In other words, filename itself contains sufficient information to | |
| 812 | +** uniquely identify the check-in, including a timestamp of the form | |
| 813 | +** YYYYMMDDHHMMSS and a prefix of the check-in hash. The timestamp | |
| 814 | +** and hash must immediately preceed the first "." in the name. | |
| 710 | 815 | */ |
| 711 | 816 | char *tar_uuid_from_name(char **pzName){ |
| 712 | 817 | char *zName = *pzName; /* Original input */ |
| 713 | 818 | int n1 = 0; /* Bytes in first prefix (tag-name) */ |
| 714 | 819 | int n2 = 0; /* Bytes in second prefix (check-in-name) */ |
| @@ -724,11 +829,12 @@ | ||
| 724 | 829 | return 0; /* More than two "/" characters seen */ |
| 725 | 830 | } |
| 726 | 831 | } |
| 727 | 832 | } |
| 728 | 833 | if( n1==0 ){ |
| 729 | - return 0; /* No prefix of any kind */ | |
| 834 | + /* Check for format (3) */ | |
| 835 | + return format_three_parser(*pzName); | |
| 730 | 836 | } |
| 731 | 837 | if( zName[n+1]==0 ){ |
| 732 | 838 | return 0; /* No filename suffix */ |
| 733 | 839 | } |
| 734 | 840 | if( n2==0 ){ |
| @@ -933,142 +1039,310 @@ | ||
| 933 | 1039 | cgi_set_content(&tarball); |
| 934 | 1040 | cgi_set_content_type("application/x-compressed"); |
| 935 | 1041 | } |
| 936 | 1042 | |
| 937 | 1043 | /* |
| 938 | -** This routine is called for each check-in on the /tarlist page to | |
| 1044 | +** This routine is called for each check-in on the /download page to | |
| 939 | 1045 | ** construct the "extra" information after the description. |
| 940 | 1046 | */ |
| 941 | -static void tarlist_extra( | |
| 1047 | +void download_extra( | |
| 942 | 1048 | Stmt *pQuery, /* Current row of the timeline query */ |
| 943 | 1049 | int tmFlags, /* Flags to www_print_timeline() */ |
| 944 | 1050 | const char *zThisUser, /* Suppress links to this user */ |
| 945 | 1051 | const char *zThisTag /* Suppress links to this tag */ |
| 946 | 1052 | ){ |
| 947 | - int rid = db_column_int(pQuery, 0); | |
| 948 | - const char *zUuid = db_column_text(pQuery, 1); | |
| 949 | - const char *zDate = db_column_text(pQuery, 2); | |
| 950 | - char *zBrName = branch_of_rid(rid); | |
| 951 | - static const char *zProject = 0; | |
| 952 | - int nProject; | |
| 953 | - char *zNm; | |
| 954 | - | |
| 955 | - if( zProject==0 ) zProject = db_get("project-name","unnamed"); | |
| 956 | - zNm = mprintf("%s-%sZ-%.8s", zProject, zDate, zUuid); | |
| 957 | - nProject = (int)strlen(zProject); | |
| 958 | - zNm[nProject+11] = 'T'; | |
| 959 | - @ <strong>%h(zBrName)</strong><br>\ | |
| 960 | - @ %z(href("%R/timeline?c=%!S&y=ci&n=11",zUuid))<button>Context</button></a> | |
| 961 | - @ %z(href("%R/tarball/%!S/%t.tar.gz",zUuid,zNm))\ | |
| 962 | - @ <button>Tarball</button></a> | |
| 963 | - @ %z(href("%R/zip/%!S/%t.zip",zUuid,zNm))\ | |
| 964 | - @ <button>ZIP Archive</button></a> | |
| 965 | - fossil_free(zBrName); | |
| 966 | - fossil_free(zNm); | |
| 967 | -} | |
| 968 | - | |
| 969 | -/* | |
| 970 | -** SETTING: suggested-tarlist width=70 block-text | |
| 971 | -** | |
| 972 | -** This setting controls the suggested tarball/ZIP downloads on the | |
| 973 | -** [[/tarlist]] page. The value is a TCL list. Each pair of items | |
| 974 | -** defines a set of check-ins to be added to the suggestion list. | |
| 975 | -** The first item of each pair is an integer count (N) and second | |
| 976 | -** item is a tag GLOB pattern (PATTERN). For each pair, the most | |
| 977 | -** recent N check-ins that have a tag matching PATTERN are added | |
| 978 | -** to the list. The special pattern "OPEN-LEAF" matches any open | |
| 979 | -** leaf check-in. | |
| 980 | -** | |
| 981 | -** Example: | |
| 982 | -** | |
| 983 | -** 3 OPEN-LEAF 3 release 1 trunk | |
| 984 | -** | |
| 985 | -** The value causes the /tarlist page to show the union of the 3 | |
| 986 | -** most recent open leaves, the three most recent check-ins marked | |
| 987 | -** "release", and the single most recent trunk check-in. | |
| 988 | -*/ | |
| 989 | - | |
| 990 | -/* | |
| 991 | -** WEBPAGE: /tarlist | |
| 1053 | + const char *zType = db_column_text(pQuery, 7); | |
| 1054 | + assert( zType!=0 ); | |
| 1055 | + if( zType[0]!='c' ){ | |
| 1056 | + timeline_extra(pQuery, tmFlags, zThisUser, zThisTag); | |
| 1057 | + }else{ | |
| 1058 | + int rid = db_column_int(pQuery, 0); | |
| 1059 | + const char *zUuid = db_column_text(pQuery, 1); | |
| 1060 | + char *zBrName = branch_of_rid(rid); | |
| 1061 | + char *zNm; | |
| 1062 | + | |
| 1063 | + if( tmFlags & TIMELINE_COLUMNAR ){ | |
| 1064 | + @ <nobr>check-in: \ | |
| 1065 | + @ %z(href("%R/info/%!S",zUuid))<span class='timelineHash'>\ | |
| 1066 | + @ %S(zUuid)</span></a></nobr><br> | |
| 1067 | + if( fossil_strcmp(zBrName,"trunk")!=0 ){ | |
| 1068 | + @ <nobr>branch: \ | |
| 1069 | + @ %z(href("%R/timeline?r=%t",zBrName))%h(zBrName)</a></nobr><br>\ | |
| 1070 | + } | |
| 1071 | + }else{ | |
| 1072 | + if( (tmFlags & TIMELINE_CLASSIC)==0 ){ | |
| 1073 | + @ check-in: %z(href("%R/info/%!S",zUuid))\ | |
| 1074 | + @ <span class='timelineHash'>%S(zUuid)</span></a> | |
| 1075 | + } | |
| 1076 | + if( (tmFlags & TIMELINE_GRAPH)==0 && fossil_strcmp(zBrName,"trunk")!=0 ){ | |
| 1077 | + @ branch: \ | |
| 1078 | + @ %z(href("%R/timeline?r=%t",zBrName))%h(zBrName)</a> | |
| 1079 | + } | |
| 1080 | + } | |
| 1081 | + zNm = archive_base_name(rid); | |
| 1082 | + @ %z(href("%R/tarball/%s.tar.gz",zNm))\ | |
| 1083 | + @ <button>Tarball</button></a> | |
| 1084 | + @ %z(href("%R/zip/%s.zip",zNm))\ | |
| 1085 | + @ <button>ZIP Archive</button></a> | |
| 1086 | + fossil_free(zBrName); | |
| 1087 | + fossil_free(zNm); | |
| 1088 | + } | |
| 1089 | +} | |
| 1090 | + | |
| 1091 | +/* | |
| 1092 | +** SETTING: suggested-downloads width=70 block-text | |
| 1093 | +** | |
| 1094 | +** This setting controls the suggested tarball/ZIP downloads on the | |
| 1095 | +** [[/download]] page. The value is a TCL list. Each set of four items | |
| 1096 | +** defines a set of check-ins to be added to the suggestion list. | |
| 1097 | +** The items in each group are: | |
| 1098 | +** | |
| 1099 | +** | COUNT TAG MAX_AGE COMMENT | |
| 1100 | +** | |
| 1101 | +** COUNT is the number of check-ins to match, starting with the most | |
| 1102 | +** recent and working bacwards in time. Check-ins match if they contain | |
| 1103 | +** the tag TAG. If MAX_AGE is not an empty string, then it specifies | |
| 1104 | +** the maximum age of any matching check-in. COMMENT is an optional | |
| 1105 | +** comment for each match. | |
| 1106 | +** | |
| 1107 | +** The special value of "OPEN-LEAF" for TAG matches any check-in that | |
| 1108 | +** is an open leaf. | |
| 1109 | +** | |
| 1110 | +** MAX_AGE is of the form "{AMT UNITS}" where AMT is a floating point | |
| 1111 | +** value and UNITS is one of "seconds", "hours", "days", "weeks", "months", | |
| 1112 | +** or "years". If MAX_AGE is an empty string then there is no age limit. | |
| 1113 | +** | |
| 1114 | +** If COMMENT is not an empty string, then it is an additional comment | |
| 1115 | +** added to the output description of the suggested download. The idea of | |
| 1116 | +** COMMENT is to explain to the reader why a check-in is a suggested | |
| 1117 | +** download. | |
| 1118 | +** | |
| 1119 | +** Example: | |
| 1120 | +** | |
| 1121 | +** | 1 trunk {} {Latest Trunk Check-in} | |
| 1122 | +** | 5 OPEN-LEAF {1 month} {Active Branch} | |
| 1123 | +** | 999 release {1 year} {Official Release} | |
| 1124 | +** | |
| 1125 | +** The value causes the /download page to show the union of the most | |
| 1126 | +** recent trunk check-in of any age, the five most recent | |
| 1127 | +** open leaves within the past month, and essentially | |
| 1128 | +** all releases within the past year. If the same check-in matches more | |
| 1129 | +** than one rule, the COMMENT of the first match is used. | |
| 1130 | +*/ | |
| 1131 | + | |
| 1132 | +/* | |
| 1133 | +** WEBPAGE: /download | |
| 992 | 1134 | ** |
| 993 | 1135 | ** Show a special no-graph timeline of recent important check-ins with |
| 994 | 1136 | ** an opportunity to pull tarballs and ZIPs. |
| 995 | 1137 | */ |
| 996 | -void tarlist_page(void){ | |
| 1138 | +void download_page(void){ | |
| 997 | 1139 | Stmt q; /* The actual timeline query */ |
| 998 | 1140 | const char *zTarlistCfg; /* Configuration string */ |
| 999 | 1141 | char **azItem; /* Decomposed elements of zTarlistCfg */ |
| 1000 | 1142 | int *anItem; /* Bytes in each term of azItem[] */ |
| 1001 | 1143 | int nItem; /* Number of terms in azItem[] */ |
| 1002 | 1144 | int i; /* Loop counter */ |
| 1003 | 1145 | int tmFlags; /* Timeline display flags */ |
| 1004 | 1146 | int n; /* Number of suggested downloads */ |
| 1147 | + double rNow; /* Current time. Julian day number */ | |
| 1148 | + int bPlainTextCom; /* Use plain-text comments */ | |
| 1005 | 1149 | |
| 1006 | 1150 | login_check_credentials(); |
| 1007 | 1151 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 1008 | 1152 | |
| 1009 | 1153 | style_set_current_feature("timeline"); |
| 1010 | - style_header("Suggested Tarballs And ZIP Archives"); | |
| 1154 | + style_header("Suggested Downloads"); | |
| 1011 | 1155 | |
| 1012 | - zTarlistCfg = db_get("suggested-tarlist","1 trunk"); | |
| 1156 | + zTarlistCfg = db_get("suggested-downloads","off"); | |
| 1013 | 1157 | db_multi_exec( |
| 1014 | - "CREATE TEMP TABLE tarlist(rid INTEGER PRIMARY KEY);" | |
| 1158 | + "CREATE TEMP TABLE tarlist(rid INTEGER PRIMARY KEY, com TEXT);" | |
| 1015 | 1159 | ); |
| 1160 | + rNow = db_double(0.0,"SELECT julianday()"); | |
| 1016 | 1161 | if( !g.interp ) Th_FossilInit(0); |
| 1017 | 1162 | Th_SplitList(g.interp, zTarlistCfg, (int)strlen(zTarlistCfg), |
| 1018 | 1163 | &azItem, &anItem, &nItem); |
| 1019 | - for(i=0; i<nItem-1; i+=2){ | |
| 1020 | - int cnt; | |
| 1021 | - char *zLabel; | |
| 1164 | + bPlainTextCom = db_get_boolean("timeline-plaintext",0); | |
| 1165 | + for(i=0; i<nItem-3; i+=4){ | |
| 1166 | + int cnt; /* The number of instances of zLabel to use */ | |
| 1167 | + char *zLabel; /* The label to match */ | |
| 1168 | + double rStart; /* Starting time, julian day number */ | |
| 1169 | + char *zComment = 0; /* Comment to apply */ | |
| 1022 | 1170 | if( anItem[i]==1 && azItem[i][0]=='*' ){ |
| 1023 | 1171 | cnt = -1; |
| 1024 | 1172 | }else if( anItem[i]<1 ){ |
| 1025 | 1173 | cnt = 0; |
| 1026 | 1174 | }else{ |
| 1027 | 1175 | cnt = atoi(azItem[i]); |
| 1028 | 1176 | } |
| 1029 | 1177 | if( cnt==0 ) continue; |
| 1030 | 1178 | zLabel = fossil_strndup(azItem[i+1],anItem[i+1]); |
| 1179 | + if( anItem[i+2]==0 ){ | |
| 1180 | + rStart = 0.0; | |
| 1181 | + }else{ | |
| 1182 | + char *zMax = fossil_strndup(azItem[i+2], anItem[i+2]); | |
| 1183 | + double r = atof(zMax); | |
| 1184 | + if( strstr(zMax,"sec") ){ | |
| 1185 | + rStart = rNow - r/86400.0; | |
| 1186 | + }else | |
| 1187 | + if( strstr(zMax,"hou") ){ | |
| 1188 | + rStart = rNow - r/24.0; | |
| 1189 | + }else | |
| 1190 | + if( strstr(zMax,"da") ){ | |
| 1191 | + rStart = rNow - r; | |
| 1192 | + }else | |
| 1193 | + if( strstr(zMax,"wee") ){ | |
| 1194 | + rStart = rNow - r*7.0; | |
| 1195 | + }else | |
| 1196 | + if( strstr(zMax,"mon") ){ | |
| 1197 | + rStart = rNow - r*30.44; | |
| 1198 | + }else | |
| 1199 | + if( strstr(zMax,"yea") ){ | |
| 1200 | + rStart = rNow - r*365.24; | |
| 1201 | + }else | |
| 1202 | + { /* Default to seconds */ | |
| 1203 | + rStart = rNow - r/86400.0; | |
| 1204 | + } | |
| 1205 | + } | |
| 1206 | + if( anItem[i+3]==0 ){ | |
| 1207 | + zComment = fossil_strdup(""); | |
| 1208 | + }else if( bPlainTextCom ){ | |
| 1209 | + zComment = mprintf("** %.*s ** ", anItem[i+3], azItem[i+3]); | |
| 1210 | + }else{ | |
| 1211 | + zComment = mprintf("<b>%.*s</b>\n<p>", anItem[i+3], azItem[i+3]); | |
| 1212 | + } | |
| 1031 | 1213 | if( fossil_strcmp("OPEN-LEAF",zLabel)==0 ){ |
| 1032 | 1214 | db_multi_exec( |
| 1033 | - "INSERT OR IGNORE INTO tarlist(rid)" | |
| 1034 | - " SELECT leaf.rid FROM leaf, event" | |
| 1215 | + "INSERT OR IGNORE INTO tarlist(rid,com)" | |
| 1216 | + " SELECT leaf.rid, %Q FROM leaf, event" | |
| 1035 | 1217 | " WHERE event.objid=leaf.rid" |
| 1218 | + " AND event.mtime>=%.6f" | |
| 1036 | 1219 | " AND NOT EXISTS(SELECT 1 FROM tagxref" |
| 1037 | 1220 | " WHERE tagxref.rid=leaf.rid" |
| 1038 | 1221 | " AND tagid=%d AND tagtype>0)" |
| 1039 | - " ORDER BY event.mtime DESC LIMIT %d", TAG_CLOSED, cnt | |
| 1222 | + " ORDER BY event.mtime DESC LIMIT %d", | |
| 1223 | + zComment, rStart, TAG_CLOSED, cnt | |
| 1040 | 1224 | ); |
| 1041 | 1225 | }else{ |
| 1042 | 1226 | db_multi_exec( |
| 1043 | 1227 | "WITH taglist(tid) AS" |
| 1044 | 1228 | " (SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q')" |
| 1045 | - "INSERT OR IGNORE INTO tarlist(rid)" | |
| 1046 | - " SELECT event.objid FROM event CROSS JOIN tagxref" | |
| 1229 | + "INSERT OR IGNORE INTO tarlist(rid,com)" | |
| 1230 | + " SELECT event.objid, %Q FROM event CROSS JOIN tagxref" | |
| 1047 | 1231 | " WHERE event.type='ci'" |
| 1232 | + " AND event.mtime>=%.6f" | |
| 1048 | 1233 | " AND tagxref.tagid IN taglist" |
| 1049 | 1234 | " AND tagtype>0" |
| 1050 | 1235 | " AND tagxref.rid=event.objid" |
| 1051 | - " ORDER BY event.mtime DESC LIMIT %d", zLabel, cnt | |
| 1236 | + " ORDER BY event.mtime DESC LIMIT %d", | |
| 1237 | + zLabel, zComment, rStart, cnt | |
| 1052 | 1238 | ); |
| 1053 | 1239 | } |
| 1054 | 1240 | fossil_free(zLabel); |
| 1241 | + fossil_free(zComment); | |
| 1055 | 1242 | } |
| 1056 | 1243 | Th_Free(g.interp, azItem); |
| 1057 | 1244 | |
| 1058 | 1245 | n = db_int(0, "SELECT count(*) FROM tarlist"); |
| 1059 | 1246 | if( n==0 ){ |
| 1060 | 1247 | @ <h2>No tarball/ZIP suggestions are available at this time</h2> |
| 1061 | 1248 | }else{ |
| 1062 | - @ <h2>%d(n) Tarball/ZIP Download Suggestions:</h2> | |
| 1249 | + @ <h2>%d(n) Tarball/ZIP Download Suggestion%s(n>1?"s":""):</h2> | |
| 1063 | 1250 | db_prepare(&q, |
| 1064 | - "%s AND blob.rid IN tarlist ORDER BY event.mtime DESC", | |
| 1251 | + "WITH matches AS (%s AND blob.rid IN (SELECT rid FROM tarlist))\n" | |
| 1252 | + "SELECT blobRid, uuid, timestamp," | |
| 1253 | + " com||comment," | |
| 1254 | + " user, leaf, bgColor, eventType, tags, tagid, brief, mtime" | |
| 1255 | + " FROM matches JOIN tarlist ON tarlist.rid=blobRid" | |
| 1256 | + " ORDER BY matches.mtime DESC", | |
| 1065 | 1257 | timeline_query_for_www() |
| 1066 | 1258 | ); |
| 1067 | 1259 | |
| 1068 | 1260 | tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL | TIMELINE_COLUMNAR |
| 1069 | 1261 | | TIMELINE_BRCOLOR; |
| 1070 | - www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, tarlist_extra); | |
| 1262 | + www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, download_extra); | |
| 1071 | 1263 | db_finalize(&q); |
| 1072 | 1264 | } |
| 1265 | + if( g.perm.Clone ){ | |
| 1266 | + char *zNm = fossil_strdup(db_get("project-name","clone")); | |
| 1267 | + sanitize_name(zNm); | |
| 1268 | + @ <hr> | |
| 1269 | + @ <h2>You Can Clone This Repository</h2> | |
| 1270 | + @ | |
| 1271 | + @ <p>Clone this repository by running a command similar to the following: | |
| 1272 | + @ <blockquote><pre> | |
| 1273 | + @ fossil clone %s(g.zBaseURL) %h(zNm).fossil | |
| 1274 | + @ </pre></blockquote> | |
| 1275 | + @ <p>A clone gives you local access to all historical content. | |
| 1276 | + @ Cloning is a bandwidth- and CPU-efficient alternative to extracting | |
| 1277 | + @ multiple tarballs and ZIPs. | |
| 1278 | + @ Do a web search for "fossil clone" or similar to find additional | |
| 1279 | + @ information about using a cloned Fossil repository. Or ask your | |
| 1280 | + @ favorite AI how to extract content from a Fossil clone. | |
| 1281 | + fossil_free(zNm); | |
| 1282 | + } | |
| 1283 | + | |
| 1284 | + style_finish_page(); | |
| 1285 | +} | |
| 1286 | + | |
| 1287 | +/* | |
| 1288 | +** WEBPAGE: rchvdwnld | |
| 1289 | +** | |
| 1290 | +** Short for "archive download". This page should have a single name= | |
| 1291 | +** query parameter that is a check-in hash. It present a menu of possible | |
| 1292 | +** download options for that check-in, including tarball, ZIP, or SQLAR. | |
| 1293 | +** | |
| 1294 | +** This is a utility page. The /dir and /tree pages sometimes have a | |
| 1295 | +** "Download" option in their submenu which redirects here. Those pages | |
| 1296 | +** used to have separate "Tarball" and "ZIP" submenu entries, but as | |
| 1297 | +** submenu entries appear in alphabetical order, that caused the two | |
| 1298 | +** submenu entries to be separated from one another, which is distracting. | |
| 1299 | +*/ | |
| 1300 | +void rchvdwnld_page(void){ | |
| 1301 | + const char *zUuid; | |
| 1302 | + char *zBase; | |
| 1303 | + int nUuid; | |
| 1304 | + int rid; | |
| 1305 | + login_check_credentials(); | |
| 1306 | + if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } | |
| 1307 | + if( robot_restrict("zip") || robot_restrict("download") ) return; | |
| 1308 | + | |
| 1309 | + zUuid = P("name"); | |
| 1310 | + if( zUuid==0 | |
| 1311 | + || (nUuid = (int)strlen(zUuid))<6 | |
| 1312 | + || !validate16(zUuid,-1) | |
| 1313 | + || (rid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUuid))==0 | |
| 1314 | + || !db_exists("SELECT 1 from event WHERE type='ci' AND objid=%d",rid) | |
| 1315 | + ){ | |
| 1316 | + fossil_redirect_home(); | |
| 1317 | + } | |
| 1318 | + zUuid = db_text(zUuid, "SELECT uuid FROM blob WHERE rid=%d", rid); | |
| 1319 | + style_header("Downloads For Check-in %!S", zUuid); | |
| 1320 | + zBase = archive_base_name(rid); | |
| 1321 | + @ <div class="section accordion">Downloads for check-in \ | |
| 1322 | + @ %z(href("%R/info/%!S",zUuid))%S(zUuid)</a></div> | |
| 1323 | + @ <div class="accordion_panel"> | |
| 1324 | + @ <table class="label-value"> | |
| 1325 | + @ <tr> | |
| 1326 | + @ <th>Tarball:</th> | |
| 1327 | + @ <td>%z(href("%R/tarball/%s.tar.gz",zBase))\ | |
| 1328 | + @ %s(g.zBaseURL)/tarball/%s(zBase).tar.gz</a></td> | |
| 1329 | + @ </tr> | |
| 1330 | + @ | |
| 1331 | + @ <tr> | |
| 1332 | + @ <th>ZIP:</th> | |
| 1333 | + @ <td>%z(href("%R/zip/%s.zip",zBase))\ | |
| 1334 | + @ %s(g.zBaseURL)/zip/%s(zBase).zip</a></td> | |
| 1335 | + @ </tr> | |
| 1336 | + @ | |
| 1337 | + @ <tr> | |
| 1338 | + @ <th>SQLAR:</th> | |
| 1339 | + @ <td>%z(href("%R/sqlar/%s.sqlar",zBase))\ | |
| 1340 | + @ %s(g.zBaseURL)/sqlar/%s(zBase).sqlar</a></td> | |
| 1341 | + @ </tr> | |
| 1342 | + @ </table></div> | |
| 1343 | + fossil_free(zBase); | |
| 1344 | + @ <div class="section accordion">Context</div><div class="accordion_panel"> | |
| 1345 | + render_checkin_context(rid, 0, 0, 0); | |
| 1346 | + @ </div> | |
| 1073 | 1347 | style_finish_page(); |
| 1074 | 1348 | } |
| 1075 | 1349 |
| --- src/tar.c | |
| +++ src/tar.c | |
| @@ -31,10 +31,65 @@ | |
| 31 | char *zPrevDir; /* Name of directory for previous entry */ |
| 32 | int nPrevDirAlloc; /* size of zPrevDir */ |
| 33 | Blob pax; /* PAX data */ |
| 34 | } tball; |
| 35 | |
| 36 | |
| 37 | /* |
| 38 | ** field lengths of 'ustar' name and prefix fields. |
| 39 | */ |
| 40 | #define USTAR_NAME_LEN 100 |
| @@ -653,19 +708,11 @@ | |
| 653 | if( fossil_strcmp("/dev/null",zOut)==0 || fossil_strcmp("",zOut)==0 ){ |
| 654 | zOut = 0; |
| 655 | } |
| 656 | |
| 657 | if( zName==0 ){ |
| 658 | zName = db_text("default-name", |
| 659 | "SELECT replace(%Q,' ','_') " |
| 660 | " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) " |
| 661 | " || substr(blob.uuid, 1, 10)" |
| 662 | " FROM event, blob" |
| 663 | " WHERE event.objid=%d" |
| 664 | " AND blob.rid=%d", |
| 665 | db_get("project-name", "unnamed"), rid, rid |
| 666 | ); |
| 667 | } |
| 668 | tarball_of_checkin(rid, zOut ? &tarball : 0, |
| 669 | zName, pInclude, pExclude, listFlag); |
| 670 | glob_free(pInclude); |
| 671 | glob_free(pExclude); |
| @@ -673,27 +720,76 @@ | |
| 673 | if( zOut ){ |
| 674 | blob_write_to_file(&tarball, zOut); |
| 675 | blob_reset(&tarball); |
| 676 | } |
| 677 | } |
| 678 | |
| 679 | /* |
| 680 | ** Check to see if the input string is of one of the following |
| 681 | ** two the forms: |
| 682 | ** |
| 683 | ** check-in-name/filename.ext (1) |
| 684 | ** tag-name/check-in-name/filename.txt (2) |
| 685 | ** |
| 686 | ** In other words, check to see if the input string contains either |
| 687 | ** a check-in name or a tag-name and a check-in name separated by |
| 688 | ** a slash. There must be either 1 or 2 "/" characters. In the |
| 689 | ** second form, tag-name must be an individual tag (not a branch-tag) |
| 690 | ** that is found on the check-in identified by the check-in-name. |
| 691 | ** |
| 692 | ** If the condition is true, then: |
| 693 | ** |
| 694 | ** * Make *pzName point to the fielname suffix only |
| 695 | ** * return a copy of the check-in name in memory from mprintf(). |
| 696 | ** |
| 697 | ** If the condition is false, leave *pzName unchanged and return either |
| 698 | ** NULL or an empty string. Normally NULL is returned, however an |
| 699 | ** empty string is returned for format (2) if check-in-name does not |
| @@ -705,10 +801,19 @@ | |
| 705 | ** |
| 706 | ** Such URLs will pass through most anti-robot filters because of the |
| 707 | ** "/tarball/release" prefix will match the suggested "robot-exception" |
| 708 | ** pattern and can still refer to an historic release rather than just |
| 709 | ** the most recent release. |
| 710 | */ |
| 711 | char *tar_uuid_from_name(char **pzName){ |
| 712 | char *zName = *pzName; /* Original input */ |
| 713 | int n1 = 0; /* Bytes in first prefix (tag-name) */ |
| 714 | int n2 = 0; /* Bytes in second prefix (check-in-name) */ |
| @@ -724,11 +829,12 @@ | |
| 724 | return 0; /* More than two "/" characters seen */ |
| 725 | } |
| 726 | } |
| 727 | } |
| 728 | if( n1==0 ){ |
| 729 | return 0; /* No prefix of any kind */ |
| 730 | } |
| 731 | if( zName[n+1]==0 ){ |
| 732 | return 0; /* No filename suffix */ |
| 733 | } |
| 734 | if( n2==0 ){ |
| @@ -933,142 +1039,310 @@ | |
| 933 | cgi_set_content(&tarball); |
| 934 | cgi_set_content_type("application/x-compressed"); |
| 935 | } |
| 936 | |
| 937 | /* |
| 938 | ** This routine is called for each check-in on the /tarlist page to |
| 939 | ** construct the "extra" information after the description. |
| 940 | */ |
| 941 | static void tarlist_extra( |
| 942 | Stmt *pQuery, /* Current row of the timeline query */ |
| 943 | int tmFlags, /* Flags to www_print_timeline() */ |
| 944 | const char *zThisUser, /* Suppress links to this user */ |
| 945 | const char *zThisTag /* Suppress links to this tag */ |
| 946 | ){ |
| 947 | int rid = db_column_int(pQuery, 0); |
| 948 | const char *zUuid = db_column_text(pQuery, 1); |
| 949 | const char *zDate = db_column_text(pQuery, 2); |
| 950 | char *zBrName = branch_of_rid(rid); |
| 951 | static const char *zProject = 0; |
| 952 | int nProject; |
| 953 | char *zNm; |
| 954 | |
| 955 | if( zProject==0 ) zProject = db_get("project-name","unnamed"); |
| 956 | zNm = mprintf("%s-%sZ-%.8s", zProject, zDate, zUuid); |
| 957 | nProject = (int)strlen(zProject); |
| 958 | zNm[nProject+11] = 'T'; |
| 959 | @ <strong>%h(zBrName)</strong><br>\ |
| 960 | @ %z(href("%R/timeline?c=%!S&y=ci&n=11",zUuid))<button>Context</button></a> |
| 961 | @ %z(href("%R/tarball/%!S/%t.tar.gz",zUuid,zNm))\ |
| 962 | @ <button>Tarball</button></a> |
| 963 | @ %z(href("%R/zip/%!S/%t.zip",zUuid,zNm))\ |
| 964 | @ <button>ZIP Archive</button></a> |
| 965 | fossil_free(zBrName); |
| 966 | fossil_free(zNm); |
| 967 | } |
| 968 | |
| 969 | /* |
| 970 | ** SETTING: suggested-tarlist width=70 block-text |
| 971 | ** |
| 972 | ** This setting controls the suggested tarball/ZIP downloads on the |
| 973 | ** [[/tarlist]] page. The value is a TCL list. Each pair of items |
| 974 | ** defines a set of check-ins to be added to the suggestion list. |
| 975 | ** The first item of each pair is an integer count (N) and second |
| 976 | ** item is a tag GLOB pattern (PATTERN). For each pair, the most |
| 977 | ** recent N check-ins that have a tag matching PATTERN are added |
| 978 | ** to the list. The special pattern "OPEN-LEAF" matches any open |
| 979 | ** leaf check-in. |
| 980 | ** |
| 981 | ** Example: |
| 982 | ** |
| 983 | ** 3 OPEN-LEAF 3 release 1 trunk |
| 984 | ** |
| 985 | ** The value causes the /tarlist page to show the union of the 3 |
| 986 | ** most recent open leaves, the three most recent check-ins marked |
| 987 | ** "release", and the single most recent trunk check-in. |
| 988 | */ |
| 989 | |
| 990 | /* |
| 991 | ** WEBPAGE: /tarlist |
| 992 | ** |
| 993 | ** Show a special no-graph timeline of recent important check-ins with |
| 994 | ** an opportunity to pull tarballs and ZIPs. |
| 995 | */ |
| 996 | void tarlist_page(void){ |
| 997 | Stmt q; /* The actual timeline query */ |
| 998 | const char *zTarlistCfg; /* Configuration string */ |
| 999 | char **azItem; /* Decomposed elements of zTarlistCfg */ |
| 1000 | int *anItem; /* Bytes in each term of azItem[] */ |
| 1001 | int nItem; /* Number of terms in azItem[] */ |
| 1002 | int i; /* Loop counter */ |
| 1003 | int tmFlags; /* Timeline display flags */ |
| 1004 | int n; /* Number of suggested downloads */ |
| 1005 | |
| 1006 | login_check_credentials(); |
| 1007 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 1008 | |
| 1009 | style_set_current_feature("timeline"); |
| 1010 | style_header("Suggested Tarballs And ZIP Archives"); |
| 1011 | |
| 1012 | zTarlistCfg = db_get("suggested-tarlist","1 trunk"); |
| 1013 | db_multi_exec( |
| 1014 | "CREATE TEMP TABLE tarlist(rid INTEGER PRIMARY KEY);" |
| 1015 | ); |
| 1016 | if( !g.interp ) Th_FossilInit(0); |
| 1017 | Th_SplitList(g.interp, zTarlistCfg, (int)strlen(zTarlistCfg), |
| 1018 | &azItem, &anItem, &nItem); |
| 1019 | for(i=0; i<nItem-1; i+=2){ |
| 1020 | int cnt; |
| 1021 | char *zLabel; |
| 1022 | if( anItem[i]==1 && azItem[i][0]=='*' ){ |
| 1023 | cnt = -1; |
| 1024 | }else if( anItem[i]<1 ){ |
| 1025 | cnt = 0; |
| 1026 | }else{ |
| 1027 | cnt = atoi(azItem[i]); |
| 1028 | } |
| 1029 | if( cnt==0 ) continue; |
| 1030 | zLabel = fossil_strndup(azItem[i+1],anItem[i+1]); |
| 1031 | if( fossil_strcmp("OPEN-LEAF",zLabel)==0 ){ |
| 1032 | db_multi_exec( |
| 1033 | "INSERT OR IGNORE INTO tarlist(rid)" |
| 1034 | " SELECT leaf.rid FROM leaf, event" |
| 1035 | " WHERE event.objid=leaf.rid" |
| 1036 | " AND NOT EXISTS(SELECT 1 FROM tagxref" |
| 1037 | " WHERE tagxref.rid=leaf.rid" |
| 1038 | " AND tagid=%d AND tagtype>0)" |
| 1039 | " ORDER BY event.mtime DESC LIMIT %d", TAG_CLOSED, cnt |
| 1040 | ); |
| 1041 | }else{ |
| 1042 | db_multi_exec( |
| 1043 | "WITH taglist(tid) AS" |
| 1044 | " (SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q')" |
| 1045 | "INSERT OR IGNORE INTO tarlist(rid)" |
| 1046 | " SELECT event.objid FROM event CROSS JOIN tagxref" |
| 1047 | " WHERE event.type='ci'" |
| 1048 | " AND tagxref.tagid IN taglist" |
| 1049 | " AND tagtype>0" |
| 1050 | " AND tagxref.rid=event.objid" |
| 1051 | " ORDER BY event.mtime DESC LIMIT %d", zLabel, cnt |
| 1052 | ); |
| 1053 | } |
| 1054 | fossil_free(zLabel); |
| 1055 | } |
| 1056 | Th_Free(g.interp, azItem); |
| 1057 | |
| 1058 | n = db_int(0, "SELECT count(*) FROM tarlist"); |
| 1059 | if( n==0 ){ |
| 1060 | @ <h2>No tarball/ZIP suggestions are available at this time</h2> |
| 1061 | }else{ |
| 1062 | @ <h2>%d(n) Tarball/ZIP Download Suggestions:</h2> |
| 1063 | db_prepare(&q, |
| 1064 | "%s AND blob.rid IN tarlist ORDER BY event.mtime DESC", |
| 1065 | timeline_query_for_www() |
| 1066 | ); |
| 1067 | |
| 1068 | tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL | TIMELINE_COLUMNAR |
| 1069 | | TIMELINE_BRCOLOR; |
| 1070 | www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, tarlist_extra); |
| 1071 | db_finalize(&q); |
| 1072 | } |
| 1073 | style_finish_page(); |
| 1074 | } |
| 1075 |
| --- src/tar.c | |
| +++ src/tar.c | |
| @@ -31,10 +31,65 @@ | |
| 31 | char *zPrevDir; /* Name of directory for previous entry */ |
| 32 | int nPrevDirAlloc; /* size of zPrevDir */ |
| 33 | Blob pax; /* PAX data */ |
| 34 | } tball; |
| 35 | |
| 36 | /* |
| 37 | ** Convert a string so that it contains only lower-case ASCII, digits, |
| 38 | ** "_" and "-". Changes are made in-place. |
| 39 | */ |
| 40 | static void sanitize_name(char *zName){ |
| 41 | int i; |
| 42 | char c; |
| 43 | for(i=0; (c = zName[i])!=0; i++){ |
| 44 | if( fossil_isupper(c) ){ |
| 45 | zName[i] = fossil_tolower(c); |
| 46 | }else if( !fossil_isalnum(c) && c!='_' && c!='-' ){ |
| 47 | if( c<=0x7f ){ |
| 48 | zName[i] = '_'; |
| 49 | }else{ |
| 50 | /* 123456789 123456789 123456 */ |
| 51 | zName[i] = "abcdefghijklmnopqrstuvwxyz"[(unsigned)c%26]; |
| 52 | } |
| 53 | } |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | /* |
| 58 | ** Compute a sensible base-name for an archive file (tarball, ZIP, or SQLAR) |
| 59 | ** based on the rid of the check-in contained in that file. |
| 60 | ** |
| 61 | ** PROJECTNAME-DATETIME-HASHPREFIX |
| 62 | ** |
| 63 | ** So that the name will be safe to use as a URL or a filename on any system, |
| 64 | ** the name is only allowed to contain lower-case ASCII alphabetics, |
| 65 | ** digits, '_' and '-'. Upper-case ASCII is converted to lower-case. All |
| 66 | ** other bytes are mapped into a lower-case alphabetic. |
| 67 | ** |
| 68 | ** The value returned is obtained from mprintf() or fossil_strdup() and should |
| 69 | ** be released by the caller using fossil_free(). |
| 70 | */ |
| 71 | char *archive_base_name(int rid){ |
| 72 | char *zPrefix; |
| 73 | char *zName; |
| 74 | zPrefix = db_get("short-project-name",0); |
| 75 | if( zPrefix==0 || zPrefix[0]==0 ){ |
| 76 | zPrefix = db_get("project-name","unnamed"); |
| 77 | } |
| 78 | zName = db_text(0, |
| 79 | "SELECT %Q||" |
| 80 | " strftime('-%%Y%%m%%d%%H%%M%%S-',event.mtime)||" |
| 81 | " substr(blob.uuid,1,10)" |
| 82 | " FROM blob, event LEFT JOIN config" |
| 83 | " WHERE blob.rid=%d" |
| 84 | " AND event.objid=%d" |
| 85 | " AND config.name='project-name'", |
| 86 | zPrefix, rid, rid); |
| 87 | fossil_free(zPrefix); |
| 88 | sanitize_name(zName); |
| 89 | return zName; |
| 90 | } |
| 91 | |
| 92 | /* |
| 93 | ** field lengths of 'ustar' name and prefix fields. |
| 94 | */ |
| 95 | #define USTAR_NAME_LEN 100 |
| @@ -653,19 +708,11 @@ | |
| 708 | if( fossil_strcmp("/dev/null",zOut)==0 || fossil_strcmp("",zOut)==0 ){ |
| 709 | zOut = 0; |
| 710 | } |
| 711 | |
| 712 | if( zName==0 ){ |
| 713 | zName = archive_base_name(rid); |
| 714 | } |
| 715 | tarball_of_checkin(rid, zOut ? &tarball : 0, |
| 716 | zName, pInclude, pExclude, listFlag); |
| 717 | glob_free(pInclude); |
| 718 | glob_free(pExclude); |
| @@ -673,27 +720,76 @@ | |
| 720 | if( zOut ){ |
| 721 | blob_write_to_file(&tarball, zOut); |
| 722 | blob_reset(&tarball); |
| 723 | } |
| 724 | } |
| 725 | |
| 726 | /* |
| 727 | ** This is a helper routine for tar_uuid_from_name(). It handles |
| 728 | ** the case where *pzName contains no "/" character. Check for |
| 729 | ** format (3). Return the hash if the name matches format (3), |
| 730 | ** or return NULL if it does not. |
| 731 | */ |
| 732 | static char *format_three_parser(const char *zName){ |
| 733 | int iDot = 0; /* Index in zName[] of the first '.' */ |
| 734 | int iDash1 = 0; /* Index in zName[] of the '-' before the timestamp */ |
| 735 | int iDash2 = 0; /* Index in zName[] of the '-' between timestamp and hash */ |
| 736 | int nHash; /* Size of the hash */ |
| 737 | char *zHash; /* A copy of the hash value */ |
| 738 | char *zDate; /* Copy of the timestamp */ |
| 739 | char *zUuid; /* Final result */ |
| 740 | int i; /* Loop query */ |
| 741 | Stmt q; /* Query to verify that hash and timestamp agree */ |
| 742 | |
| 743 | for(i=0; zName[i]; i++){ |
| 744 | char c = zName[i]; |
| 745 | if( c=='.' ){ iDot = i; break; } |
| 746 | if( c=='-' ){ iDash1 = iDash2; iDash2 = i; } |
| 747 | if( !fossil_isalnum(c) && c!='_' && c!='-' ){ break; } |
| 748 | } |
| 749 | if( iDot==0 ) return 0; |
| 750 | if( iDash1==0 ) return 0; |
| 751 | nHash = iDot - iDash2 - 1; |
| 752 | if( nHash<8 ) return 0; /* HASH value too short */ |
| 753 | if( (iDash2 - iDash1)!=15 ) return 0; /* Wrong timestamp size */ |
| 754 | zHash = fossil_strndup(&zName[iDash2+1], nHash); |
| 755 | zDate = fossil_strndup(&zName[iDash1+1], 14); |
| 756 | db_prepare(&q, |
| 757 | "SELECT blob.uuid" |
| 758 | " FROM blob JOIN event ON event.objid=blob.rid" |
| 759 | " WHERE blob.uuid GLOB '%q*'" |
| 760 | " AND strftime('%%Y%%m%%d%%H%%M%%S',event.mtime)='%q'", |
| 761 | zHash, zDate |
| 762 | ); |
| 763 | fossil_free(zHash); |
| 764 | fossil_free(zDate); |
| 765 | if( db_step(&q)==SQLITE_ROW ){ |
| 766 | zUuid = fossil_strdup(db_column_text(&q,0)); |
| 767 | }else{ |
| 768 | zUuid = 0; |
| 769 | } |
| 770 | db_finalize(&q); |
| 771 | return zUuid; |
| 772 | } |
| 773 | |
| 774 | /* |
| 775 | ** Check to see if the input string is of one of the following |
| 776 | ** two the forms: |
| 777 | ** |
| 778 | ** check-in-name/filename.ext (1) |
| 779 | ** tag-name/check-in-name/filename.ext (2) |
| 780 | ** project-datetime-hash.ext (3) |
| 781 | ** |
| 782 | ** In other words, check to see if the input string contains either |
| 783 | ** a check-in name or a tag-name and a check-in name separated by |
| 784 | ** a slash. There must be between 0 or 2 "/" characters. In the |
| 785 | ** second form, tag-name must be an individual tag (not a branch-tag) |
| 786 | ** that is found on the check-in identified by the check-in-name. |
| 787 | ** |
| 788 | ** If the condition is true, then: |
| 789 | ** |
| 790 | ** * Make *pzName point to the filename suffix only |
| 791 | ** * return a copy of the check-in name in memory from mprintf(). |
| 792 | ** |
| 793 | ** If the condition is false, leave *pzName unchanged and return either |
| 794 | ** NULL or an empty string. Normally NULL is returned, however an |
| 795 | ** empty string is returned for format (2) if check-in-name does not |
| @@ -705,10 +801,19 @@ | |
| 801 | ** |
| 802 | ** Such URLs will pass through most anti-robot filters because of the |
| 803 | ** "/tarball/release" prefix will match the suggested "robot-exception" |
| 804 | ** pattern and can still refer to an historic release rather than just |
| 805 | ** the most recent release. |
| 806 | ** |
| 807 | ** Format (3) is designed to allow URLs like this: |
| 808 | ** |
| 809 | ** /tarball/fossil-20251018193920-d6c9aee97df.tar.gz |
| 810 | ** |
| 811 | ** In other words, filename itself contains sufficient information to |
| 812 | ** uniquely identify the check-in, including a timestamp of the form |
| 813 | ** YYYYMMDDHHMMSS and a prefix of the check-in hash. The timestamp |
| 814 | ** and hash must immediately preceed the first "." in the name. |
| 815 | */ |
| 816 | char *tar_uuid_from_name(char **pzName){ |
| 817 | char *zName = *pzName; /* Original input */ |
| 818 | int n1 = 0; /* Bytes in first prefix (tag-name) */ |
| 819 | int n2 = 0; /* Bytes in second prefix (check-in-name) */ |
| @@ -724,11 +829,12 @@ | |
| 829 | return 0; /* More than two "/" characters seen */ |
| 830 | } |
| 831 | } |
| 832 | } |
| 833 | if( n1==0 ){ |
| 834 | /* Check for format (3) */ |
| 835 | return format_three_parser(*pzName); |
| 836 | } |
| 837 | if( zName[n+1]==0 ){ |
| 838 | return 0; /* No filename suffix */ |
| 839 | } |
| 840 | if( n2==0 ){ |
| @@ -933,142 +1039,310 @@ | |
| 1039 | cgi_set_content(&tarball); |
| 1040 | cgi_set_content_type("application/x-compressed"); |
| 1041 | } |
| 1042 | |
| 1043 | /* |
| 1044 | ** This routine is called for each check-in on the /download page to |
| 1045 | ** construct the "extra" information after the description. |
| 1046 | */ |
| 1047 | void download_extra( |
| 1048 | Stmt *pQuery, /* Current row of the timeline query */ |
| 1049 | int tmFlags, /* Flags to www_print_timeline() */ |
| 1050 | const char *zThisUser, /* Suppress links to this user */ |
| 1051 | const char *zThisTag /* Suppress links to this tag */ |
| 1052 | ){ |
| 1053 | const char *zType = db_column_text(pQuery, 7); |
| 1054 | assert( zType!=0 ); |
| 1055 | if( zType[0]!='c' ){ |
| 1056 | timeline_extra(pQuery, tmFlags, zThisUser, zThisTag); |
| 1057 | }else{ |
| 1058 | int rid = db_column_int(pQuery, 0); |
| 1059 | const char *zUuid = db_column_text(pQuery, 1); |
| 1060 | char *zBrName = branch_of_rid(rid); |
| 1061 | char *zNm; |
| 1062 | |
| 1063 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 1064 | @ <nobr>check-in: \ |
| 1065 | @ %z(href("%R/info/%!S",zUuid))<span class='timelineHash'>\ |
| 1066 | @ %S(zUuid)</span></a></nobr><br> |
| 1067 | if( fossil_strcmp(zBrName,"trunk")!=0 ){ |
| 1068 | @ <nobr>branch: \ |
| 1069 | @ %z(href("%R/timeline?r=%t",zBrName))%h(zBrName)</a></nobr><br>\ |
| 1070 | } |
| 1071 | }else{ |
| 1072 | if( (tmFlags & TIMELINE_CLASSIC)==0 ){ |
| 1073 | @ check-in: %z(href("%R/info/%!S",zUuid))\ |
| 1074 | @ <span class='timelineHash'>%S(zUuid)</span></a> |
| 1075 | } |
| 1076 | if( (tmFlags & TIMELINE_GRAPH)==0 && fossil_strcmp(zBrName,"trunk")!=0 ){ |
| 1077 | @ branch: \ |
| 1078 | @ %z(href("%R/timeline?r=%t",zBrName))%h(zBrName)</a> |
| 1079 | } |
| 1080 | } |
| 1081 | zNm = archive_base_name(rid); |
| 1082 | @ %z(href("%R/tarball/%s.tar.gz",zNm))\ |
| 1083 | @ <button>Tarball</button></a> |
| 1084 | @ %z(href("%R/zip/%s.zip",zNm))\ |
| 1085 | @ <button>ZIP Archive</button></a> |
| 1086 | fossil_free(zBrName); |
| 1087 | fossil_free(zNm); |
| 1088 | } |
| 1089 | } |
| 1090 | |
| 1091 | /* |
| 1092 | ** SETTING: suggested-downloads width=70 block-text |
| 1093 | ** |
| 1094 | ** This setting controls the suggested tarball/ZIP downloads on the |
| 1095 | ** [[/download]] page. The value is a TCL list. Each set of four items |
| 1096 | ** defines a set of check-ins to be added to the suggestion list. |
| 1097 | ** The items in each group are: |
| 1098 | ** |
| 1099 | ** | COUNT TAG MAX_AGE COMMENT |
| 1100 | ** |
| 1101 | ** COUNT is the number of check-ins to match, starting with the most |
| 1102 | ** recent and working bacwards in time. Check-ins match if they contain |
| 1103 | ** the tag TAG. If MAX_AGE is not an empty string, then it specifies |
| 1104 | ** the maximum age of any matching check-in. COMMENT is an optional |
| 1105 | ** comment for each match. |
| 1106 | ** |
| 1107 | ** The special value of "OPEN-LEAF" for TAG matches any check-in that |
| 1108 | ** is an open leaf. |
| 1109 | ** |
| 1110 | ** MAX_AGE is of the form "{AMT UNITS}" where AMT is a floating point |
| 1111 | ** value and UNITS is one of "seconds", "hours", "days", "weeks", "months", |
| 1112 | ** or "years". If MAX_AGE is an empty string then there is no age limit. |
| 1113 | ** |
| 1114 | ** If COMMENT is not an empty string, then it is an additional comment |
| 1115 | ** added to the output description of the suggested download. The idea of |
| 1116 | ** COMMENT is to explain to the reader why a check-in is a suggested |
| 1117 | ** download. |
| 1118 | ** |
| 1119 | ** Example: |
| 1120 | ** |
| 1121 | ** | 1 trunk {} {Latest Trunk Check-in} |
| 1122 | ** | 5 OPEN-LEAF {1 month} {Active Branch} |
| 1123 | ** | 999 release {1 year} {Official Release} |
| 1124 | ** |
| 1125 | ** The value causes the /download page to show the union of the most |
| 1126 | ** recent trunk check-in of any age, the five most recent |
| 1127 | ** open leaves within the past month, and essentially |
| 1128 | ** all releases within the past year. If the same check-in matches more |
| 1129 | ** than one rule, the COMMENT of the first match is used. |
| 1130 | */ |
| 1131 | |
| 1132 | /* |
| 1133 | ** WEBPAGE: /download |
| 1134 | ** |
| 1135 | ** Show a special no-graph timeline of recent important check-ins with |
| 1136 | ** an opportunity to pull tarballs and ZIPs. |
| 1137 | */ |
| 1138 | void download_page(void){ |
| 1139 | Stmt q; /* The actual timeline query */ |
| 1140 | const char *zTarlistCfg; /* Configuration string */ |
| 1141 | char **azItem; /* Decomposed elements of zTarlistCfg */ |
| 1142 | int *anItem; /* Bytes in each term of azItem[] */ |
| 1143 | int nItem; /* Number of terms in azItem[] */ |
| 1144 | int i; /* Loop counter */ |
| 1145 | int tmFlags; /* Timeline display flags */ |
| 1146 | int n; /* Number of suggested downloads */ |
| 1147 | double rNow; /* Current time. Julian day number */ |
| 1148 | int bPlainTextCom; /* Use plain-text comments */ |
| 1149 | |
| 1150 | login_check_credentials(); |
| 1151 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 1152 | |
| 1153 | style_set_current_feature("timeline"); |
| 1154 | style_header("Suggested Downloads"); |
| 1155 | |
| 1156 | zTarlistCfg = db_get("suggested-downloads","off"); |
| 1157 | db_multi_exec( |
| 1158 | "CREATE TEMP TABLE tarlist(rid INTEGER PRIMARY KEY, com TEXT);" |
| 1159 | ); |
| 1160 | rNow = db_double(0.0,"SELECT julianday()"); |
| 1161 | if( !g.interp ) Th_FossilInit(0); |
| 1162 | Th_SplitList(g.interp, zTarlistCfg, (int)strlen(zTarlistCfg), |
| 1163 | &azItem, &anItem, &nItem); |
| 1164 | bPlainTextCom = db_get_boolean("timeline-plaintext",0); |
| 1165 | for(i=0; i<nItem-3; i+=4){ |
| 1166 | int cnt; /* The number of instances of zLabel to use */ |
| 1167 | char *zLabel; /* The label to match */ |
| 1168 | double rStart; /* Starting time, julian day number */ |
| 1169 | char *zComment = 0; /* Comment to apply */ |
| 1170 | if( anItem[i]==1 && azItem[i][0]=='*' ){ |
| 1171 | cnt = -1; |
| 1172 | }else if( anItem[i]<1 ){ |
| 1173 | cnt = 0; |
| 1174 | }else{ |
| 1175 | cnt = atoi(azItem[i]); |
| 1176 | } |
| 1177 | if( cnt==0 ) continue; |
| 1178 | zLabel = fossil_strndup(azItem[i+1],anItem[i+1]); |
| 1179 | if( anItem[i+2]==0 ){ |
| 1180 | rStart = 0.0; |
| 1181 | }else{ |
| 1182 | char *zMax = fossil_strndup(azItem[i+2], anItem[i+2]); |
| 1183 | double r = atof(zMax); |
| 1184 | if( strstr(zMax,"sec") ){ |
| 1185 | rStart = rNow - r/86400.0; |
| 1186 | }else |
| 1187 | if( strstr(zMax,"hou") ){ |
| 1188 | rStart = rNow - r/24.0; |
| 1189 | }else |
| 1190 | if( strstr(zMax,"da") ){ |
| 1191 | rStart = rNow - r; |
| 1192 | }else |
| 1193 | if( strstr(zMax,"wee") ){ |
| 1194 | rStart = rNow - r*7.0; |
| 1195 | }else |
| 1196 | if( strstr(zMax,"mon") ){ |
| 1197 | rStart = rNow - r*30.44; |
| 1198 | }else |
| 1199 | if( strstr(zMax,"yea") ){ |
| 1200 | rStart = rNow - r*365.24; |
| 1201 | }else |
| 1202 | { /* Default to seconds */ |
| 1203 | rStart = rNow - r/86400.0; |
| 1204 | } |
| 1205 | } |
| 1206 | if( anItem[i+3]==0 ){ |
| 1207 | zComment = fossil_strdup(""); |
| 1208 | }else if( bPlainTextCom ){ |
| 1209 | zComment = mprintf("** %.*s ** ", anItem[i+3], azItem[i+3]); |
| 1210 | }else{ |
| 1211 | zComment = mprintf("<b>%.*s</b>\n<p>", anItem[i+3], azItem[i+3]); |
| 1212 | } |
| 1213 | if( fossil_strcmp("OPEN-LEAF",zLabel)==0 ){ |
| 1214 | db_multi_exec( |
| 1215 | "INSERT OR IGNORE INTO tarlist(rid,com)" |
| 1216 | " SELECT leaf.rid, %Q FROM leaf, event" |
| 1217 | " WHERE event.objid=leaf.rid" |
| 1218 | " AND event.mtime>=%.6f" |
| 1219 | " AND NOT EXISTS(SELECT 1 FROM tagxref" |
| 1220 | " WHERE tagxref.rid=leaf.rid" |
| 1221 | " AND tagid=%d AND tagtype>0)" |
| 1222 | " ORDER BY event.mtime DESC LIMIT %d", |
| 1223 | zComment, rStart, TAG_CLOSED, cnt |
| 1224 | ); |
| 1225 | }else{ |
| 1226 | db_multi_exec( |
| 1227 | "WITH taglist(tid) AS" |
| 1228 | " (SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q')" |
| 1229 | "INSERT OR IGNORE INTO tarlist(rid,com)" |
| 1230 | " SELECT event.objid, %Q FROM event CROSS JOIN tagxref" |
| 1231 | " WHERE event.type='ci'" |
| 1232 | " AND event.mtime>=%.6f" |
| 1233 | " AND tagxref.tagid IN taglist" |
| 1234 | " AND tagtype>0" |
| 1235 | " AND tagxref.rid=event.objid" |
| 1236 | " ORDER BY event.mtime DESC LIMIT %d", |
| 1237 | zLabel, zComment, rStart, cnt |
| 1238 | ); |
| 1239 | } |
| 1240 | fossil_free(zLabel); |
| 1241 | fossil_free(zComment); |
| 1242 | } |
| 1243 | Th_Free(g.interp, azItem); |
| 1244 | |
| 1245 | n = db_int(0, "SELECT count(*) FROM tarlist"); |
| 1246 | if( n==0 ){ |
| 1247 | @ <h2>No tarball/ZIP suggestions are available at this time</h2> |
| 1248 | }else{ |
| 1249 | @ <h2>%d(n) Tarball/ZIP Download Suggestion%s(n>1?"s":""):</h2> |
| 1250 | db_prepare(&q, |
| 1251 | "WITH matches AS (%s AND blob.rid IN (SELECT rid FROM tarlist))\n" |
| 1252 | "SELECT blobRid, uuid, timestamp," |
| 1253 | " com||comment," |
| 1254 | " user, leaf, bgColor, eventType, tags, tagid, brief, mtime" |
| 1255 | " FROM matches JOIN tarlist ON tarlist.rid=blobRid" |
| 1256 | " ORDER BY matches.mtime DESC", |
| 1257 | timeline_query_for_www() |
| 1258 | ); |
| 1259 | |
| 1260 | tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL | TIMELINE_COLUMNAR |
| 1261 | | TIMELINE_BRCOLOR; |
| 1262 | www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, download_extra); |
| 1263 | db_finalize(&q); |
| 1264 | } |
| 1265 | if( g.perm.Clone ){ |
| 1266 | char *zNm = fossil_strdup(db_get("project-name","clone")); |
| 1267 | sanitize_name(zNm); |
| 1268 | @ <hr> |
| 1269 | @ <h2>You Can Clone This Repository</h2> |
| 1270 | @ |
| 1271 | @ <p>Clone this repository by running a command similar to the following: |
| 1272 | @ <blockquote><pre> |
| 1273 | @ fossil clone %s(g.zBaseURL) %h(zNm).fossil |
| 1274 | @ </pre></blockquote> |
| 1275 | @ <p>A clone gives you local access to all historical content. |
| 1276 | @ Cloning is a bandwidth- and CPU-efficient alternative to extracting |
| 1277 | @ multiple tarballs and ZIPs. |
| 1278 | @ Do a web search for "fossil clone" or similar to find additional |
| 1279 | @ information about using a cloned Fossil repository. Or ask your |
| 1280 | @ favorite AI how to extract content from a Fossil clone. |
| 1281 | fossil_free(zNm); |
| 1282 | } |
| 1283 | |
| 1284 | style_finish_page(); |
| 1285 | } |
| 1286 | |
| 1287 | /* |
| 1288 | ** WEBPAGE: rchvdwnld |
| 1289 | ** |
| 1290 | ** Short for "archive download". This page should have a single name= |
| 1291 | ** query parameter that is a check-in hash. It present a menu of possible |
| 1292 | ** download options for that check-in, including tarball, ZIP, or SQLAR. |
| 1293 | ** |
| 1294 | ** This is a utility page. The /dir and /tree pages sometimes have a |
| 1295 | ** "Download" option in their submenu which redirects here. Those pages |
| 1296 | ** used to have separate "Tarball" and "ZIP" submenu entries, but as |
| 1297 | ** submenu entries appear in alphabetical order, that caused the two |
| 1298 | ** submenu entries to be separated from one another, which is distracting. |
| 1299 | */ |
| 1300 | void rchvdwnld_page(void){ |
| 1301 | const char *zUuid; |
| 1302 | char *zBase; |
| 1303 | int nUuid; |
| 1304 | int rid; |
| 1305 | login_check_credentials(); |
| 1306 | if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
| 1307 | if( robot_restrict("zip") || robot_restrict("download") ) return; |
| 1308 | |
| 1309 | zUuid = P("name"); |
| 1310 | if( zUuid==0 |
| 1311 | || (nUuid = (int)strlen(zUuid))<6 |
| 1312 | || !validate16(zUuid,-1) |
| 1313 | || (rid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUuid))==0 |
| 1314 | || !db_exists("SELECT 1 from event WHERE type='ci' AND objid=%d",rid) |
| 1315 | ){ |
| 1316 | fossil_redirect_home(); |
| 1317 | } |
| 1318 | zUuid = db_text(zUuid, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 1319 | style_header("Downloads For Check-in %!S", zUuid); |
| 1320 | zBase = archive_base_name(rid); |
| 1321 | @ <div class="section accordion">Downloads for check-in \ |
| 1322 | @ %z(href("%R/info/%!S",zUuid))%S(zUuid)</a></div> |
| 1323 | @ <div class="accordion_panel"> |
| 1324 | @ <table class="label-value"> |
| 1325 | @ <tr> |
| 1326 | @ <th>Tarball:</th> |
| 1327 | @ <td>%z(href("%R/tarball/%s.tar.gz",zBase))\ |
| 1328 | @ %s(g.zBaseURL)/tarball/%s(zBase).tar.gz</a></td> |
| 1329 | @ </tr> |
| 1330 | @ |
| 1331 | @ <tr> |
| 1332 | @ <th>ZIP:</th> |
| 1333 | @ <td>%z(href("%R/zip/%s.zip",zBase))\ |
| 1334 | @ %s(g.zBaseURL)/zip/%s(zBase).zip</a></td> |
| 1335 | @ </tr> |
| 1336 | @ |
| 1337 | @ <tr> |
| 1338 | @ <th>SQLAR:</th> |
| 1339 | @ <td>%z(href("%R/sqlar/%s.sqlar",zBase))\ |
| 1340 | @ %s(g.zBaseURL)/sqlar/%s(zBase).sqlar</a></td> |
| 1341 | @ </tr> |
| 1342 | @ </table></div> |
| 1343 | fossil_free(zBase); |
| 1344 | @ <div class="section accordion">Context</div><div class="accordion_panel"> |
| 1345 | render_checkin_context(rid, 0, 0, 0); |
| 1346 | @ </div> |
| 1347 | style_finish_page(); |
| 1348 | } |
| 1349 |
+79
-46
| --- src/timeline.c | ||
| +++ src/timeline.c | ||
| @@ -115,11 +115,13 @@ | ||
| 115 | 115 | #define TIMELINE_COMPACT 0x0001000 /* Use the "compact" view style */ |
| 116 | 116 | #define TIMELINE_VERBOSE 0x0002000 /* Use the "detailed" view style */ |
| 117 | 117 | #define TIMELINE_MODERN 0x0004000 /* Use the "modern" view style */ |
| 118 | 118 | #define TIMELINE_COLUMNAR 0x0008000 /* Use the "columns" view style */ |
| 119 | 119 | #define TIMELINE_CLASSIC 0x0010000 /* Use the "classic" view style */ |
| 120 | -#define TIMELINE_VIEWS 0x001f000 /* Mask for all of the view styles */ | |
| 120 | +#define TIMELINE_SIMPLE 0x0020000 /* Use the "simple" view style */ | |
| 121 | +#define TIMELINE_INLINE 0x0033000 /* Mask for views with in-line display */ | |
| 122 | +#define TIMELINE_VIEWS 0x003f000 /* Mask for all of the view styles */ | |
| 121 | 123 | #define TIMELINE_NOSCROLL 0x0100000 /* Don't scroll to the selection */ |
| 122 | 124 | #define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */ |
| 123 | 125 | #define TIMELINE_CHPICK 0x0400000 /* Show cherrypick merges */ |
| 124 | 126 | #define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */ |
| 125 | 127 | #define TIMELINE_XMERGE 0x1000000 /* Omit merges from off-graph nodes */ |
| @@ -179,11 +181,11 @@ | ||
| 179 | 181 | ** Example: "(check-in: [abcdefg], user: drh, tags: trunk)" |
| 180 | 182 | ** |
| 181 | 183 | ** This routine is used if no xExtra argument is supplied to |
| 182 | 184 | ** www_print_timeline(). |
| 183 | 185 | */ |
| 184 | -static void defaultExtra( | |
| 186 | +void timeline_extra( | |
| 185 | 187 | Stmt *pQuery, /* Current row of the timeline query */ |
| 186 | 188 | int tmFlags, /* Flags to www_print_timeline() */ |
| 187 | 189 | const char *zThisUser, /* Suppress links to this user */ |
| 188 | 190 | const char *zThisTag /* Suppress links to this tag */ |
| 189 | 191 | ){ |
| @@ -194,29 +196,40 @@ | ||
| 194 | 196 | const char *zUser = db_column_text(pQuery, 4); |
| 195 | 197 | const char *zTagList = db_column_text(pQuery, 8); |
| 196 | 198 | int tagid = db_column_int(pQuery, 9); |
| 197 | 199 | const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; |
| 198 | 200 | |
| 199 | - if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){ | |
| 201 | + if( (tmFlags & TIMELINE_INLINE)!=0 ){ | |
| 200 | 202 | cgi_printf("("); |
| 201 | 203 | } |
| 202 | 204 | |
| 203 | -/* Set to 1 for historical appearance. Set to 0 for new experimental look */ | |
| 204 | -#define OLD_STYLE 1 | |
| 205 | -#if OLD_STYLE | |
| 206 | 205 | if( (tmFlags & TIMELINE_CLASSIC)==0 ){ |
| 207 | 206 | if( zType[0]=='c' ){ |
| 208 | - int isLeaf = db_column_int(pQuery, 5); | |
| 209 | - if( isLeaf ){ | |
| 210 | - if( has_closed_tag(rid) ){ | |
| 211 | - @ <span class='timelineLeaf'>Closed-Leaf</span> | |
| 207 | + const char *zPrefix = 0; | |
| 208 | + static int markLeaves = -1; | |
| 209 | + if( markLeaves<0 ){ | |
| 210 | + markLeaves = db_get_int("timeline-mark-leaves",1); | |
| 211 | + if( markLeaves<0 ) markLeaves = 1; | |
| 212 | + } | |
| 213 | + if( strcmp(zUuid, MANIFEST_UUID)==0 ){ | |
| 214 | + /* This will only ever happen when Fossil is drawing a timeline for | |
| 215 | + ** its own self-host repository. If the timeline shows the specific | |
| 216 | + ** check-in corresponding to the current executable, then tag that | |
| 217 | + ** check-in with "self" */ | |
| 218 | + zPrefix = "self "; | |
| 219 | + }else if( markLeaves && db_column_int(pQuery,5) ){ | |
| 220 | + if( markLeaves==1 ){ | |
| 221 | + zPrefix = has_closed_tag(rid) ? "closed " : "leaf "; | |
| 212 | 222 | }else{ |
| 213 | - @ <span class='timelineLeaf'>Leaf</span> | |
| 223 | + zPrefix = has_closed_tag(rid) ? | |
| 224 | + "<span class='timelineLeaf'>Closed-Leaf</span>\n" : | |
| 225 | + "<span class='timelineLeaf'>Leaf</span>\n"; | |
| 214 | 226 | } |
| 215 | 227 | } |
| 216 | - cgi_printf("check-in: %z%S</a> ", | |
| 217 | - href("%R/info/%!S",zUuid),zUuid); | |
| 228 | + cgi_printf("%scheck-in: %z<span class='timelineHash'>" | |
| 229 | + "%S</span></a> ", | |
| 230 | + zPrefix, href("%R/info/%!S",zUuid),zUuid); | |
| 218 | 231 | }else if( zType[0]=='e' && tagid ){ |
| 219 | 232 | cgi_printf("technote: "); |
| 220 | 233 | hyperlink_to_event_tagid(tagid<0?-tagid:tagid); |
| 221 | 234 | }else{ |
| 222 | 235 | cgi_printf("artifact: %z%S</a> ", |
| @@ -224,11 +237,16 @@ | ||
| 224 | 237 | } |
| 225 | 238 | }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t' |
| 226 | 239 | || zType[0]=='n' || zType[0]=='f'){ |
| 227 | 240 | cgi_printf("artifact: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid); |
| 228 | 241 | } |
| 229 | -#endif /* OLD_STYLE */ | |
| 242 | + | |
| 243 | + if( (tmFlags & TIMELINE_SIMPLE)!=0 ){ | |
| 244 | + @ <span class='timelineEllipsis' id='ellipsis-%d(rid)' \ | |
| 245 | + @ data-id='%d(rid)'>...</span> | |
| 246 | + @ <span class='clutter' id='detail-%d(rid)'> | |
| 247 | + } | |
| 230 | 248 | |
| 231 | 249 | if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){ |
| 232 | 250 | char *zLink; |
| 233 | 251 | if( zType[0]!='f' || (tmFlags & TIMELINE_FORUMTXT)==0 ){ |
| 234 | 252 | zLink = mprintf("%R/timeline?u=%h&c=%t&y=a", zDispUser, zDate); |
| @@ -280,33 +298,18 @@ | ||
| 280 | 298 | href("%R/deltachain/%d",rid), rid); |
| 281 | 299 | } |
| 282 | 300 | } |
| 283 | 301 | tag_private_status(rid); |
| 284 | 302 | |
| 285 | -#if !OLD_STYLE | |
| 286 | - if( (tmFlags & TIMELINE_CLASSIC)==0 ){ | |
| 287 | - if( zType[0]=='e' && tagid ){ | |
| 288 | - char *zId = db_text(0, | |
| 289 | - "SELECT substr(tagname,7) FROM tag WHERE tagid=abs(%d)", tagid); | |
| 290 | - cgi_printf(" technote: %z%S</a>", | |
| 291 | - href("%R/technote/%t",zId), zId); | |
| 292 | - }else{ | |
| 293 | - cgi_printf(" hash: %z%S</a>", href("%R/info/%!S", zUuid), zUuid); | |
| 294 | - } | |
| 295 | - } | |
| 296 | -#endif /* !OLD_STYLE */ | |
| 303 | + if( (tmFlags & TIMELINE_SIMPLE)!=0 ){ | |
| 304 | + cgi_printf("</span>"); /* End of the declutter span */ | |
| 305 | + } | |
| 297 | 306 | |
| 298 | 307 | /* End timelineDetail */ |
| 299 | - if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){ | |
| 308 | + if( (tmFlags & TIMELINE_INLINE)!=0 ){ | |
| 300 | 309 | cgi_printf(")"); |
| 301 | 310 | } |
| 302 | - | |
| 303 | - if( tmFlags & TIMELINE_COMPACT ){ | |
| 304 | - @ </span></span> | |
| 305 | - }else{ | |
| 306 | - @ </span> | |
| 307 | - } | |
| 308 | 311 | } |
| 309 | 312 | |
| 310 | 313 | |
| 311 | 314 | /* |
| 312 | 315 | ** SETTING: timeline-truncate-at-blank boolean default=off |
| @@ -323,11 +326,27 @@ | ||
| 323 | 326 | ** on the far left-hand side of the screen, normally targets another |
| 324 | 327 | ** /timeline page that shows the entry in context. However, if this |
| 325 | 328 | ** option is turned on, that hyperlink targets the /info page showing |
| 326 | 329 | ** the details of the entry. |
| 327 | 330 | */ |
| 328 | - | |
| 331 | +/* | |
| 332 | +** SETTING: timeline-mark-leaves width=5 default=1 | |
| 333 | +** | |
| 334 | +** Determine whether or not leaf check-ins are marked as such in the | |
| 335 | +** details section of the timeline. The value is an integer between 0 | |
| 336 | +** and 2: | |
| 337 | +** | |
| 338 | +** 0 Do not show any special marking for leaf check-ins. | |
| 339 | +** | |
| 340 | +** 1 Show just "leaf" or "closed" | |
| 341 | +** | |
| 342 | +** 2 Show "Leaf" or "Closed-Leaf" with emphasis | |
| 343 | +** | |
| 344 | +** The default is currently 1. Prior to 2025-10-19, the default was 2. | |
| 345 | +** This setting has no effect on the "Classic" view, which always behaves | |
| 346 | +** as if the setting were 2. | |
| 347 | +*/ | |
| 329 | 348 | |
| 330 | 349 | /* |
| 331 | 350 | ** Output a timeline in the web format given a query. The query |
| 332 | 351 | ** should return these columns: |
| 333 | 352 | ** |
| @@ -375,11 +394,11 @@ | ||
| 375 | 394 | |
| 376 | 395 | |
| 377 | 396 | if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){ |
| 378 | 397 | vid = db_lget_int("checkout", 0); |
| 379 | 398 | } |
| 380 | - if( xExtra==0 ) xExtra = defaultExtra; | |
| 399 | + if( xExtra==0 ) xExtra = timeline_extra; | |
| 381 | 400 | zPrevDate[0] = 0; |
| 382 | 401 | mxWikiLen = db_get_int("timeline-max-comment", 0); |
| 383 | 402 | dateFormat = db_get_int("timeline-date-format", 0); |
| 384 | 403 | bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0); |
| 385 | 404 | bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0); |
| @@ -388,10 +407,12 @@ | ||
| 388 | 407 | } |
| 389 | 408 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 390 | 409 | zStyle = "Columnar"; |
| 391 | 410 | }else if( tmFlags & TIMELINE_COMPACT ){ |
| 392 | 411 | zStyle = "Compact"; |
| 412 | + }else if( tmFlags & TIMELINE_SIMPLE ){ | |
| 413 | + zStyle = "Simple"; | |
| 393 | 414 | }else if( tmFlags & TIMELINE_VERBOSE ){ |
| 394 | 415 | zStyle = "Verbose"; |
| 395 | 416 | }else if( tmFlags & TIMELINE_CLASSIC ){ |
| 396 | 417 | zStyle = "Classic"; |
| 397 | 418 | }else{ |
| @@ -738,18 +759,10 @@ | ||
| 738 | 759 | }else{ |
| 739 | 760 | cgi_printf("%W",blob_str(&comment)); |
| 740 | 761 | } |
| 741 | 762 | } |
| 742 | 763 | |
| 743 | - if( zType[0]=='c' && strcmp(zUuid, MANIFEST_UUID)==0 ){ | |
| 744 | - /* This will only ever happen when Fossil is drawing a timeline for | |
| 745 | - ** its own self-host repository. If the timeline shows the specific | |
| 746 | - ** check-in corresponding to the current executable, then tag that | |
| 747 | - ** check-in with "This is me!". */ | |
| 748 | - @ <b>← This is me!</b> | |
| 749 | - } | |
| 750 | - | |
| 751 | 764 | @ </span> |
| 752 | 765 | blob_reset(&comment); |
| 753 | 766 | |
| 754 | 767 | /* Generate extra information and hyperlinks that follow the comment. |
| 755 | 768 | ** Example: "(check-in: [abcdefg], user: drh, tags: trunk)" |
| @@ -1269,10 +1282,26 @@ | ||
| 1269 | 1282 | if( i>2 ){ |
| 1270 | 1283 | style_submenu_multichoice("y", i/2, az, isDisabled); |
| 1271 | 1284 | } |
| 1272 | 1285 | } |
| 1273 | 1286 | |
| 1287 | +/* | |
| 1288 | +** SETTING: timeline-default-style width=5 default=m | |
| 1289 | +** | |
| 1290 | +** This setting determines the default "view style" for timelines. | |
| 1291 | +** The setting should be a single character, one of the following: | |
| 1292 | +** | |
| 1293 | +** c Compact | |
| 1294 | +** j Columnar | |
| 1295 | +** m Modern | |
| 1296 | +** s Simple | |
| 1297 | +** v Verbose | |
| 1298 | +** x Classic | |
| 1299 | +** | |
| 1300 | +** The default value is m (Modern). | |
| 1301 | +*/ | |
| 1302 | + | |
| 1274 | 1303 | /* |
| 1275 | 1304 | ** Return the default value for the "ss" cookie or query parameter. |
| 1276 | 1305 | ** The "ss" cookie determines the graph style. See the |
| 1277 | 1306 | ** timeline_view_styles[] global constant for a list of choices. |
| 1278 | 1307 | */ |
| @@ -1293,10 +1322,11 @@ | ||
| 1293 | 1322 | switch( v[0] ){ |
| 1294 | 1323 | case 'c': tmFlags = TIMELINE_COMPACT; break; |
| 1295 | 1324 | case 'v': tmFlags = TIMELINE_VERBOSE; break; |
| 1296 | 1325 | case 'j': tmFlags = TIMELINE_COLUMNAR; break; |
| 1297 | 1326 | case 'x': tmFlags = TIMELINE_CLASSIC; break; |
| 1327 | + case 's': tmFlags = TIMELINE_SIMPLE; break; | |
| 1298 | 1328 | default: tmFlags = TIMELINE_MODERN; break; |
| 1299 | 1329 | } |
| 1300 | 1330 | return tmFlags; |
| 1301 | 1331 | } |
| 1302 | 1332 | |
| @@ -1305,15 +1335,16 @@ | ||
| 1305 | 1335 | */ |
| 1306 | 1336 | const char *const timeline_view_styles[] = { |
| 1307 | 1337 | "m", "Modern View", |
| 1308 | 1338 | "j", "Columnar View", |
| 1309 | 1339 | "c", "Compact View", |
| 1340 | + "s", "Simple View", | |
| 1310 | 1341 | "v", "Verbose View", |
| 1311 | 1342 | "x", "Classic View", |
| 1312 | 1343 | }; |
| 1313 | 1344 | #if INTERFACE |
| 1314 | -# define N_TIMELINE_VIEW_STYLE 5 | |
| 1345 | +# define N_TIMELINE_VIEW_STYLE 6 | |
| 1315 | 1346 | #endif |
| 1316 | 1347 | |
| 1317 | 1348 | /* |
| 1318 | 1349 | ** Add the select/option box to the timeline submenu that is used to |
| 1319 | 1350 | ** set the ss= parameter that determines the viewing mode. |
| @@ -1670,11 +1701,11 @@ | ||
| 1670 | 1701 | ** ms=MATCHSTYLE Set tag name match algorithm. One of "exact", "glob", |
| 1671 | 1702 | ** "like", or "regexp". |
| 1672 | 1703 | ** u=USER Only show items associated with USER |
| 1673 | 1704 | ** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', or 'all'. |
| 1674 | 1705 | ** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar", |
| 1675 | -* x: "Classic". | |
| 1706 | +** x: "Classic". | |
| 1676 | 1707 | ** advm Use the "Advanced" or "Busy" menu design. |
| 1677 | 1708 | ** ng No Graph. |
| 1678 | 1709 | ** ncp Omit cherrypick merges |
| 1679 | 1710 | ** nd Do not highlight the focus check-in |
| 1680 | 1711 | ** nsm Omit the submenu |
| @@ -1893,11 +1924,13 @@ | ||
| 1893 | 1924 | || (bisectLocal && !g.perm.Setup) |
| 1894 | 1925 | ){ |
| 1895 | 1926 | login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); |
| 1896 | 1927 | return; |
| 1897 | 1928 | } |
| 1898 | - if( (zBefore || zCirca) && robot_restrict("timelineX") ) return; | |
| 1929 | + if( zBefore || zCirca ){ | |
| 1930 | + if( robot_restrict("timelineX") ) return; | |
| 1931 | + } | |
| 1899 | 1932 | if( !bisectLocal ){ |
| 1900 | 1933 | etag_check(ETAG_QUERY|ETAG_COOKIE|ETAG_DATA|ETAG_CONFIG, 0); |
| 1901 | 1934 | } |
| 1902 | 1935 | cookie_read_parameter("y","y"); |
| 1903 | 1936 | zType = P("y"); |
| 1904 | 1937 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -115,11 +115,13 @@ | |
| 115 | #define TIMELINE_COMPACT 0x0001000 /* Use the "compact" view style */ |
| 116 | #define TIMELINE_VERBOSE 0x0002000 /* Use the "detailed" view style */ |
| 117 | #define TIMELINE_MODERN 0x0004000 /* Use the "modern" view style */ |
| 118 | #define TIMELINE_COLUMNAR 0x0008000 /* Use the "columns" view style */ |
| 119 | #define TIMELINE_CLASSIC 0x0010000 /* Use the "classic" view style */ |
| 120 | #define TIMELINE_VIEWS 0x001f000 /* Mask for all of the view styles */ |
| 121 | #define TIMELINE_NOSCROLL 0x0100000 /* Don't scroll to the selection */ |
| 122 | #define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */ |
| 123 | #define TIMELINE_CHPICK 0x0400000 /* Show cherrypick merges */ |
| 124 | #define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */ |
| 125 | #define TIMELINE_XMERGE 0x1000000 /* Omit merges from off-graph nodes */ |
| @@ -179,11 +181,11 @@ | |
| 179 | ** Example: "(check-in: [abcdefg], user: drh, tags: trunk)" |
| 180 | ** |
| 181 | ** This routine is used if no xExtra argument is supplied to |
| 182 | ** www_print_timeline(). |
| 183 | */ |
| 184 | static void defaultExtra( |
| 185 | Stmt *pQuery, /* Current row of the timeline query */ |
| 186 | int tmFlags, /* Flags to www_print_timeline() */ |
| 187 | const char *zThisUser, /* Suppress links to this user */ |
| 188 | const char *zThisTag /* Suppress links to this tag */ |
| 189 | ){ |
| @@ -194,29 +196,40 @@ | |
| 194 | const char *zUser = db_column_text(pQuery, 4); |
| 195 | const char *zTagList = db_column_text(pQuery, 8); |
| 196 | int tagid = db_column_int(pQuery, 9); |
| 197 | const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; |
| 198 | |
| 199 | if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){ |
| 200 | cgi_printf("("); |
| 201 | } |
| 202 | |
| 203 | /* Set to 1 for historical appearance. Set to 0 for new experimental look */ |
| 204 | #define OLD_STYLE 1 |
| 205 | #if OLD_STYLE |
| 206 | if( (tmFlags & TIMELINE_CLASSIC)==0 ){ |
| 207 | if( zType[0]=='c' ){ |
| 208 | int isLeaf = db_column_int(pQuery, 5); |
| 209 | if( isLeaf ){ |
| 210 | if( has_closed_tag(rid) ){ |
| 211 | @ <span class='timelineLeaf'>Closed-Leaf</span> |
| 212 | }else{ |
| 213 | @ <span class='timelineLeaf'>Leaf</span> |
| 214 | } |
| 215 | } |
| 216 | cgi_printf("check-in: %z%S</a> ", |
| 217 | href("%R/info/%!S",zUuid),zUuid); |
| 218 | }else if( zType[0]=='e' && tagid ){ |
| 219 | cgi_printf("technote: "); |
| 220 | hyperlink_to_event_tagid(tagid<0?-tagid:tagid); |
| 221 | }else{ |
| 222 | cgi_printf("artifact: %z%S</a> ", |
| @@ -224,11 +237,16 @@ | |
| 224 | } |
| 225 | }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t' |
| 226 | || zType[0]=='n' || zType[0]=='f'){ |
| 227 | cgi_printf("artifact: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid); |
| 228 | } |
| 229 | #endif /* OLD_STYLE */ |
| 230 | |
| 231 | if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){ |
| 232 | char *zLink; |
| 233 | if( zType[0]!='f' || (tmFlags & TIMELINE_FORUMTXT)==0 ){ |
| 234 | zLink = mprintf("%R/timeline?u=%h&c=%t&y=a", zDispUser, zDate); |
| @@ -280,33 +298,18 @@ | |
| 280 | href("%R/deltachain/%d",rid), rid); |
| 281 | } |
| 282 | } |
| 283 | tag_private_status(rid); |
| 284 | |
| 285 | #if !OLD_STYLE |
| 286 | if( (tmFlags & TIMELINE_CLASSIC)==0 ){ |
| 287 | if( zType[0]=='e' && tagid ){ |
| 288 | char *zId = db_text(0, |
| 289 | "SELECT substr(tagname,7) FROM tag WHERE tagid=abs(%d)", tagid); |
| 290 | cgi_printf(" technote: %z%S</a>", |
| 291 | href("%R/technote/%t",zId), zId); |
| 292 | }else{ |
| 293 | cgi_printf(" hash: %z%S</a>", href("%R/info/%!S", zUuid), zUuid); |
| 294 | } |
| 295 | } |
| 296 | #endif /* !OLD_STYLE */ |
| 297 | |
| 298 | /* End timelineDetail */ |
| 299 | if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){ |
| 300 | cgi_printf(")"); |
| 301 | } |
| 302 | |
| 303 | if( tmFlags & TIMELINE_COMPACT ){ |
| 304 | @ </span></span> |
| 305 | }else{ |
| 306 | @ </span> |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | |
| 311 | /* |
| 312 | ** SETTING: timeline-truncate-at-blank boolean default=off |
| @@ -323,11 +326,27 @@ | |
| 323 | ** on the far left-hand side of the screen, normally targets another |
| 324 | ** /timeline page that shows the entry in context. However, if this |
| 325 | ** option is turned on, that hyperlink targets the /info page showing |
| 326 | ** the details of the entry. |
| 327 | */ |
| 328 | |
| 329 | |
| 330 | /* |
| 331 | ** Output a timeline in the web format given a query. The query |
| 332 | ** should return these columns: |
| 333 | ** |
| @@ -375,11 +394,11 @@ | |
| 375 | |
| 376 | |
| 377 | if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){ |
| 378 | vid = db_lget_int("checkout", 0); |
| 379 | } |
| 380 | if( xExtra==0 ) xExtra = defaultExtra; |
| 381 | zPrevDate[0] = 0; |
| 382 | mxWikiLen = db_get_int("timeline-max-comment", 0); |
| 383 | dateFormat = db_get_int("timeline-date-format", 0); |
| 384 | bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0); |
| 385 | bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0); |
| @@ -388,10 +407,12 @@ | |
| 388 | } |
| 389 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 390 | zStyle = "Columnar"; |
| 391 | }else if( tmFlags & TIMELINE_COMPACT ){ |
| 392 | zStyle = "Compact"; |
| 393 | }else if( tmFlags & TIMELINE_VERBOSE ){ |
| 394 | zStyle = "Verbose"; |
| 395 | }else if( tmFlags & TIMELINE_CLASSIC ){ |
| 396 | zStyle = "Classic"; |
| 397 | }else{ |
| @@ -738,18 +759,10 @@ | |
| 738 | }else{ |
| 739 | cgi_printf("%W",blob_str(&comment)); |
| 740 | } |
| 741 | } |
| 742 | |
| 743 | if( zType[0]=='c' && strcmp(zUuid, MANIFEST_UUID)==0 ){ |
| 744 | /* This will only ever happen when Fossil is drawing a timeline for |
| 745 | ** its own self-host repository. If the timeline shows the specific |
| 746 | ** check-in corresponding to the current executable, then tag that |
| 747 | ** check-in with "This is me!". */ |
| 748 | @ <b>← This is me!</b> |
| 749 | } |
| 750 | |
| 751 | @ </span> |
| 752 | blob_reset(&comment); |
| 753 | |
| 754 | /* Generate extra information and hyperlinks that follow the comment. |
| 755 | ** Example: "(check-in: [abcdefg], user: drh, tags: trunk)" |
| @@ -1269,10 +1282,26 @@ | |
| 1269 | if( i>2 ){ |
| 1270 | style_submenu_multichoice("y", i/2, az, isDisabled); |
| 1271 | } |
| 1272 | } |
| 1273 | |
| 1274 | /* |
| 1275 | ** Return the default value for the "ss" cookie or query parameter. |
| 1276 | ** The "ss" cookie determines the graph style. See the |
| 1277 | ** timeline_view_styles[] global constant for a list of choices. |
| 1278 | */ |
| @@ -1293,10 +1322,11 @@ | |
| 1293 | switch( v[0] ){ |
| 1294 | case 'c': tmFlags = TIMELINE_COMPACT; break; |
| 1295 | case 'v': tmFlags = TIMELINE_VERBOSE; break; |
| 1296 | case 'j': tmFlags = TIMELINE_COLUMNAR; break; |
| 1297 | case 'x': tmFlags = TIMELINE_CLASSIC; break; |
| 1298 | default: tmFlags = TIMELINE_MODERN; break; |
| 1299 | } |
| 1300 | return tmFlags; |
| 1301 | } |
| 1302 | |
| @@ -1305,15 +1335,16 @@ | |
| 1305 | */ |
| 1306 | const char *const timeline_view_styles[] = { |
| 1307 | "m", "Modern View", |
| 1308 | "j", "Columnar View", |
| 1309 | "c", "Compact View", |
| 1310 | "v", "Verbose View", |
| 1311 | "x", "Classic View", |
| 1312 | }; |
| 1313 | #if INTERFACE |
| 1314 | # define N_TIMELINE_VIEW_STYLE 5 |
| 1315 | #endif |
| 1316 | |
| 1317 | /* |
| 1318 | ** Add the select/option box to the timeline submenu that is used to |
| 1319 | ** set the ss= parameter that determines the viewing mode. |
| @@ -1670,11 +1701,11 @@ | |
| 1670 | ** ms=MATCHSTYLE Set tag name match algorithm. One of "exact", "glob", |
| 1671 | ** "like", or "regexp". |
| 1672 | ** u=USER Only show items associated with USER |
| 1673 | ** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', or 'all'. |
| 1674 | ** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar", |
| 1675 | * x: "Classic". |
| 1676 | ** advm Use the "Advanced" or "Busy" menu design. |
| 1677 | ** ng No Graph. |
| 1678 | ** ncp Omit cherrypick merges |
| 1679 | ** nd Do not highlight the focus check-in |
| 1680 | ** nsm Omit the submenu |
| @@ -1893,11 +1924,13 @@ | |
| 1893 | || (bisectLocal && !g.perm.Setup) |
| 1894 | ){ |
| 1895 | login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); |
| 1896 | return; |
| 1897 | } |
| 1898 | if( (zBefore || zCirca) && robot_restrict("timelineX") ) return; |
| 1899 | if( !bisectLocal ){ |
| 1900 | etag_check(ETAG_QUERY|ETAG_COOKIE|ETAG_DATA|ETAG_CONFIG, 0); |
| 1901 | } |
| 1902 | cookie_read_parameter("y","y"); |
| 1903 | zType = P("y"); |
| 1904 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -115,11 +115,13 @@ | |
| 115 | #define TIMELINE_COMPACT 0x0001000 /* Use the "compact" view style */ |
| 116 | #define TIMELINE_VERBOSE 0x0002000 /* Use the "detailed" view style */ |
| 117 | #define TIMELINE_MODERN 0x0004000 /* Use the "modern" view style */ |
| 118 | #define TIMELINE_COLUMNAR 0x0008000 /* Use the "columns" view style */ |
| 119 | #define TIMELINE_CLASSIC 0x0010000 /* Use the "classic" view style */ |
| 120 | #define TIMELINE_SIMPLE 0x0020000 /* Use the "simple" view style */ |
| 121 | #define TIMELINE_INLINE 0x0033000 /* Mask for views with in-line display */ |
| 122 | #define TIMELINE_VIEWS 0x003f000 /* Mask for all of the view styles */ |
| 123 | #define TIMELINE_NOSCROLL 0x0100000 /* Don't scroll to the selection */ |
| 124 | #define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */ |
| 125 | #define TIMELINE_CHPICK 0x0400000 /* Show cherrypick merges */ |
| 126 | #define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */ |
| 127 | #define TIMELINE_XMERGE 0x1000000 /* Omit merges from off-graph nodes */ |
| @@ -179,11 +181,11 @@ | |
| 181 | ** Example: "(check-in: [abcdefg], user: drh, tags: trunk)" |
| 182 | ** |
| 183 | ** This routine is used if no xExtra argument is supplied to |
| 184 | ** www_print_timeline(). |
| 185 | */ |
| 186 | void timeline_extra( |
| 187 | Stmt *pQuery, /* Current row of the timeline query */ |
| 188 | int tmFlags, /* Flags to www_print_timeline() */ |
| 189 | const char *zThisUser, /* Suppress links to this user */ |
| 190 | const char *zThisTag /* Suppress links to this tag */ |
| 191 | ){ |
| @@ -194,29 +196,40 @@ | |
| 196 | const char *zUser = db_column_text(pQuery, 4); |
| 197 | const char *zTagList = db_column_text(pQuery, 8); |
| 198 | int tagid = db_column_int(pQuery, 9); |
| 199 | const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; |
| 200 | |
| 201 | if( (tmFlags & TIMELINE_INLINE)!=0 ){ |
| 202 | cgi_printf("("); |
| 203 | } |
| 204 | |
| 205 | if( (tmFlags & TIMELINE_CLASSIC)==0 ){ |
| 206 | if( zType[0]=='c' ){ |
| 207 | const char *zPrefix = 0; |
| 208 | static int markLeaves = -1; |
| 209 | if( markLeaves<0 ){ |
| 210 | markLeaves = db_get_int("timeline-mark-leaves",1); |
| 211 | if( markLeaves<0 ) markLeaves = 1; |
| 212 | } |
| 213 | if( strcmp(zUuid, MANIFEST_UUID)==0 ){ |
| 214 | /* This will only ever happen when Fossil is drawing a timeline for |
| 215 | ** its own self-host repository. If the timeline shows the specific |
| 216 | ** check-in corresponding to the current executable, then tag that |
| 217 | ** check-in with "self" */ |
| 218 | zPrefix = "self "; |
| 219 | }else if( markLeaves && db_column_int(pQuery,5) ){ |
| 220 | if( markLeaves==1 ){ |
| 221 | zPrefix = has_closed_tag(rid) ? "closed " : "leaf "; |
| 222 | }else{ |
| 223 | zPrefix = has_closed_tag(rid) ? |
| 224 | "<span class='timelineLeaf'>Closed-Leaf</span>\n" : |
| 225 | "<span class='timelineLeaf'>Leaf</span>\n"; |
| 226 | } |
| 227 | } |
| 228 | cgi_printf("%scheck-in: %z<span class='timelineHash'>" |
| 229 | "%S</span></a> ", |
| 230 | zPrefix, href("%R/info/%!S",zUuid),zUuid); |
| 231 | }else if( zType[0]=='e' && tagid ){ |
| 232 | cgi_printf("technote: "); |
| 233 | hyperlink_to_event_tagid(tagid<0?-tagid:tagid); |
| 234 | }else{ |
| 235 | cgi_printf("artifact: %z%S</a> ", |
| @@ -224,11 +237,16 @@ | |
| 237 | } |
| 238 | }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t' |
| 239 | || zType[0]=='n' || zType[0]=='f'){ |
| 240 | cgi_printf("artifact: %z%S</a> ",href("%R/info/%!S",zUuid),zUuid); |
| 241 | } |
| 242 | |
| 243 | if( (tmFlags & TIMELINE_SIMPLE)!=0 ){ |
| 244 | @ <span class='timelineEllipsis' id='ellipsis-%d(rid)' \ |
| 245 | @ data-id='%d(rid)'>...</span> |
| 246 | @ <span class='clutter' id='detail-%d(rid)'> |
| 247 | } |
| 248 | |
| 249 | if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){ |
| 250 | char *zLink; |
| 251 | if( zType[0]!='f' || (tmFlags & TIMELINE_FORUMTXT)==0 ){ |
| 252 | zLink = mprintf("%R/timeline?u=%h&c=%t&y=a", zDispUser, zDate); |
| @@ -280,33 +298,18 @@ | |
| 298 | href("%R/deltachain/%d",rid), rid); |
| 299 | } |
| 300 | } |
| 301 | tag_private_status(rid); |
| 302 | |
| 303 | if( (tmFlags & TIMELINE_SIMPLE)!=0 ){ |
| 304 | cgi_printf("</span>"); /* End of the declutter span */ |
| 305 | } |
| 306 | |
| 307 | /* End timelineDetail */ |
| 308 | if( (tmFlags & TIMELINE_INLINE)!=0 ){ |
| 309 | cgi_printf(")"); |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | |
| 314 | /* |
| 315 | ** SETTING: timeline-truncate-at-blank boolean default=off |
| @@ -323,11 +326,27 @@ | |
| 326 | ** on the far left-hand side of the screen, normally targets another |
| 327 | ** /timeline page that shows the entry in context. However, if this |
| 328 | ** option is turned on, that hyperlink targets the /info page showing |
| 329 | ** the details of the entry. |
| 330 | */ |
| 331 | /* |
| 332 | ** SETTING: timeline-mark-leaves width=5 default=1 |
| 333 | ** |
| 334 | ** Determine whether or not leaf check-ins are marked as such in the |
| 335 | ** details section of the timeline. The value is an integer between 0 |
| 336 | ** and 2: |
| 337 | ** |
| 338 | ** 0 Do not show any special marking for leaf check-ins. |
| 339 | ** |
| 340 | ** 1 Show just "leaf" or "closed" |
| 341 | ** |
| 342 | ** 2 Show "Leaf" or "Closed-Leaf" with emphasis |
| 343 | ** |
| 344 | ** The default is currently 1. Prior to 2025-10-19, the default was 2. |
| 345 | ** This setting has no effect on the "Classic" view, which always behaves |
| 346 | ** as if the setting were 2. |
| 347 | */ |
| 348 | |
| 349 | /* |
| 350 | ** Output a timeline in the web format given a query. The query |
| 351 | ** should return these columns: |
| 352 | ** |
| @@ -375,11 +394,11 @@ | |
| 394 | |
| 395 | |
| 396 | if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){ |
| 397 | vid = db_lget_int("checkout", 0); |
| 398 | } |
| 399 | if( xExtra==0 ) xExtra = timeline_extra; |
| 400 | zPrevDate[0] = 0; |
| 401 | mxWikiLen = db_get_int("timeline-max-comment", 0); |
| 402 | dateFormat = db_get_int("timeline-date-format", 0); |
| 403 | bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0); |
| 404 | bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0); |
| @@ -388,10 +407,12 @@ | |
| 407 | } |
| 408 | if( tmFlags & TIMELINE_COLUMNAR ){ |
| 409 | zStyle = "Columnar"; |
| 410 | }else if( tmFlags & TIMELINE_COMPACT ){ |
| 411 | zStyle = "Compact"; |
| 412 | }else if( tmFlags & TIMELINE_SIMPLE ){ |
| 413 | zStyle = "Simple"; |
| 414 | }else if( tmFlags & TIMELINE_VERBOSE ){ |
| 415 | zStyle = "Verbose"; |
| 416 | }else if( tmFlags & TIMELINE_CLASSIC ){ |
| 417 | zStyle = "Classic"; |
| 418 | }else{ |
| @@ -738,18 +759,10 @@ | |
| 759 | }else{ |
| 760 | cgi_printf("%W",blob_str(&comment)); |
| 761 | } |
| 762 | } |
| 763 | |
| 764 | @ </span> |
| 765 | blob_reset(&comment); |
| 766 | |
| 767 | /* Generate extra information and hyperlinks that follow the comment. |
| 768 | ** Example: "(check-in: [abcdefg], user: drh, tags: trunk)" |
| @@ -1269,10 +1282,26 @@ | |
| 1282 | if( i>2 ){ |
| 1283 | style_submenu_multichoice("y", i/2, az, isDisabled); |
| 1284 | } |
| 1285 | } |
| 1286 | |
| 1287 | /* |
| 1288 | ** SETTING: timeline-default-style width=5 default=m |
| 1289 | ** |
| 1290 | ** This setting determines the default "view style" for timelines. |
| 1291 | ** The setting should be a single character, one of the following: |
| 1292 | ** |
| 1293 | ** c Compact |
| 1294 | ** j Columnar |
| 1295 | ** m Modern |
| 1296 | ** s Simple |
| 1297 | ** v Verbose |
| 1298 | ** x Classic |
| 1299 | ** |
| 1300 | ** The default value is m (Modern). |
| 1301 | */ |
| 1302 | |
| 1303 | /* |
| 1304 | ** Return the default value for the "ss" cookie or query parameter. |
| 1305 | ** The "ss" cookie determines the graph style. See the |
| 1306 | ** timeline_view_styles[] global constant for a list of choices. |
| 1307 | */ |
| @@ -1293,10 +1322,11 @@ | |
| 1322 | switch( v[0] ){ |
| 1323 | case 'c': tmFlags = TIMELINE_COMPACT; break; |
| 1324 | case 'v': tmFlags = TIMELINE_VERBOSE; break; |
| 1325 | case 'j': tmFlags = TIMELINE_COLUMNAR; break; |
| 1326 | case 'x': tmFlags = TIMELINE_CLASSIC; break; |
| 1327 | case 's': tmFlags = TIMELINE_SIMPLE; break; |
| 1328 | default: tmFlags = TIMELINE_MODERN; break; |
| 1329 | } |
| 1330 | return tmFlags; |
| 1331 | } |
| 1332 | |
| @@ -1305,15 +1335,16 @@ | |
| 1335 | */ |
| 1336 | const char *const timeline_view_styles[] = { |
| 1337 | "m", "Modern View", |
| 1338 | "j", "Columnar View", |
| 1339 | "c", "Compact View", |
| 1340 | "s", "Simple View", |
| 1341 | "v", "Verbose View", |
| 1342 | "x", "Classic View", |
| 1343 | }; |
| 1344 | #if INTERFACE |
| 1345 | # define N_TIMELINE_VIEW_STYLE 6 |
| 1346 | #endif |
| 1347 | |
| 1348 | /* |
| 1349 | ** Add the select/option box to the timeline submenu that is used to |
| 1350 | ** set the ss= parameter that determines the viewing mode. |
| @@ -1670,11 +1701,11 @@ | |
| 1701 | ** ms=MATCHSTYLE Set tag name match algorithm. One of "exact", "glob", |
| 1702 | ** "like", or "regexp". |
| 1703 | ** u=USER Only show items associated with USER |
| 1704 | ** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', or 'all'. |
| 1705 | ** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar", |
| 1706 | ** x: "Classic". |
| 1707 | ** advm Use the "Advanced" or "Busy" menu design. |
| 1708 | ** ng No Graph. |
| 1709 | ** ncp Omit cherrypick merges |
| 1710 | ** nd Do not highlight the focus check-in |
| 1711 | ** nsm Omit the submenu |
| @@ -1893,11 +1924,13 @@ | |
| 1924 | || (bisectLocal && !g.perm.Setup) |
| 1925 | ){ |
| 1926 | login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); |
| 1927 | return; |
| 1928 | } |
| 1929 | if( zBefore || zCirca ){ |
| 1930 | if( robot_restrict("timelineX") ) return; |
| 1931 | } |
| 1932 | if( !bisectLocal ){ |
| 1933 | etag_check(ETAG_QUERY|ETAG_COOKIE|ETAG_DATA|ETAG_CONFIG, 0); |
| 1934 | } |
| 1935 | cookie_read_parameter("y","y"); |
| 1936 | zType = P("y"); |
| 1937 |
+1
-9
| --- src/zip.c | ||
| +++ src/zip.c | ||
| @@ -864,19 +864,11 @@ | ||
| 864 | 864 | if( fossil_strcmp(zOut,"")==0 || fossil_strcmp(zOut,"/dev/null")==0 ){ |
| 865 | 865 | zOut = 0; |
| 866 | 866 | } |
| 867 | 867 | |
| 868 | 868 | if( zName==0 ){ |
| 869 | - zName = db_text("default-name", | |
| 870 | - "SELECT replace(%Q,' ','_') " | |
| 871 | - " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) " | |
| 872 | - " || substr(blob.uuid, 1, 10)" | |
| 873 | - " FROM event, blob" | |
| 874 | - " WHERE event.objid=%d" | |
| 875 | - " AND blob.rid=%d", | |
| 876 | - db_get("project-name", "unnamed"), rid, rid | |
| 877 | - ); | |
| 869 | + zName = archive_base_name(rid); | |
| 878 | 870 | } |
| 879 | 871 | zip_of_checkin(eType, rid, zOut ? &zip : 0, |
| 880 | 872 | zName, pInclude, pExclude, listFlag); |
| 881 | 873 | glob_free(pInclude); |
| 882 | 874 | glob_free(pExclude); |
| 883 | 875 |
| --- src/zip.c | |
| +++ src/zip.c | |
| @@ -864,19 +864,11 @@ | |
| 864 | if( fossil_strcmp(zOut,"")==0 || fossil_strcmp(zOut,"/dev/null")==0 ){ |
| 865 | zOut = 0; |
| 866 | } |
| 867 | |
| 868 | if( zName==0 ){ |
| 869 | zName = db_text("default-name", |
| 870 | "SELECT replace(%Q,' ','_') " |
| 871 | " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) " |
| 872 | " || substr(blob.uuid, 1, 10)" |
| 873 | " FROM event, blob" |
| 874 | " WHERE event.objid=%d" |
| 875 | " AND blob.rid=%d", |
| 876 | db_get("project-name", "unnamed"), rid, rid |
| 877 | ); |
| 878 | } |
| 879 | zip_of_checkin(eType, rid, zOut ? &zip : 0, |
| 880 | zName, pInclude, pExclude, listFlag); |
| 881 | glob_free(pInclude); |
| 882 | glob_free(pExclude); |
| 883 |
| --- src/zip.c | |
| +++ src/zip.c | |
| @@ -864,19 +864,11 @@ | |
| 864 | if( fossil_strcmp(zOut,"")==0 || fossil_strcmp(zOut,"/dev/null")==0 ){ |
| 865 | zOut = 0; |
| 866 | } |
| 867 | |
| 868 | if( zName==0 ){ |
| 869 | zName = archive_base_name(rid); |
| 870 | } |
| 871 | zip_of_checkin(eType, rid, zOut ? &zip : 0, |
| 872 | zName, pInclude, pExclude, listFlag); |
| 873 | glob_free(pInclude); |
| 874 | glob_free(pExclude); |
| 875 |
+34
-2
| --- www/changes.wiki | ||
| +++ www/changes.wiki | ||
| @@ -2,16 +2,48 @@ | ||
| 2 | 2 | |
| 3 | 3 | <h2 id='v2_28'>Changes for version 2.28 (pending)</h2><ol> |
| 4 | 4 | <li> Improvements to [./antibot.wiki|anti-robot defenses]:<ol type="a"> |
| 5 | 5 | <li> The default configuration now allows robots to download any tarball |
| 6 | 6 | or similar, to better support of automated build systems. |
| 7 | - <li> No special tag "zipX" for the [/help/robot-restrict|robot-restrict] | |
| 7 | + <li> New special tag "zipX" for the [/help/robot-restrict|robot-restrict] | |
| 8 | 8 | setting blocks robot access to tarballs, but with exceptions to support |
| 9 | 9 | automated build systems. |
| 10 | + <li> Enhancements to the default value for the | |
| 11 | + [/help/robot-restrict|robot-restrict setting]. | |
| 10 | 12 | </ol> |
| 11 | 13 | <li> A drop-down menu of recent branches is now possible for the submenu, and |
| 12 | - is used in the code browser. | |
| 14 | + is used in the [/dir?ci=trunk|code browser]. | |
| 15 | + <li> Easier access to generated tarballs and ZIPs:<ol type="a"> | |
| 16 | + <li> When in the [/dir?ci=trunk|code browser] at the top-level, | |
| 17 | + a new "Download" submenu option is available to take the | |
| 18 | + user to a page where he can download a tarball or ZIP archive. | |
| 19 | + <li> New [/help/www/download|/download page] is available. When | |
| 20 | + configured using the new | |
| 21 | + [/help/suggested-downloads|suggested-downloads setting], a | |
| 22 | + link to [/download] named "Tarballs and ZIPs" appears in the | |
| 23 | + [/sitemap] and thus on the hamburger menu. | |
| 24 | + <li> The filenames for tarballs and ZIPs are now standardized to | |
| 25 | + include a timestamp and a hash prefix. | |
| 26 | + </ol> | |
| 27 | + <li> Timeline enhancements:<ol type="a"> | |
| 28 | + <li> A new "Simple" view is available. This is compromise between "Verbose" | |
| 29 | + and "Compact" that shows only the check-in hash rather than the full | |
| 30 | + detail section. There is an ellipsis that one can click on to see the | |
| 31 | + full detail text. | |
| 32 | + <li> The artifact hash in the detail section of each timeline entry is now | |
| 33 | + emphasized (controlled by CSS) to make it easier to locate amid all | |
| 34 | + the other text and links. | |
| 35 | + <li> When clicking on the ellipsis in "Compact" or "Simple" views, the ellipsis | |
| 36 | + is replaced by a left arrow ("←") which can be clicked to hide the extra | |
| 37 | + detail again. | |
| 38 | + <li> New setting [/help/timeline-mark-leaves|timeline-mark-leaves] controls | |
| 39 | + whether or not leaf check-ins are marked in the timeline. | |
| 40 | + <li> "No-graph" timelines (using the "ng" query parameter) now show | |
| 41 | + branch colors and bare check-in circles on the left. The check-in | |
| 42 | + circles appear, but no lines connecting them. | |
| 43 | + ([/timeline?ng|example]). | |
| 44 | + </ol> | |
| 13 | 45 | <li> The [/help/timeline|timeline command] is enhanced with the new |
| 14 | 46 | "<tt>-u|--for-user</tt>" option. |
| 15 | 47 | </ol> |
| 16 | 48 | |
| 17 | 49 | <h2 id='v2_27'>Changes for version 2.27 (2025-09-30)</h2><ol> |
| 18 | 50 |
| --- www/changes.wiki | |
| +++ www/changes.wiki | |
| @@ -2,16 +2,48 @@ | |
| 2 | |
| 3 | <h2 id='v2_28'>Changes for version 2.28 (pending)</h2><ol> |
| 4 | <li> Improvements to [./antibot.wiki|anti-robot defenses]:<ol type="a"> |
| 5 | <li> The default configuration now allows robots to download any tarball |
| 6 | or similar, to better support of automated build systems. |
| 7 | <li> No special tag "zipX" for the [/help/robot-restrict|robot-restrict] |
| 8 | setting blocks robot access to tarballs, but with exceptions to support |
| 9 | automated build systems. |
| 10 | </ol> |
| 11 | <li> A drop-down menu of recent branches is now possible for the submenu, and |
| 12 | is used in the code browser. |
| 13 | <li> The [/help/timeline|timeline command] is enhanced with the new |
| 14 | "<tt>-u|--for-user</tt>" option. |
| 15 | </ol> |
| 16 | |
| 17 | <h2 id='v2_27'>Changes for version 2.27 (2025-09-30)</h2><ol> |
| 18 |
| --- www/changes.wiki | |
| +++ www/changes.wiki | |
| @@ -2,16 +2,48 @@ | |
| 2 | |
| 3 | <h2 id='v2_28'>Changes for version 2.28 (pending)</h2><ol> |
| 4 | <li> Improvements to [./antibot.wiki|anti-robot defenses]:<ol type="a"> |
| 5 | <li> The default configuration now allows robots to download any tarball |
| 6 | or similar, to better support of automated build systems. |
| 7 | <li> New special tag "zipX" for the [/help/robot-restrict|robot-restrict] |
| 8 | setting blocks robot access to tarballs, but with exceptions to support |
| 9 | automated build systems. |
| 10 | <li> Enhancements to the default value for the |
| 11 | [/help/robot-restrict|robot-restrict setting]. |
| 12 | </ol> |
| 13 | <li> A drop-down menu of recent branches is now possible for the submenu, and |
| 14 | is used in the [/dir?ci=trunk|code browser]. |
| 15 | <li> Easier access to generated tarballs and ZIPs:<ol type="a"> |
| 16 | <li> When in the [/dir?ci=trunk|code browser] at the top-level, |
| 17 | a new "Download" submenu option is available to take the |
| 18 | user to a page where he can download a tarball or ZIP archive. |
| 19 | <li> New [/help/www/download|/download page] is available. When |
| 20 | configured using the new |
| 21 | [/help/suggested-downloads|suggested-downloads setting], a |
| 22 | link to [/download] named "Tarballs and ZIPs" appears in the |
| 23 | [/sitemap] and thus on the hamburger menu. |
| 24 | <li> The filenames for tarballs and ZIPs are now standardized to |
| 25 | include a timestamp and a hash prefix. |
| 26 | </ol> |
| 27 | <li> Timeline enhancements:<ol type="a"> |
| 28 | <li> A new "Simple" view is available. This is compromise between "Verbose" |
| 29 | and "Compact" that shows only the check-in hash rather than the full |
| 30 | detail section. There is an ellipsis that one can click on to see the |
| 31 | full detail text. |
| 32 | <li> The artifact hash in the detail section of each timeline entry is now |
| 33 | emphasized (controlled by CSS) to make it easier to locate amid all |
| 34 | the other text and links. |
| 35 | <li> When clicking on the ellipsis in "Compact" or "Simple" views, the ellipsis |
| 36 | is replaced by a left arrow ("←") which can be clicked to hide the extra |
| 37 | detail again. |
| 38 | <li> New setting [/help/timeline-mark-leaves|timeline-mark-leaves] controls |
| 39 | whether or not leaf check-ins are marked in the timeline. |
| 40 | <li> "No-graph" timelines (using the "ng" query parameter) now show |
| 41 | branch colors and bare check-in circles on the left. The check-in |
| 42 | circles appear, but no lines connecting them. |
| 43 | ([/timeline?ng|example]). |
| 44 | </ol> |
| 45 | <li> The [/help/timeline|timeline command] is enhanced with the new |
| 46 | "<tt>-u|--for-user</tt>" option. |
| 47 | </ol> |
| 48 | |
| 49 | <h2 id='v2_27'>Changes for version 2.27 (2025-09-30)</h2><ol> |
| 50 |