Fossil SCM

Add ms=EXACT|LIKE|GLOB|REGEXP timeline query parameter which changes the interpretation of the t= and r= query parameters to exact (default), SQL LIKE, glob, or regular expression matching. Other than exact matching, the t= or r= parameter value is interpreted as a whitespace- or comma-delimited list of match patterns, with the option to quote a pattern using single or double quotes in case it contains commas or whitespace. For regular expression matching only, backslash can also be used to inhibit delimiter interpretation of the next character. At this point, the only way to use this feature is to type ms= directly into the URL; the UI still needs to be updated. Be aware for LIKE interpretation that % needs to be written as %25.

andygoth 2016-10-23 15:36 trunk
Commit be58684514f8c7dd1f1f076814d179fef2c94805
2 files changed +167 -21 +2
+167 -21
--- src/timeline.c
+++ src/timeline.c
@@ -1218,10 +1218,116 @@
12181218
if( zChng==0 || zChng[0]==0 ) return;
12191219
blob_appendf(pDescription, " that include changes to files matching %Q",
12201220
zChng);
12211221
}
12221222
1223
+/*
1224
+** Tag match expression type code.
1225
+*/
1226
+typedef enum {
1227
+ MS_EXACT, /* Matches a single tag by exact string comparison. */
1228
+ MS_LIKE, /* Matches tags against a list of LIKE patterns. */
1229
+ MS_GLOB, /* Matches tags against a list of GLOB patterns. */
1230
+ MS_REGEXP /* Matches tags against a list of regular expressions. */
1231
+} MatchStyle;
1232
+
1233
+/*
1234
+** Construct the tag match expression.
1235
+*/
1236
+static const char *tagMatchExpression(
1237
+ MatchStyle matchStyle, /* Match style code */
1238
+ const char *zTag, /* Tag name, match pattern, or list of patterns */
1239
+ int *pCount /* Pointer to match pattern count variable */
1240
+){
1241
+ Blob blob = BLOB_INITIALIZER;
1242
+ const char *zSep = "(", *zPre, *zSuf;
1243
+ char cDel;
1244
+ int i, dummy;
1245
+
1246
+ /* Protect against NULL count pointer. */
1247
+ if( !pCount ){
1248
+ pCount = &dummy;
1249
+ }
1250
+
1251
+ /* Decide pattern prefix and suffix strings according to match style. */
1252
+ if( matchStyle==MS_EXACT ){
1253
+ /* Optimize exact matches by looking up the numeric ID in advance. Bypass
1254
+ * the remainder of this function. */
1255
+ *pCount = 1;
1256
+ return mprintf("(tagid=%d)", db_int(-1,
1257
+ "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag));
1258
+ }else if( matchStyle==MS_LIKE ){
1259
+ zPre = "LIKE 'sym-";
1260
+ zSuf = "'";
1261
+ }else if( matchStyle==MS_GLOB ){
1262
+ zPre = "GLOB 'sym-";
1263
+ zSuf = "'";
1264
+ }else/* if( matchStyle==MS_REGEXP )*/{
1265
+ zPre = "REGEXP '^sym-";
1266
+ zSuf = "$'";
1267
+ }
1268
+
1269
+ /* The following code is glob_expr() modified to support LIKE and REGEXP, plus
1270
+ * adjust each pattern to start with "sym-" and be anchored at end. In REGEXP
1271
+ * mode, it allows backslash to protect delimiter characters. */
1272
+
1273
+ /* Convert the list of matches into an SQL expression. */
1274
+ *pCount = 0;
1275
+ blob_zero(&blob);
1276
+ while( 1 ){
1277
+ /* Skip leading delimiters. */
1278
+ for( ; fossil_isspace(*zTag) || *zTag==','; ++zTag );
1279
+
1280
+ /* Next non-delimiter character determines quoting. */
1281
+ if( !*zTag ){
1282
+ /* Terminate loop at end of string. */
1283
+ break;
1284
+ }else if( *zTag=='\'' || *zTag=='"' ){
1285
+ /* If word is quoted, prepare to stop at end quote. */
1286
+ cDel = *zTag;
1287
+ ++zTag;
1288
+ }else{
1289
+ /* If word is not quoted, prepare to stop at delimiter. */
1290
+ cDel = ',';
1291
+ }
1292
+
1293
+ /* Find the next delimiter character or end of string. */
1294
+ for( i=0; zTag[i] && zTag[i]!=cDel; ++i ){
1295
+ /* If delimiter is comma, also recognize spaces as delimiters. */
1296
+ if( cDel==',' && fossil_isspace(zTag[i]) ){
1297
+ break;
1298
+ }
1299
+
1300
+ /* In regexp mode, ignore delimiters following backslashes. */
1301
+ if( matchStyle==MS_REGEXP && zTag[i]=='\\' && zTag[i+1] ){
1302
+ ++i;
1303
+ }
1304
+ }
1305
+
1306
+ /* Incorporate the match word into the final expression. */
1307
+ blob_appendf(&blob, "%stagname %s%#q%s", zSep, zPre, i, zTag, zSuf);
1308
+
1309
+ /* Keep track of the number of match expressions. */
1310
+ ++*pCount;
1311
+
1312
+ /* Prepare for the next match word. */
1313
+ zTag += i;
1314
+ if( cDel!=',' && *zTag==cDel ){
1315
+ ++zTag;
1316
+ }
1317
+ zSep = " OR ";
1318
+ }
1319
+
1320
+ /* Finalize and extract the SQL expression. */
1321
+ if( *pCount ){
1322
+ blob_append(&blob, ")", 1);
1323
+ return blob_str(&blob);
1324
+ }
1325
+
1326
+ /* If execution reaches this point, the pattern was empty. Return NULL. */
1327
+ return 0;
1328
+}
12231329
12241330
/*
12251331
** WEBPAGE: timeline
12261332
**
12271333
** Query parameters:
@@ -1234,10 +1340,11 @@
12341340
** p=CHECKIN parents and ancestors of CHECKIN
12351341
** d=CHECKIN descendants of CHECIN
12361342
** dp=CHECKIN The same as d=CHECKIN&p=CHECKIN
12371343
** t=TAG show only check-ins with the given TAG
12381344
** r=TAG show check-ins related to TAG
1345
+** ms=STYLE sets tag match style to EXACT, LIKE, GLOB, REGEXP
12391346
** u=USER only show items associated with USER
12401347
** y=TYPE 'ci', 'w', 't', 'e', or (default) 'all'
12411348
** ng No Graph.
12421349
** nd Do not highlight the focus check-in
12431350
** v Show details of files changed
@@ -1277,10 +1384,14 @@
12771384
const char *zBefore = P("b"); /* Events before this time */
12781385
const char *zCirca = P("c"); /* Events near this time */
12791386
const char *zMark = P("m"); /* Mark this event or an event this time */
12801387
const char *zTagName = P("t"); /* Show events with this tag */
12811388
const char *zBrName = P("r"); /* Show events related to this tag */
1389
+ const char *zMatchStyle = P("ms"); /* Tag/branch match style string */
1390
+ MatchStyle matchStyle = MS_EXACT; /* Match style code */
1391
+ const char *zTagSql = 0; /* Tag/branch match SQL expression */
1392
+ int tagMatchCount = 0; /* Number of tag match patterns */
12821393
const char *zSearch = P("s"); /* Search string */
12831394
const char *zUses = P("uf"); /* Only show check-ins hold this file */
12841395
const char *zYearMonth = P("ym"); /* Show check-ins for the given YYYY-MM */
12851396
const char *zYearWeek = P("yw"); /* Check-ins for YYYY-WW (week-of-year) */
12861397
const char *zDay = P("ymd"); /* Check-ins for the day YYYY-MM-DD */
@@ -1287,11 +1398,10 @@
12871398
const char *zChng = P("chng"); /* List of GLOBs for files that changed */
12881399
int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */
12891400
int renameOnly = P("namechng")!=0; /* Show only check-ins that rename files */
12901401
int forkOnly = PB("forks"); /* Show only forks and their children */
12911402
int bisectOnly = PB("bisect"); /* Show the check-ins of the bisect */
1292
- int tagid; /* Tag ID */
12931403
int tmFlags = 0; /* Timeline flags */
12941404
const char *zThisTag = 0; /* Suppress links to this tag */
12951405
const char *zThisUser = 0; /* Suppress links to this user */
12961406
HQuery url; /* URL for various branch links */
12971407
int from_rid = name_to_typed_rid(P("from"),"ci"); /* from= for paths */
@@ -1339,30 +1449,48 @@
13391449
login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
13401450
return;
13411451
}
13421452
url_initialize(&url, "timeline");
13431453
cgi_query_parameters_to_url(&url);
1344
- if( zTagName && g.perm.Read ){
1345
- tagid = db_int(-1,"SELECT tagid FROM tag WHERE tagname='sym-%q'",zTagName);
1454
+
1455
+ /* Identify the tag or branch name or match pattern. */
1456
+ if( zTagName ){
13461457
zThisTag = zTagName;
1458
+ }else if( zBrName ){
1459
+ zThisTag = zBrName;
1460
+ }
1461
+
1462
+ /* Interpet the tag style string. */
1463
+ if( zThisTag ){
1464
+ if( fossil_stricmp(zMatchStyle, "LIKE")==0 ){
1465
+ matchStyle = MS_LIKE;
1466
+ }else if( fossil_stricmp(zMatchStyle, "GLOB")==0 ){
1467
+ matchStyle = MS_GLOB;
1468
+ }else if( fossil_stricmp(zMatchStyle, "REGEXP")==0 ){
1469
+ matchStyle = MS_REGEXP;
1470
+ }
1471
+ }
1472
+
1473
+ /* Construct the tag match expression. */
1474
+ if( zThisTag ){
1475
+ zTagSql = tagMatchExpression(matchStyle, zThisTag, &tagMatchCount);
1476
+ }
1477
+
1478
+ if( zTagName && g.perm.Read ){
13471479
style_submenu_element("Related", "Related", "%s",
13481480
url_render(&url, "r", zTagName, "t", 0));
13491481
}else if( zBrName && g.perm.Read ){
1350
- tagid = db_int(-1,"SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName);
1351
- zThisTag = zBrName;
13521482
style_submenu_element("Branch Only", "only", "%s",
13531483
url_render(&url, "t", zBrName, "r", 0));
1354
- }else{
1355
- tagid = 0;
13561484
}
13571485
if( zMark && zMark[0]==0 ){
13581486
if( zAfter ) zMark = zAfter;
13591487
if( zBefore ) zMark = zBefore;
13601488
if( zCirca ) zMark = zCirca;
13611489
}
1362
- if( tagid
1363
- && db_int(0,"SELECT count(*) FROM tagxref WHERE tagid=%d",tagid)<=nEntry
1490
+ if( (zTagSql && db_int(0,"SELECT count(*) "
1491
+ "FROM tagxref NATURAL JOIN tag WHERE %s",zTagSql/*safe-for-%s*/)<=nEntry)
13641492
){
13651493
nEntry = -1;
13661494
zCirca = 0;
13671495
}
13681496
if( zType[0]=='a' ){
@@ -1587,14 +1715,14 @@
15871715
}
15881716
else if( zDay ){
15891717
blob_append_sql(&cond, " AND %Q=strftime('%%Y-%%m-%%d',event.mtime) ",
15901718
zDay);
15911719
}
1592
- if( tagid ){
1720
+ if( zTagSql ){
15931721
blob_append_sql(&cond,
1594
- " AND (EXISTS(SELECT 1 FROM tagxref"
1595
- " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n", tagid);
1722
+ " AND (EXISTS(SELECT 1 FROM tagxref NATURAL JOIN tag"
1723
+ " WHERE %s AND tagtype>0 AND rid=blob.rid)\n", zTagSql/*safe-for-%s*/);
15961724
15971725
if( zBrName ){
15981726
/* The next two blob_appendf() calls add SQL that causes check-ins that
15991727
** are not part of the branch which are parents or children of the
16001728
** branch to be included in the report. This related check-ins are
@@ -1601,12 +1729,12 @@
16011729
** useful in helping to visualize what has happened on a quiescent
16021730
** branch that is infrequently merged with a much more activate branch.
16031731
*/
16041732
blob_append_sql(&cond,
16051733
" OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=cid"
1606
- " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)\n",
1607
- tagid
1734
+ " NATURAL JOIN tag WHERE %s AND tagtype>0 AND pid=blob.rid)\n",
1735
+ zTagSql/*safe-for-%s*/
16081736
);
16091737
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
16101738
blob_append_sql(&cond,
16111739
" AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid"
16121740
" WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)\n",
@@ -1614,12 +1742,12 @@
16141742
);
16151743
}
16161744
if( P("mionly")==0 ){
16171745
blob_append_sql(&cond,
16181746
" OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=pid"
1619
- " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)\n",
1620
- tagid
1747
+ " NATURAL JOIN tag WHERE %s AND tagtype>0 AND cid=blob.rid)\n",
1748
+ zTagSql/*safe-for-%s*/
16211749
);
16221750
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
16231751
blob_append_sql(&cond,
16241752
" AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
16251753
" WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)\n",
@@ -1766,15 +1894,33 @@
17661894
}
17671895
if( zUser ){
17681896
blob_appendf(&desc, " by user %h", zUser);
17691897
tmFlags |= TIMELINE_DISJOINT;
17701898
}
1771
- if( zTagName ){
1772
- blob_appendf(&desc, " tagged with \"%h\"", zTagName);
1773
- tmFlags |= TIMELINE_DISJOINT;
1774
- }else if( zBrName ){
1775
- blob_appendf(&desc, " related to \"%h\"", zBrName);
1899
+ if( zThisTag ){
1900
+ if( matchStyle!=MS_EXACT ){
1901
+ if( zTagName ){
1902
+ blob_append(&desc, " with tags matching ", -1);
1903
+ }else{
1904
+ blob_append(&desc, " related to tags matching ", -1);
1905
+ }
1906
+ if( matchStyle==MS_LIKE ){
1907
+ blob_append(&desc, " SQL LIKE pattern", -1);
1908
+ }else if( matchStyle==MS_GLOB ){
1909
+ blob_append(&desc, " glob pattern", -1);
1910
+ }else/* if( matchStyle==MS_REGEXP )*/{
1911
+ blob_append(&desc, " regular expression", -1);
1912
+ }
1913
+ if( tagMatchCount!=1 ){
1914
+ blob_append(&desc, "s", 1);
1915
+ }
1916
+ blob_appendf(&desc, " (%h)", zThisTag);
1917
+ }else if( zTagName ){
1918
+ blob_appendf(&desc, " tagged with \"%h\"", zTagName);
1919
+ }else{
1920
+ blob_appendf(&desc, " related to \"%h\"", zBrName);
1921
+ }
17761922
tmFlags |= TIMELINE_DISJOINT;
17771923
}
17781924
addFileGlobDescription(zChng, &desc);
17791925
if( rAfter>0.0 ){
17801926
if( rBefore>0.0 ){
17811927
--- src/timeline.c
+++ src/timeline.c
@@ -1218,10 +1218,116 @@
1218 if( zChng==0 || zChng[0]==0 ) return;
1219 blob_appendf(pDescription, " that include changes to files matching %Q",
1220 zChng);
1221 }
1222
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1223
1224 /*
1225 ** WEBPAGE: timeline
1226 **
1227 ** Query parameters:
@@ -1234,10 +1340,11 @@
1234 ** p=CHECKIN parents and ancestors of CHECKIN
1235 ** d=CHECKIN descendants of CHECIN
1236 ** dp=CHECKIN The same as d=CHECKIN&p=CHECKIN
1237 ** t=TAG show only check-ins with the given TAG
1238 ** r=TAG show check-ins related to TAG
 
1239 ** u=USER only show items associated with USER
1240 ** y=TYPE 'ci', 'w', 't', 'e', or (default) 'all'
1241 ** ng No Graph.
1242 ** nd Do not highlight the focus check-in
1243 ** v Show details of files changed
@@ -1277,10 +1384,14 @@
1277 const char *zBefore = P("b"); /* Events before this time */
1278 const char *zCirca = P("c"); /* Events near this time */
1279 const char *zMark = P("m"); /* Mark this event or an event this time */
1280 const char *zTagName = P("t"); /* Show events with this tag */
1281 const char *zBrName = P("r"); /* Show events related to this tag */
 
 
 
 
1282 const char *zSearch = P("s"); /* Search string */
1283 const char *zUses = P("uf"); /* Only show check-ins hold this file */
1284 const char *zYearMonth = P("ym"); /* Show check-ins for the given YYYY-MM */
1285 const char *zYearWeek = P("yw"); /* Check-ins for YYYY-WW (week-of-year) */
1286 const char *zDay = P("ymd"); /* Check-ins for the day YYYY-MM-DD */
@@ -1287,11 +1398,10 @@
1287 const char *zChng = P("chng"); /* List of GLOBs for files that changed */
1288 int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */
1289 int renameOnly = P("namechng")!=0; /* Show only check-ins that rename files */
1290 int forkOnly = PB("forks"); /* Show only forks and their children */
1291 int bisectOnly = PB("bisect"); /* Show the check-ins of the bisect */
1292 int tagid; /* Tag ID */
1293 int tmFlags = 0; /* Timeline flags */
1294 const char *zThisTag = 0; /* Suppress links to this tag */
1295 const char *zThisUser = 0; /* Suppress links to this user */
1296 HQuery url; /* URL for various branch links */
1297 int from_rid = name_to_typed_rid(P("from"),"ci"); /* from= for paths */
@@ -1339,30 +1449,48 @@
1339 login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
1340 return;
1341 }
1342 url_initialize(&url, "timeline");
1343 cgi_query_parameters_to_url(&url);
1344 if( zTagName && g.perm.Read ){
1345 tagid = db_int(-1,"SELECT tagid FROM tag WHERE tagname='sym-%q'",zTagName);
 
1346 zThisTag = zTagName;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1347 style_submenu_element("Related", "Related", "%s",
1348 url_render(&url, "r", zTagName, "t", 0));
1349 }else if( zBrName && g.perm.Read ){
1350 tagid = db_int(-1,"SELECT tagid FROM tag WHERE tagname='sym-%q'",zBrName);
1351 zThisTag = zBrName;
1352 style_submenu_element("Branch Only", "only", "%s",
1353 url_render(&url, "t", zBrName, "r", 0));
1354 }else{
1355 tagid = 0;
1356 }
1357 if( zMark && zMark[0]==0 ){
1358 if( zAfter ) zMark = zAfter;
1359 if( zBefore ) zMark = zBefore;
1360 if( zCirca ) zMark = zCirca;
1361 }
1362 if( tagid
1363 && db_int(0,"SELECT count(*) FROM tagxref WHERE tagid=%d",tagid)<=nEntry
1364 ){
1365 nEntry = -1;
1366 zCirca = 0;
1367 }
1368 if( zType[0]=='a' ){
@@ -1587,14 +1715,14 @@
1587 }
1588 else if( zDay ){
1589 blob_append_sql(&cond, " AND %Q=strftime('%%Y-%%m-%%d',event.mtime) ",
1590 zDay);
1591 }
1592 if( tagid ){
1593 blob_append_sql(&cond,
1594 " AND (EXISTS(SELECT 1 FROM tagxref"
1595 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n", tagid);
1596
1597 if( zBrName ){
1598 /* The next two blob_appendf() calls add SQL that causes check-ins that
1599 ** are not part of the branch which are parents or children of the
1600 ** branch to be included in the report. This related check-ins are
@@ -1601,12 +1729,12 @@
1601 ** useful in helping to visualize what has happened on a quiescent
1602 ** branch that is infrequently merged with a much more activate branch.
1603 */
1604 blob_append_sql(&cond,
1605 " OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=cid"
1606 " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)\n",
1607 tagid
1608 );
1609 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1610 blob_append_sql(&cond,
1611 " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid"
1612 " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)\n",
@@ -1614,12 +1742,12 @@
1614 );
1615 }
1616 if( P("mionly")==0 ){
1617 blob_append_sql(&cond,
1618 " OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=pid"
1619 " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)\n",
1620 tagid
1621 );
1622 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1623 blob_append_sql(&cond,
1624 " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
1625 " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)\n",
@@ -1766,15 +1894,33 @@
1766 }
1767 if( zUser ){
1768 blob_appendf(&desc, " by user %h", zUser);
1769 tmFlags |= TIMELINE_DISJOINT;
1770 }
1771 if( zTagName ){
1772 blob_appendf(&desc, " tagged with \"%h\"", zTagName);
1773 tmFlags |= TIMELINE_DISJOINT;
1774 }else if( zBrName ){
1775 blob_appendf(&desc, " related to \"%h\"", zBrName);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1776 tmFlags |= TIMELINE_DISJOINT;
1777 }
1778 addFileGlobDescription(zChng, &desc);
1779 if( rAfter>0.0 ){
1780 if( rBefore>0.0 ){
1781
--- src/timeline.c
+++ src/timeline.c
@@ -1218,10 +1218,116 @@
1218 if( zChng==0 || zChng[0]==0 ) return;
1219 blob_appendf(pDescription, " that include changes to files matching %Q",
1220 zChng);
1221 }
1222
1223 /*
1224 ** Tag match expression type code.
1225 */
1226 typedef enum {
1227 MS_EXACT, /* Matches a single tag by exact string comparison. */
1228 MS_LIKE, /* Matches tags against a list of LIKE patterns. */
1229 MS_GLOB, /* Matches tags against a list of GLOB patterns. */
1230 MS_REGEXP /* Matches tags against a list of regular expressions. */
1231 } MatchStyle;
1232
1233 /*
1234 ** Construct the tag match expression.
1235 */
1236 static const char *tagMatchExpression(
1237 MatchStyle matchStyle, /* Match style code */
1238 const char *zTag, /* Tag name, match pattern, or list of patterns */
1239 int *pCount /* Pointer to match pattern count variable */
1240 ){
1241 Blob blob = BLOB_INITIALIZER;
1242 const char *zSep = "(", *zPre, *zSuf;
1243 char cDel;
1244 int i, dummy;
1245
1246 /* Protect against NULL count pointer. */
1247 if( !pCount ){
1248 pCount = &dummy;
1249 }
1250
1251 /* Decide pattern prefix and suffix strings according to match style. */
1252 if( matchStyle==MS_EXACT ){
1253 /* Optimize exact matches by looking up the numeric ID in advance. Bypass
1254 * the remainder of this function. */
1255 *pCount = 1;
1256 return mprintf("(tagid=%d)", db_int(-1,
1257 "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag));
1258 }else if( matchStyle==MS_LIKE ){
1259 zPre = "LIKE 'sym-";
1260 zSuf = "'";
1261 }else if( matchStyle==MS_GLOB ){
1262 zPre = "GLOB 'sym-";
1263 zSuf = "'";
1264 }else/* if( matchStyle==MS_REGEXP )*/{
1265 zPre = "REGEXP '^sym-";
1266 zSuf = "$'";
1267 }
1268
1269 /* The following code is glob_expr() modified to support LIKE and REGEXP, plus
1270 * adjust each pattern to start with "sym-" and be anchored at end. In REGEXP
1271 * mode, it allows backslash to protect delimiter characters. */
1272
1273 /* Convert the list of matches into an SQL expression. */
1274 *pCount = 0;
1275 blob_zero(&blob);
1276 while( 1 ){
1277 /* Skip leading delimiters. */
1278 for( ; fossil_isspace(*zTag) || *zTag==','; ++zTag );
1279
1280 /* Next non-delimiter character determines quoting. */
1281 if( !*zTag ){
1282 /* Terminate loop at end of string. */
1283 break;
1284 }else if( *zTag=='\'' || *zTag=='"' ){
1285 /* If word is quoted, prepare to stop at end quote. */
1286 cDel = *zTag;
1287 ++zTag;
1288 }else{
1289 /* If word is not quoted, prepare to stop at delimiter. */
1290 cDel = ',';
1291 }
1292
1293 /* Find the next delimiter character or end of string. */
1294 for( i=0; zTag[i] && zTag[i]!=cDel; ++i ){
1295 /* If delimiter is comma, also recognize spaces as delimiters. */
1296 if( cDel==',' && fossil_isspace(zTag[i]) ){
1297 break;
1298 }
1299
1300 /* In regexp mode, ignore delimiters following backslashes. */
1301 if( matchStyle==MS_REGEXP && zTag[i]=='\\' && zTag[i+1] ){
1302 ++i;
1303 }
1304 }
1305
1306 /* Incorporate the match word into the final expression. */
1307 blob_appendf(&blob, "%stagname %s%#q%s", zSep, zPre, i, zTag, zSuf);
1308
1309 /* Keep track of the number of match expressions. */
1310 ++*pCount;
1311
1312 /* Prepare for the next match word. */
1313 zTag += i;
1314 if( cDel!=',' && *zTag==cDel ){
1315 ++zTag;
1316 }
1317 zSep = " OR ";
1318 }
1319
1320 /* Finalize and extract the SQL expression. */
1321 if( *pCount ){
1322 blob_append(&blob, ")", 1);
1323 return blob_str(&blob);
1324 }
1325
1326 /* If execution reaches this point, the pattern was empty. Return NULL. */
1327 return 0;
1328 }
1329
1330 /*
1331 ** WEBPAGE: timeline
1332 **
1333 ** Query parameters:
@@ -1234,10 +1340,11 @@
1340 ** p=CHECKIN parents and ancestors of CHECKIN
1341 ** d=CHECKIN descendants of CHECIN
1342 ** dp=CHECKIN The same as d=CHECKIN&p=CHECKIN
1343 ** t=TAG show only check-ins with the given TAG
1344 ** r=TAG show check-ins related to TAG
1345 ** ms=STYLE sets tag match style to EXACT, LIKE, GLOB, REGEXP
1346 ** u=USER only show items associated with USER
1347 ** y=TYPE 'ci', 'w', 't', 'e', or (default) 'all'
1348 ** ng No Graph.
1349 ** nd Do not highlight the focus check-in
1350 ** v Show details of files changed
@@ -1277,10 +1384,14 @@
1384 const char *zBefore = P("b"); /* Events before this time */
1385 const char *zCirca = P("c"); /* Events near this time */
1386 const char *zMark = P("m"); /* Mark this event or an event this time */
1387 const char *zTagName = P("t"); /* Show events with this tag */
1388 const char *zBrName = P("r"); /* Show events related to this tag */
1389 const char *zMatchStyle = P("ms"); /* Tag/branch match style string */
1390 MatchStyle matchStyle = MS_EXACT; /* Match style code */
1391 const char *zTagSql = 0; /* Tag/branch match SQL expression */
1392 int tagMatchCount = 0; /* Number of tag match patterns */
1393 const char *zSearch = P("s"); /* Search string */
1394 const char *zUses = P("uf"); /* Only show check-ins hold this file */
1395 const char *zYearMonth = P("ym"); /* Show check-ins for the given YYYY-MM */
1396 const char *zYearWeek = P("yw"); /* Check-ins for YYYY-WW (week-of-year) */
1397 const char *zDay = P("ymd"); /* Check-ins for the day YYYY-MM-DD */
@@ -1287,11 +1398,10 @@
1398 const char *zChng = P("chng"); /* List of GLOBs for files that changed */
1399 int useDividers = P("nd")==0; /* Show dividers if "nd" is missing */
1400 int renameOnly = P("namechng")!=0; /* Show only check-ins that rename files */
1401 int forkOnly = PB("forks"); /* Show only forks and their children */
1402 int bisectOnly = PB("bisect"); /* Show the check-ins of the bisect */
 
1403 int tmFlags = 0; /* Timeline flags */
1404 const char *zThisTag = 0; /* Suppress links to this tag */
1405 const char *zThisUser = 0; /* Suppress links to this user */
1406 HQuery url; /* URL for various branch links */
1407 int from_rid = name_to_typed_rid(P("from"),"ci"); /* from= for paths */
@@ -1339,30 +1449,48 @@
1449 login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
1450 return;
1451 }
1452 url_initialize(&url, "timeline");
1453 cgi_query_parameters_to_url(&url);
1454
1455 /* Identify the tag or branch name or match pattern. */
1456 if( zTagName ){
1457 zThisTag = zTagName;
1458 }else if( zBrName ){
1459 zThisTag = zBrName;
1460 }
1461
1462 /* Interpet the tag style string. */
1463 if( zThisTag ){
1464 if( fossil_stricmp(zMatchStyle, "LIKE")==0 ){
1465 matchStyle = MS_LIKE;
1466 }else if( fossil_stricmp(zMatchStyle, "GLOB")==0 ){
1467 matchStyle = MS_GLOB;
1468 }else if( fossil_stricmp(zMatchStyle, "REGEXP")==0 ){
1469 matchStyle = MS_REGEXP;
1470 }
1471 }
1472
1473 /* Construct the tag match expression. */
1474 if( zThisTag ){
1475 zTagSql = tagMatchExpression(matchStyle, zThisTag, &tagMatchCount);
1476 }
1477
1478 if( zTagName && g.perm.Read ){
1479 style_submenu_element("Related", "Related", "%s",
1480 url_render(&url, "r", zTagName, "t", 0));
1481 }else if( zBrName && g.perm.Read ){
 
 
1482 style_submenu_element("Branch Only", "only", "%s",
1483 url_render(&url, "t", zBrName, "r", 0));
 
 
1484 }
1485 if( zMark && zMark[0]==0 ){
1486 if( zAfter ) zMark = zAfter;
1487 if( zBefore ) zMark = zBefore;
1488 if( zCirca ) zMark = zCirca;
1489 }
1490 if( (zTagSql && db_int(0,"SELECT count(*) "
1491 "FROM tagxref NATURAL JOIN tag WHERE %s",zTagSql/*safe-for-%s*/)<=nEntry)
1492 ){
1493 nEntry = -1;
1494 zCirca = 0;
1495 }
1496 if( zType[0]=='a' ){
@@ -1587,14 +1715,14 @@
1715 }
1716 else if( zDay ){
1717 blob_append_sql(&cond, " AND %Q=strftime('%%Y-%%m-%%d',event.mtime) ",
1718 zDay);
1719 }
1720 if( zTagSql ){
1721 blob_append_sql(&cond,
1722 " AND (EXISTS(SELECT 1 FROM tagxref NATURAL JOIN tag"
1723 " WHERE %s AND tagtype>0 AND rid=blob.rid)\n", zTagSql/*safe-for-%s*/);
1724
1725 if( zBrName ){
1726 /* The next two blob_appendf() calls add SQL that causes check-ins that
1727 ** are not part of the branch which are parents or children of the
1728 ** branch to be included in the report. This related check-ins are
@@ -1601,12 +1729,12 @@
1729 ** useful in helping to visualize what has happened on a quiescent
1730 ** branch that is infrequently merged with a much more activate branch.
1731 */
1732 blob_append_sql(&cond,
1733 " OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=cid"
1734 " NATURAL JOIN tag WHERE %s AND tagtype>0 AND pid=blob.rid)\n",
1735 zTagSql/*safe-for-%s*/
1736 );
1737 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1738 blob_append_sql(&cond,
1739 " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid"
1740 " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)\n",
@@ -1614,12 +1742,12 @@
1742 );
1743 }
1744 if( P("mionly")==0 ){
1745 blob_append_sql(&cond,
1746 " OR EXISTS(SELECT 1 FROM plink CROSS JOIN tagxref ON rid=pid"
1747 " NATURAL JOIN tag WHERE %s AND tagtype>0 AND cid=blob.rid)\n",
1748 zTagSql/*safe-for-%s*/
1749 );
1750 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
1751 blob_append_sql(&cond,
1752 " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid"
1753 " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)\n",
@@ -1766,15 +1894,33 @@
1894 }
1895 if( zUser ){
1896 blob_appendf(&desc, " by user %h", zUser);
1897 tmFlags |= TIMELINE_DISJOINT;
1898 }
1899 if( zThisTag ){
1900 if( matchStyle!=MS_EXACT ){
1901 if( zTagName ){
1902 blob_append(&desc, " with tags matching ", -1);
1903 }else{
1904 blob_append(&desc, " related to tags matching ", -1);
1905 }
1906 if( matchStyle==MS_LIKE ){
1907 blob_append(&desc, " SQL LIKE pattern", -1);
1908 }else if( matchStyle==MS_GLOB ){
1909 blob_append(&desc, " glob pattern", -1);
1910 }else/* if( matchStyle==MS_REGEXP )*/{
1911 blob_append(&desc, " regular expression", -1);
1912 }
1913 if( tagMatchCount!=1 ){
1914 blob_append(&desc, "s", 1);
1915 }
1916 blob_appendf(&desc, " (%h)", zThisTag);
1917 }else if( zTagName ){
1918 blob_appendf(&desc, " tagged with \"%h\"", zTagName);
1919 }else{
1920 blob_appendf(&desc, " related to \"%h\"", zBrName);
1921 }
1922 tmFlags |= TIMELINE_DISJOINT;
1923 }
1924 addFileGlobDescription(zChng, &desc);
1925 if( rAfter>0.0 ){
1926 if( rBefore>0.0 ){
1927
--- www/changes.wiki
+++ www/changes.wiki
@@ -26,10 +26,12 @@
2626
queries are filled in using HTTP query parameter values.
2727
* Added support for [./childprojects.wiki|child projects] that are
2828
able to pull from their parent but not push.
2929
* Added the -nocomplain option to the TH1 "query" command.
3030
* Added support for the chng=GLOBLIST query parameter on the
31
+ [/help?cmd=/timeline|/timeline] webpage.
32
+ * Added support for the ms=EXACT|LIKE|GLOB|REGEXP query parameter on the
3133
[/help?cmd=/timeline|/timeline] webpage.
3234
3335
<h2>Changes for Version 1.35 (2016-06-14)</h2>
3436
3537
* Enable symlinks by default on all non-Windows platforms.
3638
--- www/changes.wiki
+++ www/changes.wiki
@@ -26,10 +26,12 @@
26 queries are filled in using HTTP query parameter values.
27 * Added support for [./childprojects.wiki|child projects] that are
28 able to pull from their parent but not push.
29 * Added the -nocomplain option to the TH1 "query" command.
30 * Added support for the chng=GLOBLIST query parameter on the
 
 
31 [/help?cmd=/timeline|/timeline] webpage.
32
33 <h2>Changes for Version 1.35 (2016-06-14)</h2>
34
35 * Enable symlinks by default on all non-Windows platforms.
36
--- www/changes.wiki
+++ www/changes.wiki
@@ -26,10 +26,12 @@
26 queries are filled in using HTTP query parameter values.
27 * Added support for [./childprojects.wiki|child projects] that are
28 able to pull from their parent but not push.
29 * Added the -nocomplain option to the TH1 "query" command.
30 * Added support for the chng=GLOBLIST query parameter on the
31 [/help?cmd=/timeline|/timeline] webpage.
32 * Added support for the ms=EXACT|LIKE|GLOB|REGEXP query parameter on the
33 [/help?cmd=/timeline|/timeline] webpage.
34
35 <h2>Changes for Version 1.35 (2016-06-14)</h2>
36
37 * Enable symlinks by default on all non-Windows platforms.
38

Keyboard Shortcuts

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