Fossil SCM

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.

drh 2019-05-17 19:49 trunk merge
Commit d14590dbff44620dc47e062e1a3b0832c8c735e7b46f581630af5f50684c1ea8
--- src/default_css.txt
+++ src/default_css.txt
@@ -189,14 +189,14 @@
189189
border-left: 7px solid #600000;
190190
}
191191
.tl-line.warp {
192192
background: #600000;
193193
}
194
-.tl-line.dotted {
194
+.tl-line.dotted.v {
195195
width: 0px;
196
- border-top: 0px dotted #888;
197
- border-left: 2px dotted #888;
196
+ border-left-width: 2px;
197
+ border-left-style: dotted;
198198
background: rgba(255,255,255,0);
199199
}
200200
span.tagDsp {
201201
font-weight: bold;
202202
}
203203
--- src/default_css.txt
+++ src/default_css.txt
@@ -189,14 +189,14 @@
189 border-left: 7px solid #600000;
190 }
191 .tl-line.warp {
192 background: #600000;
193 }
194 .tl-line.dotted {
195 width: 0px;
196 border-top: 0px dotted #888;
197 border-left: 2px dotted #888;
198 background: rgba(255,255,255,0);
199 }
200 span.tagDsp {
201 font-weight: bold;
202 }
203
--- src/default_css.txt
+++ src/default_css.txt
@@ -189,14 +189,14 @@
189 border-left: 7px solid #600000;
190 }
191 .tl-line.warp {
192 background: #600000;
193 }
194 .tl-line.dotted.v {
195 width: 0px;
196 border-left-width: 2px;
197 border-left-style: dotted;
198 background: rgba(255,255,255,0);
199 }
200 span.tagDsp {
201 font-weight: bold;
202 }
203
+75 -35
--- src/graph.c
+++ src/graph.c
@@ -18,10 +18,30 @@
1818
** This file contains code to compute a revision history graph.
1919
*/
2020
#include "config.h"
2121
#include "graph.h"
2222
#include <assert.h>
23
+
24
+/* Notes:
25
+**
26
+** The graph is laid out in 1 or more "rails". A "rail" is a vertical
27
+** band in the graph in which one can place nodes or arrows connecting
28
+** nodes. There can be between 1 and GR_MAX_RAIL rails. If the graph
29
+** is to complex to be displayed in GR_MAX_RAIL rails, it is omitted.
30
+**
31
+** A "riser" is the thick line that comes out of the top of a node and
32
+** goes up to the next node on the branch, or to the top of the screen.
33
+** A "descender" is a thick line that comes out of the bottom of a node
34
+** and proceeds down to the bottom of the page.
35
+**
36
+** Invoke graph_init() to create a new GraphContext object. Then
37
+** call graph_add_row() to add nodes, one by one, to the graph.
38
+** Nodes must be added in display order, from top to bottom.
39
+** Then invoke graph_render() to run the layout algorithm. The
40
+** layout algorithm computes which rails all of the nodes sit on, and
41
+** the rails used for merge arrows.
42
+*/
2343
2444
#if INTERFACE
2545
2646
#define GR_MAX_RAIL 40 /* Max number of "rails" to display */
2747
@@ -33,11 +53,11 @@
3353
** The nParent field is -1 for entires that do not participate in the graph
3454
** but which are included just so that we can capture their background color.
3555
*/
3656
struct GraphRow {
3757
int rid; /* The rid for the check-in */
38
- i8 nParent; /* Number of parents. -1 for technote lines */
58
+ i8 nParent; /* Number of parents. */
3959
i8 nCherrypick; /* Subset of aParent that are cherrypicks */
4060
i8 nNonCherrypick; /* Number of non-cherrypick parents */
4161
int *aParent; /* Array of parents. 0 element is primary .*/
4262
char *zBranch; /* Branch name */
4363
char *zBgClr; /* Background Color */
@@ -44,11 +64,11 @@
4464
char zUuid[HNAME_MAX+1]; /* Check-in for file ID */
4565
4666
GraphRow *pNext; /* Next row down in the list of all rows */
4767
GraphRow *pPrev; /* Previous row */
4868
49
- int idx; /* Row index. First is 1. 0 used for "none" */
69
+ int idx; /* Row index. Top row is smallest. */
5070
int idxTop; /* Direct descendent highest up on the graph */
5171
GraphRow *pChild; /* Child immediately above this node */
5272
u8 isDup; /* True if this is duplicate of a prior entry */
5373
u8 isLeaf; /* True if this is a leaf node */
5474
u8 isStepParent; /* pChild is actually a step-child */
@@ -61,11 +81,10 @@
6181
int aiRiser[GR_MAX_RAIL]; /* Risers from this node to a higher row. */
6282
int mergeUpto; /* Draw the mergeOut rail up to this level */
6383
int cherrypickUpto; /* Continue the mergeOut rail up to here */
6484
u64 mergeDown; /* Draw merge lines up from bottom of graph */
6585
u64 cherrypickDown; /* Draw cherrypick lines up from bottom */
66
-
6786
u64 railInUse; /* Mask of occupied rails at this row */
6887
};
6988
7089
/* Context while building a graph
7190
*/
@@ -83,10 +102,17 @@
83102
84103
#endif
85104
86105
/* The N-th bit */
87106
#define BIT(N) (((u64)1)<<(N))
107
+
108
+/*
109
+** Number of rows before and answer a node with a riser or descender
110
+** that goes off-screen before we can reuse that rail.
111
+*/
112
+#define RISER_MARGIN 4
113
+
88114
89115
/*
90116
** Malloc for zeroed space. Panic if unable to provide the
91117
** requested space.
92118
*/
@@ -246,11 +272,11 @@
246272
for(pRow=p->pFirst; pRow && pRow->idx<top; pRow=pRow->pNext){}
247273
while( pRow && pRow->idx<=btm ){
248274
inUseMask |= pRow->railInUse;
249275
pRow = pRow->pNext;
250276
}
251
- for(i=0; i<32; i++){
277
+ for(i=0; i<GR_MAX_RAIL; i++){
252278
if( (inUseMask & BIT(i))==0 ){
253279
int dist;
254280
if( iNearto<=0 ){
255281
return i;
256282
}
@@ -268,11 +294,11 @@
268294
}
269295
270296
/*
271297
** Assign all children of node pBottom to the same rail as pBottom.
272298
*/
273
-static void assignChildrenToRail(GraphRow *pBottom){
299
+static void assignChildrenToRail(GraphRow *pBottom, u32 tmFlags){
274300
int iRail = pBottom->iRail;
275301
GraphRow *pCurrent;
276302
GraphRow *pPrior;
277303
u64 mask = ((u64)1)<<iRail;
278304
@@ -287,10 +313,18 @@
287313
while( pPrior->idx > pCurrent->idx ){
288314
pPrior->railInUse |= mask;
289315
pPrior = pPrior->pPrev;
290316
assert( pPrior!=0 );
291317
}
318
+ }
319
+ /* Mask of additional rows for the riser to infinity */
320
+ if( !pPrior->isLeaf && (tmFlags & TIMELINE_DISJOINT)==0 ){
321
+ int n = RISER_MARGIN;
322
+ GraphRow *p;
323
+ for(p=pPrior; p && (n--)>0; p=p->pPrev){
324
+ p->railInUse |= mask;
325
+ }
292326
}
293327
}
294328
295329
/*
296330
** Create a merge-arrow riser going from pParent up to pChild.
@@ -305,20 +339,21 @@
305339
u64 mask;
306340
GraphRow *pLoop;
307341
308342
if( pParent->mergeOut<0 ){
309343
u = pParent->aiRiser[pParent->iRail];
310
- if( u>=0 && u<pChild->idx ){
344
+ if( u>0 && u<pChild->idx ){
311345
/* The thick arrow up to the next primary child of pDesc goes
312346
** further up than the thin merge arrow riser, so draw them both
313347
** on the same rail. */
314348
pParent->mergeOut = pParent->iRail;
315
- }else{
349
+ }else{
316350
/* The thin merge arrow riser is taller than the thick primary
317351
** child riser, so use separate rails. */
318352
int iTarget = pParent->iRail;
319
- pParent->mergeOut = findFreeRail(p, pChild->idx, pParent->idx-1, iTarget);
353
+ int iBtm = pParent->idx - (u==0 ? RISER_MARGIN : 1);
354
+ pParent->mergeOut = findFreeRail(p, pChild->idx, iBtm, iTarget);
320355
mask = BIT(pParent->mergeOut);
321356
for(pLoop=pChild->pNext; pLoop && pLoop->rid!=pParent->rid;
322357
pLoop=pLoop->pNext){
323358
pLoop->railInUse |= mask;
324359
}
@@ -353,16 +388,18 @@
353388
}
354389
}
355390
}
356391
357392
/*
358
-** Draw a riser from pRow to the top of the graph
393
+** Draw a riser from pRow upward to indicate that it is going
394
+** to a node that is off the graph to the top.
359395
*/
360396
static void riser_to_top(GraphRow *pRow){
361397
u64 mask = BIT(pRow->iRail);
398
+ int n = RISER_MARGIN;
362399
pRow->aiRiser[pRow->iRail] = 0;
363
- while( pRow ){
400
+ while( pRow && (n--)>0 ){
364401
pRow->railInUse |= mask;
365402
pRow = pRow->pPrev;
366403
}
367404
}
368405
@@ -378,10 +415,11 @@
378415
** The tmFlags parameter is zero or more of the TIMELINE_* constants.
379416
** Only the following are honored:
380417
**
381418
** TIMELINE_DISJOINT: Omit descenders
382419
** TIMELINE_FILLGAPS: Use step-children
420
+** TIMELINE_XMERGE: Omit off-graph merge lines
383421
*/
384422
void graph_finish(GraphContext *p, u32 tmFlags){
385423
GraphRow *pRow, *pDesc, *pDup, *pLoop, *pParent;
386424
int i, j;
387425
u64 mask;
@@ -421,11 +459,11 @@
421459
** A merge parent is a prior check-in from which changes were merged into
422460
** the current check-in. If a merge parent is not in the visible section
423461
** of this graph, then no arrows will be drawn for it, so remove it from
424462
** the aParent[] array.
425463
*/
426
- if( omitDescenders ){
464
+ if( (tmFlags & (TIMELINE_DISJOINT|TIMELINE_XMERGE))!=0 ){
427465
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
428466
for(i=1; i<pRow->nParent; i++){
429467
if( hashFind(p, pRow->aParent[i])==0 ){
430468
memmove(pRow->aParent+i, pRow->aParent+i+1,
431469
sizeof(pRow->aParent[0])*(pRow->nParent-i-1));
@@ -477,71 +515,72 @@
477515
if( pRow->nParent<=0 ) continue; /* Root node */
478516
pParent = hashFind(p, pRow->aParent[0]);
479517
if( pParent==0 ) continue; /* Parent off-screen */
480518
if( pParent->zBranch!=pRow->zBranch ) continue; /* Different branch */
481519
if( pParent->idx <= pRow->idx ){
482
- pParent->timeWarp = 1;
483
- continue; /* Time-warp */
484
- }
485
- if( pRow->idxTop < pParent->idxTop ){
520
+ pParent->timeWarp = 1;
521
+ }else if( pRow->idx < pParent->idx ){
486522
pParent->pChild = pRow;
487
- pParent->idxTop = pRow->idxTop;
488523
}
489524
}
490525
491526
if( tmFlags & TIMELINE_FILLGAPS ){
492
- /* If a node has no pChild, and there is a later node (a node higher
493
- ** up on the graph) in the same branch that has no parent, then make
494
- ** the lower node a step-child of the upper node.
527
+ /* If a node has no pChild but there is a node higher up in the graph
528
+ ** that is in the same branch and that other node has no parent in
529
+ ** the graph, the lower node a step-child of the upper node. This will
530
+ ** be represented on the graph by a thick dotted line without an arrowhead.
495531
*/
496532
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
497533
if( pRow->pChild ) continue;
498534
for(pLoop=pRow->pPrev; pLoop; pLoop=pLoop->pPrev){
499535
if( pLoop->nParent>0
500536
&& pLoop->zBranch==pRow->zBranch
501537
&& hashFind(p,pLoop->aParent[0])==0
502538
){
503539
pRow->pChild = pLoop;
504
- pRow->idxTop = pLoop->idxTop;
505540
pRow->isStepParent = 1;
506541
pLoop->aParent[0] = pRow->rid;
507542
break;
508543
}
509544
}
510545
}
511546
}
547
+
548
+ /* Set the idxTop values for all entries. The idxTop value is the
549
+ ** "idx" value for the top entry in its stack of children.
550
+ */
551
+ for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
552
+ GraphRow *pChild = pRow->pChild;
553
+ if( pChild && pRow->idxTop>pChild->idxTop ){
554
+ pRow->idxTop = pChild->idxTop;
555
+ }
556
+ }
512557
513558
/* Identify rows where the primary parent is off screen. Assign
514
- ** each to a rail and draw descenders to the bottom of the screen.
559
+ ** each to a rail and draw descenders downward.
515560
**
516561
** Strive to put the "trunk" branch on far left.
517562
*/
518563
zTrunk = persistBranchName(p, "trunk");
519564
for(i=0; i<2; i++){
520565
for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
566
+ if( i==0 && pRow->zBranch!=zTrunk ) continue;
567
+ if( pRow->iRail>=0 ) continue;
521568
if( pRow->isDup ) continue;
522569
if( pRow->nParent<0 ) continue;
523
- if( i==0 ){
524
- if( pRow->zBranch!=zTrunk ) continue;
525
- }else {
526
- if( pRow->iRail>=0 ) continue;
527
- }
528570
if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){
529
- if( omitDescenders ){
530
- pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx, 0);
531
- }else{
532
- pRow->iRail = ++p->mxRail;
533
- }
571
+ pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx+RISER_MARGIN, 0);
534572
if( p->mxRail>=GR_MAX_RAIL ) return;
535573
mask = BIT(pRow->iRail);
536574
if( !omitDescenders ){
575
+ int n = RISER_MARGIN;
537576
pRow->bDescender = pRow->nParent>0;
538
- for(pLoop=pRow; pLoop; pLoop=pLoop->pNext){
577
+ for(pLoop=pRow; pLoop && (n--)>0; pLoop=pLoop->pNext){
539578
pLoop->railInUse |= mask;
540579
}
541580
}
542
- assignChildrenToRail(pRow);
581
+ assignChildrenToRail(pRow, tmFlags);
543582
}
544583
}
545584
}
546585
547586
/* Assign rails to all rows that are still unassigned.
@@ -570,11 +609,12 @@
570609
continue;
571610
}
572611
if( pParent->idx>pRow->idx ){
573612
/* Common case: Child occurs after parent and is above the
574613
** parent in the timeline */
575
- pRow->iRail = findFreeRail(p, 0, pParent->idx, pParent->iRail);
614
+ pRow->iRail = findFreeRail(p, pRow->idxTop, pParent->idx,
615
+ pParent->iRail);
576616
if( p->mxRail>=GR_MAX_RAIL ) return;
577617
pParent->aiRiser[pRow->iRail] = pRow->idx;
578618
}else{
579619
/* Timewarp case: Child occurs earlier in time than parent and
580620
** appears below the parent in the timeline. */
@@ -591,11 +631,11 @@
591631
}
592632
}
593633
mask = BIT(pRow->iRail);
594634
pRow->railInUse |= mask;
595635
if( pRow->pChild ){
596
- assignChildrenToRail(pRow);
636
+ assignChildrenToRail(pRow, tmFlags);
597637
}else if( !omitDescenders && count_nonbranch_children(pRow->rid)!=0 ){
598638
if( !pRow->timeWarp ) riser_to_top(pRow);
599639
}
600640
if( pParent ){
601641
for(pLoop=pParent->pPrev; pLoop && pLoop!=pRow; pLoop=pLoop->pPrev){
602642
--- src/graph.c
+++ src/graph.c
@@ -18,10 +18,30 @@
18 ** This file contains code to compute a revision history graph.
19 */
20 #include "config.h"
21 #include "graph.h"
22 #include <assert.h>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
24 #if INTERFACE
25
26 #define GR_MAX_RAIL 40 /* Max number of "rails" to display */
27
@@ -33,11 +53,11 @@
33 ** The nParent field is -1 for entires that do not participate in the graph
34 ** but which are included just so that we can capture their background color.
35 */
36 struct GraphRow {
37 int rid; /* The rid for the check-in */
38 i8 nParent; /* Number of parents. -1 for technote lines */
39 i8 nCherrypick; /* Subset of aParent that are cherrypicks */
40 i8 nNonCherrypick; /* Number of non-cherrypick parents */
41 int *aParent; /* Array of parents. 0 element is primary .*/
42 char *zBranch; /* Branch name */
43 char *zBgClr; /* Background Color */
@@ -44,11 +64,11 @@
44 char zUuid[HNAME_MAX+1]; /* Check-in for file ID */
45
46 GraphRow *pNext; /* Next row down in the list of all rows */
47 GraphRow *pPrev; /* Previous row */
48
49 int idx; /* Row index. First is 1. 0 used for "none" */
50 int idxTop; /* Direct descendent highest up on the graph */
51 GraphRow *pChild; /* Child immediately above this node */
52 u8 isDup; /* True if this is duplicate of a prior entry */
53 u8 isLeaf; /* True if this is a leaf node */
54 u8 isStepParent; /* pChild is actually a step-child */
@@ -61,11 +81,10 @@
61 int aiRiser[GR_MAX_RAIL]; /* Risers from this node to a higher row. */
62 int mergeUpto; /* Draw the mergeOut rail up to this level */
63 int cherrypickUpto; /* Continue the mergeOut rail up to here */
64 u64 mergeDown; /* Draw merge lines up from bottom of graph */
65 u64 cherrypickDown; /* Draw cherrypick lines up from bottom */
66
67 u64 railInUse; /* Mask of occupied rails at this row */
68 };
69
70 /* Context while building a graph
71 */
@@ -83,10 +102,17 @@
83
84 #endif
85
86 /* The N-th bit */
87 #define BIT(N) (((u64)1)<<(N))
 
 
 
 
 
 
 
88
89 /*
90 ** Malloc for zeroed space. Panic if unable to provide the
91 ** requested space.
92 */
@@ -246,11 +272,11 @@
246 for(pRow=p->pFirst; pRow && pRow->idx<top; pRow=pRow->pNext){}
247 while( pRow && pRow->idx<=btm ){
248 inUseMask |= pRow->railInUse;
249 pRow = pRow->pNext;
250 }
251 for(i=0; i<32; i++){
252 if( (inUseMask & BIT(i))==0 ){
253 int dist;
254 if( iNearto<=0 ){
255 return i;
256 }
@@ -268,11 +294,11 @@
268 }
269
270 /*
271 ** Assign all children of node pBottom to the same rail as pBottom.
272 */
273 static void assignChildrenToRail(GraphRow *pBottom){
274 int iRail = pBottom->iRail;
275 GraphRow *pCurrent;
276 GraphRow *pPrior;
277 u64 mask = ((u64)1)<<iRail;
278
@@ -287,10 +313,18 @@
287 while( pPrior->idx > pCurrent->idx ){
288 pPrior->railInUse |= mask;
289 pPrior = pPrior->pPrev;
290 assert( pPrior!=0 );
291 }
 
 
 
 
 
 
 
 
292 }
293 }
294
295 /*
296 ** Create a merge-arrow riser going from pParent up to pChild.
@@ -305,20 +339,21 @@
305 u64 mask;
306 GraphRow *pLoop;
307
308 if( pParent->mergeOut<0 ){
309 u = pParent->aiRiser[pParent->iRail];
310 if( u>=0 && u<pChild->idx ){
311 /* The thick arrow up to the next primary child of pDesc goes
312 ** further up than the thin merge arrow riser, so draw them both
313 ** on the same rail. */
314 pParent->mergeOut = pParent->iRail;
315 }else{
316 /* The thin merge arrow riser is taller than the thick primary
317 ** child riser, so use separate rails. */
318 int iTarget = pParent->iRail;
319 pParent->mergeOut = findFreeRail(p, pChild->idx, pParent->idx-1, iTarget);
 
320 mask = BIT(pParent->mergeOut);
321 for(pLoop=pChild->pNext; pLoop && pLoop->rid!=pParent->rid;
322 pLoop=pLoop->pNext){
323 pLoop->railInUse |= mask;
324 }
@@ -353,16 +388,18 @@
353 }
354 }
355 }
356
357 /*
358 ** Draw a riser from pRow to the top of the graph
 
359 */
360 static void riser_to_top(GraphRow *pRow){
361 u64 mask = BIT(pRow->iRail);
 
362 pRow->aiRiser[pRow->iRail] = 0;
363 while( pRow ){
364 pRow->railInUse |= mask;
365 pRow = pRow->pPrev;
366 }
367 }
368
@@ -378,10 +415,11 @@
378 ** The tmFlags parameter is zero or more of the TIMELINE_* constants.
379 ** Only the following are honored:
380 **
381 ** TIMELINE_DISJOINT: Omit descenders
382 ** TIMELINE_FILLGAPS: Use step-children
 
383 */
384 void graph_finish(GraphContext *p, u32 tmFlags){
385 GraphRow *pRow, *pDesc, *pDup, *pLoop, *pParent;
386 int i, j;
387 u64 mask;
@@ -421,11 +459,11 @@
421 ** A merge parent is a prior check-in from which changes were merged into
422 ** the current check-in. If a merge parent is not in the visible section
423 ** of this graph, then no arrows will be drawn for it, so remove it from
424 ** the aParent[] array.
425 */
426 if( omitDescenders ){
427 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
428 for(i=1; i<pRow->nParent; i++){
429 if( hashFind(p, pRow->aParent[i])==0 ){
430 memmove(pRow->aParent+i, pRow->aParent+i+1,
431 sizeof(pRow->aParent[0])*(pRow->nParent-i-1));
@@ -477,71 +515,72 @@
477 if( pRow->nParent<=0 ) continue; /* Root node */
478 pParent = hashFind(p, pRow->aParent[0]);
479 if( pParent==0 ) continue; /* Parent off-screen */
480 if( pParent->zBranch!=pRow->zBranch ) continue; /* Different branch */
481 if( pParent->idx <= pRow->idx ){
482 pParent->timeWarp = 1;
483 continue; /* Time-warp */
484 }
485 if( pRow->idxTop < pParent->idxTop ){
486 pParent->pChild = pRow;
487 pParent->idxTop = pRow->idxTop;
488 }
489 }
490
491 if( tmFlags & TIMELINE_FILLGAPS ){
492 /* If a node has no pChild, and there is a later node (a node higher
493 ** up on the graph) in the same branch that has no parent, then make
494 ** the lower node a step-child of the upper node.
 
495 */
496 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
497 if( pRow->pChild ) continue;
498 for(pLoop=pRow->pPrev; pLoop; pLoop=pLoop->pPrev){
499 if( pLoop->nParent>0
500 && pLoop->zBranch==pRow->zBranch
501 && hashFind(p,pLoop->aParent[0])==0
502 ){
503 pRow->pChild = pLoop;
504 pRow->idxTop = pLoop->idxTop;
505 pRow->isStepParent = 1;
506 pLoop->aParent[0] = pRow->rid;
507 break;
508 }
509 }
510 }
511 }
 
 
 
 
 
 
 
 
 
 
512
513 /* Identify rows where the primary parent is off screen. Assign
514 ** each to a rail and draw descenders to the bottom of the screen.
515 **
516 ** Strive to put the "trunk" branch on far left.
517 */
518 zTrunk = persistBranchName(p, "trunk");
519 for(i=0; i<2; i++){
520 for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
 
 
521 if( pRow->isDup ) continue;
522 if( pRow->nParent<0 ) continue;
523 if( i==0 ){
524 if( pRow->zBranch!=zTrunk ) continue;
525 }else {
526 if( pRow->iRail>=0 ) continue;
527 }
528 if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){
529 if( omitDescenders ){
530 pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx, 0);
531 }else{
532 pRow->iRail = ++p->mxRail;
533 }
534 if( p->mxRail>=GR_MAX_RAIL ) return;
535 mask = BIT(pRow->iRail);
536 if( !omitDescenders ){
 
537 pRow->bDescender = pRow->nParent>0;
538 for(pLoop=pRow; pLoop; pLoop=pLoop->pNext){
539 pLoop->railInUse |= mask;
540 }
541 }
542 assignChildrenToRail(pRow);
543 }
544 }
545 }
546
547 /* Assign rails to all rows that are still unassigned.
@@ -570,11 +609,12 @@
570 continue;
571 }
572 if( pParent->idx>pRow->idx ){
573 /* Common case: Child occurs after parent and is above the
574 ** parent in the timeline */
575 pRow->iRail = findFreeRail(p, 0, pParent->idx, pParent->iRail);
 
576 if( p->mxRail>=GR_MAX_RAIL ) return;
577 pParent->aiRiser[pRow->iRail] = pRow->idx;
578 }else{
579 /* Timewarp case: Child occurs earlier in time than parent and
580 ** appears below the parent in the timeline. */
@@ -591,11 +631,11 @@
591 }
592 }
593 mask = BIT(pRow->iRail);
594 pRow->railInUse |= mask;
595 if( pRow->pChild ){
596 assignChildrenToRail(pRow);
597 }else if( !omitDescenders && count_nonbranch_children(pRow->rid)!=0 ){
598 if( !pRow->timeWarp ) riser_to_top(pRow);
599 }
600 if( pParent ){
601 for(pLoop=pParent->pPrev; pLoop && pLoop!=pRow; pLoop=pLoop->pPrev){
602
--- src/graph.c
+++ src/graph.c
@@ -18,10 +18,30 @@
18 ** This file contains code to compute a revision history graph.
19 */
20 #include "config.h"
21 #include "graph.h"
22 #include <assert.h>
23
24 /* Notes:
25 **
26 ** The graph is laid out in 1 or more "rails". A "rail" is a vertical
27 ** band in the graph in which one can place nodes or arrows connecting
28 ** nodes. There can be between 1 and GR_MAX_RAIL rails. If the graph
29 ** is to complex to be displayed in GR_MAX_RAIL rails, it is omitted.
30 **
31 ** A "riser" is the thick line that comes out of the top of a node and
32 ** goes up to the next node on the branch, or to the top of the screen.
33 ** A "descender" is a thick line that comes out of the bottom of a node
34 ** and proceeds down to the bottom of the page.
35 **
36 ** Invoke graph_init() to create a new GraphContext object. Then
37 ** call graph_add_row() to add nodes, one by one, to the graph.
38 ** Nodes must be added in display order, from top to bottom.
39 ** Then invoke graph_render() to run the layout algorithm. The
40 ** layout algorithm computes which rails all of the nodes sit on, and
41 ** the rails used for merge arrows.
42 */
43
44 #if INTERFACE
45
46 #define GR_MAX_RAIL 40 /* Max number of "rails" to display */
47
@@ -33,11 +53,11 @@
53 ** The nParent field is -1 for entires that do not participate in the graph
54 ** but which are included just so that we can capture their background color.
55 */
56 struct GraphRow {
57 int rid; /* The rid for the check-in */
58 i8 nParent; /* Number of parents. */
59 i8 nCherrypick; /* Subset of aParent that are cherrypicks */
60 i8 nNonCherrypick; /* Number of non-cherrypick parents */
61 int *aParent; /* Array of parents. 0 element is primary .*/
62 char *zBranch; /* Branch name */
63 char *zBgClr; /* Background Color */
@@ -44,11 +64,11 @@
64 char zUuid[HNAME_MAX+1]; /* Check-in for file ID */
65
66 GraphRow *pNext; /* Next row down in the list of all rows */
67 GraphRow *pPrev; /* Previous row */
68
69 int idx; /* Row index. Top row is smallest. */
70 int idxTop; /* Direct descendent highest up on the graph */
71 GraphRow *pChild; /* Child immediately above this node */
72 u8 isDup; /* True if this is duplicate of a prior entry */
73 u8 isLeaf; /* True if this is a leaf node */
74 u8 isStepParent; /* pChild is actually a step-child */
@@ -61,11 +81,10 @@
81 int aiRiser[GR_MAX_RAIL]; /* Risers from this node to a higher row. */
82 int mergeUpto; /* Draw the mergeOut rail up to this level */
83 int cherrypickUpto; /* Continue the mergeOut rail up to here */
84 u64 mergeDown; /* Draw merge lines up from bottom of graph */
85 u64 cherrypickDown; /* Draw cherrypick lines up from bottom */
 
86 u64 railInUse; /* Mask of occupied rails at this row */
87 };
88
89 /* Context while building a graph
90 */
@@ -83,10 +102,17 @@
102
103 #endif
104
105 /* The N-th bit */
106 #define BIT(N) (((u64)1)<<(N))
107
108 /*
109 ** Number of rows before and answer a node with a riser or descender
110 ** that goes off-screen before we can reuse that rail.
111 */
112 #define RISER_MARGIN 4
113
114
115 /*
116 ** Malloc for zeroed space. Panic if unable to provide the
117 ** requested space.
118 */
@@ -246,11 +272,11 @@
272 for(pRow=p->pFirst; pRow && pRow->idx<top; pRow=pRow->pNext){}
273 while( pRow && pRow->idx<=btm ){
274 inUseMask |= pRow->railInUse;
275 pRow = pRow->pNext;
276 }
277 for(i=0; i<GR_MAX_RAIL; i++){
278 if( (inUseMask & BIT(i))==0 ){
279 int dist;
280 if( iNearto<=0 ){
281 return i;
282 }
@@ -268,11 +294,11 @@
294 }
295
296 /*
297 ** Assign all children of node pBottom to the same rail as pBottom.
298 */
299 static void assignChildrenToRail(GraphRow *pBottom, u32 tmFlags){
300 int iRail = pBottom->iRail;
301 GraphRow *pCurrent;
302 GraphRow *pPrior;
303 u64 mask = ((u64)1)<<iRail;
304
@@ -287,10 +313,18 @@
313 while( pPrior->idx > pCurrent->idx ){
314 pPrior->railInUse |= mask;
315 pPrior = pPrior->pPrev;
316 assert( pPrior!=0 );
317 }
318 }
319 /* Mask of additional rows for the riser to infinity */
320 if( !pPrior->isLeaf && (tmFlags & TIMELINE_DISJOINT)==0 ){
321 int n = RISER_MARGIN;
322 GraphRow *p;
323 for(p=pPrior; p && (n--)>0; p=p->pPrev){
324 p->railInUse |= mask;
325 }
326 }
327 }
328
329 /*
330 ** Create a merge-arrow riser going from pParent up to pChild.
@@ -305,20 +339,21 @@
339 u64 mask;
340 GraphRow *pLoop;
341
342 if( pParent->mergeOut<0 ){
343 u = pParent->aiRiser[pParent->iRail];
344 if( u>0 && u<pChild->idx ){
345 /* The thick arrow up to the next primary child of pDesc goes
346 ** further up than the thin merge arrow riser, so draw them both
347 ** on the same rail. */
348 pParent->mergeOut = pParent->iRail;
349 }else{
350 /* The thin merge arrow riser is taller than the thick primary
351 ** child riser, so use separate rails. */
352 int iTarget = pParent->iRail;
353 int iBtm = pParent->idx - (u==0 ? RISER_MARGIN : 1);
354 pParent->mergeOut = findFreeRail(p, pChild->idx, iBtm, iTarget);
355 mask = BIT(pParent->mergeOut);
356 for(pLoop=pChild->pNext; pLoop && pLoop->rid!=pParent->rid;
357 pLoop=pLoop->pNext){
358 pLoop->railInUse |= mask;
359 }
@@ -353,16 +388,18 @@
388 }
389 }
390 }
391
392 /*
393 ** Draw a riser from pRow upward to indicate that it is going
394 ** to a node that is off the graph to the top.
395 */
396 static void riser_to_top(GraphRow *pRow){
397 u64 mask = BIT(pRow->iRail);
398 int n = RISER_MARGIN;
399 pRow->aiRiser[pRow->iRail] = 0;
400 while( pRow && (n--)>0 ){
401 pRow->railInUse |= mask;
402 pRow = pRow->pPrev;
403 }
404 }
405
@@ -378,10 +415,11 @@
415 ** The tmFlags parameter is zero or more of the TIMELINE_* constants.
416 ** Only the following are honored:
417 **
418 ** TIMELINE_DISJOINT: Omit descenders
419 ** TIMELINE_FILLGAPS: Use step-children
420 ** TIMELINE_XMERGE: Omit off-graph merge lines
421 */
422 void graph_finish(GraphContext *p, u32 tmFlags){
423 GraphRow *pRow, *pDesc, *pDup, *pLoop, *pParent;
424 int i, j;
425 u64 mask;
@@ -421,11 +459,11 @@
459 ** A merge parent is a prior check-in from which changes were merged into
460 ** the current check-in. If a merge parent is not in the visible section
461 ** of this graph, then no arrows will be drawn for it, so remove it from
462 ** the aParent[] array.
463 */
464 if( (tmFlags & (TIMELINE_DISJOINT|TIMELINE_XMERGE))!=0 ){
465 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
466 for(i=1; i<pRow->nParent; i++){
467 if( hashFind(p, pRow->aParent[i])==0 ){
468 memmove(pRow->aParent+i, pRow->aParent+i+1,
469 sizeof(pRow->aParent[0])*(pRow->nParent-i-1));
@@ -477,71 +515,72 @@
515 if( pRow->nParent<=0 ) continue; /* Root node */
516 pParent = hashFind(p, pRow->aParent[0]);
517 if( pParent==0 ) continue; /* Parent off-screen */
518 if( pParent->zBranch!=pRow->zBranch ) continue; /* Different branch */
519 if( pParent->idx <= pRow->idx ){
520 pParent->timeWarp = 1;
521 }else if( pRow->idx < pParent->idx ){
 
 
522 pParent->pChild = pRow;
 
523 }
524 }
525
526 if( tmFlags & TIMELINE_FILLGAPS ){
527 /* If a node has no pChild but there is a node higher up in the graph
528 ** that is in the same branch and that other node has no parent in
529 ** the graph, the lower node a step-child of the upper node. This will
530 ** be represented on the graph by a thick dotted line without an arrowhead.
531 */
532 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
533 if( pRow->pChild ) continue;
534 for(pLoop=pRow->pPrev; pLoop; pLoop=pLoop->pPrev){
535 if( pLoop->nParent>0
536 && pLoop->zBranch==pRow->zBranch
537 && hashFind(p,pLoop->aParent[0])==0
538 ){
539 pRow->pChild = pLoop;
 
540 pRow->isStepParent = 1;
541 pLoop->aParent[0] = pRow->rid;
542 break;
543 }
544 }
545 }
546 }
547
548 /* Set the idxTop values for all entries. The idxTop value is the
549 ** "idx" value for the top entry in its stack of children.
550 */
551 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
552 GraphRow *pChild = pRow->pChild;
553 if( pChild && pRow->idxTop>pChild->idxTop ){
554 pRow->idxTop = pChild->idxTop;
555 }
556 }
557
558 /* Identify rows where the primary parent is off screen. Assign
559 ** each to a rail and draw descenders downward.
560 **
561 ** Strive to put the "trunk" branch on far left.
562 */
563 zTrunk = persistBranchName(p, "trunk");
564 for(i=0; i<2; i++){
565 for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
566 if( i==0 && pRow->zBranch!=zTrunk ) continue;
567 if( pRow->iRail>=0 ) continue;
568 if( pRow->isDup ) continue;
569 if( pRow->nParent<0 ) continue;
 
 
 
 
 
570 if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){
571 pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx+RISER_MARGIN, 0);
 
 
 
 
572 if( p->mxRail>=GR_MAX_RAIL ) return;
573 mask = BIT(pRow->iRail);
574 if( !omitDescenders ){
575 int n = RISER_MARGIN;
576 pRow->bDescender = pRow->nParent>0;
577 for(pLoop=pRow; pLoop && (n--)>0; pLoop=pLoop->pNext){
578 pLoop->railInUse |= mask;
579 }
580 }
581 assignChildrenToRail(pRow, tmFlags);
582 }
583 }
584 }
585
586 /* Assign rails to all rows that are still unassigned.
@@ -570,11 +609,12 @@
609 continue;
610 }
611 if( pParent->idx>pRow->idx ){
612 /* Common case: Child occurs after parent and is above the
613 ** parent in the timeline */
614 pRow->iRail = findFreeRail(p, pRow->idxTop, pParent->idx,
615 pParent->iRail);
616 if( p->mxRail>=GR_MAX_RAIL ) return;
617 pParent->aiRiser[pRow->iRail] = pRow->idx;
618 }else{
619 /* Timewarp case: Child occurs earlier in time than parent and
620 ** appears below the parent in the timeline. */
@@ -591,11 +631,11 @@
631 }
632 }
633 mask = BIT(pRow->iRail);
634 pRow->railInUse |= mask;
635 if( pRow->pChild ){
636 assignChildrenToRail(pRow, tmFlags);
637 }else if( !omitDescenders && count_nonbranch_children(pRow->rid)!=0 ){
638 if( !pRow->timeWarp ) riser_to_top(pRow);
639 }
640 if( pParent ){
641 for(pLoop=pParent->pPrev; pLoop && pLoop!=pRow; pLoop=pLoop->pPrev){
642
+31 -8
--- src/graph.js
+++ src/graph.js
@@ -183,11 +183,11 @@
183183
cls += "v";
184184
}else{
185185
y1 = y0+elem.w;
186186
cls += "h";
187187
}
188
- drawBox(cls,color,x0,y0,x1,y1);
188
+ return drawBox(cls,color,x0,y0,x1,y1);
189189
}
190190
function drawUpArrow(from,to,color){
191191
var y = to.y + node.h;
192192
var arrowSpace = from.y - y + (!from.id || from.r!=to.r ? node.h/2 : 0);
193193
var arw = arrowSpace < arrow.h*1.5 ? arrowSmall : arrow;
@@ -197,15 +197,16 @@
197197
drawLine(line,color,x,y0,null,y1);
198198
x = to.x + (node.w-arw.w)/2;
199199
var n = drawBox(arw.cls,null,x,y);
200200
if(color) n.style.borderBottomColor = color;
201201
}
202
- function drawUpDotted(from,to,color){
202
+ function drawDotted(from,to,color){
203203
var x = to.x + (node.w-line.w)/2;
204204
var y0 = from.y + node.h/2;
205205
var y1 = Math.ceil(to.y + node.h);
206
- drawLine(dotLine,color,x,y0,null,y1);
206
+ var n = drawLine(dotLine,null,x,y0,null,y1)
207
+ if( color ) n.style.borderColor = color
207208
}
208209
/* Draw thin horizontal or vertical lines representing merges */
209210
function drawMergeLine(x0,y0,x1,y1){
210211
drawLine(mLine,null,x0,y0,x1,y1);
211212
}
@@ -243,29 +244,51 @@
243244
e = document.getElementById("md"+p.id);
244245
if(e) e.style.backgroundColor = p.bg;
245246
}
246247
if( p.r<0 ) return;
247248
if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg);
248
- if( p.sb>0 ) drawUpDotted(p,tx.rowinfo[p.sb-tx.iTopRow],null);
249
+ if( p.sb>0 ) drawDotted(p,tx.rowinfo[p.sb-tx.iTopRow],p.fg);
249250
var cls = node.cls;
250251
if( p.hasOwnProperty('mi') && p.mi.length ) cls += " merge";
251252
if( p.f&1 ) cls += " leaf";
252253
var n = drawBox(cls,p.bg,p.x,p.y);
253254
n.id = "tln"+p.id;
254255
n.onclick = clickOnNode;
255256
n.style.zIndex = 10;
256257
if( !tx.omitDescenders ){
257
- if( p.u==0 ) drawUpArrow(p,{x: p.x, y: -node.h},p.fg);
258
- if( p.hasOwnProperty('d') ) drawUpArrow({x: p.x, y: btm-node.h/2},p,p.fg);
258
+ if( p.u==0 ){
259
+ if( p.hasOwnProperty('mo') && p.r==p.mo ){
260
+ var ix = p.hasOwnProperty('cu') ? p.cu : p.mu;
261
+ var top = tx.rowinfo[ix-tx.iTopRow]
262
+ drawUpArrow(p,{x: p.x, y: top.y-node.h}, p.fg);
263
+ }else if( p.y>100 ){
264
+ drawUpArrow(p,{x: p.x, y: p.y-50}, p.fg);
265
+ }else{
266
+ drawUpArrow(p,{x: p.x, y: 0},p.fg);
267
+ }
268
+ }
269
+ if( p.hasOwnProperty('d') ){
270
+ if( p.y + 150 >= btm ){
271
+ drawUpArrow({x: p.x, y: btm - node.h/2},p,p.fg);
272
+ }else{
273
+ drawUpArrow({x: p.x, y: p.y+50},p,p.fg);
274
+ drawDotted({x: p.x, y: p.y+63},{x: p.x, y: p.y+50-node.h/2},p.fg);
275
+ }
276
+ }
259277
}
260278
if( p.hasOwnProperty('mo') ){
261279
var x0 = p.x + node.w/2;
262280
var x1 = p.mo*railPitch + node.w/2;
263281
var u = tx.rowinfo[p.mu-tx.iTopRow];
264282
var y1 = miLineY(u);
265
- if( p.u<0 || p.mo!=p.r ){
266
- x1 += mergeLines[p.mo] = -mLine.w/2;
283
+ if( p.u<=0 || p.mo!=p.r ){
284
+ if( p.u==0 && p.mo==p.r ){
285
+ mergeLines[p.mo] = u.r<p.r ? -mergeOffset-mLine.w : mergeOffset;
286
+ }else{
287
+ mergeLines[p.mo] = -mLine.w/2;
288
+ }
289
+ x1 += mergeLines[p.mo]
267290
var y0 = p.y+2;
268291
if( p.mu==p.id ){
269292
drawCherrypickLine(x0,y0,x1+(x0<x1 ? mLine.w : 0),null);
270293
y1 = y0;
271294
}else{
272295
--- src/graph.js
+++ src/graph.js
@@ -183,11 +183,11 @@
183 cls += "v";
184 }else{
185 y1 = y0+elem.w;
186 cls += "h";
187 }
188 drawBox(cls,color,x0,y0,x1,y1);
189 }
190 function drawUpArrow(from,to,color){
191 var y = to.y + node.h;
192 var arrowSpace = from.y - y + (!from.id || from.r!=to.r ? node.h/2 : 0);
193 var arw = arrowSpace < arrow.h*1.5 ? arrowSmall : arrow;
@@ -197,15 +197,16 @@
197 drawLine(line,color,x,y0,null,y1);
198 x = to.x + (node.w-arw.w)/2;
199 var n = drawBox(arw.cls,null,x,y);
200 if(color) n.style.borderBottomColor = color;
201 }
202 function drawUpDotted(from,to,color){
203 var x = to.x + (node.w-line.w)/2;
204 var y0 = from.y + node.h/2;
205 var y1 = Math.ceil(to.y + node.h);
206 drawLine(dotLine,color,x,y0,null,y1);
 
207 }
208 /* Draw thin horizontal or vertical lines representing merges */
209 function drawMergeLine(x0,y0,x1,y1){
210 drawLine(mLine,null,x0,y0,x1,y1);
211 }
@@ -243,29 +244,51 @@
243 e = document.getElementById("md"+p.id);
244 if(e) e.style.backgroundColor = p.bg;
245 }
246 if( p.r<0 ) return;
247 if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg);
248 if( p.sb>0 ) drawUpDotted(p,tx.rowinfo[p.sb-tx.iTopRow],null);
249 var cls = node.cls;
250 if( p.hasOwnProperty('mi') && p.mi.length ) cls += " merge";
251 if( p.f&1 ) cls += " leaf";
252 var n = drawBox(cls,p.bg,p.x,p.y);
253 n.id = "tln"+p.id;
254 n.onclick = clickOnNode;
255 n.style.zIndex = 10;
256 if( !tx.omitDescenders ){
257 if( p.u==0 ) drawUpArrow(p,{x: p.x, y: -node.h},p.fg);
258 if( p.hasOwnProperty('d') ) drawUpArrow({x: p.x, y: btm-node.h/2},p,p.fg);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259 }
260 if( p.hasOwnProperty('mo') ){
261 var x0 = p.x + node.w/2;
262 var x1 = p.mo*railPitch + node.w/2;
263 var u = tx.rowinfo[p.mu-tx.iTopRow];
264 var y1 = miLineY(u);
265 if( p.u<0 || p.mo!=p.r ){
266 x1 += mergeLines[p.mo] = -mLine.w/2;
 
 
 
 
 
267 var y0 = p.y+2;
268 if( p.mu==p.id ){
269 drawCherrypickLine(x0,y0,x1+(x0<x1 ? mLine.w : 0),null);
270 y1 = y0;
271 }else{
272
--- src/graph.js
+++ src/graph.js
@@ -183,11 +183,11 @@
183 cls += "v";
184 }else{
185 y1 = y0+elem.w;
186 cls += "h";
187 }
188 return drawBox(cls,color,x0,y0,x1,y1);
189 }
190 function drawUpArrow(from,to,color){
191 var y = to.y + node.h;
192 var arrowSpace = from.y - y + (!from.id || from.r!=to.r ? node.h/2 : 0);
193 var arw = arrowSpace < arrow.h*1.5 ? arrowSmall : arrow;
@@ -197,15 +197,16 @@
197 drawLine(line,color,x,y0,null,y1);
198 x = to.x + (node.w-arw.w)/2;
199 var n = drawBox(arw.cls,null,x,y);
200 if(color) n.style.borderBottomColor = color;
201 }
202 function drawDotted(from,to,color){
203 var x = to.x + (node.w-line.w)/2;
204 var y0 = from.y + node.h/2;
205 var y1 = Math.ceil(to.y + node.h);
206 var n = drawLine(dotLine,null,x,y0,null,y1)
207 if( color ) n.style.borderColor = color
208 }
209 /* Draw thin horizontal or vertical lines representing merges */
210 function drawMergeLine(x0,y0,x1,y1){
211 drawLine(mLine,null,x0,y0,x1,y1);
212 }
@@ -243,29 +244,51 @@
244 e = document.getElementById("md"+p.id);
245 if(e) e.style.backgroundColor = p.bg;
246 }
247 if( p.r<0 ) return;
248 if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg);
249 if( p.sb>0 ) drawDotted(p,tx.rowinfo[p.sb-tx.iTopRow],p.fg);
250 var cls = node.cls;
251 if( p.hasOwnProperty('mi') && p.mi.length ) cls += " merge";
252 if( p.f&1 ) cls += " leaf";
253 var n = drawBox(cls,p.bg,p.x,p.y);
254 n.id = "tln"+p.id;
255 n.onclick = clickOnNode;
256 n.style.zIndex = 10;
257 if( !tx.omitDescenders ){
258 if( p.u==0 ){
259 if( p.hasOwnProperty('mo') && p.r==p.mo ){
260 var ix = p.hasOwnProperty('cu') ? p.cu : p.mu;
261 var top = tx.rowinfo[ix-tx.iTopRow]
262 drawUpArrow(p,{x: p.x, y: top.y-node.h}, p.fg);
263 }else if( p.y>100 ){
264 drawUpArrow(p,{x: p.x, y: p.y-50}, p.fg);
265 }else{
266 drawUpArrow(p,{x: p.x, y: 0},p.fg);
267 }
268 }
269 if( p.hasOwnProperty('d') ){
270 if( p.y + 150 >= btm ){
271 drawUpArrow({x: p.x, y: btm - node.h/2},p,p.fg);
272 }else{
273 drawUpArrow({x: p.x, y: p.y+50},p,p.fg);
274 drawDotted({x: p.x, y: p.y+63},{x: p.x, y: p.y+50-node.h/2},p.fg);
275 }
276 }
277 }
278 if( p.hasOwnProperty('mo') ){
279 var x0 = p.x + node.w/2;
280 var x1 = p.mo*railPitch + node.w/2;
281 var u = tx.rowinfo[p.mu-tx.iTopRow];
282 var y1 = miLineY(u);
283 if( p.u<=0 || p.mo!=p.r ){
284 if( p.u==0 && p.mo==p.r ){
285 mergeLines[p.mo] = u.r<p.r ? -mergeOffset-mLine.w : mergeOffset;
286 }else{
287 mergeLines[p.mo] = -mLine.w/2;
288 }
289 x1 += mergeLines[p.mo]
290 var y0 = p.y+2;
291 if( p.mu==p.id ){
292 drawCherrypickLine(x0,y0,x1+(x0<x1 ? mLine.w : 0),null);
293 y1 = y0;
294 }else{
295
+1
--- src/info.c
+++ src/info.c
@@ -284,10 +284,11 @@
284284
db_prepare(&q, "%s", blob_sql_text(&sql));
285285
www_print_timeline(&q,
286286
TIMELINE_GRAPH
287287
|TIMELINE_FILLGAPS
288288
|TIMELINE_NOSCROLL
289
+ |TIMELINE_XMERGE
289290
|TIMELINE_CHPICK,
290291
0, 0, rid, 0);
291292
db_finalize(&q);
292293
}
293294
294295
--- src/info.c
+++ src/info.c
@@ -284,10 +284,11 @@
284 db_prepare(&q, "%s", blob_sql_text(&sql));
285 www_print_timeline(&q,
286 TIMELINE_GRAPH
287 |TIMELINE_FILLGAPS
288 |TIMELINE_NOSCROLL
 
289 |TIMELINE_CHPICK,
290 0, 0, rid, 0);
291 db_finalize(&q);
292 }
293
294
--- src/info.c
+++ src/info.c
@@ -284,10 +284,11 @@
284 db_prepare(&q, "%s", blob_sql_text(&sql));
285 www_print_timeline(&q,
286 TIMELINE_GRAPH
287 |TIMELINE_FILLGAPS
288 |TIMELINE_NOSCROLL
289 |TIMELINE_XMERGE
290 |TIMELINE_CHPICK,
291 0, 0, rid, 0);
292 db_finalize(&q);
293 }
294
295
+1 -1
--- src/tag.c
+++ src/tag.c
@@ -750,11 +750,11 @@
750750
}
751751
db_prepare(&q, "%s ORDER BY event.mtime DESC /*sort*/", blob_sql_text(&sql));
752752
blob_reset(&sql);
753753
/* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too
754754
** many descenders to (off-screen) parents. */
755
- tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL;
755
+ tmFlags = TIMELINE_XMERGE | TIMELINE_FILLGAPS | TIMELINE_NOSCROLL;
756756
if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH;
757757
if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR;
758758
if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR;
759759
www_print_timeline(&q, tmFlags, 0, 0, 0, 0);
760760
db_finalize(&q);
761761
--- src/tag.c
+++ src/tag.c
@@ -750,11 +750,11 @@
750 }
751 db_prepare(&q, "%s ORDER BY event.mtime DESC /*sort*/", blob_sql_text(&sql));
752 blob_reset(&sql);
753 /* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too
754 ** many descenders to (off-screen) parents. */
755 tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL;
756 if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH;
757 if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR;
758 if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR;
759 www_print_timeline(&q, tmFlags, 0, 0, 0, 0);
760 db_finalize(&q);
761
--- src/tag.c
+++ src/tag.c
@@ -750,11 +750,11 @@
750 }
751 db_prepare(&q, "%s ORDER BY event.mtime DESC /*sort*/", blob_sql_text(&sql));
752 blob_reset(&sql);
753 /* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too
754 ** many descenders to (off-screen) parents. */
755 tmFlags = TIMELINE_XMERGE | TIMELINE_FILLGAPS | TIMELINE_NOSCROLL;
756 if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH;
757 if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR;
758 if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR;
759 www_print_timeline(&q, tmFlags, 0, 0, 0, 0);
760 db_finalize(&q);
761
+41 -36
--- src/timeline.c
+++ src/timeline.c
@@ -91,32 +91,33 @@
9191
9292
/*
9393
** Allowed flags for the tmFlags argument to www_print_timeline
9494
*/
9595
#if INTERFACE
96
-#define TIMELINE_ARTID 0x000001 /* Show artifact IDs on non-check-in lines*/
97
-#define TIMELINE_LEAFONLY 0x000002 /* Show "Leaf" but not "Merge", "Fork" etc*/
98
-#define TIMELINE_BRIEF 0x000004 /* Combine adjacent elements of same obj */
99
-#define TIMELINE_GRAPH 0x000008 /* Compute a graph */
100
-#define TIMELINE_DISJOINT 0x000010 /* Elements are not contiguous */
101
-#define TIMELINE_FCHANGES 0x000020 /* Detail file changes */
102
-#define TIMELINE_BRCOLOR 0x000040 /* Background color by branch name */
103
-#define TIMELINE_UCOLOR 0x000080 /* Background color by user */
104
-#define TIMELINE_FRENAMES 0x000100 /* Detail only file name changes */
105
-#define TIMELINE_UNHIDE 0x000200 /* Unhide check-ins with "hidden" tag */
106
-#define TIMELINE_SHOWRID 0x000400 /* Show RID values in addition to UUIDs */
107
-#define TIMELINE_BISECT 0x000800 /* Show supplimental bisect information */
108
-#define TIMELINE_COMPACT 0x001000 /* Use the "compact" view style */
109
-#define TIMELINE_VERBOSE 0x002000 /* Use the "detailed" view style */
110
-#define TIMELINE_MODERN 0x004000 /* Use the "modern" view style */
111
-#define TIMELINE_COLUMNAR 0x008000 /* Use the "columns" view style */
112
-#define TIMELINE_CLASSIC 0x010000 /* Use the "classic" view style */
113
-#define TIMELINE_VIEWS 0x01f000 /* Mask for all of the view styles */
114
-#define TIMELINE_NOSCROLL 0x100000 /* Don't scroll to the selection */
115
-#define TIMELINE_FILEDIFF 0x200000 /* Show File differences, not ckin diffs */
116
-#define TIMELINE_CHPICK 0x400000 /* Show cherrypick merges */
117
-#define TIMELINE_FILLGAPS 0x800000 /* Dotted lines for missing nodes */
96
+#define TIMELINE_ARTID 0x0000001 /* Show artifact IDs on non-check-in lines*/
97
+#define TIMELINE_LEAFONLY 0x0000002 /* Show "Leaf" but not "Merge", "Fork" etc*/
98
+#define TIMELINE_BRIEF 0x0000004 /* Combine adjacent elements of same obj */
99
+#define TIMELINE_GRAPH 0x0000008 /* Compute a graph */
100
+#define TIMELINE_DISJOINT 0x0000010 /* Elements are not contiguous */
101
+#define TIMELINE_FCHANGES 0x0000020 /* Detail file changes */
102
+#define TIMELINE_BRCOLOR 0x0000040 /* Background color by branch name */
103
+#define TIMELINE_UCOLOR 0x0000080 /* Background color by user */
104
+#define TIMELINE_FRENAMES 0x0000100 /* Detail only file name changes */
105
+#define TIMELINE_UNHIDE 0x0000200 /* Unhide check-ins with "hidden" tag */
106
+#define TIMELINE_SHOWRID 0x0000400 /* Show RID values in addition to UUIDs */
107
+#define TIMELINE_BISECT 0x0000800 /* Show supplimental bisect information */
108
+#define TIMELINE_COMPACT 0x0001000 /* Use the "compact" view style */
109
+#define TIMELINE_VERBOSE 0x0002000 /* Use the "detailed" view style */
110
+#define TIMELINE_MODERN 0x0004000 /* Use the "modern" view style */
111
+#define TIMELINE_COLUMNAR 0x0008000 /* Use the "columns" view style */
112
+#define TIMELINE_CLASSIC 0x0010000 /* Use the "classic" view style */
113
+#define TIMELINE_VIEWS 0x001f000 /* Mask for all of the view styles */
114
+#define TIMELINE_NOSCROLL 0x0100000 /* Don't scroll to the selection */
115
+#define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */
116
+#define TIMELINE_CHPICK 0x0400000 /* Show cherrypick merges */
117
+#define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */
118
+#define TIMELINE_XMERGE 0x1000000 /* Omit merges from off-graph nodes */
118119
#endif
119120
120121
/*
121122
** Hash a string and use the hash to determine a background color.
122123
*/
@@ -297,12 +298,12 @@
297298
if( (tmFlags & TIMELINE_CHPICK)!=0
298299
&& !db_table_exists("repository","cherrypick")
299300
){
300301
tmFlags &= ~TIMELINE_CHPICK;
301302
}
302
-
303
- @ <table id="timelineTable%d(iTableId)" class="timelineTable">
303
+ @ <table id="timelineTable%d(iTableId)" class="timelineTable"> \
304
+ @ <!-- tmFlags: 0x%x(tmFlags) -->
304305
blob_zero(&comment);
305306
while( db_step(pQuery)==SQLITE_ROW ){
306307
int rid = db_column_int(pQuery, 0);
307308
const char *zUuid = db_column_text(pQuery, 1);
308309
int isLeaf = db_column_int(pQuery, 5);
@@ -867,11 +868,12 @@
867868
** is iTopRow and numbers increase moving down the timeline.
868869
** bg: The background color for this row
869870
** r: The "rail" that the node for this row sits on. The left-most
870871
** rail is 0 and the number increases to the right.
871872
** d: If exists and true then there is a "descender" - an arrow
872
- ** coming from the bottom of the page straight up to this node.
873
+ ** coming from the bottom of the page or further down on the page
874
+ ** straight up to this node.
873875
** mo: "merge-out". If it exists, this is the rail position
874876
** for the upward portion of a merge arrow. The merge arrow goes as
875877
** a solid normal merge line up to the row identified by "mu" and
876878
** then as a dashed cherrypick merge line up further to "cu".
877879
** If this value is omitted if there are no merge children.
@@ -879,11 +881,12 @@
879881
** Only exists if "mo" exists.
880882
** cu: Extend the mu merge arrow up to this row as a cherrypick
881883
** merge line, if this value exists.
882884
** u: Draw a thick child-line out of the top of this node and up to
883885
** the node with an id equal to this value. 0 if it is straight to
884
- ** the top of the page, -1 if there is no thick-line riser.
886
+ ** the top of the page or just up a little wasy, -1 if there is
887
+ ** no thick-line riser (if the node is a leaf).
885888
** sb: Draw a dotted child-line out of the top of this node up to the
886889
** node with the id equal to the value. This is like "u" except
887890
** that the line is dotted instead of solid and has no arrow.
888891
** Mnemonic: "Same Branch".
889892
** f: 0x01: a leaf node.
@@ -1494,11 +1497,11 @@
14941497
** nd Do not highlight the focus check-in
14951498
** v Show details of files changed
14961499
** f=CHECKIN Show family (immediate parents and children) of CHECKIN
14971500
** from=CHECKIN Path from...
14981501
** to=CHECKIN ... to this
1499
-** shorest ... show only the shortest path
1502
+** shortest ... show only the shortest path
15001503
** rel ... also show related checkins
15011504
** uf=FILE_HASH Show only check-ins that contain the given file version
15021505
** chng=GLOBLIST Show only check-ins that involve changes to a file whose
15031506
** name matches one of the comma-separate GLOBLIST
15041507
** brbg Background color from branch name
@@ -1691,12 +1694,12 @@
16911694
tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH | TIMELINE_CHPICK;
16921695
}else{
16931696
tmFlags |= TIMELINE_GRAPH | TIMELINE_CHPICK;
16941697
}
16951698
if( related ){
1696
- tmFlags |= TIMELINE_FILLGAPS;
1697
-// tmFlags &= ~TIMELINE_DISJOINT;
1699
+ tmFlags |= TIMELINE_FILLGAPS | TIMELINE_XMERGE;
1700
+ tmFlags &= ~TIMELINE_DISJOINT;
16981701
}
16991702
if( PB("ncp") ){
17001703
tmFlags &= ~TIMELINE_CHPICK;
17011704
}
17021705
if( PB("ng") || zSearch!=0 ){
@@ -1858,10 +1861,11 @@
18581861
db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related");
18591862
}
18601863
blob_append_sql(&sql, " AND event.objid IN pathnode");
18611864
addFileGlobExclusion(zChng, &sql);
18621865
tmFlags |= TIMELINE_DISJOINT;
1866
+ tmFlags &= ~TIMELINE_CHPICK;
18631867
db_multi_exec("%s", blob_sql_text(&sql));
18641868
if( advancedMenu ){
18651869
style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
18661870
}
18671871
blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath);
@@ -1879,11 +1883,11 @@
18791883
}else if( (p_rid || d_rid) && g.perm.Read && zTagSql==0 ){
18801884
/* If p= or d= is present, ignore all other parameters other than n= */
18811885
char *zUuid;
18821886
int np, nd;
18831887
1884
- tmFlags |= TIMELINE_DISJOINT;
1888
+ tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
18851889
if( p_rid && d_rid ){
18861890
if( p_rid!=d_rid ) p_rid = d_rid;
18871891
if( P("n")==0 ) nEntry = 10;
18881892
}
18891893
db_multi_exec(
@@ -1947,11 +1951,11 @@
19471951
db_multi_exec("%s", blob_sql_text(&sql));
19481952
if( useDividers ) selectedRid = f_rid;
19491953
blob_appendf(&desc, "Parents and children of check-in ");
19501954
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
19511955
blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%!S", zUuid), zUuid);
1952
- tmFlags |= TIMELINE_DISJOINT;
1956
+ tmFlags |= TIMELINE_XMERGE;
19531957
if( advancedMenu ){
19541958
style_submenu_checkbox("unhide", "Unhide", 0, 0);
19551959
style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
19561960
}
19571961
}else{
@@ -1959,13 +1963,14 @@
19591963
int n;
19601964
const char *zEType = "event";
19611965
char *zDate;
19621966
Blob cond;
19631967
blob_zero(&cond);
1968
+ tmFlags |= TIMELINE_FILLGAPS;
19641969
if( zChng && *zChng ){
19651970
addFileGlobExclusion(zChng, &cond);
1966
- tmFlags |= TIMELINE_DISJOINT;
1971
+ tmFlags |= TIMELINE_XMERGE;
19671972
}
19681973
if( zUses ){
19691974
blob_append_sql(&cond, " AND event.objid IN usesfile ");
19701975
}
19711976
if( renameOnly ){
@@ -2220,15 +2225,15 @@
22202225
}
22212226
if( zUses ){
22222227
char *zFilenames = names_of_file(zUses);
22232228
blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
22242229
href("%R/artifact/%!S",zUses), zUses);
2225
- tmFlags |= TIMELINE_DISJOINT;
2230
+ tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
22262231
}
22272232
if( renameOnly ){
22282233
blob_appendf(&desc, " that contain filename changes");
2229
- tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES;
2234
+ tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
22302235
}
22312236
if( forkOnly ){
22322237
blob_appendf(&desc, " associated with forks");
22332238
tmFlags |= TIMELINE_DISJOINT;
22342239
}
@@ -2240,11 +2245,11 @@
22402245
blob_appendf(&desc, " that participate in a cherrypick merge");
22412246
tmFlags |= TIMELINE_CHPICK|TIMELINE_DISJOINT;
22422247
}
22432248
if( zUser ){
22442249
blob_appendf(&desc, " by user %h", zUser);
2245
- tmFlags |= TIMELINE_DISJOINT;
2250
+ tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
22462251
}
22472252
if( zTagSql ){
22482253
if( matchStyle==MS_EXACT ){
22492254
if( related ){
22502255
blob_appendf(&desc, " related to %h", zMatchDesc);
@@ -2256,11 +2261,11 @@
22562261
blob_appendf(&desc, " related to tags matching %h", zMatchDesc);
22572262
}else{
22582263
blob_appendf(&desc, " with tags matching %h", zMatchDesc);
22592264
}
22602265
}
2261
- tmFlags |= TIMELINE_DISJOINT;
2266
+ tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
22622267
}
22632268
addFileGlobDescription(zChng, &desc);
22642269
if( rAfter>0.0 ){
22652270
if( rBefore>0.0 ){
22662271
blob_appendf(&desc, " occurring between %h and %h.<br />",
22672272
--- src/timeline.c
+++ src/timeline.c
@@ -91,32 +91,33 @@
91
92 /*
93 ** Allowed flags for the tmFlags argument to www_print_timeline
94 */
95 #if INTERFACE
96 #define TIMELINE_ARTID 0x000001 /* Show artifact IDs on non-check-in lines*/
97 #define TIMELINE_LEAFONLY 0x000002 /* Show "Leaf" but not "Merge", "Fork" etc*/
98 #define TIMELINE_BRIEF 0x000004 /* Combine adjacent elements of same obj */
99 #define TIMELINE_GRAPH 0x000008 /* Compute a graph */
100 #define TIMELINE_DISJOINT 0x000010 /* Elements are not contiguous */
101 #define TIMELINE_FCHANGES 0x000020 /* Detail file changes */
102 #define TIMELINE_BRCOLOR 0x000040 /* Background color by branch name */
103 #define TIMELINE_UCOLOR 0x000080 /* Background color by user */
104 #define TIMELINE_FRENAMES 0x000100 /* Detail only file name changes */
105 #define TIMELINE_UNHIDE 0x000200 /* Unhide check-ins with "hidden" tag */
106 #define TIMELINE_SHOWRID 0x000400 /* Show RID values in addition to UUIDs */
107 #define TIMELINE_BISECT 0x000800 /* Show supplimental bisect information */
108 #define TIMELINE_COMPACT 0x001000 /* Use the "compact" view style */
109 #define TIMELINE_VERBOSE 0x002000 /* Use the "detailed" view style */
110 #define TIMELINE_MODERN 0x004000 /* Use the "modern" view style */
111 #define TIMELINE_COLUMNAR 0x008000 /* Use the "columns" view style */
112 #define TIMELINE_CLASSIC 0x010000 /* Use the "classic" view style */
113 #define TIMELINE_VIEWS 0x01f000 /* Mask for all of the view styles */
114 #define TIMELINE_NOSCROLL 0x100000 /* Don't scroll to the selection */
115 #define TIMELINE_FILEDIFF 0x200000 /* Show File differences, not ckin diffs */
116 #define TIMELINE_CHPICK 0x400000 /* Show cherrypick merges */
117 #define TIMELINE_FILLGAPS 0x800000 /* Dotted lines for missing nodes */
 
118 #endif
119
120 /*
121 ** Hash a string and use the hash to determine a background color.
122 */
@@ -297,12 +298,12 @@
297 if( (tmFlags & TIMELINE_CHPICK)!=0
298 && !db_table_exists("repository","cherrypick")
299 ){
300 tmFlags &= ~TIMELINE_CHPICK;
301 }
302
303 @ <table id="timelineTable%d(iTableId)" class="timelineTable">
304 blob_zero(&comment);
305 while( db_step(pQuery)==SQLITE_ROW ){
306 int rid = db_column_int(pQuery, 0);
307 const char *zUuid = db_column_text(pQuery, 1);
308 int isLeaf = db_column_int(pQuery, 5);
@@ -867,11 +868,12 @@
867 ** is iTopRow and numbers increase moving down the timeline.
868 ** bg: The background color for this row
869 ** r: The "rail" that the node for this row sits on. The left-most
870 ** rail is 0 and the number increases to the right.
871 ** d: If exists and true then there is a "descender" - an arrow
872 ** coming from the bottom of the page straight up to this node.
 
873 ** mo: "merge-out". If it exists, this is the rail position
874 ** for the upward portion of a merge arrow. The merge arrow goes as
875 ** a solid normal merge line up to the row identified by "mu" and
876 ** then as a dashed cherrypick merge line up further to "cu".
877 ** If this value is omitted if there are no merge children.
@@ -879,11 +881,12 @@
879 ** Only exists if "mo" exists.
880 ** cu: Extend the mu merge arrow up to this row as a cherrypick
881 ** merge line, if this value exists.
882 ** u: Draw a thick child-line out of the top of this node and up to
883 ** the node with an id equal to this value. 0 if it is straight to
884 ** the top of the page, -1 if there is no thick-line riser.
 
885 ** sb: Draw a dotted child-line out of the top of this node up to the
886 ** node with the id equal to the value. This is like "u" except
887 ** that the line is dotted instead of solid and has no arrow.
888 ** Mnemonic: "Same Branch".
889 ** f: 0x01: a leaf node.
@@ -1494,11 +1497,11 @@
1494 ** nd Do not highlight the focus check-in
1495 ** v Show details of files changed
1496 ** f=CHECKIN Show family (immediate parents and children) of CHECKIN
1497 ** from=CHECKIN Path from...
1498 ** to=CHECKIN ... to this
1499 ** shorest ... show only the shortest path
1500 ** rel ... also show related checkins
1501 ** uf=FILE_HASH Show only check-ins that contain the given file version
1502 ** chng=GLOBLIST Show only check-ins that involve changes to a file whose
1503 ** name matches one of the comma-separate GLOBLIST
1504 ** brbg Background color from branch name
@@ -1691,12 +1694,12 @@
1691 tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH | TIMELINE_CHPICK;
1692 }else{
1693 tmFlags |= TIMELINE_GRAPH | TIMELINE_CHPICK;
1694 }
1695 if( related ){
1696 tmFlags |= TIMELINE_FILLGAPS;
1697 // tmFlags &= ~TIMELINE_DISJOINT;
1698 }
1699 if( PB("ncp") ){
1700 tmFlags &= ~TIMELINE_CHPICK;
1701 }
1702 if( PB("ng") || zSearch!=0 ){
@@ -1858,10 +1861,11 @@
1858 db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related");
1859 }
1860 blob_append_sql(&sql, " AND event.objid IN pathnode");
1861 addFileGlobExclusion(zChng, &sql);
1862 tmFlags |= TIMELINE_DISJOINT;
 
1863 db_multi_exec("%s", blob_sql_text(&sql));
1864 if( advancedMenu ){
1865 style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
1866 }
1867 blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath);
@@ -1879,11 +1883,11 @@
1879 }else if( (p_rid || d_rid) && g.perm.Read && zTagSql==0 ){
1880 /* If p= or d= is present, ignore all other parameters other than n= */
1881 char *zUuid;
1882 int np, nd;
1883
1884 tmFlags |= TIMELINE_DISJOINT;
1885 if( p_rid && d_rid ){
1886 if( p_rid!=d_rid ) p_rid = d_rid;
1887 if( P("n")==0 ) nEntry = 10;
1888 }
1889 db_multi_exec(
@@ -1947,11 +1951,11 @@
1947 db_multi_exec("%s", blob_sql_text(&sql));
1948 if( useDividers ) selectedRid = f_rid;
1949 blob_appendf(&desc, "Parents and children of check-in ");
1950 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
1951 blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%!S", zUuid), zUuid);
1952 tmFlags |= TIMELINE_DISJOINT;
1953 if( advancedMenu ){
1954 style_submenu_checkbox("unhide", "Unhide", 0, 0);
1955 style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
1956 }
1957 }else{
@@ -1959,13 +1963,14 @@
1959 int n;
1960 const char *zEType = "event";
1961 char *zDate;
1962 Blob cond;
1963 blob_zero(&cond);
 
1964 if( zChng && *zChng ){
1965 addFileGlobExclusion(zChng, &cond);
1966 tmFlags |= TIMELINE_DISJOINT;
1967 }
1968 if( zUses ){
1969 blob_append_sql(&cond, " AND event.objid IN usesfile ");
1970 }
1971 if( renameOnly ){
@@ -2220,15 +2225,15 @@
2220 }
2221 if( zUses ){
2222 char *zFilenames = names_of_file(zUses);
2223 blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
2224 href("%R/artifact/%!S",zUses), zUses);
2225 tmFlags |= TIMELINE_DISJOINT;
2226 }
2227 if( renameOnly ){
2228 blob_appendf(&desc, " that contain filename changes");
2229 tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES;
2230 }
2231 if( forkOnly ){
2232 blob_appendf(&desc, " associated with forks");
2233 tmFlags |= TIMELINE_DISJOINT;
2234 }
@@ -2240,11 +2245,11 @@
2240 blob_appendf(&desc, " that participate in a cherrypick merge");
2241 tmFlags |= TIMELINE_CHPICK|TIMELINE_DISJOINT;
2242 }
2243 if( zUser ){
2244 blob_appendf(&desc, " by user %h", zUser);
2245 tmFlags |= TIMELINE_DISJOINT;
2246 }
2247 if( zTagSql ){
2248 if( matchStyle==MS_EXACT ){
2249 if( related ){
2250 blob_appendf(&desc, " related to %h", zMatchDesc);
@@ -2256,11 +2261,11 @@
2256 blob_appendf(&desc, " related to tags matching %h", zMatchDesc);
2257 }else{
2258 blob_appendf(&desc, " with tags matching %h", zMatchDesc);
2259 }
2260 }
2261 tmFlags |= TIMELINE_DISJOINT;
2262 }
2263 addFileGlobDescription(zChng, &desc);
2264 if( rAfter>0.0 ){
2265 if( rBefore>0.0 ){
2266 blob_appendf(&desc, " occurring between %h and %h.<br />",
2267
--- src/timeline.c
+++ src/timeline.c
@@ -91,32 +91,33 @@
91
92 /*
93 ** Allowed flags for the tmFlags argument to www_print_timeline
94 */
95 #if INTERFACE
96 #define TIMELINE_ARTID 0x0000001 /* Show artifact IDs on non-check-in lines*/
97 #define TIMELINE_LEAFONLY 0x0000002 /* Show "Leaf" but not "Merge", "Fork" etc*/
98 #define TIMELINE_BRIEF 0x0000004 /* Combine adjacent elements of same obj */
99 #define TIMELINE_GRAPH 0x0000008 /* Compute a graph */
100 #define TIMELINE_DISJOINT 0x0000010 /* Elements are not contiguous */
101 #define TIMELINE_FCHANGES 0x0000020 /* Detail file changes */
102 #define TIMELINE_BRCOLOR 0x0000040 /* Background color by branch name */
103 #define TIMELINE_UCOLOR 0x0000080 /* Background color by user */
104 #define TIMELINE_FRENAMES 0x0000100 /* Detail only file name changes */
105 #define TIMELINE_UNHIDE 0x0000200 /* Unhide check-ins with "hidden" tag */
106 #define TIMELINE_SHOWRID 0x0000400 /* Show RID values in addition to UUIDs */
107 #define TIMELINE_BISECT 0x0000800 /* Show supplimental bisect information */
108 #define TIMELINE_COMPACT 0x0001000 /* Use the "compact" view style */
109 #define TIMELINE_VERBOSE 0x0002000 /* Use the "detailed" view style */
110 #define TIMELINE_MODERN 0x0004000 /* Use the "modern" view style */
111 #define TIMELINE_COLUMNAR 0x0008000 /* Use the "columns" view style */
112 #define TIMELINE_CLASSIC 0x0010000 /* Use the "classic" view style */
113 #define TIMELINE_VIEWS 0x001f000 /* Mask for all of the view styles */
114 #define TIMELINE_NOSCROLL 0x0100000 /* Don't scroll to the selection */
115 #define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */
116 #define TIMELINE_CHPICK 0x0400000 /* Show cherrypick merges */
117 #define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */
118 #define TIMELINE_XMERGE 0x1000000 /* Omit merges from off-graph nodes */
119 #endif
120
121 /*
122 ** Hash a string and use the hash to determine a background color.
123 */
@@ -297,12 +298,12 @@
298 if( (tmFlags & TIMELINE_CHPICK)!=0
299 && !db_table_exists("repository","cherrypick")
300 ){
301 tmFlags &= ~TIMELINE_CHPICK;
302 }
303 @ <table id="timelineTable%d(iTableId)" class="timelineTable"> \
304 @ <!-- tmFlags: 0x%x(tmFlags) -->
305 blob_zero(&comment);
306 while( db_step(pQuery)==SQLITE_ROW ){
307 int rid = db_column_int(pQuery, 0);
308 const char *zUuid = db_column_text(pQuery, 1);
309 int isLeaf = db_column_int(pQuery, 5);
@@ -867,11 +868,12 @@
868 ** is iTopRow and numbers increase moving down the timeline.
869 ** bg: The background color for this row
870 ** r: The "rail" that the node for this row sits on. The left-most
871 ** rail is 0 and the number increases to the right.
872 ** d: If exists and true then there is a "descender" - an arrow
873 ** coming from the bottom of the page or further down on the page
874 ** straight up to this node.
875 ** mo: "merge-out". If it exists, this is the rail position
876 ** for the upward portion of a merge arrow. The merge arrow goes as
877 ** a solid normal merge line up to the row identified by "mu" and
878 ** then as a dashed cherrypick merge line up further to "cu".
879 ** If this value is omitted if there are no merge children.
@@ -879,11 +881,12 @@
881 ** Only exists if "mo" exists.
882 ** cu: Extend the mu merge arrow up to this row as a cherrypick
883 ** merge line, if this value exists.
884 ** u: Draw a thick child-line out of the top of this node and up to
885 ** the node with an id equal to this value. 0 if it is straight to
886 ** the top of the page or just up a little wasy, -1 if there is
887 ** no thick-line riser (if the node is a leaf).
888 ** sb: Draw a dotted child-line out of the top of this node up to the
889 ** node with the id equal to the value. This is like "u" except
890 ** that the line is dotted instead of solid and has no arrow.
891 ** Mnemonic: "Same Branch".
892 ** f: 0x01: a leaf node.
@@ -1494,11 +1497,11 @@
1497 ** nd Do not highlight the focus check-in
1498 ** v Show details of files changed
1499 ** f=CHECKIN Show family (immediate parents and children) of CHECKIN
1500 ** from=CHECKIN Path from...
1501 ** to=CHECKIN ... to this
1502 ** shortest ... show only the shortest path
1503 ** rel ... also show related checkins
1504 ** uf=FILE_HASH Show only check-ins that contain the given file version
1505 ** chng=GLOBLIST Show only check-ins that involve changes to a file whose
1506 ** name matches one of the comma-separate GLOBLIST
1507 ** brbg Background color from branch name
@@ -1691,12 +1694,12 @@
1694 tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH | TIMELINE_CHPICK;
1695 }else{
1696 tmFlags |= TIMELINE_GRAPH | TIMELINE_CHPICK;
1697 }
1698 if( related ){
1699 tmFlags |= TIMELINE_FILLGAPS | TIMELINE_XMERGE;
1700 tmFlags &= ~TIMELINE_DISJOINT;
1701 }
1702 if( PB("ncp") ){
1703 tmFlags &= ~TIMELINE_CHPICK;
1704 }
1705 if( PB("ng") || zSearch!=0 ){
@@ -1858,10 +1861,11 @@
1861 db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related");
1862 }
1863 blob_append_sql(&sql, " AND event.objid IN pathnode");
1864 addFileGlobExclusion(zChng, &sql);
1865 tmFlags |= TIMELINE_DISJOINT;
1866 tmFlags &= ~TIMELINE_CHPICK;
1867 db_multi_exec("%s", blob_sql_text(&sql));
1868 if( advancedMenu ){
1869 style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
1870 }
1871 blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath);
@@ -1879,11 +1883,11 @@
1883 }else if( (p_rid || d_rid) && g.perm.Read && zTagSql==0 ){
1884 /* If p= or d= is present, ignore all other parameters other than n= */
1885 char *zUuid;
1886 int np, nd;
1887
1888 tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
1889 if( p_rid && d_rid ){
1890 if( p_rid!=d_rid ) p_rid = d_rid;
1891 if( P("n")==0 ) nEntry = 10;
1892 }
1893 db_multi_exec(
@@ -1947,11 +1951,11 @@
1951 db_multi_exec("%s", blob_sql_text(&sql));
1952 if( useDividers ) selectedRid = f_rid;
1953 blob_appendf(&desc, "Parents and children of check-in ");
1954 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", f_rid);
1955 blob_appendf(&desc, "%z[%S]</a>", href("%R/info/%!S", zUuid), zUuid);
1956 tmFlags |= TIMELINE_XMERGE;
1957 if( advancedMenu ){
1958 style_submenu_checkbox("unhide", "Unhide", 0, 0);
1959 style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
1960 }
1961 }else{
@@ -1959,13 +1963,14 @@
1963 int n;
1964 const char *zEType = "event";
1965 char *zDate;
1966 Blob cond;
1967 blob_zero(&cond);
1968 tmFlags |= TIMELINE_FILLGAPS;
1969 if( zChng && *zChng ){
1970 addFileGlobExclusion(zChng, &cond);
1971 tmFlags |= TIMELINE_XMERGE;
1972 }
1973 if( zUses ){
1974 blob_append_sql(&cond, " AND event.objid IN usesfile ");
1975 }
1976 if( renameOnly ){
@@ -2220,15 +2225,15 @@
2225 }
2226 if( zUses ){
2227 char *zFilenames = names_of_file(zUses);
2228 blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
2229 href("%R/artifact/%!S",zUses), zUses);
2230 tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
2231 }
2232 if( renameOnly ){
2233 blob_appendf(&desc, " that contain filename changes");
2234 tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
2235 }
2236 if( forkOnly ){
2237 blob_appendf(&desc, " associated with forks");
2238 tmFlags |= TIMELINE_DISJOINT;
2239 }
@@ -2240,11 +2245,11 @@
2245 blob_appendf(&desc, " that participate in a cherrypick merge");
2246 tmFlags |= TIMELINE_CHPICK|TIMELINE_DISJOINT;
2247 }
2248 if( zUser ){
2249 blob_appendf(&desc, " by user %h", zUser);
2250 tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
2251 }
2252 if( zTagSql ){
2253 if( matchStyle==MS_EXACT ){
2254 if( related ){
2255 blob_appendf(&desc, " related to %h", zMatchDesc);
@@ -2256,11 +2261,11 @@
2261 blob_appendf(&desc, " related to tags matching %h", zMatchDesc);
2262 }else{
2263 blob_appendf(&desc, " with tags matching %h", zMatchDesc);
2264 }
2265 }
2266 tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
2267 }
2268 addFileGlobDescription(zChng, &desc);
2269 if( rAfter>0.0 ){
2270 if( rBefore>0.0 ){
2271 blob_appendf(&desc, " occurring between %h and %h.<br />",
2272

Keyboard Shortcuts

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