Fossil SCM
In the /timeline page, add support for (from,ft) and (from,bt) query parameter pairs.
Commit
793c492415496a03edb8054c4603f3237754bff73d139f14d1c3740e3a1fbec9
Parent
015d7b7a417057c…
1 file changed
+135
-6
+135
-6
| --- src/timeline.c | ||
| +++ src/timeline.c | ||
| @@ -1568,10 +1568,97 @@ | ||
| 1568 | 1568 | |
| 1569 | 1569 | /* It looks like this may be a date. Return it with punctuation added. */ |
| 1570 | 1570 | return zEDate; |
| 1571 | 1571 | } |
| 1572 | 1572 | |
| 1573 | +/* | |
| 1574 | +** Find the first check-in encountered with a particular tag | |
| 1575 | +** when moving either forwards are backwards in time from a | |
| 1576 | +** particular starting point (iFrom). Return the rid of that | |
| 1577 | +** first check-in. If there are no check-ins in the decendent | |
| 1578 | +** or ancestor set of check-in iFrom that match the tag, then | |
| 1579 | +** return 0. | |
| 1580 | +*/ | |
| 1581 | +static int timeline_endpoint( | |
| 1582 | + int iFrom, /* Starting point */ | |
| 1583 | + const char *zEnd, /* Tag we are searching for */ | |
| 1584 | + int bForward /* 1: forwards in time (descendents) 0: backwards */ | |
| 1585 | +){ | |
| 1586 | + int tagId; | |
| 1587 | + int endId = 0; | |
| 1588 | + Stmt q; | |
| 1589 | + int ans = 0; | |
| 1590 | + | |
| 1591 | + tagId = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zEnd); | |
| 1592 | + if( tagId==0 ){ | |
| 1593 | + endId = symbolic_name_to_rid(zEnd, "ci"); | |
| 1594 | + if( endId==0 ) return 0; | |
| 1595 | + } | |
| 1596 | + if( bForward ){ | |
| 1597 | + if( tagId ){ | |
| 1598 | + db_prepare(&q, | |
| 1599 | + "WITH RECURSIVE dx(id,mtime) AS (" | |
| 1600 | + " SELECT %d, event.mtime FROM event WHERE objid=%d" | |
| 1601 | + " UNION ALL" | |
| 1602 | + " SELECT plink.cid, plink.mtime" | |
| 1603 | + " FROM dx, plink" | |
| 1604 | + " WHERE plink.pid=dx.id" | |
| 1605 | + " ORDER BY plink.mtime)" | |
| 1606 | + "SELECT id FROM dx, tagxref" | |
| 1607 | + " WHERE tagid=%d AND tagtype>0 AND rid=id LIMIT 1", | |
| 1608 | + iFrom, iFrom, tagId | |
| 1609 | + ); | |
| 1610 | + }else{ | |
| 1611 | + db_prepare(&q, | |
| 1612 | + "WITH RECURSIVE dx(id,mtime) AS (" | |
| 1613 | + " SELECT %d, event.mtime FROM event WHERE objid=%d" | |
| 1614 | + " UNION ALL" | |
| 1615 | + " SELECT plink.cid, plink.mtime" | |
| 1616 | + " FROM dx, plink" | |
| 1617 | + " WHERE plink.pid=dx.id" | |
| 1618 | + " AND plink.mtime<=(SELECT mtime FROM event WHERE objid=%d)" | |
| 1619 | + " ORDER BY plink.mtime)" | |
| 1620 | + "SELECT id FROM dx WHERE id=%d", | |
| 1621 | + iFrom, iFrom, endId, endId | |
| 1622 | + ); | |
| 1623 | + } | |
| 1624 | + }else{ | |
| 1625 | + if( tagId ){ | |
| 1626 | + db_prepare(&q, | |
| 1627 | + "WITH RECURSIVE dx(id,mtime) AS (" | |
| 1628 | + " SELECT %d, event.mtime FROM event WHERE objid=%d" | |
| 1629 | + " UNION ALL" | |
| 1630 | + " SELECT plink.pid, event.mtime" | |
| 1631 | + " FROM dx, plink, event" | |
| 1632 | + " WHERE plink.cid=dx.id AND event.objid=plink.pid" | |
| 1633 | + " ORDER BY event.mtime DESC)" | |
| 1634 | + "SELECT id FROM dx, tagxref" | |
| 1635 | + " WHERE tagid=%d AND tagtype>0 AND rid=id LIMIT 1", | |
| 1636 | + iFrom, iFrom, tagId | |
| 1637 | + ); | |
| 1638 | + }else{ | |
| 1639 | + db_prepare(&q, | |
| 1640 | + "WITH RECURSIVE dx(id,mtime) AS (" | |
| 1641 | + " SELECT %d, event.mtime FROM event WHERE objid=%d" | |
| 1642 | + " UNION ALL" | |
| 1643 | + " SELECT plink.pid, event.mtime" | |
| 1644 | + " FROM dx, plink, event" | |
| 1645 | + " WHERE plink.cid=dx.id AND event.objid=plink.pid" | |
| 1646 | + " AND event.mtime>=(SELECT mtime FROM event WHERE objid=%d)" | |
| 1647 | + " ORDER BY event.mtime DESC)" | |
| 1648 | + "SELECT id FROM dx WHERE id=%d", | |
| 1649 | + iFrom, iFrom, endId, endId | |
| 1650 | + ); | |
| 1651 | + } | |
| 1652 | + } | |
| 1653 | + if( db_step(&q)==SQLITE_ROW ){ | |
| 1654 | + ans = db_column_int(&q, 0); | |
| 1655 | + } | |
| 1656 | + db_finalize(&q); | |
| 1657 | + return ans; | |
| 1658 | +} | |
| 1659 | + | |
| 1573 | 1660 | |
| 1574 | 1661 | /* |
| 1575 | 1662 | ** WEBPAGE: timeline |
| 1576 | 1663 | ** |
| 1577 | 1664 | ** Query parameters: |
| @@ -1598,14 +1685,16 @@ | ||
| 1598 | 1685 | ** bt=PRIOR ... going back to PRIOR |
| 1599 | 1686 | ** d=CHECKIN Children and descendants of CHECKIN |
| 1600 | 1687 | ** ft=DESCENDANT ... going forward to DESCENDANT |
| 1601 | 1688 | ** dp=CHECKIN Same as 'd=CHECKIN&p=CHECKIN' |
| 1602 | 1689 | ** df=CHECKIN Same as 'd=CHECKIN&n1=all&nd'. Mnemonic: "Derived From" |
| 1603 | -** bt=CHECKIN In conjunction with p=CX, this means show all | |
| 1604 | -** ancestors of CX going back to the time of CHECKIN. | |
| 1605 | -** All qualifying check-ins are shown unless there | |
| 1606 | -** is also an n= or n1= query parameter. | |
| 1690 | +** bt=CHECKIN "Back To". Show ancenstors going back to CHECKIN | |
| 1691 | +** p=CX ... from CX back to time of CHECKIN | |
| 1692 | +** from=CX ... shortest path from CX back to CHECKIN | |
| 1693 | +** ft=CHECKIN "Forward To": Show decendents forward to CHECKIN | |
| 1694 | +** d=CX ... from CX up to the time of CHECKIN | |
| 1695 | +** from=CX ... shortest path from CX up to CHECKIN | |
| 1607 | 1696 | ** t=TAG Show only check-ins with the given TAG |
| 1608 | 1697 | ** r=TAG Show check-ins related to TAG, equivalent to t=TAG&rel |
| 1609 | 1698 | ** tl=TAGLIST Shorthand for t=TAGLIST&ms=brlist |
| 1610 | 1699 | ** rl=TAGLIST Shorthand for r=TAGLIST&ms=brlist |
| 1611 | 1700 | ** rel Show related check-ins as well as those matching t=TAG |
| @@ -1627,10 +1716,12 @@ | ||
| 1627 | 1716 | ** f=CHECKIN Show family (immediate parents and children) of CHECKIN |
| 1628 | 1717 | ** from=CHECKIN Path from... |
| 1629 | 1718 | ** to=CHECKIN ... to this |
| 1630 | 1719 | ** shortest ... show only the shortest path |
| 1631 | 1720 | ** rel ... also show related checkins |
| 1721 | +** bt=PRIOR ... path from CHECKIN back to PRIOR | |
| 1722 | +** ft=LATER ... path from CHECKIN forward to LATER | |
| 1632 | 1723 | ** uf=FILE_HASH Show only check-ins that contain the given file version |
| 1633 | 1724 | ** All qualifying check-ins are shown unless there is |
| 1634 | 1725 | ** also an n= or n1= query parameter. |
| 1635 | 1726 | ** chng=GLOBLIST Show only check-ins that involve changes to a file whose |
| 1636 | 1727 | ** name matches one of the comma-separate GLOBLIST |
| @@ -1727,10 +1818,11 @@ | ||
| 1727 | 1818 | int disableY = 0; /* Disable type selector on submenu */ |
| 1728 | 1819 | int advancedMenu = 0; /* Use the advanced menu design */ |
| 1729 | 1820 | char *zPlural; /* Ending for plural forms */ |
| 1730 | 1821 | int showCherrypicks = 1; /* True to show cherrypick merges */ |
| 1731 | 1822 | int haveParameterN; /* True if n= query parameter present */ |
| 1823 | + int from_to_mode = 0; /* 0: from,to. 1: from,ft 2: from,bt */ | |
| 1732 | 1824 | |
| 1733 | 1825 | url_initialize(&url, "timeline"); |
| 1734 | 1826 | cgi_query_parameters_to_url(&url); |
| 1735 | 1827 | |
| 1736 | 1828 | (void)P_NoBot("ss") |
| @@ -2043,10 +2135,29 @@ | ||
| 2043 | 2135 | blob_append_sql(&sql, |
| 2044 | 2136 | " AND NOT EXISTS(SELECT 1 FROM tagxref" |
| 2045 | 2137 | " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n", |
| 2046 | 2138 | TAG_HIDDEN |
| 2047 | 2139 | ); |
| 2140 | + } | |
| 2141 | + if( from_rid && !to_rid && (P("ft")!=0 || P("bt")!=0) ){ | |
| 2142 | + const char *zTo = P("ft"); | |
| 2143 | + if( zTo ){ | |
| 2144 | + from_to_mode = 1; | |
| 2145 | + to_rid = timeline_endpoint(from_rid, zTo, 1); | |
| 2146 | + }else{ | |
| 2147 | + from_to_mode = 2; | |
| 2148 | + zTo = P("bt"); | |
| 2149 | + to_rid = timeline_endpoint(from_rid, zTo, 0); | |
| 2150 | + } | |
| 2151 | + if( to_rid ){ | |
| 2152 | + cgi_replace_parameter("to", zTo); | |
| 2153 | + if( selectedRid==0 ) selectedRid = from_rid; | |
| 2154 | + if( secondaryRid==0 ) secondaryRid = to_rid; | |
| 2155 | + }else{ | |
| 2156 | + blob_appendf(&desc, "There is no path from %h %s to %h.<br>Instead: ", | |
| 2157 | + P("from"), from_to_mode==1 ? "forward" : "back", zTo); | |
| 2158 | + } | |
| 2048 | 2159 | } |
| 2049 | 2160 | if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){ |
| 2050 | 2161 | /* If from= and to= are present, display all nodes on a path connecting |
| 2051 | 2162 | ** the two */ |
| 2052 | 2163 | PathNode *p = 0; |
| @@ -2054,11 +2165,17 @@ | ||
| 2054 | 2165 | const char *zTo = 0; |
| 2055 | 2166 | Blob ins; |
| 2056 | 2167 | int nNodeOnPath = 0; |
| 2057 | 2168 | |
| 2058 | 2169 | if( from_rid && to_rid ){ |
| 2059 | - p = path_shortest(from_rid, to_rid, noMerge, 0, 0); | |
| 2170 | + if( from_to_mode==0 ){ | |
| 2171 | + p = path_shortest(from_rid, to_rid, noMerge, 0, 0); | |
| 2172 | + }else if( from_to_mode==1 ){ | |
| 2173 | + p = path_shortest(from_rid, to_rid, 0, 1, 0); | |
| 2174 | + }else{ | |
| 2175 | + p = path_shortest(to_rid, from_rid, 0, 1, 0); | |
| 2176 | + } | |
| 2060 | 2177 | zFrom = P("from"); |
| 2061 | 2178 | zTo = P("to"); |
| 2062 | 2179 | }else{ |
| 2063 | 2180 | if( path_common_ancestor(me_rid, you_rid) ){ |
| 2064 | 2181 | p = path_first(); |
| @@ -2122,14 +2239,26 @@ | ||
| 2122 | 2239 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 2123 | 2240 | if( advancedMenu ){ |
| 2124 | 2241 | style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0); |
| 2125 | 2242 | } |
| 2126 | 2243 | nNodeOnPath = db_int(0, "SELECT count(*) FROM temp.pathnode"); |
| 2127 | - blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath); | |
| 2244 | + if( from_to_mode>0 ){ | |
| 2245 | + blob_appendf(&desc, "%d check-ins on the shorted path from ",nNodeOnPath); | |
| 2246 | + }else{ | |
| 2247 | + blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath); | |
| 2248 | + } | |
| 2249 | + if( from_rid==selectedRid ){ | |
| 2250 | + blob_appendf(&desc, "<span class='timelineSelected'>"); | |
| 2251 | + } | |
| 2128 | 2252 | blob_appendf(&desc, "%z%h</a>", href("%R/info/%h", zFrom), zFrom); |
| 2253 | + if( from_rid==selectedRid ) blob_appendf(&desc, "</span>"); | |
| 2129 | 2254 | blob_append(&desc, " to ", -1); |
| 2255 | + if( to_rid==secondaryRid ){ | |
| 2256 | + blob_appendf(&desc, "<span class='timelineSelected timelineSecondary'>"); | |
| 2257 | + } | |
| 2130 | 2258 | blob_appendf(&desc, "%z%h</a>", href("%R/info/%h",zTo), zTo); |
| 2259 | + if( to_rid==secondaryRid ) blob_appendf(&desc, "</span>"); | |
| 2131 | 2260 | if( related ){ |
| 2132 | 2261 | int nRelated = db_int(0, "SELECT count(*) FROM timeline") - nNodeOnPath; |
| 2133 | 2262 | if( nRelated>0 ){ |
| 2134 | 2263 | blob_appendf(&desc, " and %d related check-in%s", nRelated, |
| 2135 | 2264 | nRelated>1 ? "s" : ""); |
| 2136 | 2265 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -1568,10 +1568,97 @@ | |
| 1568 | |
| 1569 | /* It looks like this may be a date. Return it with punctuation added. */ |
| 1570 | return zEDate; |
| 1571 | } |
| 1572 | |
| 1573 | |
| 1574 | /* |
| 1575 | ** WEBPAGE: timeline |
| 1576 | ** |
| 1577 | ** Query parameters: |
| @@ -1598,14 +1685,16 @@ | |
| 1598 | ** bt=PRIOR ... going back to PRIOR |
| 1599 | ** d=CHECKIN Children and descendants of CHECKIN |
| 1600 | ** ft=DESCENDANT ... going forward to DESCENDANT |
| 1601 | ** dp=CHECKIN Same as 'd=CHECKIN&p=CHECKIN' |
| 1602 | ** df=CHECKIN Same as 'd=CHECKIN&n1=all&nd'. Mnemonic: "Derived From" |
| 1603 | ** bt=CHECKIN In conjunction with p=CX, this means show all |
| 1604 | ** ancestors of CX going back to the time of CHECKIN. |
| 1605 | ** All qualifying check-ins are shown unless there |
| 1606 | ** is also an n= or n1= query parameter. |
| 1607 | ** t=TAG Show only check-ins with the given TAG |
| 1608 | ** r=TAG Show check-ins related to TAG, equivalent to t=TAG&rel |
| 1609 | ** tl=TAGLIST Shorthand for t=TAGLIST&ms=brlist |
| 1610 | ** rl=TAGLIST Shorthand for r=TAGLIST&ms=brlist |
| 1611 | ** rel Show related check-ins as well as those matching t=TAG |
| @@ -1627,10 +1716,12 @@ | |
| 1627 | ** f=CHECKIN Show family (immediate parents and children) of CHECKIN |
| 1628 | ** from=CHECKIN Path from... |
| 1629 | ** to=CHECKIN ... to this |
| 1630 | ** shortest ... show only the shortest path |
| 1631 | ** rel ... also show related checkins |
| 1632 | ** uf=FILE_HASH Show only check-ins that contain the given file version |
| 1633 | ** All qualifying check-ins are shown unless there is |
| 1634 | ** also an n= or n1= query parameter. |
| 1635 | ** chng=GLOBLIST Show only check-ins that involve changes to a file whose |
| 1636 | ** name matches one of the comma-separate GLOBLIST |
| @@ -1727,10 +1818,11 @@ | |
| 1727 | int disableY = 0; /* Disable type selector on submenu */ |
| 1728 | int advancedMenu = 0; /* Use the advanced menu design */ |
| 1729 | char *zPlural; /* Ending for plural forms */ |
| 1730 | int showCherrypicks = 1; /* True to show cherrypick merges */ |
| 1731 | int haveParameterN; /* True if n= query parameter present */ |
| 1732 | |
| 1733 | url_initialize(&url, "timeline"); |
| 1734 | cgi_query_parameters_to_url(&url); |
| 1735 | |
| 1736 | (void)P_NoBot("ss") |
| @@ -2043,10 +2135,29 @@ | |
| 2043 | blob_append_sql(&sql, |
| 2044 | " AND NOT EXISTS(SELECT 1 FROM tagxref" |
| 2045 | " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n", |
| 2046 | TAG_HIDDEN |
| 2047 | ); |
| 2048 | } |
| 2049 | if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){ |
| 2050 | /* If from= and to= are present, display all nodes on a path connecting |
| 2051 | ** the two */ |
| 2052 | PathNode *p = 0; |
| @@ -2054,11 +2165,17 @@ | |
| 2054 | const char *zTo = 0; |
| 2055 | Blob ins; |
| 2056 | int nNodeOnPath = 0; |
| 2057 | |
| 2058 | if( from_rid && to_rid ){ |
| 2059 | p = path_shortest(from_rid, to_rid, noMerge, 0, 0); |
| 2060 | zFrom = P("from"); |
| 2061 | zTo = P("to"); |
| 2062 | }else{ |
| 2063 | if( path_common_ancestor(me_rid, you_rid) ){ |
| 2064 | p = path_first(); |
| @@ -2122,14 +2239,26 @@ | |
| 2122 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 2123 | if( advancedMenu ){ |
| 2124 | style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0); |
| 2125 | } |
| 2126 | nNodeOnPath = db_int(0, "SELECT count(*) FROM temp.pathnode"); |
| 2127 | blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath); |
| 2128 | blob_appendf(&desc, "%z%h</a>", href("%R/info/%h", zFrom), zFrom); |
| 2129 | blob_append(&desc, " to ", -1); |
| 2130 | blob_appendf(&desc, "%z%h</a>", href("%R/info/%h",zTo), zTo); |
| 2131 | if( related ){ |
| 2132 | int nRelated = db_int(0, "SELECT count(*) FROM timeline") - nNodeOnPath; |
| 2133 | if( nRelated>0 ){ |
| 2134 | blob_appendf(&desc, " and %d related check-in%s", nRelated, |
| 2135 | nRelated>1 ? "s" : ""); |
| 2136 |
| --- src/timeline.c | |
| +++ src/timeline.c | |
| @@ -1568,10 +1568,97 @@ | |
| 1568 | |
| 1569 | /* It looks like this may be a date. Return it with punctuation added. */ |
| 1570 | return zEDate; |
| 1571 | } |
| 1572 | |
| 1573 | /* |
| 1574 | ** Find the first check-in encountered with a particular tag |
| 1575 | ** when moving either forwards are backwards in time from a |
| 1576 | ** particular starting point (iFrom). Return the rid of that |
| 1577 | ** first check-in. If there are no check-ins in the decendent |
| 1578 | ** or ancestor set of check-in iFrom that match the tag, then |
| 1579 | ** return 0. |
| 1580 | */ |
| 1581 | static int timeline_endpoint( |
| 1582 | int iFrom, /* Starting point */ |
| 1583 | const char *zEnd, /* Tag we are searching for */ |
| 1584 | int bForward /* 1: forwards in time (descendents) 0: backwards */ |
| 1585 | ){ |
| 1586 | int tagId; |
| 1587 | int endId = 0; |
| 1588 | Stmt q; |
| 1589 | int ans = 0; |
| 1590 | |
| 1591 | tagId = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zEnd); |
| 1592 | if( tagId==0 ){ |
| 1593 | endId = symbolic_name_to_rid(zEnd, "ci"); |
| 1594 | if( endId==0 ) return 0; |
| 1595 | } |
| 1596 | if( bForward ){ |
| 1597 | if( tagId ){ |
| 1598 | db_prepare(&q, |
| 1599 | "WITH RECURSIVE dx(id,mtime) AS (" |
| 1600 | " SELECT %d, event.mtime FROM event WHERE objid=%d" |
| 1601 | " UNION ALL" |
| 1602 | " SELECT plink.cid, plink.mtime" |
| 1603 | " FROM dx, plink" |
| 1604 | " WHERE plink.pid=dx.id" |
| 1605 | " ORDER BY plink.mtime)" |
| 1606 | "SELECT id FROM dx, tagxref" |
| 1607 | " WHERE tagid=%d AND tagtype>0 AND rid=id LIMIT 1", |
| 1608 | iFrom, iFrom, tagId |
| 1609 | ); |
| 1610 | }else{ |
| 1611 | db_prepare(&q, |
| 1612 | "WITH RECURSIVE dx(id,mtime) AS (" |
| 1613 | " SELECT %d, event.mtime FROM event WHERE objid=%d" |
| 1614 | " UNION ALL" |
| 1615 | " SELECT plink.cid, plink.mtime" |
| 1616 | " FROM dx, plink" |
| 1617 | " WHERE plink.pid=dx.id" |
| 1618 | " AND plink.mtime<=(SELECT mtime FROM event WHERE objid=%d)" |
| 1619 | " ORDER BY plink.mtime)" |
| 1620 | "SELECT id FROM dx WHERE id=%d", |
| 1621 | iFrom, iFrom, endId, endId |
| 1622 | ); |
| 1623 | } |
| 1624 | }else{ |
| 1625 | if( tagId ){ |
| 1626 | db_prepare(&q, |
| 1627 | "WITH RECURSIVE dx(id,mtime) AS (" |
| 1628 | " SELECT %d, event.mtime FROM event WHERE objid=%d" |
| 1629 | " UNION ALL" |
| 1630 | " SELECT plink.pid, event.mtime" |
| 1631 | " FROM dx, plink, event" |
| 1632 | " WHERE plink.cid=dx.id AND event.objid=plink.pid" |
| 1633 | " ORDER BY event.mtime DESC)" |
| 1634 | "SELECT id FROM dx, tagxref" |
| 1635 | " WHERE tagid=%d AND tagtype>0 AND rid=id LIMIT 1", |
| 1636 | iFrom, iFrom, tagId |
| 1637 | ); |
| 1638 | }else{ |
| 1639 | db_prepare(&q, |
| 1640 | "WITH RECURSIVE dx(id,mtime) AS (" |
| 1641 | " SELECT %d, event.mtime FROM event WHERE objid=%d" |
| 1642 | " UNION ALL" |
| 1643 | " SELECT plink.pid, event.mtime" |
| 1644 | " FROM dx, plink, event" |
| 1645 | " WHERE plink.cid=dx.id AND event.objid=plink.pid" |
| 1646 | " AND event.mtime>=(SELECT mtime FROM event WHERE objid=%d)" |
| 1647 | " ORDER BY event.mtime DESC)" |
| 1648 | "SELECT id FROM dx WHERE id=%d", |
| 1649 | iFrom, iFrom, endId, endId |
| 1650 | ); |
| 1651 | } |
| 1652 | } |
| 1653 | if( db_step(&q)==SQLITE_ROW ){ |
| 1654 | ans = db_column_int(&q, 0); |
| 1655 | } |
| 1656 | db_finalize(&q); |
| 1657 | return ans; |
| 1658 | } |
| 1659 | |
| 1660 | |
| 1661 | /* |
| 1662 | ** WEBPAGE: timeline |
| 1663 | ** |
| 1664 | ** Query parameters: |
| @@ -1598,14 +1685,16 @@ | |
| 1685 | ** bt=PRIOR ... going back to PRIOR |
| 1686 | ** d=CHECKIN Children and descendants of CHECKIN |
| 1687 | ** ft=DESCENDANT ... going forward to DESCENDANT |
| 1688 | ** dp=CHECKIN Same as 'd=CHECKIN&p=CHECKIN' |
| 1689 | ** df=CHECKIN Same as 'd=CHECKIN&n1=all&nd'. Mnemonic: "Derived From" |
| 1690 | ** bt=CHECKIN "Back To". Show ancenstors going back to CHECKIN |
| 1691 | ** p=CX ... from CX back to time of CHECKIN |
| 1692 | ** from=CX ... shortest path from CX back to CHECKIN |
| 1693 | ** ft=CHECKIN "Forward To": Show decendents forward to CHECKIN |
| 1694 | ** d=CX ... from CX up to the time of CHECKIN |
| 1695 | ** from=CX ... shortest path from CX up to CHECKIN |
| 1696 | ** t=TAG Show only check-ins with the given TAG |
| 1697 | ** r=TAG Show check-ins related to TAG, equivalent to t=TAG&rel |
| 1698 | ** tl=TAGLIST Shorthand for t=TAGLIST&ms=brlist |
| 1699 | ** rl=TAGLIST Shorthand for r=TAGLIST&ms=brlist |
| 1700 | ** rel Show related check-ins as well as those matching t=TAG |
| @@ -1627,10 +1716,12 @@ | |
| 1716 | ** f=CHECKIN Show family (immediate parents and children) of CHECKIN |
| 1717 | ** from=CHECKIN Path from... |
| 1718 | ** to=CHECKIN ... to this |
| 1719 | ** shortest ... show only the shortest path |
| 1720 | ** rel ... also show related checkins |
| 1721 | ** bt=PRIOR ... path from CHECKIN back to PRIOR |
| 1722 | ** ft=LATER ... path from CHECKIN forward to LATER |
| 1723 | ** uf=FILE_HASH Show only check-ins that contain the given file version |
| 1724 | ** All qualifying check-ins are shown unless there is |
| 1725 | ** also an n= or n1= query parameter. |
| 1726 | ** chng=GLOBLIST Show only check-ins that involve changes to a file whose |
| 1727 | ** name matches one of the comma-separate GLOBLIST |
| @@ -1727,10 +1818,11 @@ | |
| 1818 | int disableY = 0; /* Disable type selector on submenu */ |
| 1819 | int advancedMenu = 0; /* Use the advanced menu design */ |
| 1820 | char *zPlural; /* Ending for plural forms */ |
| 1821 | int showCherrypicks = 1; /* True to show cherrypick merges */ |
| 1822 | int haveParameterN; /* True if n= query parameter present */ |
| 1823 | int from_to_mode = 0; /* 0: from,to. 1: from,ft 2: from,bt */ |
| 1824 | |
| 1825 | url_initialize(&url, "timeline"); |
| 1826 | cgi_query_parameters_to_url(&url); |
| 1827 | |
| 1828 | (void)P_NoBot("ss") |
| @@ -2043,10 +2135,29 @@ | |
| 2135 | blob_append_sql(&sql, |
| 2136 | " AND NOT EXISTS(SELECT 1 FROM tagxref" |
| 2137 | " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n", |
| 2138 | TAG_HIDDEN |
| 2139 | ); |
| 2140 | } |
| 2141 | if( from_rid && !to_rid && (P("ft")!=0 || P("bt")!=0) ){ |
| 2142 | const char *zTo = P("ft"); |
| 2143 | if( zTo ){ |
| 2144 | from_to_mode = 1; |
| 2145 | to_rid = timeline_endpoint(from_rid, zTo, 1); |
| 2146 | }else{ |
| 2147 | from_to_mode = 2; |
| 2148 | zTo = P("bt"); |
| 2149 | to_rid = timeline_endpoint(from_rid, zTo, 0); |
| 2150 | } |
| 2151 | if( to_rid ){ |
| 2152 | cgi_replace_parameter("to", zTo); |
| 2153 | if( selectedRid==0 ) selectedRid = from_rid; |
| 2154 | if( secondaryRid==0 ) secondaryRid = to_rid; |
| 2155 | }else{ |
| 2156 | blob_appendf(&desc, "There is no path from %h %s to %h.<br>Instead: ", |
| 2157 | P("from"), from_to_mode==1 ? "forward" : "back", zTo); |
| 2158 | } |
| 2159 | } |
| 2160 | if( ((from_rid && to_rid) || (me_rid && you_rid)) && g.perm.Read ){ |
| 2161 | /* If from= and to= are present, display all nodes on a path connecting |
| 2162 | ** the two */ |
| 2163 | PathNode *p = 0; |
| @@ -2054,11 +2165,17 @@ | |
| 2165 | const char *zTo = 0; |
| 2166 | Blob ins; |
| 2167 | int nNodeOnPath = 0; |
| 2168 | |
| 2169 | if( from_rid && to_rid ){ |
| 2170 | if( from_to_mode==0 ){ |
| 2171 | p = path_shortest(from_rid, to_rid, noMerge, 0, 0); |
| 2172 | }else if( from_to_mode==1 ){ |
| 2173 | p = path_shortest(from_rid, to_rid, 0, 1, 0); |
| 2174 | }else{ |
| 2175 | p = path_shortest(to_rid, from_rid, 0, 1, 0); |
| 2176 | } |
| 2177 | zFrom = P("from"); |
| 2178 | zTo = P("to"); |
| 2179 | }else{ |
| 2180 | if( path_common_ancestor(me_rid, you_rid) ){ |
| 2181 | p = path_first(); |
| @@ -2122,14 +2239,26 @@ | |
| 2239 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 2240 | if( advancedMenu ){ |
| 2241 | style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0); |
| 2242 | } |
| 2243 | nNodeOnPath = db_int(0, "SELECT count(*) FROM temp.pathnode"); |
| 2244 | if( from_to_mode>0 ){ |
| 2245 | blob_appendf(&desc, "%d check-ins on the shorted path from ",nNodeOnPath); |
| 2246 | }else{ |
| 2247 | blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath); |
| 2248 | } |
| 2249 | if( from_rid==selectedRid ){ |
| 2250 | blob_appendf(&desc, "<span class='timelineSelected'>"); |
| 2251 | } |
| 2252 | blob_appendf(&desc, "%z%h</a>", href("%R/info/%h", zFrom), zFrom); |
| 2253 | if( from_rid==selectedRid ) blob_appendf(&desc, "</span>"); |
| 2254 | blob_append(&desc, " to ", -1); |
| 2255 | if( to_rid==secondaryRid ){ |
| 2256 | blob_appendf(&desc, "<span class='timelineSelected timelineSecondary'>"); |
| 2257 | } |
| 2258 | blob_appendf(&desc, "%z%h</a>", href("%R/info/%h",zTo), zTo); |
| 2259 | if( to_rid==secondaryRid ) blob_appendf(&desc, "</span>"); |
| 2260 | if( related ){ |
| 2261 | int nRelated = db_int(0, "SELECT count(*) FROM timeline") - nNodeOnPath; |
| 2262 | if( nRelated>0 ){ |
| 2263 | blob_appendf(&desc, " and %d related check-in%s", nRelated, |
| 2264 | nRelated>1 ? "s" : ""); |
| 2265 |