Fossil

Check-in [d14590db]
Login

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 | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:d14590dbff44620dc47e062e1a3b0832c8c735e7b46f581630af5f50684c1ea8
User & Date: drh 2019-05-17 19:49:55
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: 344a3331 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: d14590db 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: e0186fdb 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: b9e36291 user: mistachkin tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/default_css.txt.

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 {
  width: 0px;
  border-top: 0px dotted #888;
  border-left: 2px dotted #888;
  background: rgba(255,255,255,0);
}
span.tagDsp {
  font-weight: bold;
}
span.wikiError {
  font-weight: bold;







|

|
|







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
..
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
..
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
..
81
82
83
84
85
86
87







88
89
90
91
92
93
94
...
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
...
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
...
285
286
287
288
289
290
291








292
293
294
295
296
297
298
...
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318

319
320
321
322
323
324
325
326
...
351
352
353
354
355
356
357
358

359
360
361

362
363
364
365
366
367
368
369
370
...
376
377
378
379
380
381
382

383
384
385
386
387
388
389
...
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
...
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494

495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512










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
...
568
569
570
571
572
573
574
575

576
577
578
579
580
581
582
...
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
*******************************************************************************
**
** 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
................................................................................
** 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.  -1 for technote lines */
  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.  First is 1.  0 used for "none" */
  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 */
................................................................................
  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 */
................................................................................
  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);
................................................................................
  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<32; i++){
    if( (inUseMask & BIT(i))==0 ){
      int dist;
      if( iNearto<=0 ){
        return i;
      }
      dist = i - iNearto;
      if( dist<0 ) dist = -dist;
................................................................................
  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){
  int iRail = pBottom->iRail;
  GraphRow *pCurrent;
  GraphRow *pPrior;
  u64 mask = ((u64)1)<<iRail;

  pBottom->railInUse |= mask;
  pPrior = pBottom;
................................................................................
    pCurrent->railInUse |= mask;
    pPrior->aiRiser[iRail] = pCurrent->idx;
    while( pPrior->idx > pCurrent->idx ){
      pPrior->railInUse |= mask;
      pPrior = pPrior->pPrev;
      assert( pPrior!=0 );
    }








  }
}

/*
** Create a merge-arrow riser going from pParent up to pChild.
*/
static void createMergeRiser(
................................................................................
){
  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;

      pParent->mergeOut = findFreeRail(p, pChild->idx, pParent->idx-1, iTarget);
      mask = BIT(pParent->mergeOut);
      for(pLoop=pChild->pNext; pLoop && pLoop->rid!=pParent->rid;
           pLoop=pLoop->pNext){
        pLoop->railInUse |= mask;
      }
    }
  }
................................................................................
    ){
      p->mxRail++;
    }
  }
}

/*
** Draw a riser from pRow to the top of the graph

*/
static void riser_to_top(GraphRow *pRow){
  u64 mask = BIT(pRow->iRail);

  pRow->aiRiser[pRow->iRail] = 0;
  while( pRow ){
    pRow->railInUse |= mask;
    pRow = pRow->pPrev;
  }
}


/*
................................................................................
** 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

*/
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;
................................................................................
  **
  ** 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( omitDescenders ){
    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 ){
................................................................................
  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;
       continue;                                       /* Time-warp */
    }
    if( pRow->idxTop < pParent->idxTop ){
      pParent->pChild = pRow;
      pParent->idxTop = pRow->idxTop;
    }
  }

  if( tmFlags & TIMELINE_FILLGAPS ){
    /* If a node has no pChild, and there is a later node (a node higher
    ** up on the graph) in the same branch that has no parent, then make
    ** the lower node a step-child of the upper node.

    */
    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->idxTop = pLoop->idxTop;
          pRow->isStepParent = 1;
          pLoop->aParent[0] = pRow->rid;
          break;
        }
      }
    }
  }











  /* Identify rows where the primary parent is off screen.  Assign
  ** each to a rail and draw descenders to the bottom of the screen.
  **
  ** 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( pRow->isDup ) continue;
      if( pRow->nParent<0 ) continue;
      if( i==0 ){
        if( pRow->zBranch!=zTrunk ) continue;
      }else {
        if( pRow->iRail>=0 ) continue;
      }
      if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){
        if( omitDescenders ){
          pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx, 0);
        }else{
          pRow->iRail = ++p->mxRail;
        }
        if( p->mxRail>=GR_MAX_RAIL ) return;
        mask = BIT(pRow->iRail);
        if( !omitDescenders ){

          pRow->bDescender = pRow->nParent>0;
          for(pLoop=pRow; pLoop; pLoop=pLoop->pNext){
            pLoop->railInUse |= mask;
          }
        }
        assignChildrenToRail(pRow);
      }
    }
  }

  /* Assign rails to all rows that are still unassigned.
  */
  for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
................................................................................
        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, 0, 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;
................................................................................
          pLoop->railInUse |= mask;
        }
      }
    }
    mask = BIT(pRow->iRail);
    pRow->railInUse |= mask;
    if( pRow->pChild ){
      assignChildrenToRail(pRow);
    }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;
      }







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|










|







 







<







 







>
>
>
>
>
>
>







 







|







 







|







 







>
>
>
>
>
>
>
>







 







|




|



>
|







 







|
>



>

|







 







>







 







|







 







|
<
<
|

<




|
|
|
>









<








>
>
>
>
>
>
>
>
>
>

|






>
>


<
<
<
<
<

<
|
<
<
<



>

|



|







 







|
>







 







|







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
..
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
..
79
80
81
82
83
84
85

86
87
88
89
90
91
92
...
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
...
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
...
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
...
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
...
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
...
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
...
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
...
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
...
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
...
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
...
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
*******************************************************************************
**
** 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
................................................................................
** 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 */
................................................................................
  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 */
................................................................................
  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);
................................................................................
  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;
................................................................................
  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;
................................................................................
    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(
................................................................................
){
  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;
      }
    }
  }
................................................................................
    ){
      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;
  }
}


/*
................................................................................
** 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;
................................................................................
  **
  ** 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 ){
................................................................................
  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){
................................................................................
        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;
................................................................................
          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
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
...
241
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
    if( x1===null ){
      x1 = x0+elem.w;
      cls += "v";
    }else{
      y1 = y0+elem.w;
      cls += "h";
    }
    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 drawUpDotted(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);
    drawLine(dotLine,color,x,y0,null,y1);

  }
  /* 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);
................................................................................
      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 ) drawUpDotted(p,tx.rowinfo[p.sb-tx.iTopRow],null);
    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 ) drawUpArrow(p,{x: p.x, y: -node.h},p.fg);


      if( p.hasOwnProperty('d') ) drawUpArrow({x: p.x, y: btm-node.h/2},p,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 ){



        x1 += mergeLines[p.mo] = -mLine.w/2;


        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);







|













|



|
>







 







|








>
>
>
>
>
>
>
>
|
>
>
|
>
>
>
>
>
>
>






|
>
>
>
|
>
>







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
...
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
    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);
................................................................................
      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
755
756
757
758
759
760
761
762
763
      " 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_DISJOINT | 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();
}







|








748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
      " 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
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
...
295
296
297
298
299
300
301
302
303

304
305
306
307
308
309
310
...
865
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
....
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
....
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
....
1856
1857
1858
1859
1860
1861
1862

1863
1864
1865
1866
1867
1868
1869
....
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
....
1945
1946
1947
1948
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
....
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
....
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
....
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
  }
}

/*
** Allowed flags for the tmFlags argument to www_print_timeline
*/
#if INTERFACE
#define TIMELINE_ARTID    0x000001  /* Show artifact IDs on non-check-in lines*/
#define TIMELINE_LEAFONLY 0x000002  /* Show "Leaf" but not "Merge", "Fork" etc*/
#define TIMELINE_BRIEF    0x000004  /* Combine adjacent elements of same obj */
#define TIMELINE_GRAPH    0x000008  /* Compute a graph */
#define TIMELINE_DISJOINT 0x000010  /* Elements are not contiguous */
#define TIMELINE_FCHANGES 0x000020  /* Detail file changes */
#define TIMELINE_BRCOLOR  0x000040  /* Background color by branch name */
#define TIMELINE_UCOLOR   0x000080  /* Background color by user */
#define TIMELINE_FRENAMES 0x000100  /* Detail only file name changes */
#define TIMELINE_UNHIDE   0x000200  /* Unhide check-ins with "hidden" tag */
#define TIMELINE_SHOWRID  0x000400  /* Show RID values in addition to UUIDs */
#define TIMELINE_BISECT   0x000800  /* Show supplimental bisect information */
#define TIMELINE_COMPACT  0x001000  /* Use the "compact" view style */
#define TIMELINE_VERBOSE  0x002000  /* Use the "detailed" view style */
#define TIMELINE_MODERN   0x004000  /* Use the "modern" view style */
#define TIMELINE_COLUMNAR 0x008000  /* Use the "columns" view style */
#define TIMELINE_CLASSIC  0x010000  /* Use the "classic" view style */
#define TIMELINE_VIEWS    0x01f000  /* Mask for all of the view styles */
#define TIMELINE_NOSCROLL 0x100000  /* Don't scroll to the selection */
#define TIMELINE_FILEDIFF 0x200000  /* Show File differences, not ckin diffs */
#define TIMELINE_CHPICK   0x400000  /* Show cherrypick merges */
#define TIMELINE_FILLGAPS 0x800000  /* Dotted lines for missing nodes */

#endif

/*
** Hash a string and use the hash to determine a background color.
*/
char *hash_color(const char *z){
  int i;                       /* Loop counter */
................................................................................
    TAG_BRANCH
  );
  if( (tmFlags & TIMELINE_CHPICK)!=0
   && !db_table_exists("repository","cherrypick")
  ){
    tmFlags &= ~TIMELINE_CHPICK;
  }

  @ <table id="timelineTable%d(iTableId)" class="timelineTable">

  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);
................................................................................
    **   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 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, -1 if there is no thick-line riser.

    **   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
................................................................................
**    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
**      shorest         ... 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
................................................................................
  }
  if( zType[0]=='a' ){
    tmFlags |= TIMELINE_BRIEF | TIMELINE_GRAPH | TIMELINE_CHPICK;
  }else{
    tmFlags |= TIMELINE_GRAPH | TIMELINE_CHPICK;
  }
  if( related ){
    tmFlags |= TIMELINE_FILLGAPS;
//    tmFlags &= ~TIMELINE_DISJOINT;
  }
  if( PB("ncp") ){
    tmFlags &= ~TIMELINE_CHPICK;
  }
  if( PB("ng") || zSearch!=0 ){
    tmFlags &= ~(TIMELINE_GRAPH|TIMELINE_CHPICK);
  }
................................................................................
        }
      }
      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);
................................................................................
    }
    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_DISJOINT;
    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)"
    );
................................................................................
    }
    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_DISJOINT;
    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);

    if( zChng && *zChng ){
      addFileGlobExclusion(zChng, &cond);
      tmFlags |= TIMELINE_DISJOINT;
    }
    if( zUses ){
      blob_append_sql(&cond, " AND event.objid IN usesfile ");
    }
    if( renameOnly ){
      blob_append_sql(&cond, " AND event.objid IN rnfile ");
    }
................................................................................
    }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_DISJOINT;
    }
    if( renameOnly ){
      blob_appendf(&desc, " that contain filename changes");
      tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES;
    }
    if( forkOnly ){
      blob_appendf(&desc, " associated with forks");
      tmFlags |= TIMELINE_DISJOINT;
    }
    if( bisectLocal || zBisect!=0 ){
      blob_appendf(&desc, " in a bisect");
................................................................................
    }
    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_DISJOINT;
    }
    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_DISJOINT;
    }
    addFileGlobDescription(zChng, &desc);
    if( rAfter>0.0 ){
      if( rBefore>0.0 ){
        blob_appendf(&desc, " occurring between %h and %h.<br />",
                     zAfter, zBefore);
      }else{







|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>







 







<
|
>







 







|
>











|
>







 







|







 







|
|







 







>







 







|







 







|











>


|







 







|



|







 







|







 







|







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
...
296
297
298
299
300
301
302

303
304
305
306
307
308
309
310
311
...
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
....
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
....
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
....
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
....
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
....
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
....
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
....
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
....
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
  }
}

/*
** 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 */
................................................................................
    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);
................................................................................
    **   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
................................................................................
**    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
................................................................................
  }
  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);
  }
................................................................................
        }
      }
      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);
................................................................................
    }
    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)"
    );
................................................................................
    }
    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 ");
    }
................................................................................
    }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");
................................................................................
    }
    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{