Fossil SCM

Add the /tarlist page with infrastructure changes to facilitate. Fix the "ng" (no-graph) query parameter so that it still colors timeline entries appropriately and still shows node circles, it just omits all the connecting lines.

drh 2025-10-17 11:18 trunk merge
Commit 9cc36d23543044690da4ea8cb6d9e547de0e225a821e1300524dac430dbb8aca
+31 -12
--- src/branch.c
+++ src/branch.c
@@ -1019,11 +1019,17 @@
10191019
/*
10201020
** This routine is called while for each check-in that is rendered by
10211021
** the timeline of a "brlist" page. Add some additional hyperlinks
10221022
** to the end of the line.
10231023
*/
1024
-static void brtimeline_extra(int rid){
1024
+static void brtimeline_extra(
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);
10251031
Stmt q;
10261032
if( !g.perm.Hyperlink ) return;
10271033
db_prepare(&q,
10281034
"SELECT substr(tagname,5) FROM tagxref, tag"
10291035
" WHERE tagxref.rid=%d"
@@ -1032,27 +1038,32 @@
10321038
" AND tag.tagname GLOB 'sym-*'",
10331039
rid
10341040
);
10351041
while( db_step(&q)==SQLITE_ROW ){
10361042
const char *zTagName = db_column_text(&q, 0);
1043
+#define OLD_STYLE 1
1044
+#if OLD_STYLE
10371045
@ %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
10381052
}
10391053
db_finalize(&q);
10401054
}
10411055
10421056
/*
10431057
** WEBPAGE: brtimeline
10441058
**
1045
-** Show a timeline of all branches
1059
+** List the first check of every branch, starting with the most recent
1060
+** and going backwards in time.
10461061
**
10471062
** Query parameters:
10481063
**
1049
-** ng No graph
1050
-** nohidden Hide check-ins with "hidden" tag
1051
-** onlyhidden Show only check-ins with "hidden" tag
1052
-** brbg Background color by branch name
1053
-** ubg Background color by user name
1064
+** ubg Color the graph by user, not by branch.
10541065
*/
10551066
void brtimeline_page(void){
10561067
Blob sql = empty_blob;
10571068
Stmt q;
10581069
int tmFlags; /* Timeline display flags */
@@ -1062,15 +1073,18 @@
10621073
login_check_credentials();
10631074
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
10641075
10651076
style_set_current_feature("branch");
10661077
style_header("Branches");
1067
- style_submenu_element("List", "brlist");
1078
+ style_submenu_element("Branch List", "brlist");
10681079
login_anonymous_available();
1080
+#if OLD_STYLE
10691081
timeline_ss_submenu();
1082
+#endif
10701083
cgi_check_for_malice();
1071
- @ <h2>The initial check-in for each branch:</h2>
1084
+ @ <h2>First check-in for every branch, starting with the most recent
1085
+ @ and going backwards in time.</h2>
10721086
blob_append(&sql, timeline_query_for_www(), -1);
10731087
blob_append_sql(&sql,
10741088
"AND blob.rid IN (SELECT rid FROM tagxref"
10751089
" WHERE tagtype>0 AND tagid=%d AND srcid!=0)", TAG_BRANCH);
10761090
if( fNoHidden || fOnlyHidden ){
@@ -1083,13 +1097,18 @@
10831097
db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql));
10841098
blob_reset(&sql);
10851099
/* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too
10861100
** many descenders to (off-screen) parents. */
10871101
tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL;
1088
- if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH;
1089
- if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR;
1090
- if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR;
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
+ }
10911110
www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, brtimeline_extra);
10921111
db_finalize(&q);
10931112
style_finish_page();
10941113
}
10951114
10961115
--- src/branch.c
+++ src/branch.c
@@ -1019,11 +1019,17 @@
1019 /*
1020 ** This routine is called while for each check-in that is rendered by
1021 ** the timeline of a "brlist" page. Add some additional hyperlinks
1022 ** to the end of the line.
1023 */
1024 static void brtimeline_extra(int rid){
 
 
 
 
 
 
1025 Stmt q;
1026 if( !g.perm.Hyperlink ) return;
1027 db_prepare(&q,
1028 "SELECT substr(tagname,5) FROM tagxref, tag"
1029 " WHERE tagxref.rid=%d"
@@ -1032,27 +1038,32 @@
1032 " AND tag.tagname GLOB 'sym-*'",
1033 rid
1034 );
1035 while( db_step(&q)==SQLITE_ROW ){
1036 const char *zTagName = db_column_text(&q, 0);
 
 
1037 @ %z(href("%R/timeline?r=%T",zTagName))[timeline]</a>
 
 
 
 
 
 
1038 }
1039 db_finalize(&q);
1040 }
1041
1042 /*
1043 ** WEBPAGE: brtimeline
1044 **
1045 ** Show a timeline of all branches
 
1046 **
1047 ** Query parameters:
1048 **
1049 ** ng No graph
1050 ** nohidden Hide check-ins with "hidden" tag
1051 ** onlyhidden Show only check-ins with "hidden" tag
1052 ** brbg Background color by branch name
1053 ** ubg Background color by user name
1054 */
1055 void brtimeline_page(void){
1056 Blob sql = empty_blob;
1057 Stmt q;
1058 int tmFlags; /* Timeline display flags */
@@ -1062,15 +1073,18 @@
1062 login_check_credentials();
1063 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1064
1065 style_set_current_feature("branch");
1066 style_header("Branches");
1067 style_submenu_element("List", "brlist");
1068 login_anonymous_available();
 
1069 timeline_ss_submenu();
 
1070 cgi_check_for_malice();
1071 @ <h2>The initial check-in for each branch:</h2>
 
1072 blob_append(&sql, timeline_query_for_www(), -1);
1073 blob_append_sql(&sql,
1074 "AND blob.rid IN (SELECT rid FROM tagxref"
1075 " WHERE tagtype>0 AND tagid=%d AND srcid!=0)", TAG_BRANCH);
1076 if( fNoHidden || fOnlyHidden ){
@@ -1083,13 +1097,18 @@
1083 db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql));
1084 blob_reset(&sql);
1085 /* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too
1086 ** many descenders to (off-screen) parents. */
1087 tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL;
1088 if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH;
1089 if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR;
1090 if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR;
 
 
 
 
 
1091 www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, brtimeline_extra);
1092 db_finalize(&q);
1093 style_finish_page();
1094 }
1095
1096
--- src/branch.c
+++ src/branch.c
@@ -1019,11 +1019,17 @@
1019 /*
1020 ** This routine is called while for each check-in that is rendered by
1021 ** the timeline of a "brlist" page. Add some additional hyperlinks
1022 ** to the end of the line.
1023 */
1024 static void brtimeline_extra(
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"
@@ -1032,27 +1038,32 @@
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 **
1059 ** List the first check of every branch, starting with the most recent
1060 ** and going backwards in time.
1061 **
1062 ** Query parameters:
1063 **
1064 ** ubg Color the graph by user, not by branch.
 
 
 
 
1065 */
1066 void brtimeline_page(void){
1067 Blob sql = empty_blob;
1068 Stmt q;
1069 int tmFlags; /* Timeline display flags */
@@ -1062,15 +1073,18 @@
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 ){
@@ -1083,13 +1097,18 @@
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 www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, brtimeline_extra);
1111 db_finalize(&q);
1112 style_finish_page();
1113 }
1114
1115
+2 -2
--- src/db.c
+++ src/db.c
@@ -3599,16 +3599,16 @@
35993599
36003600
/*
36013601
** Return true if the string zVal represents "true" (or "false").
36023602
*/
36033603
int is_truth(const char *zVal){
3604
- static const char *const azOn[] = { "on", "yes", "true", "1" };
3604
+ static const char *const azOn[] = { "on", "yes", "true" };
36053605
int i;
36063606
for(i=0; i<count(azOn); i++){
36073607
if( fossil_stricmp(zVal,azOn[i])==0 ) return 1;
36083608
}
3609
- return 0;
3609
+ return atoi(zVal);
36103610
}
36113611
int is_false(const char *zVal){
36123612
static const char *const azOff[] = { "off", "no", "false", "0" };
36133613
int i;
36143614
for(i=0; i<count(azOff); i++){
36153615
--- src/db.c
+++ src/db.c
@@ -3599,16 +3599,16 @@
3599
3600 /*
3601 ** Return true if the string zVal represents "true" (or "false").
3602 */
3603 int is_truth(const char *zVal){
3604 static const char *const azOn[] = { "on", "yes", "true", "1" };
3605 int i;
3606 for(i=0; i<count(azOn); i++){
3607 if( fossil_stricmp(zVal,azOn[i])==0 ) return 1;
3608 }
3609 return 0;
3610 }
3611 int is_false(const char *zVal){
3612 static const char *const azOff[] = { "off", "no", "false", "0" };
3613 int i;
3614 for(i=0; i<count(azOff); i++){
3615
--- src/db.c
+++ src/db.c
@@ -3599,16 +3599,16 @@
3599
3600 /*
3601 ** Return true if the string zVal represents "true" (or "false").
3602 */
3603 int is_truth(const char *zVal){
3604 static const char *const azOn[] = { "on", "yes", "true" };
3605 int i;
3606 for(i=0; i<count(azOn); i++){
3607 if( fossil_stricmp(zVal,azOn[i])==0 ) return 1;
3608 }
3609 return atoi(zVal);
3610 }
3611 int is_false(const char *zVal){
3612 static const char *const azOff[] = { "off", "no", "false", "0" };
3613 int i;
3614 for(i=0; i<count(azOff); i++){
3615
+21
--- src/setup.c
+++ src/setup.c
@@ -1425,10 +1425,31 @@
14251425
@ <p>The default value is blank, meaning no added entries.
14261426
@ (Property: sitemap-extra)
14271427
@ <p>
14281428
textarea_attribute("Custom Sitemap Entries", 8, 80,
14291429
"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);
14301451
@ <hr>
14311452
@ <p><input type="submit" name="submit" value="Apply Changes"></p>
14321453
@ </div></form>
14331454
db_end_transaction(0);
14341455
style_finish_page();
14351456
--- src/setup.c
+++ src/setup.c
@@ -1425,10 +1425,31 @@
1425 @ <p>The default value is blank, meaning no added entries.
1426 @ (Property: sitemap-extra)
1427 @ <p>
1428 textarea_attribute("Custom Sitemap Entries", 8, 80,
1429 "sitemap-extra", "smextra", "", 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1430 @ <hr>
1431 @ <p><input type="submit" name="submit" value="Apply Changes"></p>
1432 @ </div></form>
1433 db_end_transaction(0);
1434 style_finish_page();
1435
--- src/setup.c
+++ src/setup.c
@@ -1425,10 +1425,31 @@
1425 @ <p>The default value is blank, meaning no added entries.
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/sitemap.c
+++ src/sitemap.c
@@ -132,10 +132,13 @@
132132
@ <li>%z(href("%R/uvlist"))Unversioned Files</a>
133133
if( g.perm.Write && zEditGlob[0]!=0 ){
134134
@ <li>%z(href("%R/fileedit"))On-line File Editor</li>
135135
}
136136
@ </ul>
137
+ }
138
+ if( g.perm.Zip && db_get_boolean("suggested-tarlist",1)!=0 ){
139
+ @ <li>%z(href("%R/tarlist"))Tarballs and ZIPs</a>
137140
}
138141
if( g.perm.Read ){
139142
@ <li>%z(href("%R/timeline"))Project Timeline</a>
140143
@ <ul>
141144
@ <li>%z(href("%R/reports"))Activity Reports</a></li>
142145
--- src/sitemap.c
+++ src/sitemap.c
@@ -132,10 +132,13 @@
132 @ <li>%z(href("%R/uvlist"))Unversioned Files</a>
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.Read ){
139 @ <li>%z(href("%R/timeline"))Project Timeline</a>
140 @ <ul>
141 @ <li>%z(href("%R/reports"))Activity Reports</a></li>
142
--- src/sitemap.c
+++ src/sitemap.c
@@ -132,10 +132,13 @@
132 @ <li>%z(href("%R/uvlist"))Unversioned Files</a>
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
+139
--- src/tar.c
+++ src/tar.c
@@ -931,5 +931,144 @@
931931
g.zOpenRevision = 0;
932932
blob_reset(&cacheKey);
933933
cgi_set_content(&tarball);
934934
cgi_set_content_type("application/x-compressed");
935935
}
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&nbsp;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
+}
9361075
--- src/tar.c
+++ src/tar.c
@@ -931,5 +931,144 @@
931 g.zOpenRevision = 0;
932 blob_reset(&cacheKey);
933 cgi_set_content(&tarball);
934 cgi_set_content_type("application/x-compressed");
935 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
936
--- src/tar.c
+++ src/tar.c
@@ -931,5 +931,144 @@
931 g.zOpenRevision = 0;
932 blob_reset(&cacheKey);
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&nbsp;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
+199 -137
--- src/timeline.c
+++ src/timeline.c
@@ -169,10 +169,165 @@
169169
sqlite3_result_text(context, pPost->zWiki, -1, SQLITE_TRANSIENT);
170170
manifest_destroy(pPost);
171171
}
172172
}
173173
174
+
175
+/*
176
+** This routine generates the default "extra" text after the description
177
+** in a timeline.
178
+**
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
+){
190
+ int rid = db_column_int(pQuery, 0);
191
+ const char *zUuid = db_column_text(pQuery, 1);
192
+ const char *zDate = db_column_text(pQuery, 2);
193
+ const char *zType = db_column_text(pQuery, 7);
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:&nbsp;%z%S</a> ",
217
+ href("%R/info/%!S",zUuid),zUuid);
218
+ }else if( zType[0]=='e' && tagid ){
219
+ cgi_printf("technote:&nbsp;");
220
+ hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
221
+ }else{
222
+ cgi_printf("artifact:&nbsp;%z%S</a> ",
223
+ href("%R/info/%!S",zUuid),zUuid);
224
+ }
225
+ }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t'
226
+ || zType[0]=='n' || zType[0]=='f'){
227
+ cgi_printf("artifact:&nbsp;%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);
235
+ }else{
236
+ zLink = mprintf("%R/timeline?u=%h&c=%t&y=a&vfx", zDispUser, zDate);
237
+ }
238
+ cgi_printf("user:&nbsp;%z%h</a>", href("%z",zLink), zDispUser);
239
+ }else{
240
+ cgi_printf("user:&nbsp;%h", zDispUser);
241
+ }
242
+
243
+ /* Generate the "tags: TAGLIST" at the end of the comment, together
244
+ ** with hyperlinks to the tag list.
245
+ */
246
+ if( zTagList && zTagList[0]==0 ) zTagList = 0;
247
+ if( zTagList ){
248
+ if( g.perm.Hyperlink ){
249
+ int i;
250
+ const char *z = zTagList;
251
+ Blob links;
252
+ blob_zero(&links);
253
+ while( z && z[0] ){
254
+ for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){}
255
+ if( zThisTag==0 || memcmp(z, zThisTag, i)!=0 || zThisTag[i]!=0 ){
256
+ blob_appendf(&links,
257
+ "%z%#h</a>%.2s",
258
+ href("%R/timeline?r=%#t&c=%t",i,z,zDate), i,z, &z[i]
259
+ );
260
+ }else{
261
+ blob_appendf(&links, "%#h", i+2, z);
262
+ }
263
+ if( z[i]==0 ) break;
264
+ z += i+2;
265
+ }
266
+ cgi_printf(" tags:&nbsp;%s", blob_str(&links));
267
+ blob_reset(&links);
268
+ }else{
269
+ cgi_printf(" tags:&nbsp;%h", zTagList);
270
+ }
271
+ }
272
+
273
+ if( tmFlags & TIMELINE_SHOWRID ){
274
+ int srcId = delta_source_rid(rid);
275
+ if( srcId ){
276
+ cgi_printf(" id:&nbsp;%z%d&larr;%d</a>",
277
+ href("%R/deltachain/%d",rid), rid, srcId);
278
+ }else{
279
+ cgi_printf(" id:&nbsp;%z%d</a>",
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:&nbsp;%z%S</a>",
291
+ href("%R/technote/%t",zId), zId);
292
+ }else{
293
+ cgi_printf(" hash:&nbsp;%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
313
+**
314
+** If enabled, check-in comments displayed on the timeline are truncated
315
+** at the first blank line of the comment text. The comment text after
316
+** the first blank line is only seen in the /info or similar pages that
317
+** show details about the check-in.
318
+*/
319
+/*
320
+** SETTING: timeline-tslink-info boolean default=off
321
+**
322
+** The hyperlink on the timestamp associated with each timeline entry,
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
+
174329
175330
/*
176331
** Output a timeline in the web format given a query. The query
177332
** should return these columns:
178333
**
@@ -194,11 +349,11 @@
194349
const char *zThisUser, /* Suppress links to this user */
195350
const char *zThisTag, /* Suppress links to this tag */
196351
Matcher *pLeftBranch, /* Comparison function to use for zLeftBranch */
197352
int selectedRid, /* Highlight the line with this RID value or zero */
198353
int secondRid, /* Secondary highlight (or zero) */
199
- void (*xExtra)(int) /* Routine to call on each line of display */
354
+ void (*xExtra)(Stmt*,int,const char*,const char*) /* generate "extra" text */
200355
){
201356
int mxWikiLen;
202357
Blob comment;
203358
int prevTagid = 0;
204359
int suppressCnt = 0;
@@ -214,35 +369,21 @@
214369
const char *zStyle; /* Sub-name for classes for the style */
215370
const char *zDateFmt;
216371
int iTableId = timeline_tableid();
217372
int bTimestampLinksToInfo; /* True if timestamp hyperlinks go to the /info
218373
** page rather than the /timeline page */
374
+ char *zMainBranch = db_get("main-branch","trunk");
375
+
219376
220377
if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){
221378
vid = db_lget_int("checkout", 0);
222379
}
380
+ if( xExtra==0 ) xExtra = defaultExtra;
223381
zPrevDate[0] = 0;
224382
mxWikiLen = db_get_int("timeline-max-comment", 0);
225383
dateFormat = db_get_int("timeline-date-format", 0);
226
-/*
227
-** SETTING: timeline-truncate-at-blank boolean default=off
228
-**
229
-** If enabled, check-in comments displayed on the timeline are truncated
230
-** at the first blank line of the comment text. The comment text after
231
-** the first blank line is only seen in the /info or similar pages that
232
-** show details about the check-in.
233
-*/
234384
bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0);
235
-/*
236
-** SETTING: timeline-tslink-info boolean default=off
237
-**
238
-** The hyperlink on the timestamp associated with each timeline entry,
239
-** on the far left-hand side of the screen, normally targets another
240
-** /timeline page that shows the entry in context. However, if this
241
-** option is turned on, that hyperlink targets the /info page showing
242
-** the details of the entry.
243
-*/
244385
bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0);
245386
if( (tmFlags & TIMELINE_VIEWS)==0 ){
246387
tmFlags |= timeline_ss_cookie();
247388
}
248389
if( tmFlags & TIMELINE_COLUMNAR ){
@@ -256,13 +397,11 @@
256397
}else{
257398
zStyle = "Modern";
258399
}
259400
zDateFmt = P("datefmt");
260401
if( zDateFmt ) dateFormat = atoi(zDateFmt);
261
- if( tmFlags & TIMELINE_GRAPH ){
262
- pGraph = graph_init();
263
- }
402
+ pGraph = graph_init();
264403
if( (tmFlags & TIMELINE_CHPICK)!=0
265404
&& !db_table_exists("repository","cherrypick")
266405
){
267406
tmFlags &= ~TIMELINE_CHPICK;
268407
}
@@ -275,23 +414,20 @@
275414
int isLeaf = db_column_int(pQuery, 5);
276415
const char *zBgClr = db_column_text(pQuery, 6);
277416
const char *zDate = db_column_text(pQuery, 2);
278417
const char *zType = db_column_text(pQuery, 7);
279418
const char *zUser = db_column_text(pQuery, 4);
280
- const char *zTagList = db_column_text(pQuery, 8);
281419
int tagid = db_column_int(pQuery, 9);
282
- const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
283420
char *zBr = 0; /* Branch */
284421
int commentColumn = 3; /* Column containing comment text */
285422
int modPending; /* Pending moderation */
286423
char *zDateLink; /* URL for the link on the timestamp */
287424
int drawDetailEllipsis; /* True to show ellipsis in place of detail */
288425
int gidx = 0; /* Graph row identifier */
289426
int isSelectedOrCurrent = 0; /* True if current row is selected */
290427
const char *zExtraClass = "";
291428
char zTime[20];
292
- char *zMainBranch = db_get("main-branch","trunk");
293429
294430
if( zDate==0 ){
295431
zDate = "YYYY-MM-DD HH:MM:SS"; /* Something wrong with the repo */
296432
}
297433
modPending = moderation_pending(rid);
@@ -447,32 +583,34 @@
447583
if( zType[0]=='c' && pGraph ){
448584
int nParent = 0;
449585
int nCherrypick = 0;
450586
GraphRowId aParent[GR_MAX_RAIL];
451587
static Stmt qparent;
452
- db_static_prepare(&qparent,
453
- "SELECT pid FROM plink"
454
- " WHERE cid=:rid AND pid NOT IN phantom"
455
- " ORDER BY isprim DESC /*sort*/"
456
- );
457
- db_bind_int(&qparent, ":rid", rid);
458
- while( db_step(&qparent)==SQLITE_ROW && nParent<count(aParent) ){
459
- aParent[nParent++] = db_column_int(&qparent, 0);
460
- }
461
- db_reset(&qparent);
462
- if( (tmFlags & TIMELINE_CHPICK)!=0 && nParent>0 ){
463
- static Stmt qcherrypick;
464
- db_static_prepare(&qcherrypick,
465
- "SELECT parentid FROM cherrypick"
466
- " WHERE childid=:rid AND parentid NOT IN phantom"
467
- );
468
- db_bind_int(&qcherrypick, ":rid", rid);
469
- while( db_step(&qcherrypick)==SQLITE_ROW && nParent<count(aParent) ){
470
- aParent[nParent++] = db_column_int(&qcherrypick, 0);
471
- nCherrypick++;
472
- }
473
- db_reset(&qcherrypick);
588
+ if( tmFlags & TIMELINE_GRAPH ){
589
+ db_static_prepare(&qparent,
590
+ "SELECT pid FROM plink"
591
+ " WHERE cid=:rid AND pid NOT IN phantom"
592
+ " ORDER BY isprim DESC /*sort*/"
593
+ );
594
+ db_bind_int(&qparent, ":rid", rid);
595
+ while( db_step(&qparent)==SQLITE_ROW && nParent<count(aParent) ){
596
+ aParent[nParent++] = db_column_int(&qparent, 0);
597
+ }
598
+ db_reset(&qparent);
599
+ if( (tmFlags & TIMELINE_CHPICK)!=0 && nParent>0 ){
600
+ static Stmt qcherrypick;
601
+ db_static_prepare(&qcherrypick,
602
+ "SELECT parentid FROM cherrypick"
603
+ " WHERE childid=:rid AND parentid NOT IN phantom"
604
+ );
605
+ db_bind_int(&qcherrypick, ":rid", rid);
606
+ while( db_step(&qcherrypick)==SQLITE_ROW && nParent<count(aParent) ){
607
+ aParent[nParent++] = db_column_int(&qcherrypick, 0);
608
+ nCherrypick++;
609
+ }
610
+ db_reset(&qcherrypick);
611
+ }
474612
}
475613
gidx = graph_add_row(pGraph, rid, nParent, nCherrypick, aParent,
476614
zBr, zBgClr, zUuid,
477615
isLeaf ? isLeaf + 2 * has_closed_tag(rid) : 0);
478616
@ <div id="m%d(gidx)" class="tl-nodemark"></div>
@@ -611,11 +749,11 @@
611749
}
612750
613751
@ </span>
614752
blob_reset(&comment);
615753
616
- /* Generate extra information and hyperlinks to follow the comment.
754
+ /* Generate extra information and hyperlinks that follow the comment.
617755
** Example: "(check-in: [abcdefg], user: drh, tags: trunk)"
618756
*/
619757
if( drawDetailEllipsis ){
620758
@ <span class='timelineEllipsis' id='ellipsis-%d(rid)' \
621759
@ data-id='%d(rid)'>...</span>
@@ -629,101 +767,17 @@
629767
}
630768
if( tmFlags & TIMELINE_COMPACT ){
631769
cgi_printf("<span class='clutter' id='detail-%d'>",rid);
632770
}
633771
cgi_printf("<span class='timeline%sDetail'>", zStyle);
634
- if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){
635
- cgi_printf("(");
636
- }
637
-
638
- if( (tmFlags & TIMELINE_CLASSIC)==0 ){
639
- if( zType[0]=='c' ){
640
- if( isLeaf ){
641
- if( has_closed_tag(rid) ){
642
- @ <span class='timelineLeaf'>Closed-Leaf</span>
643
- }else{
644
- @ <span class='timelineLeaf'>Leaf</span>
645
- }
646
- }
647
- cgi_printf("check-in:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
648
- }else if( zType[0]=='e' && tagid ){
649
- cgi_printf("technote:&nbsp;");
650
- hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
651
- }else{
652
- cgi_printf("artifact:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
653
- }
654
- }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t'
655
- || zType[0]=='n' || zType[0]=='f'){
656
- cgi_printf("artifact:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
657
- }
658
-
659
- if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){
660
- char *zLink;
661
- if( zType[0]!='f' || (tmFlags & TIMELINE_FORUMTXT)==0 ){
662
- zLink = mprintf("%R/timeline?u=%h&c=%t&y=a", zDispUser, zDate);
663
- }else{
664
- zLink = mprintf("%R/timeline?u=%h&c=%t&y=a&vfx", zDispUser, zDate);
665
- }
666
- cgi_printf("user:&nbsp;%z%h</a>", href("%z",zLink), zDispUser);
667
- }else{
668
- cgi_printf("user:&nbsp;%h", zDispUser);
669
- }
670
-
671
- /* Generate the "tags: TAGLIST" at the end of the comment, together
672
- ** with hyperlinks to the tag list.
673
- */
674
- if( zTagList && zTagList[0]==0 ) zTagList = 0;
675
- if( zTagList ){
676
- if( g.perm.Hyperlink ){
677
- int i;
678
- const char *z = zTagList;
679
- Blob links;
680
- blob_zero(&links);
681
- while( z && z[0] ){
682
- for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){}
683
- if( zThisTag==0 || memcmp(z, zThisTag, i)!=0 || zThisTag[i]!=0 ){
684
- blob_appendf(&links,
685
- "%z%#h</a>%.2s",
686
- href("%R/timeline?r=%#t&c=%t",i,z,zDate), i,z, &z[i]
687
- );
688
- }else{
689
- blob_appendf(&links, "%#h", i+2, z);
690
- }
691
- if( z[i]==0 ) break;
692
- z += i+2;
693
- }
694
- cgi_printf(" tags:&nbsp;%s", blob_str(&links));
695
- blob_reset(&links);
696
- }else{
697
- cgi_printf(" tags:&nbsp;%h", zTagList);
698
- }
699
- }
700
-
701
- if( tmFlags & TIMELINE_SHOWRID ){
702
- int srcId = delta_source_rid(rid);
703
- if( srcId ){
704
- cgi_printf(" id:&nbsp;%z%d&larr;%d</a>",
705
- href("%R/deltachain/%d",rid), rid, srcId);
706
- }else{
707
- cgi_printf(" id:&nbsp;%z%d</a>",
708
- href("%R/deltachain/%d",rid), rid);
709
- }
710
- }
711
- tag_private_status(rid);
712
- if( xExtra ){
713
- xExtra(rid);
714
- }
715
- /* End timelineDetail */
716
- if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){
717
- cgi_printf(")");
718
- }
772
+ xExtra(pQuery, tmFlags, zThisUser, zThisTag);
719773
if( tmFlags & TIMELINE_COMPACT ){
720774
@ </span></span>
721775
}else{
722776
@ </span>
723777
}
724
-
778
+
725779
/* Generate the file-change list if requested */
726780
if( (tmFlags & (TIMELINE_FCHANGES|TIMELINE_FRENAMES))!=0
727781
&& zType[0]=='c' && g.perm.Hyperlink
728782
){
729783
int inUl = 0;
@@ -909,11 +963,13 @@
909963
int omitDescenders; /* True to omit descenders */
910964
int scrollToSelect; /* True to scroll to the selection */
911965
int dwellTimeout; /* Milliseconds to wait for tooltips to show */
912966
int closeTimeout; /* Milliseconds to wait for tooltips to close */
913967
u8 *aiMap; /* The rail map */
968
+ u8 bNoGraph; /* True to show a minimal graph */
914969
970
+ bNoGraph = (tmFlags & TIMELINE_GRAPH)==0;
915971
iRailPitch = atoi(PD("railpitch","0"));
916972
showArrowheads = skin_detail_boolean("timeline-arrowheads");
917973
circleNodes = skin_detail_boolean("timeline-circle-nodes");
918974
colorGraph = skin_detail_boolean("timeline-color-graph-lines");
919975
iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0;
@@ -931,11 +987,11 @@
931987
@ "nomo": %d(PB("nomo")),
932988
@ "iTopRow": %d(iTopRow),
933989
@ "omitDescenders": %d(omitDescenders),
934990
@ "fileDiff": %d(fileDiff),
935991
@ "scrollToSelect": %d(scrollToSelect),
936
- @ "nrail": %d(pGraph->mxRail+1),
992
+ @ "nrail": %d(bNoGraph?1:pGraph->mxRail+1),
937993
@ "baseUrl": "%R",
938994
@ "dwellTimeout": %d(dwellTimeout),
939995
@ "closeTimeout": %d(closeTimeout),
940996
@ "hashDigits": %d(hash_digits(1)),
941997
@ "bottomRowId": "btm-%d(iTableId)",
@@ -993,12 +1049,16 @@
9931049
aiMap = pGraph->aiRailMap;
9941050
for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
9951051
int k = 0;
9961052
cgi_printf("{\"id\":%d,", pRow->idx);
9971053
cgi_printf("\"bg\":\"%s\",", pRow->zBgClr);
998
- cgi_printf("\"r\":%d,", pRow->iRail>=0 ? aiMap[pRow->iRail] : -1);
999
- if( pRow->bDescender ){
1054
+ if( bNoGraph ){
1055
+ cgi_printf("\"r\":0,"); /* Chng to ":-1" to omit node circles */
1056
+ }else{
1057
+ cgi_printf("\"r\":%d,", pRow->iRail>=0 ? aiMap[pRow->iRail] : -1);
1058
+ }
1059
+ if( pRow->bDescender && !bNoGraph ){
10001060
cgi_printf("\"d\":%d,", pRow->bDescender);
10011061
}
10021062
if( pRow->mergeOut>=0 ){
10031063
cgi_printf("\"mo\":%d,", aiMap[pRow->mergeOut]);
10041064
if( pRow->mergeUpto==0 ) pRow->mergeUpto = pRow->idx;
@@ -1005,11 +1065,13 @@
10051065
cgi_printf("\"mu\":%d,", pRow->mergeUpto);
10061066
if( pRow->cherrypickUpto>0 && pRow->cherrypickUpto<=pRow->mergeUpto ){
10071067
cgi_printf("\"cu\":%d,", pRow->cherrypickUpto);
10081068
}
10091069
}
1010
- if( pRow->isStepParent ){
1070
+ if( bNoGraph ){
1071
+ cgi_printf("\"u\":-1,");
1072
+ }else if( pRow->isStepParent ){
10111073
cgi_printf("\"sb\":%d,", pRow->aiRiser[pRow->iRail]);
10121074
}else{
10131075
cgi_printf("\"u\":%d,", pRow->aiRiser[pRow->iRail]);
10141076
}
10151077
k = 0;
10161078
--- src/timeline.c
+++ src/timeline.c
@@ -169,10 +169,165 @@
169 sqlite3_result_text(context, pPost->zWiki, -1, SQLITE_TRANSIENT);
170 manifest_destroy(pPost);
171 }
172 }
173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
175 /*
176 ** Output a timeline in the web format given a query. The query
177 ** should return these columns:
178 **
@@ -194,11 +349,11 @@
194 const char *zThisUser, /* Suppress links to this user */
195 const char *zThisTag, /* Suppress links to this tag */
196 Matcher *pLeftBranch, /* Comparison function to use for zLeftBranch */
197 int selectedRid, /* Highlight the line with this RID value or zero */
198 int secondRid, /* Secondary highlight (or zero) */
199 void (*xExtra)(int) /* Routine to call on each line of display */
200 ){
201 int mxWikiLen;
202 Blob comment;
203 int prevTagid = 0;
204 int suppressCnt = 0;
@@ -214,35 +369,21 @@
214 const char *zStyle; /* Sub-name for classes for the style */
215 const char *zDateFmt;
216 int iTableId = timeline_tableid();
217 int bTimestampLinksToInfo; /* True if timestamp hyperlinks go to the /info
218 ** page rather than the /timeline page */
 
 
219
220 if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){
221 vid = db_lget_int("checkout", 0);
222 }
 
223 zPrevDate[0] = 0;
224 mxWikiLen = db_get_int("timeline-max-comment", 0);
225 dateFormat = db_get_int("timeline-date-format", 0);
226 /*
227 ** SETTING: timeline-truncate-at-blank boolean default=off
228 **
229 ** If enabled, check-in comments displayed on the timeline are truncated
230 ** at the first blank line of the comment text. The comment text after
231 ** the first blank line is only seen in the /info or similar pages that
232 ** show details about the check-in.
233 */
234 bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0);
235 /*
236 ** SETTING: timeline-tslink-info boolean default=off
237 **
238 ** The hyperlink on the timestamp associated with each timeline entry,
239 ** on the far left-hand side of the screen, normally targets another
240 ** /timeline page that shows the entry in context. However, if this
241 ** option is turned on, that hyperlink targets the /info page showing
242 ** the details of the entry.
243 */
244 bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0);
245 if( (tmFlags & TIMELINE_VIEWS)==0 ){
246 tmFlags |= timeline_ss_cookie();
247 }
248 if( tmFlags & TIMELINE_COLUMNAR ){
@@ -256,13 +397,11 @@
256 }else{
257 zStyle = "Modern";
258 }
259 zDateFmt = P("datefmt");
260 if( zDateFmt ) dateFormat = atoi(zDateFmt);
261 if( tmFlags & TIMELINE_GRAPH ){
262 pGraph = graph_init();
263 }
264 if( (tmFlags & TIMELINE_CHPICK)!=0
265 && !db_table_exists("repository","cherrypick")
266 ){
267 tmFlags &= ~TIMELINE_CHPICK;
268 }
@@ -275,23 +414,20 @@
275 int isLeaf = db_column_int(pQuery, 5);
276 const char *zBgClr = db_column_text(pQuery, 6);
277 const char *zDate = db_column_text(pQuery, 2);
278 const char *zType = db_column_text(pQuery, 7);
279 const char *zUser = db_column_text(pQuery, 4);
280 const char *zTagList = db_column_text(pQuery, 8);
281 int tagid = db_column_int(pQuery, 9);
282 const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
283 char *zBr = 0; /* Branch */
284 int commentColumn = 3; /* Column containing comment text */
285 int modPending; /* Pending moderation */
286 char *zDateLink; /* URL for the link on the timestamp */
287 int drawDetailEllipsis; /* True to show ellipsis in place of detail */
288 int gidx = 0; /* Graph row identifier */
289 int isSelectedOrCurrent = 0; /* True if current row is selected */
290 const char *zExtraClass = "";
291 char zTime[20];
292 char *zMainBranch = db_get("main-branch","trunk");
293
294 if( zDate==0 ){
295 zDate = "YYYY-MM-DD HH:MM:SS"; /* Something wrong with the repo */
296 }
297 modPending = moderation_pending(rid);
@@ -447,32 +583,34 @@
447 if( zType[0]=='c' && pGraph ){
448 int nParent = 0;
449 int nCherrypick = 0;
450 GraphRowId aParent[GR_MAX_RAIL];
451 static Stmt qparent;
452 db_static_prepare(&qparent,
453 "SELECT pid FROM plink"
454 " WHERE cid=:rid AND pid NOT IN phantom"
455 " ORDER BY isprim DESC /*sort*/"
456 );
457 db_bind_int(&qparent, ":rid", rid);
458 while( db_step(&qparent)==SQLITE_ROW && nParent<count(aParent) ){
459 aParent[nParent++] = db_column_int(&qparent, 0);
460 }
461 db_reset(&qparent);
462 if( (tmFlags & TIMELINE_CHPICK)!=0 && nParent>0 ){
463 static Stmt qcherrypick;
464 db_static_prepare(&qcherrypick,
465 "SELECT parentid FROM cherrypick"
466 " WHERE childid=:rid AND parentid NOT IN phantom"
467 );
468 db_bind_int(&qcherrypick, ":rid", rid);
469 while( db_step(&qcherrypick)==SQLITE_ROW && nParent<count(aParent) ){
470 aParent[nParent++] = db_column_int(&qcherrypick, 0);
471 nCherrypick++;
472 }
473 db_reset(&qcherrypick);
 
 
474 }
475 gidx = graph_add_row(pGraph, rid, nParent, nCherrypick, aParent,
476 zBr, zBgClr, zUuid,
477 isLeaf ? isLeaf + 2 * has_closed_tag(rid) : 0);
478 @ <div id="m%d(gidx)" class="tl-nodemark"></div>
@@ -611,11 +749,11 @@
611 }
612
613 @ </span>
614 blob_reset(&comment);
615
616 /* Generate extra information and hyperlinks to follow the comment.
617 ** Example: "(check-in: [abcdefg], user: drh, tags: trunk)"
618 */
619 if( drawDetailEllipsis ){
620 @ <span class='timelineEllipsis' id='ellipsis-%d(rid)' \
621 @ data-id='%d(rid)'>...</span>
@@ -629,101 +767,17 @@
629 }
630 if( tmFlags & TIMELINE_COMPACT ){
631 cgi_printf("<span class='clutter' id='detail-%d'>",rid);
632 }
633 cgi_printf("<span class='timeline%sDetail'>", zStyle);
634 if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){
635 cgi_printf("(");
636 }
637
638 if( (tmFlags & TIMELINE_CLASSIC)==0 ){
639 if( zType[0]=='c' ){
640 if( isLeaf ){
641 if( has_closed_tag(rid) ){
642 @ <span class='timelineLeaf'>Closed-Leaf</span>
643 }else{
644 @ <span class='timelineLeaf'>Leaf</span>
645 }
646 }
647 cgi_printf("check-in:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
648 }else if( zType[0]=='e' && tagid ){
649 cgi_printf("technote:&nbsp;");
650 hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
651 }else{
652 cgi_printf("artifact:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
653 }
654 }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t'
655 || zType[0]=='n' || zType[0]=='f'){
656 cgi_printf("artifact:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
657 }
658
659 if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){
660 char *zLink;
661 if( zType[0]!='f' || (tmFlags & TIMELINE_FORUMTXT)==0 ){
662 zLink = mprintf("%R/timeline?u=%h&c=%t&y=a", zDispUser, zDate);
663 }else{
664 zLink = mprintf("%R/timeline?u=%h&c=%t&y=a&vfx", zDispUser, zDate);
665 }
666 cgi_printf("user:&nbsp;%z%h</a>", href("%z",zLink), zDispUser);
667 }else{
668 cgi_printf("user:&nbsp;%h", zDispUser);
669 }
670
671 /* Generate the "tags: TAGLIST" at the end of the comment, together
672 ** with hyperlinks to the tag list.
673 */
674 if( zTagList && zTagList[0]==0 ) zTagList = 0;
675 if( zTagList ){
676 if( g.perm.Hyperlink ){
677 int i;
678 const char *z = zTagList;
679 Blob links;
680 blob_zero(&links);
681 while( z && z[0] ){
682 for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){}
683 if( zThisTag==0 || memcmp(z, zThisTag, i)!=0 || zThisTag[i]!=0 ){
684 blob_appendf(&links,
685 "%z%#h</a>%.2s",
686 href("%R/timeline?r=%#t&c=%t",i,z,zDate), i,z, &z[i]
687 );
688 }else{
689 blob_appendf(&links, "%#h", i+2, z);
690 }
691 if( z[i]==0 ) break;
692 z += i+2;
693 }
694 cgi_printf(" tags:&nbsp;%s", blob_str(&links));
695 blob_reset(&links);
696 }else{
697 cgi_printf(" tags:&nbsp;%h", zTagList);
698 }
699 }
700
701 if( tmFlags & TIMELINE_SHOWRID ){
702 int srcId = delta_source_rid(rid);
703 if( srcId ){
704 cgi_printf(" id:&nbsp;%z%d&larr;%d</a>",
705 href("%R/deltachain/%d",rid), rid, srcId);
706 }else{
707 cgi_printf(" id:&nbsp;%z%d</a>",
708 href("%R/deltachain/%d",rid), rid);
709 }
710 }
711 tag_private_status(rid);
712 if( xExtra ){
713 xExtra(rid);
714 }
715 /* End timelineDetail */
716 if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){
717 cgi_printf(")");
718 }
719 if( tmFlags & TIMELINE_COMPACT ){
720 @ </span></span>
721 }else{
722 @ </span>
723 }
724
725 /* Generate the file-change list if requested */
726 if( (tmFlags & (TIMELINE_FCHANGES|TIMELINE_FRENAMES))!=0
727 && zType[0]=='c' && g.perm.Hyperlink
728 ){
729 int inUl = 0;
@@ -909,11 +963,13 @@
909 int omitDescenders; /* True to omit descenders */
910 int scrollToSelect; /* True to scroll to the selection */
911 int dwellTimeout; /* Milliseconds to wait for tooltips to show */
912 int closeTimeout; /* Milliseconds to wait for tooltips to close */
913 u8 *aiMap; /* The rail map */
 
914
 
915 iRailPitch = atoi(PD("railpitch","0"));
916 showArrowheads = skin_detail_boolean("timeline-arrowheads");
917 circleNodes = skin_detail_boolean("timeline-circle-nodes");
918 colorGraph = skin_detail_boolean("timeline-color-graph-lines");
919 iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0;
@@ -931,11 +987,11 @@
931 @ "nomo": %d(PB("nomo")),
932 @ "iTopRow": %d(iTopRow),
933 @ "omitDescenders": %d(omitDescenders),
934 @ "fileDiff": %d(fileDiff),
935 @ "scrollToSelect": %d(scrollToSelect),
936 @ "nrail": %d(pGraph->mxRail+1),
937 @ "baseUrl": "%R",
938 @ "dwellTimeout": %d(dwellTimeout),
939 @ "closeTimeout": %d(closeTimeout),
940 @ "hashDigits": %d(hash_digits(1)),
941 @ "bottomRowId": "btm-%d(iTableId)",
@@ -993,12 +1049,16 @@
993 aiMap = pGraph->aiRailMap;
994 for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
995 int k = 0;
996 cgi_printf("{\"id\":%d,", pRow->idx);
997 cgi_printf("\"bg\":\"%s\",", pRow->zBgClr);
998 cgi_printf("\"r\":%d,", pRow->iRail>=0 ? aiMap[pRow->iRail] : -1);
999 if( pRow->bDescender ){
 
 
 
 
1000 cgi_printf("\"d\":%d,", pRow->bDescender);
1001 }
1002 if( pRow->mergeOut>=0 ){
1003 cgi_printf("\"mo\":%d,", aiMap[pRow->mergeOut]);
1004 if( pRow->mergeUpto==0 ) pRow->mergeUpto = pRow->idx;
@@ -1005,11 +1065,13 @@
1005 cgi_printf("\"mu\":%d,", pRow->mergeUpto);
1006 if( pRow->cherrypickUpto>0 && pRow->cherrypickUpto<=pRow->mergeUpto ){
1007 cgi_printf("\"cu\":%d,", pRow->cherrypickUpto);
1008 }
1009 }
1010 if( pRow->isStepParent ){
 
 
1011 cgi_printf("\"sb\":%d,", pRow->aiRiser[pRow->iRail]);
1012 }else{
1013 cgi_printf("\"u\":%d,", pRow->aiRiser[pRow->iRail]);
1014 }
1015 k = 0;
1016
--- src/timeline.c
+++ src/timeline.c
@@ -169,10 +169,165 @@
169 sqlite3_result_text(context, pPost->zWiki, -1, SQLITE_TRANSIENT);
170 manifest_destroy(pPost);
171 }
172 }
173
174
175 /*
176 ** This routine generates the default "extra" text after the description
177 ** in a timeline.
178 **
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 ){
190 int rid = db_column_int(pQuery, 0);
191 const char *zUuid = db_column_text(pQuery, 1);
192 const char *zDate = db_column_text(pQuery, 2);
193 const char *zType = db_column_text(pQuery, 7);
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:&nbsp;%z%S</a> ",
217 href("%R/info/%!S",zUuid),zUuid);
218 }else if( zType[0]=='e' && tagid ){
219 cgi_printf("technote:&nbsp;");
220 hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
221 }else{
222 cgi_printf("artifact:&nbsp;%z%S</a> ",
223 href("%R/info/%!S",zUuid),zUuid);
224 }
225 }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t'
226 || zType[0]=='n' || zType[0]=='f'){
227 cgi_printf("artifact:&nbsp;%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);
235 }else{
236 zLink = mprintf("%R/timeline?u=%h&c=%t&y=a&vfx", zDispUser, zDate);
237 }
238 cgi_printf("user:&nbsp;%z%h</a>", href("%z",zLink), zDispUser);
239 }else{
240 cgi_printf("user:&nbsp;%h", zDispUser);
241 }
242
243 /* Generate the "tags: TAGLIST" at the end of the comment, together
244 ** with hyperlinks to the tag list.
245 */
246 if( zTagList && zTagList[0]==0 ) zTagList = 0;
247 if( zTagList ){
248 if( g.perm.Hyperlink ){
249 int i;
250 const char *z = zTagList;
251 Blob links;
252 blob_zero(&links);
253 while( z && z[0] ){
254 for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){}
255 if( zThisTag==0 || memcmp(z, zThisTag, i)!=0 || zThisTag[i]!=0 ){
256 blob_appendf(&links,
257 "%z%#h</a>%.2s",
258 href("%R/timeline?r=%#t&c=%t",i,z,zDate), i,z, &z[i]
259 );
260 }else{
261 blob_appendf(&links, "%#h", i+2, z);
262 }
263 if( z[i]==0 ) break;
264 z += i+2;
265 }
266 cgi_printf(" tags:&nbsp;%s", blob_str(&links));
267 blob_reset(&links);
268 }else{
269 cgi_printf(" tags:&nbsp;%h", zTagList);
270 }
271 }
272
273 if( tmFlags & TIMELINE_SHOWRID ){
274 int srcId = delta_source_rid(rid);
275 if( srcId ){
276 cgi_printf(" id:&nbsp;%z%d&larr;%d</a>",
277 href("%R/deltachain/%d",rid), rid, srcId);
278 }else{
279 cgi_printf(" id:&nbsp;%z%d</a>",
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:&nbsp;%z%S</a>",
291 href("%R/technote/%t",zId), zId);
292 }else{
293 cgi_printf(" hash:&nbsp;%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
313 **
314 ** If enabled, check-in comments displayed on the timeline are truncated
315 ** at the first blank line of the comment text. The comment text after
316 ** the first blank line is only seen in the /info or similar pages that
317 ** show details about the check-in.
318 */
319 /*
320 ** SETTING: timeline-tslink-info boolean default=off
321 **
322 ** The hyperlink on the timestamp associated with each timeline entry,
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 **
@@ -194,11 +349,11 @@
349 const char *zThisUser, /* Suppress links to this user */
350 const char *zThisTag, /* Suppress links to this tag */
351 Matcher *pLeftBranch, /* Comparison function to use for zLeftBranch */
352 int selectedRid, /* Highlight the line with this RID value or zero */
353 int secondRid, /* Secondary highlight (or zero) */
354 void (*xExtra)(Stmt*,int,const char*,const char*) /* generate "extra" text */
355 ){
356 int mxWikiLen;
357 Blob comment;
358 int prevTagid = 0;
359 int suppressCnt = 0;
@@ -214,35 +369,21 @@
369 const char *zStyle; /* Sub-name for classes for the style */
370 const char *zDateFmt;
371 int iTableId = timeline_tableid();
372 int bTimestampLinksToInfo; /* True if timestamp hyperlinks go to the /info
373 ** page rather than the /timeline page */
374 char *zMainBranch = db_get("main-branch","trunk");
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);
386 if( (tmFlags & TIMELINE_VIEWS)==0 ){
387 tmFlags |= timeline_ss_cookie();
388 }
389 if( tmFlags & TIMELINE_COLUMNAR ){
@@ -256,13 +397,11 @@
397 }else{
398 zStyle = "Modern";
399 }
400 zDateFmt = P("datefmt");
401 if( zDateFmt ) dateFormat = atoi(zDateFmt);
402 pGraph = graph_init();
 
 
403 if( (tmFlags & TIMELINE_CHPICK)!=0
404 && !db_table_exists("repository","cherrypick")
405 ){
406 tmFlags &= ~TIMELINE_CHPICK;
407 }
@@ -275,23 +414,20 @@
414 int isLeaf = db_column_int(pQuery, 5);
415 const char *zBgClr = db_column_text(pQuery, 6);
416 const char *zDate = db_column_text(pQuery, 2);
417 const char *zType = db_column_text(pQuery, 7);
418 const char *zUser = db_column_text(pQuery, 4);
 
419 int tagid = db_column_int(pQuery, 9);
 
420 char *zBr = 0; /* Branch */
421 int commentColumn = 3; /* Column containing comment text */
422 int modPending; /* Pending moderation */
423 char *zDateLink; /* URL for the link on the timestamp */
424 int drawDetailEllipsis; /* True to show ellipsis in place of detail */
425 int gidx = 0; /* Graph row identifier */
426 int isSelectedOrCurrent = 0; /* True if current row is selected */
427 const char *zExtraClass = "";
428 char zTime[20];
 
429
430 if( zDate==0 ){
431 zDate = "YYYY-MM-DD HH:MM:SS"; /* Something wrong with the repo */
432 }
433 modPending = moderation_pending(rid);
@@ -447,32 +583,34 @@
583 if( zType[0]=='c' && pGraph ){
584 int nParent = 0;
585 int nCherrypick = 0;
586 GraphRowId aParent[GR_MAX_RAIL];
587 static Stmt qparent;
588 if( tmFlags & TIMELINE_GRAPH ){
589 db_static_prepare(&qparent,
590 "SELECT pid FROM plink"
591 " WHERE cid=:rid AND pid NOT IN phantom"
592 " ORDER BY isprim DESC /*sort*/"
593 );
594 db_bind_int(&qparent, ":rid", rid);
595 while( db_step(&qparent)==SQLITE_ROW && nParent<count(aParent) ){
596 aParent[nParent++] = db_column_int(&qparent, 0);
597 }
598 db_reset(&qparent);
599 if( (tmFlags & TIMELINE_CHPICK)!=0 && nParent>0 ){
600 static Stmt qcherrypick;
601 db_static_prepare(&qcherrypick,
602 "SELECT parentid FROM cherrypick"
603 " WHERE childid=:rid AND parentid NOT IN phantom"
604 );
605 db_bind_int(&qcherrypick, ":rid", rid);
606 while( db_step(&qcherrypick)==SQLITE_ROW && nParent<count(aParent) ){
607 aParent[nParent++] = db_column_int(&qcherrypick, 0);
608 nCherrypick++;
609 }
610 db_reset(&qcherrypick);
611 }
612 }
613 gidx = graph_add_row(pGraph, rid, nParent, nCherrypick, aParent,
614 zBr, zBgClr, zUuid,
615 isLeaf ? isLeaf + 2 * has_closed_tag(rid) : 0);
616 @ <div id="m%d(gidx)" class="tl-nodemark"></div>
@@ -611,11 +749,11 @@
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)"
756 */
757 if( drawDetailEllipsis ){
758 @ <span class='timelineEllipsis' id='ellipsis-%d(rid)' \
759 @ data-id='%d(rid)'>...</span>
@@ -629,101 +767,17 @@
767 }
768 if( tmFlags & TIMELINE_COMPACT ){
769 cgi_printf("<span class='clutter' id='detail-%d'>",rid);
770 }
771 cgi_printf("<span class='timeline%sDetail'>", zStyle);
772 xExtra(pQuery, tmFlags, zThisUser, zThisTag);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
773 if( tmFlags & TIMELINE_COMPACT ){
774 @ </span></span>
775 }else{
776 @ </span>
777 }
778
779 /* Generate the file-change list if requested */
780 if( (tmFlags & (TIMELINE_FCHANGES|TIMELINE_FRENAMES))!=0
781 && zType[0]=='c' && g.perm.Hyperlink
782 ){
783 int inUl = 0;
@@ -909,11 +963,13 @@
963 int omitDescenders; /* True to omit descenders */
964 int scrollToSelect; /* True to scroll to the selection */
965 int dwellTimeout; /* Milliseconds to wait for tooltips to show */
966 int closeTimeout; /* Milliseconds to wait for tooltips to close */
967 u8 *aiMap; /* The rail map */
968 u8 bNoGraph; /* True to show a minimal graph */
969
970 bNoGraph = (tmFlags & TIMELINE_GRAPH)==0;
971 iRailPitch = atoi(PD("railpitch","0"));
972 showArrowheads = skin_detail_boolean("timeline-arrowheads");
973 circleNodes = skin_detail_boolean("timeline-circle-nodes");
974 colorGraph = skin_detail_boolean("timeline-color-graph-lines");
975 iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0;
@@ -931,11 +987,11 @@
987 @ "nomo": %d(PB("nomo")),
988 @ "iTopRow": %d(iTopRow),
989 @ "omitDescenders": %d(omitDescenders),
990 @ "fileDiff": %d(fileDiff),
991 @ "scrollToSelect": %d(scrollToSelect),
992 @ "nrail": %d(bNoGraph?1:pGraph->mxRail+1),
993 @ "baseUrl": "%R",
994 @ "dwellTimeout": %d(dwellTimeout),
995 @ "closeTimeout": %d(closeTimeout),
996 @ "hashDigits": %d(hash_digits(1)),
997 @ "bottomRowId": "btm-%d(iTableId)",
@@ -993,12 +1049,16 @@
1049 aiMap = pGraph->aiRailMap;
1050 for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
1051 int k = 0;
1052 cgi_printf("{\"id\":%d,", pRow->idx);
1053 cgi_printf("\"bg\":\"%s\",", pRow->zBgClr);
1054 if( bNoGraph ){
1055 cgi_printf("\"r\":0,"); /* Chng to ":-1" to omit node circles */
1056 }else{
1057 cgi_printf("\"r\":%d,", pRow->iRail>=0 ? aiMap[pRow->iRail] : -1);
1058 }
1059 if( pRow->bDescender && !bNoGraph ){
1060 cgi_printf("\"d\":%d,", pRow->bDescender);
1061 }
1062 if( pRow->mergeOut>=0 ){
1063 cgi_printf("\"mo\":%d,", aiMap[pRow->mergeOut]);
1064 if( pRow->mergeUpto==0 ) pRow->mergeUpto = pRow->idx;
@@ -1005,11 +1065,13 @@
1065 cgi_printf("\"mu\":%d,", pRow->mergeUpto);
1066 if( pRow->cherrypickUpto>0 && pRow->cherrypickUpto<=pRow->mergeUpto ){
1067 cgi_printf("\"cu\":%d,", pRow->cherrypickUpto);
1068 }
1069 }
1070 if( bNoGraph ){
1071 cgi_printf("\"u\":-1,");
1072 }else if( pRow->isStepParent ){
1073 cgi_printf("\"sb\":%d,", pRow->aiRiser[pRow->iRail]);
1074 }else{
1075 cgi_printf("\"u\":%d,", pRow->aiRiser[pRow->iRail]);
1076 }
1077 k = 0;
1078

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button