Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Improvements to the timeline graph layout: (1) Use a dotted vertical line to indicate a gab of one or more check-ins in a branch. (2) Do not necessarily draw branch lines all the way to the top or bottom of the page. Leave space for the rail to be reused by other branches. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
d14590dbff44620dc47e062e1a3b0832 |
| User & Date: | drh 2019-05-17 19:49:55.986 |
Context
|
2019-05-17
| ||
| 23:34 | On Windows, avoid using BIO_ptr_ctrl with OpenSSL 1.1.1b as it does not appear to work as expected. ... (check-in: 344a3331d3 user: mistachkin tags: trunk) | |
| 19:49 | Improvements to the timeline graph layout: (1) Use a dotted vertical line to indicate a gab of one or more check-ins in a branch. (2) Do not necessarily draw branch lines all the way to the top or bottom of the page. Leave space for the rail to be reused by other branches. ... (check-in: d14590dbff user: drh tags: trunk) | |
| 19:30 | In the graph layout, make sure that the idxTop value is properly relayed across gaps. ... (Closed-Leaf check-in: e0186fdb5e user: drh tags: graph-improvements) | |
| 19:07 | When attempting to determine the Fossil user information, do not smash the global URL information which may contain an alternate URL used for sync operations (e.g. when using 'fossil sync --once'). This fixes the ability to work offline and synchronize with a USB thumbdrive ala sneaker-net. ... (check-in: b9e36291f7 user: mistachkin tags: trunk) | |
Changes
Changes to src/default_css.txt.
| ︙ | ︙ | |||
187 188 189 190 191 192 193 |
margin-left: 1px;
border-width: 3px 0;
border-left: 7px solid #600000;
}
.tl-line.warp {
background: #600000;
}
| | | | | 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
margin-left: 1px;
border-width: 3px 0;
border-left: 7px solid #600000;
}
.tl-line.warp {
background: #600000;
}
.tl-line.dotted.v {
width: 0px;
border-left-width: 2px;
border-left-style: dotted;
background: rgba(255,255,255,0);
}
span.tagDsp {
font-weight: bold;
}
span.wikiError {
font-weight: bold;
|
| ︙ | ︙ |
Changes to src/graph.c.
| ︙ | ︙ | |||
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
*******************************************************************************
**
** This file contains code to compute a revision history graph.
*/
#include "config.h"
#include "graph.h"
#include <assert.h>
#if INTERFACE
#define GR_MAX_RAIL 40 /* Max number of "rails" to display */
/* The graph appears vertically beside a timeline. Each row in the
** timeline corresponds to a row in the graph. GraphRow.idx is 0 for
** the top-most row and increases moving down. Hence (in the absence of
** time skew) parents have a larger index than their children.
**
** The nParent field is -1 for entires that do not participate in the graph
** but which are included just so that we can capture their background color.
*/
struct GraphRow {
int rid; /* The rid for the check-in */
| > > > > > > > > > > > > > > > > > > > > | | < | 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
*******************************************************************************
**
** This file contains code to compute a revision history graph.
*/
#include "config.h"
#include "graph.h"
#include <assert.h>
/* Notes:
**
** The graph is laid out in 1 or more "rails". A "rail" is a vertical
** band in the graph in which one can place nodes or arrows connecting
** nodes. There can be between 1 and GR_MAX_RAIL rails. If the graph
** is to complex to be displayed in GR_MAX_RAIL rails, it is omitted.
**
** A "riser" is the thick line that comes out of the top of a node and
** goes up to the next node on the branch, or to the top of the screen.
** A "descender" is a thick line that comes out of the bottom of a node
** and proceeds down to the bottom of the page.
**
** Invoke graph_init() to create a new GraphContext object. Then
** call graph_add_row() to add nodes, one by one, to the graph.
** Nodes must be added in display order, from top to bottom.
** Then invoke graph_render() to run the layout algorithm. The
** layout algorithm computes which rails all of the nodes sit on, and
** the rails used for merge arrows.
*/
#if INTERFACE
#define GR_MAX_RAIL 40 /* Max number of "rails" to display */
/* The graph appears vertically beside a timeline. Each row in the
** timeline corresponds to a row in the graph. GraphRow.idx is 0 for
** the top-most row and increases moving down. Hence (in the absence of
** time skew) parents have a larger index than their children.
**
** The nParent field is -1 for entires that do not participate in the graph
** but which are included just so that we can capture their background color.
*/
struct GraphRow {
int rid; /* The rid for the check-in */
i8 nParent; /* Number of parents. */
i8 nCherrypick; /* Subset of aParent that are cherrypicks */
i8 nNonCherrypick; /* Number of non-cherrypick parents */
int *aParent; /* Array of parents. 0 element is primary .*/
char *zBranch; /* Branch name */
char *zBgClr; /* Background Color */
char zUuid[HNAME_MAX+1]; /* Check-in for file ID */
GraphRow *pNext; /* Next row down in the list of all rows */
GraphRow *pPrev; /* Previous row */
int idx; /* Row index. Top row is smallest. */
int idxTop; /* Direct descendent highest up on the graph */
GraphRow *pChild; /* Child immediately above this node */
u8 isDup; /* True if this is duplicate of a prior entry */
u8 isLeaf; /* True if this is a leaf node */
u8 isStepParent; /* pChild is actually a step-child */
u8 hasNormalOutMerge; /* Is parent of at laest 1 non-cherrypick merge */
u8 timeWarp; /* Child is earlier in time */
u8 bDescender; /* True if riser from bottom of graph to here. */
i8 iRail; /* Which rail this check-in appears on. 0-based.*/
i8 mergeOut; /* Merge out to this rail. -1 if no merge-out */
u8 mergeIn[GR_MAX_RAIL]; /* Merge in from non-zero rails */
int aiRiser[GR_MAX_RAIL]; /* Risers from this node to a higher row. */
int mergeUpto; /* Draw the mergeOut rail up to this level */
int cherrypickUpto; /* Continue the mergeOut rail up to here */
u64 mergeDown; /* Draw merge lines up from bottom of graph */
u64 cherrypickDown; /* Draw cherrypick lines up from bottom */
u64 railInUse; /* Mask of occupied rails at this row */
};
/* Context while building a graph
*/
struct GraphContext {
int nErr; /* Number of errors encountered */
|
| ︙ | ︙ | |||
81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */
};
#endif
/* The N-th bit */
#define BIT(N) (((u64)1)<<(N))
/*
** Malloc for zeroed space. Panic if unable to provide the
** requested space.
*/
void *safeMalloc(int nByte){
void *p = fossil_malloc(nByte);
| > > > > > > > | 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */
};
#endif
/* The N-th bit */
#define BIT(N) (((u64)1)<<(N))
/*
** Number of rows before and answer a node with a riser or descender
** that goes off-screen before we can reuse that rail.
*/
#define RISER_MARGIN 4
/*
** Malloc for zeroed space. Panic if unable to provide the
** requested space.
*/
void *safeMalloc(int nByte){
void *p = fossil_malloc(nByte);
|
| ︙ | ︙ | |||
244 245 246 247 248 249 250 |
int iBestDist = 9999;
u64 inUseMask = 0;
for(pRow=p->pFirst; pRow && pRow->idx<top; pRow=pRow->pNext){}
while( pRow && pRow->idx<=btm ){
inUseMask |= pRow->railInUse;
pRow = pRow->pNext;
}
| | | 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
int iBestDist = 9999;
u64 inUseMask = 0;
for(pRow=p->pFirst; pRow && pRow->idx<top; pRow=pRow->pNext){}
while( pRow && pRow->idx<=btm ){
inUseMask |= pRow->railInUse;
pRow = pRow->pNext;
}
for(i=0; i<GR_MAX_RAIL; i++){
if( (inUseMask & BIT(i))==0 ){
int dist;
if( iNearto<=0 ){
return i;
}
dist = i - iNearto;
if( dist<0 ) dist = -dist;
|
| ︙ | ︙ | |||
266 267 268 269 270 271 272 | if( iBest>p->mxRail ) p->mxRail = iBest; return iBest; } /* ** Assign all children of node pBottom to the same rail as pBottom. */ | | > > > > > > > > | | > | | 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 |
if( iBest>p->mxRail ) p->mxRail = iBest;
return iBest;
}
/*
** Assign all children of node pBottom to the same rail as pBottom.
*/
static void assignChildrenToRail(GraphRow *pBottom, u32 tmFlags){
int iRail = pBottom->iRail;
GraphRow *pCurrent;
GraphRow *pPrior;
u64 mask = ((u64)1)<<iRail;
pBottom->railInUse |= mask;
pPrior = pBottom;
for(pCurrent=pBottom->pChild; pCurrent; pCurrent=pCurrent->pChild){
assert( pPrior->idx > pCurrent->idx );
assert( pCurrent->iRail<0 );
pCurrent->iRail = iRail;
pCurrent->railInUse |= mask;
pPrior->aiRiser[iRail] = pCurrent->idx;
while( pPrior->idx > pCurrent->idx ){
pPrior->railInUse |= mask;
pPrior = pPrior->pPrev;
assert( pPrior!=0 );
}
}
/* Mask of additional rows for the riser to infinity */
if( !pPrior->isLeaf && (tmFlags & TIMELINE_DISJOINT)==0 ){
int n = RISER_MARGIN;
GraphRow *p;
for(p=pPrior; p && (n--)>0; p=p->pPrev){
p->railInUse |= mask;
}
}
}
/*
** Create a merge-arrow riser going from pParent up to pChild.
*/
static void createMergeRiser(
GraphContext *p,
GraphRow *pParent,
GraphRow *pChild,
int isCherrypick
){
int u;
u64 mask;
GraphRow *pLoop;
if( pParent->mergeOut<0 ){
u = pParent->aiRiser[pParent->iRail];
if( u>0 && u<pChild->idx ){
/* The thick arrow up to the next primary child of pDesc goes
** further up than the thin merge arrow riser, so draw them both
** on the same rail. */
pParent->mergeOut = pParent->iRail;
}else{
/* The thin merge arrow riser is taller than the thick primary
** child riser, so use separate rails. */
int iTarget = pParent->iRail;
int iBtm = pParent->idx - (u==0 ? RISER_MARGIN : 1);
pParent->mergeOut = findFreeRail(p, pChild->idx, iBtm, iTarget);
mask = BIT(pParent->mergeOut);
for(pLoop=pChild->pNext; pLoop && pLoop->rid!=pParent->rid;
pLoop=pLoop->pNext){
pLoop->railInUse |= mask;
}
}
}
|
| ︙ | ︙ | |||
351 352 353 354 355 356 357 |
){
p->mxRail++;
}
}
}
/*
| | > > | > | 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 |
){
p->mxRail++;
}
}
}
/*
** Draw a riser from pRow upward to indicate that it is going
** to a node that is off the graph to the top.
*/
static void riser_to_top(GraphRow *pRow){
u64 mask = BIT(pRow->iRail);
int n = RISER_MARGIN;
pRow->aiRiser[pRow->iRail] = 0;
while( pRow && (n--)>0 ){
pRow->railInUse |= mask;
pRow = pRow->pPrev;
}
}
/*
** Compute the complete graph
**
** When primary or merge parents are off-screen, normally a line is drawn
** from the node down to the bottom of the graph. This line is called a
** "descender". But if the omitDescenders flag is true, then lines down
** to the bottom of the screen are omitted.
**
** The tmFlags parameter is zero or more of the TIMELINE_* constants.
** Only the following are honored:
**
** TIMELINE_DISJOINT: Omit descenders
** TIMELINE_FILLGAPS: Use step-children
** TIMELINE_XMERGE: Omit off-graph merge lines
*/
void graph_finish(GraphContext *p, u32 tmFlags){
GraphRow *pRow, *pDesc, *pDup, *pLoop, *pParent;
int i, j;
u64 mask;
int hasDup = 0; /* True if one or more isDup entries */
const char *zTrunk;
|
| ︙ | ︙ | |||
419 420 421 422 423 424 425 | ** ** Each node has one primary parent and zero or more "merge" parents. ** A merge parent is a prior check-in from which changes were merged into ** the current check-in. If a merge parent is not in the visible section ** of this graph, then no arrows will be drawn for it, so remove it from ** the aParent[] array. */ | | | 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 |
**
** Each node has one primary parent and zero or more "merge" parents.
** A merge parent is a prior check-in from which changes were merged into
** the current check-in. If a merge parent is not in the visible section
** of this graph, then no arrows will be drawn for it, so remove it from
** the aParent[] array.
*/
if( (tmFlags & (TIMELINE_DISJOINT|TIMELINE_XMERGE))!=0 ){
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
for(i=1; i<pRow->nParent; i++){
if( hashFind(p, pRow->aParent[i])==0 ){
memmove(pRow->aParent+i, pRow->aParent+i+1,
sizeof(pRow->aParent[0])*(pRow->nParent-i-1));
pRow->nParent--;
if( i<pRow->nNonCherrypick ){
|
| ︙ | ︙ | |||
475 476 477 478 479 480 481 |
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
if( pRow->isDup ) continue;
if( pRow->nParent<=0 ) continue; /* Root node */
pParent = hashFind(p, pRow->aParent[0]);
if( pParent==0 ) continue; /* Parent off-screen */
if( pParent->zBranch!=pRow->zBranch ) continue; /* Different branch */
if( pParent->idx <= pRow->idx ){
| | < < | < | | | > < > > > > > > > > > > | > > < < < < < < | < < < > | | | 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 |
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
if( pRow->isDup ) continue;
if( pRow->nParent<=0 ) continue; /* Root node */
pParent = hashFind(p, pRow->aParent[0]);
if( pParent==0 ) continue; /* Parent off-screen */
if( pParent->zBranch!=pRow->zBranch ) continue; /* Different branch */
if( pParent->idx <= pRow->idx ){
pParent->timeWarp = 1;
}else if( pRow->idx < pParent->idx ){
pParent->pChild = pRow;
}
}
if( tmFlags & TIMELINE_FILLGAPS ){
/* If a node has no pChild but there is a node higher up in the graph
** that is in the same branch and that other node has no parent in
** the graph, the lower node a step-child of the upper node. This will
** be represented on the graph by a thick dotted line without an arrowhead.
*/
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
if( pRow->pChild ) continue;
for(pLoop=pRow->pPrev; pLoop; pLoop=pLoop->pPrev){
if( pLoop->nParent>0
&& pLoop->zBranch==pRow->zBranch
&& hashFind(p,pLoop->aParent[0])==0
){
pRow->pChild = pLoop;
pRow->isStepParent = 1;
pLoop->aParent[0] = pRow->rid;
break;
}
}
}
}
/* Set the idxTop values for all entries. The idxTop value is the
** "idx" value for the top entry in its stack of children.
*/
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
GraphRow *pChild = pRow->pChild;
if( pChild && pRow->idxTop>pChild->idxTop ){
pRow->idxTop = pChild->idxTop;
}
}
/* Identify rows where the primary parent is off screen. Assign
** each to a rail and draw descenders downward.
**
** Strive to put the "trunk" branch on far left.
*/
zTrunk = persistBranchName(p, "trunk");
for(i=0; i<2; i++){
for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
if( i==0 && pRow->zBranch!=zTrunk ) continue;
if( pRow->iRail>=0 ) continue;
if( pRow->isDup ) continue;
if( pRow->nParent<0 ) continue;
if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){
pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx+RISER_MARGIN, 0);
if( p->mxRail>=GR_MAX_RAIL ) return;
mask = BIT(pRow->iRail);
if( !omitDescenders ){
int n = RISER_MARGIN;
pRow->bDescender = pRow->nParent>0;
for(pLoop=pRow; pLoop && (n--)>0; pLoop=pLoop->pNext){
pLoop->railInUse |= mask;
}
}
assignChildrenToRail(pRow, tmFlags);
}
}
}
/* Assign rails to all rows that are still unassigned.
*/
for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
|
| ︙ | ︙ | |||
568 569 570 571 572 573 574 |
if( p->mxRail>=GR_MAX_RAIL ) return;
pRow->railInUse = BIT(pRow->iRail);
continue;
}
if( pParent->idx>pRow->idx ){
/* Common case: Child occurs after parent and is above the
** parent in the timeline */
| | > | | 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 |
if( p->mxRail>=GR_MAX_RAIL ) return;
pRow->railInUse = BIT(pRow->iRail);
continue;
}
if( pParent->idx>pRow->idx ){
/* Common case: Child occurs after parent and is above the
** parent in the timeline */
pRow->iRail = findFreeRail(p, pRow->idxTop, pParent->idx,
pParent->iRail);
if( p->mxRail>=GR_MAX_RAIL ) return;
pParent->aiRiser[pRow->iRail] = pRow->idx;
}else{
/* Timewarp case: Child occurs earlier in time than parent and
** appears below the parent in the timeline. */
int iDownRail = ++p->mxRail;
if( iDownRail<1 ) iDownRail = ++p->mxRail;
pRow->iRail = ++p->mxRail;
if( p->mxRail>=GR_MAX_RAIL ) return;
pRow->railInUse = BIT(pRow->iRail);
pParent->aiRiser[iDownRail] = pRow->idx;
mask = BIT(iDownRail);
for(pLoop=p->pFirst; pLoop; pLoop=pLoop->pNext){
pLoop->railInUse |= mask;
}
}
}
mask = BIT(pRow->iRail);
pRow->railInUse |= mask;
if( pRow->pChild ){
assignChildrenToRail(pRow, tmFlags);
}else if( !omitDescenders && count_nonbranch_children(pRow->rid)!=0 ){
if( !pRow->timeWarp ) riser_to_top(pRow);
}
if( pParent ){
for(pLoop=pParent->pPrev; pLoop && pLoop!=pRow; pLoop=pLoop->pPrev){
pLoop->railInUse |= mask;
}
|
| ︙ | ︙ |
Changes to src/graph.js.
| ︙ | ︙ | |||
181 182 183 184 185 186 187 |
if( x1===null ){
x1 = x0+elem.w;
cls += "v";
}else{
y1 = y0+elem.w;
cls += "h";
}
| | | | > | 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
if( x1===null ){
x1 = x0+elem.w;
cls += "v";
}else{
y1 = y0+elem.w;
cls += "h";
}
return drawBox(cls,color,x0,y0,x1,y1);
}
function drawUpArrow(from,to,color){
var y = to.y + node.h;
var arrowSpace = from.y - y + (!from.id || from.r!=to.r ? node.h/2 : 0);
var arw = arrowSpace < arrow.h*1.5 ? arrowSmall : arrow;
var x = to.x + (node.w-line.w)/2;
var y0 = from.y + node.h/2;
var y1 = Math.ceil(to.y + node.h + arw.h/2);
drawLine(line,color,x,y0,null,y1);
x = to.x + (node.w-arw.w)/2;
var n = drawBox(arw.cls,null,x,y);
if(color) n.style.borderBottomColor = color;
}
function drawDotted(from,to,color){
var x = to.x + (node.w-line.w)/2;
var y0 = from.y + node.h/2;
var y1 = Math.ceil(to.y + node.h);
var n = drawLine(dotLine,null,x,y0,null,y1)
if( color ) n.style.borderColor = color
}
/* Draw thin horizontal or vertical lines representing merges */
function drawMergeLine(x0,y0,x1,y1){
drawLine(mLine,null,x0,y0,x1,y1);
}
function drawCherrypickLine(x0,y0,x1,y1){
drawLine(cpLine,null,x0,y0,x1,y1);
|
| ︙ | ︙ | |||
241 242 243 244 245 246 247 |
var e = document.getElementById("mc"+p.id);
if(e) e.style.backgroundColor = p.bg;
e = document.getElementById("md"+p.id);
if(e) e.style.backgroundColor = p.bg;
}
if( p.r<0 ) return;
if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg);
| | > > > > > > > > | > > | > > > > > > > | > > > > > | | 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
var e = document.getElementById("mc"+p.id);
if(e) e.style.backgroundColor = p.bg;
e = document.getElementById("md"+p.id);
if(e) e.style.backgroundColor = p.bg;
}
if( p.r<0 ) return;
if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg);
if( p.sb>0 ) drawDotted(p,tx.rowinfo[p.sb-tx.iTopRow],p.fg);
var cls = node.cls;
if( p.hasOwnProperty('mi') && p.mi.length ) cls += " merge";
if( p.f&1 ) cls += " leaf";
var n = drawBox(cls,p.bg,p.x,p.y);
n.id = "tln"+p.id;
n.onclick = clickOnNode;
n.style.zIndex = 10;
if( !tx.omitDescenders ){
if( p.u==0 ){
if( p.hasOwnProperty('mo') && p.r==p.mo ){
var ix = p.hasOwnProperty('cu') ? p.cu : p.mu;
var top = tx.rowinfo[ix-tx.iTopRow]
drawUpArrow(p,{x: p.x, y: top.y-node.h}, p.fg);
}else if( p.y>100 ){
drawUpArrow(p,{x: p.x, y: p.y-50}, p.fg);
}else{
drawUpArrow(p,{x: p.x, y: 0},p.fg);
}
}
if( p.hasOwnProperty('d') ){
if( p.y + 150 >= btm ){
drawUpArrow({x: p.x, y: btm - node.h/2},p,p.fg);
}else{
drawUpArrow({x: p.x, y: p.y+50},p,p.fg);
drawDotted({x: p.x, y: p.y+63},{x: p.x, y: p.y+50-node.h/2},p.fg);
}
}
}
if( p.hasOwnProperty('mo') ){
var x0 = p.x + node.w/2;
var x1 = p.mo*railPitch + node.w/2;
var u = tx.rowinfo[p.mu-tx.iTopRow];
var y1 = miLineY(u);
if( p.u<=0 || p.mo!=p.r ){
if( p.u==0 && p.mo==p.r ){
mergeLines[p.mo] = u.r<p.r ? -mergeOffset-mLine.w : mergeOffset;
}else{
mergeLines[p.mo] = -mLine.w/2;
}
x1 += mergeLines[p.mo]
var y0 = p.y+2;
if( p.mu==p.id ){
drawCherrypickLine(x0,y0,x1+(x0<x1 ? mLine.w : 0),null);
y1 = y0;
}else{
drawMergeLine(x0,y0,x1+(x0<x1 ? mLine.w : 0),null);
drawMergeLine(x1,y0+mLine.w,null,y1);
|
| ︙ | ︙ |
Changes to src/info.c.
| ︙ | ︙ | |||
282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
}
blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC");
db_prepare(&q, "%s", blob_sql_text(&sql));
www_print_timeline(&q,
TIMELINE_GRAPH
|TIMELINE_FILLGAPS
|TIMELINE_NOSCROLL
|TIMELINE_CHPICK,
0, 0, rid, 0);
db_finalize(&q);
}
/*
** Show a graph all wiki, tickets, and check-ins that refer to object zUuid.
| > | 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
}
blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC");
db_prepare(&q, "%s", blob_sql_text(&sql));
www_print_timeline(&q,
TIMELINE_GRAPH
|TIMELINE_FILLGAPS
|TIMELINE_NOSCROLL
|TIMELINE_XMERGE
|TIMELINE_CHPICK,
0, 0, rid, 0);
db_finalize(&q);
}
/*
** Show a graph all wiki, tickets, and check-ins that refer to object zUuid.
|
| ︙ | ︙ |
Changes to src/tag.c.
| ︙ | ︙ | |||
748 749 750 751 752 753 754 |
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
zUnaryOp/*safe-for-%s*/, TAG_HIDDEN);
}
db_prepare(&q, "%s ORDER BY event.mtime DESC /*sort*/", blob_sql_text(&sql));
blob_reset(&sql);
/* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too
** many descenders to (off-screen) parents. */
| | | 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 |
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
zUnaryOp/*safe-for-%s*/, TAG_HIDDEN);
}
db_prepare(&q, "%s ORDER BY event.mtime DESC /*sort*/", blob_sql_text(&sql));
blob_reset(&sql);
/* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too
** many descenders to (off-screen) parents. */
tmFlags = TIMELINE_XMERGE | TIMELINE_FILLGAPS | TIMELINE_NOSCROLL;
if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH;
if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR;
if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR;
www_print_timeline(&q, tmFlags, 0, 0, 0, 0);
db_finalize(&q);
@ <br />
style_footer();
|
| ︙ | ︙ |
Changes to src/timeline.c.
| ︙ | ︙ | |||
89 90 91 92 93 94 95 | } } /* ** Allowed flags for the tmFlags argument to www_print_timeline */ #if INTERFACE | | | | | | | | | | | | | | | | | | | | | | | > | 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
}
}
/*
** Allowed flags for the tmFlags argument to www_print_timeline
*/
#if INTERFACE
#define TIMELINE_ARTID 0x0000001 /* Show artifact IDs on non-check-in lines*/
#define TIMELINE_LEAFONLY 0x0000002 /* Show "Leaf" but not "Merge", "Fork" etc*/
#define TIMELINE_BRIEF 0x0000004 /* Combine adjacent elements of same obj */
#define TIMELINE_GRAPH 0x0000008 /* Compute a graph */
#define TIMELINE_DISJOINT 0x0000010 /* Elements are not contiguous */
#define TIMELINE_FCHANGES 0x0000020 /* Detail file changes */
#define TIMELINE_BRCOLOR 0x0000040 /* Background color by branch name */
#define TIMELINE_UCOLOR 0x0000080 /* Background color by user */
#define TIMELINE_FRENAMES 0x0000100 /* Detail only file name changes */
#define TIMELINE_UNHIDE 0x0000200 /* Unhide check-ins with "hidden" tag */
#define TIMELINE_SHOWRID 0x0000400 /* Show RID values in addition to UUIDs */
#define TIMELINE_BISECT 0x0000800 /* Show supplimental bisect information */
#define TIMELINE_COMPACT 0x0001000 /* Use the "compact" view style */
#define TIMELINE_VERBOSE 0x0002000 /* Use the "detailed" view style */
#define TIMELINE_MODERN 0x0004000 /* Use the "modern" view style */
#define TIMELINE_COLUMNAR 0x0008000 /* Use the "columns" view style */
#define TIMELINE_CLASSIC 0x0010000 /* Use the "classic" view style */
#define TIMELINE_VIEWS 0x001f000 /* Mask for all of the view styles */
#define TIMELINE_NOSCROLL 0x0100000 /* Don't scroll to the selection */
#define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */
#define TIMELINE_CHPICK 0x0400000 /* Show cherrypick merges */
#define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */
#define TIMELINE_XMERGE 0x1000000 /* Omit merges from off-graph nodes */
#endif
/*
** Hash a string and use the hash to determine a background color.
*/
char *hash_color(const char *z){
int i; /* Loop counter */
|
| ︙ | ︙ | |||
295 296 297 298 299 300 301 |
TAG_BRANCH
);
if( (tmFlags & TIMELINE_CHPICK)!=0
&& !db_table_exists("repository","cherrypick")
){
tmFlags &= ~TIMELINE_CHPICK;
}
| < | > | 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
TAG_BRANCH
);
if( (tmFlags & TIMELINE_CHPICK)!=0
&& !db_table_exists("repository","cherrypick")
){
tmFlags &= ~TIMELINE_CHPICK;
}
@ <table id="timelineTable%d(iTableId)" class="timelineTable"> \
@ <!-- tmFlags: 0x%x(tmFlags) -->
blob_zero(&comment);
while( db_step(pQuery)==SQLITE_ROW ){
int rid = db_column_int(pQuery, 0);
const char *zUuid = db_column_text(pQuery, 1);
int isLeaf = db_column_int(pQuery, 5);
const char *zBgClr = db_column_text(pQuery, 6);
const char *zDate = db_column_text(pQuery, 2);
|
| ︙ | ︙ | |||
865 866 867 868 869 870 871 |
** id: The id of the <div> element for the row. This is an integer.
** to get an actual id, prepend "m" to the integer. The top node
** is iTopRow and numbers increase moving down the timeline.
** bg: The background color for this row
** r: The "rail" that the node for this row sits on. The left-most
** rail is 0 and the number increases to the right.
** d: If exists and true then there is a "descender" - an arrow
| | > | > | 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 |
** id: The id of the <div> element for the row. This is an integer.
** to get an actual id, prepend "m" to the integer. The top node
** is iTopRow and numbers increase moving down the timeline.
** bg: The background color for this row
** r: The "rail" that the node for this row sits on. The left-most
** rail is 0 and the number increases to the right.
** d: If exists and true then there is a "descender" - an arrow
** coming from the bottom of the page or further down on the page
** straight up to this node.
** mo: "merge-out". If it exists, this is the rail position
** for the upward portion of a merge arrow. The merge arrow goes as
** a solid normal merge line up to the row identified by "mu" and
** then as a dashed cherrypick merge line up further to "cu".
** If this value is omitted if there are no merge children.
** mu: The id of the row which is the top of the merge-out arrow.
** Only exists if "mo" exists.
** cu: Extend the mu merge arrow up to this row as a cherrypick
** merge line, if this value exists.
** u: Draw a thick child-line out of the top of this node and up to
** the node with an id equal to this value. 0 if it is straight to
** the top of the page or just up a little wasy, -1 if there is
** no thick-line riser (if the node is a leaf).
** sb: Draw a dotted child-line out of the top of this node up to the
** node with the id equal to the value. This is like "u" except
** that the line is dotted instead of solid and has no arrow.
** Mnemonic: "Same Branch".
** f: 0x01: a leaf node.
** au: An array of integers that define thick-line risers for branches.
** The integers are in pairs. For each pair, the first integer is
|
| ︙ | ︙ | |||
1492 1493 1494 1495 1496 1497 1498 | ** ng No Graph. ** ncp Omit cherrypick merges ** nd Do not highlight the focus check-in ** v Show details of files changed ** f=CHECKIN Show family (immediate parents and children) of CHECKIN ** from=CHECKIN Path from... ** to=CHECKIN ... to this | | | 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 | ** ng No Graph. ** ncp Omit cherrypick merges ** nd Do not highlight the focus check-in ** v Show details of files changed ** f=CHECKIN Show family (immediate parents and children) of CHECKIN ** from=CHECKIN Path from... ** to=CHECKIN ... to this ** shortest ... show only the shortest path ** rel ... also show related checkins ** uf=FILE_HASH Show only check-ins that contain the given file version ** chng=GLOBLIST Show only check-ins that involve changes to a file whose ** name matches one of the comma-separate GLOBLIST ** brbg Background color from branch name ** ubg Background color from user ** namechng Show only check-ins that have filename changes |
| ︙ | ︙ | |||
1689 1690 1691 1692 1693 1694 1695 |
}
if( zType[0]=='a' ){
tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH | TIMELINE_CHPICK;
}else{
tmFlags |= TIMELINE_GRAPH | TIMELINE_CHPICK;
}
if( related ){
| | | | 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 |
}
if( zType[0]=='a' ){
tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH | TIMELINE_CHPICK;
}else{
tmFlags |= TIMELINE_GRAPH | TIMELINE_CHPICK;
}
if( related ){
tmFlags |= TIMELINE_FILLGAPS | TIMELINE_XMERGE;
tmFlags &= ~TIMELINE_DISJOINT;
}
if( PB("ncp") ){
tmFlags &= ~TIMELINE_CHPICK;
}
if( PB("ng") || zSearch!=0 ){
tmFlags &= ~(TIMELINE_GRAPH|TIMELINE_CHPICK);
}
|
| ︙ | ︙ | |||
1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 |
}
}
db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related");
}
blob_append_sql(&sql, " AND event.objid IN pathnode");
addFileGlobExclusion(zChng, &sql);
tmFlags |= TIMELINE_DISJOINT;
db_multi_exec("%s", blob_sql_text(&sql));
if( advancedMenu ){
style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
}
blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath);
blob_appendf(&desc, "%z[%h]</a>", href("%R/info/%h", zFrom), zFrom);
blob_append(&desc, " to ", -1);
| > | 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 |
}
}
db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related");
}
blob_append_sql(&sql, " AND event.objid IN pathnode");
addFileGlobExclusion(zChng, &sql);
tmFlags |= TIMELINE_DISJOINT;
tmFlags &= ~TIMELINE_CHPICK;
db_multi_exec("%s", blob_sql_text(&sql));
if( advancedMenu ){
style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
}
blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath);
blob_appendf(&desc, "%z[%h]</a>", href("%R/info/%h", zFrom), zFrom);
blob_append(&desc, " to ", -1);
|
| ︙ | ︙ | |||
1877 1878 1879 1880 1881 1882 1883 |
}
addFileGlobDescription(zChng, &desc);
}else if( (p_rid || d_rid) && g.perm.Read && zTagSql==0 ){
/* If p= or d= is present, ignore all other parameters other than n= */
char *zUuid;
int np, nd;
| | | 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 |
}
addFileGlobDescription(zChng, &desc);
}else if( (p_rid || d_rid) && g.perm.Read && zTagSql==0 ){
/* If p= or d= is present, ignore all other parameters other than n= */
char *zUuid;
int np, nd;
tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
if( p_rid && d_rid ){
if( p_rid!=d_rid ) p_rid = d_rid;
if( P("n")==0 ) nEntry = 10;
}
db_multi_exec(
"CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
);
|
| ︙ | ︙ | |||
1945 1946 1947 1948 1949 1950 1951 |
}
blob_append_sql(&sql, " AND event.objid IN ok");
db_multi_exec("%s", blob_sql_text(&sql));
if( useDividers ) selectedRid = f_rid;
blob_appendf(&desc, "Parents and children of check-in ");
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%!S", zUuid), zUuid);
| | > | | 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 |
}
blob_append_sql(&sql, " AND event.objid IN ok");
db_multi_exec("%s", blob_sql_text(&sql));
if( useDividers ) selectedRid = f_rid;
blob_appendf(&desc, "Parents and children of check-in ");
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%!S", zUuid), zUuid);
tmFlags |= TIMELINE_XMERGE;
if( advancedMenu ){
style_submenu_checkbox("unhide", "Unhide", 0, 0);
style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
}
}else{
/* Otherwise, a timeline based on a span of time */
int n;
const char *zEType = "event";
char *zDate;
Blob cond;
blob_zero(&cond);
tmFlags |= TIMELINE_FILLGAPS;
if( zChng && *zChng ){
addFileGlobExclusion(zChng, &cond);
tmFlags |= TIMELINE_XMERGE;
}
if( zUses ){
blob_append_sql(&cond, " AND event.objid IN usesfile ");
}
if( renameOnly ){
blob_append_sql(&cond, " AND event.objid IN rnfile ");
}
|
| ︙ | ︙ | |||
2218 2219 2220 2221 2222 2223 2224 |
}else{
blob_appendf(&desc, "%d %s%s", n, zEType, zPlural);
}
if( zUses ){
char *zFilenames = names_of_file(zUses);
blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
href("%R/artifact/%!S",zUses), zUses);
| | | | | | 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 |
}else{
blob_appendf(&desc, "%d %s%s", n, zEType, zPlural);
}
if( zUses ){
char *zFilenames = names_of_file(zUses);
blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
href("%R/artifact/%!S",zUses), zUses);
tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
}
if( renameOnly ){
blob_appendf(&desc, " that contain filename changes");
tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
}
if( forkOnly ){
blob_appendf(&desc, " associated with forks");
tmFlags |= TIMELINE_DISJOINT;
}
if( bisectLocal || zBisect!=0 ){
blob_appendf(&desc, " in a bisect");
tmFlags |= TIMELINE_DISJOINT;
}
if( cpOnly && showCherrypicks ){
blob_appendf(&desc, " that participate in a cherrypick merge");
tmFlags |= TIMELINE_CHPICK|TIMELINE_DISJOINT;
}
if( zUser ){
blob_appendf(&desc, " by user %h", zUser);
tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
}
if( zTagSql ){
if( matchStyle==MS_EXACT ){
if( related ){
blob_appendf(&desc, " related to %h", zMatchDesc);
}else{
blob_appendf(&desc, " tagged with %h", zMatchDesc);
}
}else{
if( related ){
blob_appendf(&desc, " related to tags matching %h", zMatchDesc);
}else{
blob_appendf(&desc, " with tags matching %h", zMatchDesc);
}
}
tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
}
addFileGlobDescription(zChng, &desc);
if( rAfter>0.0 ){
if( rBefore>0.0 ){
blob_appendf(&desc, " occurring between %h and %h.<br />",
zAfter, zBefore);
}else{
|
| ︙ | ︙ |