Fossil SCM

fossil-scm / src / graph.c
Blame History Raw 1085 lines
1
/*
2
** Copyright (c) 2010 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
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 too 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
** A "merge riser" is a thin line going up out of a node to indicate a
37
** merge or cherrypick. (Cherrypicks are drawn with thin dashed lines.
38
** Merges are drawn with thin solid lines.) A "merge riser" might go
39
** stright up out of the top of a leaf node, but for non-leaves, they
40
** go horizontally to their assigned rail first, then up.
41
**
42
** Invoke graph_init() to create a new GraphContext object. Then
43
** call graph_add_row() to add nodes, one by one, to the graph.
44
** Nodes must be added in display order, from top to bottom.
45
** Then invoke graph_render() to run the layout algorithm. The
46
** layout algorithm computes which rail each nodes sit on, and
47
** the rails used for merge arrows.
48
*/
49
50
#if INTERFACE
51
52
/*
53
** The type of integer identifiers for rows of the graph.
54
**
55
** For a normal /timeline graph, the identifiers are never that big
56
** an ordinary 32-bit int will work fine. But for the /finfo page,
57
** the identifier is a combination of the BLOB.RID and the FILENAME.FNID
58
** values, and so it can become quite large for repos that have both many
59
** check-ins and many files. For this reason, we make the identifier
60
** a 64-bit integer, to dramatically reduce the risk of an overflow.
61
*/
62
typedef sqlite3_int64 GraphRowId;
63
64
#define GR_MAX_RAIL 64 /* Max number of "rails" to display */
65
66
/* The graph appears vertically beside a timeline. Each row in the
67
** timeline corresponds to a row in the graph. GraphRow.idx is 0 for
68
** the top-most row and increases moving down. Hence (in the absence of
69
** time skew) parents have a larger index than their children.
70
**
71
** The nParent field is -1 for entires that do not participate in the graph
72
** but which are included just so that we can capture their background color.
73
*/
74
struct GraphRow {
75
GraphRowId rid; /* The rid for the check-in */
76
i8 nParent; /* Number of parents. */
77
i8 nCherrypick; /* Subset of aParent that are cherrypicks */
78
i8 nNonCherrypick; /* Number of non-cherrypick parents */
79
u8 nMergeChild; /* Number of merge children */
80
GraphRowId *aParent; /* Array of parents. 0 element is primary .*/
81
char *zBranch; /* Branch name */
82
char *zBgClr; /* Background Color */
83
char zUuid[HNAME_MAX+1]; /* Check-in for file ID */
84
85
GraphRow *pNext; /* Next row down in the list of all rows */
86
GraphRow *pPrev; /* Previous row */
87
88
int idx; /* Row index. Top row is smallest. */
89
int idxTop; /* Direct descendant highest up on the graph */
90
GraphRow *pChild; /* Child immediately above this node */
91
u8 isDup; /* True if this is duplicate of a prior entry */
92
u8 isLeaf; /* True if this is a leaf node */
93
u8 isStepParent; /* pChild is actually a step-child. The thick
94
** arrow up to the child is dashed, not solid */
95
u8 hasNormalOutMerge; /* Is parent of at laest 1 non-cherrypick merge */
96
u8 timeWarp; /* Child is earlier in time */
97
u8 bDescender; /* True if riser from bottom of graph to here. */
98
u8 selfUp; /* Space above this node but belonging */
99
i8 iRail; /* Which rail this check-in appears on. 0-based.*/
100
i8 mergeOut; /* Merge out to this rail. -1 if no merge-out */
101
u8 mergeIn[GR_MAX_RAIL]; /* Merge in from non-zero rails */
102
int aiRiser[GR_MAX_RAIL]; /* Risers from this node to a higher row. */
103
int mergeUpto; /* Draw the mergeOut rail up to this level */
104
int cherrypickUpto; /* Continue the mergeOut rail up to here */
105
u64 mergeDown; /* Draw merge lines up from bottom of graph */
106
u64 cherrypickDown; /* Draw cherrypick lines up from bottom */
107
u64 railInUse; /* Mask of occupied rails at this row */
108
};
109
110
/* Context while building a graph
111
*/
112
struct GraphContext {
113
int nErr; /* Number of errors encountered */
114
int mxRail; /* Number of rails required to render the graph */
115
GraphRow *pFirst; /* First row in the list. Top row of graph. */
116
GraphRow *pLast; /* Last row in the list. Bottom row of graph. */
117
int nBranch; /* Number of distinct branches */
118
char **azBranch; /* Names of the branches */
119
int nRow; /* Number of rows */
120
int nHash; /* Number of slots in apHash[] */
121
u8 hasOffsetMergeRiser; /* Merge arrow from leaf goes up on a different
122
** rail that the node */
123
u8 bOverfull; /* Unable to allocate sufficient rails */
124
u64 mergeRail; /* Rails used for merge lines */
125
GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */
126
u8 aiRailMap[GR_MAX_RAIL+1]; /* Mapping of rails to actually columns */
127
};
128
129
#endif
130
131
/* The N-th bit */
132
#define BIT(N) (((u64)1)<<(N))
133
134
/*
135
** Number of rows before and after a node with a riser or descender
136
** that goes off-screen before we can reuse that rail.
137
*/
138
#define RISER_MARGIN 4
139
140
141
/*
142
** Malloc for zeroed space. Panic if unable to provide the
143
** requested space.
144
*/
145
void *safeMalloc(int nByte){
146
void *p = fossil_malloc(nByte);
147
memset(p, 0, nByte);
148
return p;
149
}
150
151
/*
152
** Create and initialize a GraphContext
153
*/
154
GraphContext *graph_init(void){
155
return (GraphContext*)safeMalloc( sizeof(GraphContext) );
156
}
157
158
/*
159
** Clear all content from a graph
160
*/
161
static void graph_clear(GraphContext *p){
162
int i;
163
GraphRow *pRow;
164
while( p->pFirst ){
165
pRow = p->pFirst;
166
p->pFirst = pRow->pNext;
167
free(pRow);
168
}
169
for(i=0; i<p->nBranch; i++) free(p->azBranch[i]);
170
free(p->azBranch);
171
free(p->apHash);
172
memset(p, 0, sizeof(*p));
173
p->nErr = 1;
174
}
175
176
/*
177
** Destroy a GraphContext;
178
*/
179
void graph_free(GraphContext *p){
180
graph_clear(p);
181
free(p);
182
}
183
184
/*
185
** Insert a row into the hash table. pRow->rid is the key. Keys must
186
** be unique. If there is already another row with the same rid,
187
** overwrite the prior entry if and only if the overwrite flag is set.
188
*/
189
static void hashInsert(GraphContext *p, GraphRow *pRow, int overwrite){
190
int h;
191
h = pRow->rid % p->nHash;
192
while( p->apHash[h] && p->apHash[h]->rid!=pRow->rid ){
193
h++;
194
if( h>=p->nHash ) h = 0;
195
}
196
if( p->apHash[h]==0 || overwrite ){
197
p->apHash[h] = pRow;
198
}
199
}
200
201
/*
202
** Look up the row with rid.
203
*/
204
static GraphRow *hashFind(GraphContext *p, GraphRowId rid){
205
int h = rid % p->nHash;
206
while( p->apHash[h] && p->apHash[h]->rid!=rid ){
207
h++;
208
if( h>=p->nHash ) h = 0;
209
}
210
return p->apHash[h];
211
}
212
213
/*
214
** Return the canonical pointer for a given branch name.
215
** Multiple calls to this routine with equivalent strings
216
** will return the same pointer.
217
**
218
** The returned value is a pointer to a (readonly) string that
219
** has the useful property that strings can be checked for
220
** equality by comparing pointers.
221
**
222
** Note: also used for background color names.
223
*/
224
static char *persistBranchName(GraphContext *p, const char *zBranch){
225
int i;
226
for(i=0; i<p->nBranch; i++){
227
if( fossil_strcmp(zBranch, p->azBranch[i])==0 ) return p->azBranch[i];
228
}
229
p->nBranch++;
230
p->azBranch = fossil_realloc(p->azBranch, sizeof(char*)*p->nBranch);
231
p->azBranch[p->nBranch-1] = fossil_strdup(zBranch);
232
return p->azBranch[p->nBranch-1];
233
}
234
235
/*
236
** Add a new row to the graph context. Rows are added from top to bottom.
237
*/
238
int graph_add_row(
239
GraphContext *p, /* The context to which the row is added */
240
GraphRowId rid, /* RID for the check-in */
241
int nParent, /* Number of parents */
242
int nCherrypick, /* How many of aParent[] are actually cherrypicks */
243
GraphRowId *aParent, /* Array of parents */
244
const char *zBranch, /* Branch for this check-in */
245
const char *zBgClr, /* Background color. NULL or "" for white. */
246
const char *zUuid, /* hash name of the object being graphed */
247
int isLeaf /* True if this row is a leaf */
248
){
249
GraphRow *pRow;
250
int nByte;
251
static int nRow = 0;
252
253
if( p->nErr ) return 0;
254
nByte = sizeof(GraphRow);
255
if( nParent>0 ) nByte += sizeof(pRow->aParent[0])*nParent;
256
pRow = (GraphRow*)safeMalloc( nByte );
257
pRow->aParent = nParent>0 ? (GraphRowId*)&pRow[1] : 0;
258
pRow->rid = rid;
259
if( nCherrypick>=nParent ){
260
nCherrypick = nParent-1; /* Safety. Should never happen. */
261
}
262
pRow->nParent = nParent;
263
pRow->nCherrypick = nCherrypick;
264
pRow->nNonCherrypick = nParent - nCherrypick;
265
pRow->zBranch = persistBranchName(p, zBranch);
266
if( zUuid==0 ) zUuid = "";
267
sqlite3_snprintf(sizeof(pRow->zUuid), pRow->zUuid, "%s", zUuid);
268
pRow->isLeaf = isLeaf;
269
memset(pRow->aiRiser, -1, sizeof(pRow->aiRiser));
270
if( zBgClr==0 ) zBgClr = "";
271
pRow->zBgClr = persistBranchName(p, zBgClr);
272
if( nParent>0 ) memcpy(pRow->aParent, aParent, sizeof(aParent[0])*nParent);
273
if( p->pFirst==0 ){
274
p->pFirst = pRow;
275
}else{
276
p->pLast->pNext = pRow;
277
}
278
p->pLast = pRow;
279
p->nRow++;
280
pRow->idx = pRow->idxTop = ++nRow;
281
return pRow->idx;
282
}
283
284
/*
285
** Return the index of a rail currently not in use for any row between
286
** top and bottom, inclusive.
287
*/
288
static int findFreeRail(
289
GraphContext *p, /* The graph context */
290
int top, int btm, /* Span of rows for which the rail is needed */
291
int iNearto, /* Find rail nearest to this rail */
292
int bMergeRail /* This rail will be used for a merge line */
293
){
294
GraphRow *pRow;
295
int i;
296
int iBest = 0;
297
int iBestDist = 9999;
298
u64 inUseMask = 0;
299
for(pRow=p->pFirst; pRow && pRow->idx<top; pRow=pRow->pNext){}
300
while( pRow && pRow->idx<=btm ){
301
inUseMask |= pRow->railInUse;
302
pRow = pRow->pNext;
303
}
304
305
/* First look for a match that honors bMergeRail */
306
for(i=0; i<=p->mxRail; i++){
307
u64 m = BIT(i);
308
int dist;
309
if( inUseMask & m ) continue;
310
if( (bMergeRail!=0) != ((p->mergeRail & m)!=0) ) continue;
311
if( iNearto<=0 ){
312
iBest = i;
313
iBestDist = 1;
314
break;
315
}
316
dist = i - iNearto;
317
if( dist<0 ) dist = -dist;
318
if( dist<iBestDist ){
319
iBestDist = dist;
320
iBest = i;
321
}
322
}
323
324
/* If no match, consider all possible rails */
325
if( iBestDist>1000 ){
326
for(i=0; i<=p->mxRail+1; i++){
327
int dist;
328
if( inUseMask & BIT(i) ) continue;
329
if( iNearto<=0 ){
330
iBest = i;
331
iBestDist = 1;
332
break;
333
}
334
dist = i - iNearto;
335
if( dist<0 ) dist = -dist;
336
if( dist<iBestDist ){
337
iBestDist = dist;
338
iBest = i;
339
}
340
}
341
}
342
if( iBestDist>1000 ){
343
p->bOverfull = 1;
344
iBest = GR_MAX_RAIL;
345
}
346
if( iBest>GR_MAX_RAIL ){
347
p->bOverfull = 1;
348
iBest = GR_MAX_RAIL;
349
}
350
if( iBest>p->mxRail ) p->mxRail = iBest;
351
if( bMergeRail ) p->mergeRail |= BIT(iBest);
352
return iBest;
353
}
354
355
/*
356
** Assign all children of node pBottom to the same rail as pBottom.
357
*/
358
static void assignChildrenToRail(GraphRow *pBottom, u32 tmFlags){
359
int iRail = pBottom->iRail;
360
GraphRow *pCurrent;
361
GraphRow *pPrior;
362
u64 mask = ((u64)1)<<iRail;
363
364
pBottom->railInUse |= mask;
365
pPrior = pBottom;
366
for(pCurrent=pBottom->pChild; pCurrent; pCurrent=pCurrent->pChild){
367
assert( pPrior->idx > pCurrent->idx );
368
assert( pCurrent->iRail<0 );
369
if( pPrior->timeWarp ) break;
370
pCurrent->iRail = iRail;
371
pCurrent->railInUse |= mask;
372
pPrior->aiRiser[iRail] = pCurrent->idx;
373
while( pPrior->idx > pCurrent->idx ){
374
pPrior->railInUse |= mask;
375
pPrior = pPrior->pPrev;
376
assert( pPrior!=0 );
377
}
378
}
379
/* Mask of additional rows for the riser to infinity */
380
if( !pPrior->isLeaf && (tmFlags & TIMELINE_DISJOINT)==0 ){
381
int n = RISER_MARGIN;
382
GraphRow *p;
383
pPrior->selfUp = 0;
384
for(p=pPrior; p && (n--)>0; p=p->pPrev){
385
pPrior->selfUp++;
386
p->railInUse |= mask;
387
}
388
}
389
}
390
391
392
/*
393
** Check to see if rail iRail is clear from pBottom up to and including
394
** pTop.
395
*/
396
static int railIsClear(GraphRow *pBottom, int iTop, int iRail){
397
u64 m = BIT(iRail);
398
while( pBottom && pBottom->idx>=iTop ){
399
if( pBottom->railInUse & m ) return 0;
400
pBottom = pBottom->pPrev;
401
}
402
return 1;
403
}
404
405
/*
406
** Create a merge-arrow riser going from pParent up to pChild.
407
*/
408
static void createMergeRiser(
409
GraphContext *p,
410
GraphRow *pParent, /* Lower node from which the merge line begins */
411
GraphRow *pChild, /* Upper node at which the merge line ends */
412
int isCherrypick /* True for a cherry-pick merge */
413
){
414
int u;
415
u64 mask;
416
GraphRow *pLoop;
417
418
if( pParent->mergeOut<0 ){
419
u = pParent->aiRiser[pParent->iRail];
420
if( u<0 && railIsClear(pParent->pPrev, pChild->idx, pParent->iRail) ){
421
/* pParent is a leaf and the merge-line can be drawn straight up.*/
422
pParent->mergeOut = pParent->iRail;
423
mask = BIT(pParent->iRail);
424
for(pLoop=pChild->pNext; pLoop && pLoop->rid!=pParent->rid;
425
pLoop=pLoop->pNext){
426
pLoop->railInUse |= mask;
427
}
428
}else if( u>0 && u<pChild->idx ){
429
/* The thick arrow up to the next primary child of pDesc goes
430
** further up than the thin merge arrow riser, so draw them both
431
** on the same rail. */
432
pParent->mergeOut = pParent->iRail;
433
}else if( pParent->idx - pChild->idx < pParent->selfUp ){
434
pParent->mergeOut = pParent->iRail;
435
}else{
436
/* The thin merge arrow riser is taller than the thick primary
437
** child riser, so use separate rails. */
438
int iTarget = pParent->iRail;
439
if( u<0 ) p->hasOffsetMergeRiser = 1;
440
pParent->mergeOut = findFreeRail(p, pChild->idx, pParent->idx-1,
441
iTarget, 1);
442
mask = BIT(pParent->mergeOut);
443
for(pLoop=pChild->pNext; pLoop && pLoop->rid!=pParent->rid;
444
pLoop=pLoop->pNext){
445
pLoop->railInUse |= mask;
446
}
447
}
448
}
449
if( isCherrypick ){
450
if( pParent->cherrypickUpto==0 || pParent->cherrypickUpto > pChild->idx ){
451
pParent->cherrypickUpto = pChild->idx;
452
}
453
}else{
454
pParent->hasNormalOutMerge = 1;
455
if( pParent->mergeUpto==0 || pParent->mergeUpto > pChild->idx ){
456
pParent->mergeUpto = pChild->idx;
457
}
458
}
459
pChild->mergeIn[pParent->mergeOut] = isCherrypick ? 2 : 1;
460
}
461
462
/*
463
** Compute the maximum rail number.
464
*/
465
static void find_max_rail(GraphContext *p){
466
GraphRow *pRow;
467
p->mxRail = 0;
468
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
469
if( pRow->iRail>p->mxRail ) p->mxRail = pRow->iRail;
470
if( pRow->mergeOut>p->mxRail ) p->mxRail = pRow->mergeOut;
471
while( p->mxRail<GR_MAX_RAIL
472
&& (pRow->mergeDown|pRow->cherrypickDown)>(BIT(p->mxRail+1)-1)
473
){
474
p->mxRail++;
475
}
476
}
477
}
478
479
/*
480
** Draw a riser from pRow upward to indicate that it is going
481
** to a node that is off the graph to the top.
482
*/
483
static void riser_to_top(GraphRow *pRow){
484
u64 mask = BIT(pRow->iRail);
485
int n = RISER_MARGIN;
486
pRow->aiRiser[pRow->iRail] = 0;
487
while( pRow && (n--)>0 ){
488
pRow->railInUse |= mask;
489
pRow = pRow->pPrev;
490
}
491
}
492
493
/*
494
** Compute the complete graph
495
**
496
** When primary or merge parents are off-screen, normally a line is drawn
497
** from the node down to the bottom of the graph. This line is called a
498
** "descender". But if the omitDescenders flag is true, then lines down
499
** to the bottom of the screen are omitted.
500
**
501
** The tmFlags parameter is zero or more of the TIMELINE_* constants.
502
** Only the following are honored:
503
**
504
** TIMELINE_DISJOINT: Omit descenders
505
** TIMELINE_FILLGAPS: Use step-children
506
** TIMELINE_XMERGE: Omit off-graph merge lines
507
*/
508
void graph_finish(
509
GraphContext *p, /* The graph to be laid out */
510
Matcher *pLeftBranch, /* Compares true for left-most branch */
511
u32 tmFlags /* TIMELINE flags */
512
){
513
GraphRow *pRow, *pDesc, *pDup, *pLoop, *pParent;
514
int i, j;
515
u64 mask;
516
int hasDup = 0; /* True if one or more isDup entries */
517
const char *zTrunk;
518
const char *zMainBranch;
519
u8 *aMap; /* Copy of p->aiRailMap */
520
int omitDescenders = (tmFlags & TIMELINE_DISJOINT)!=0;
521
int nTimewarp = 0;
522
int riserMargin = (tmFlags & TIMELINE_DISJOINT) ? 0 : RISER_MARGIN;
523
524
/* If mergeRiserFrom[X]==Y that means rail X holds a merge riser
525
** coming up from the bottom of the graph from off-screen check-in Y
526
** where Y is the RID. There is no riser on rail X if mergeRiserFrom[X]==0.
527
*/
528
GraphRowId mergeRiserFrom[GR_MAX_RAIL];
529
530
if( p==0 || p->pFirst==0 || p->nErr ) return;
531
p->nErr = 1; /* Assume an error until proven otherwise */
532
533
/* Initialize all rows */
534
p->nHash = p->nRow*2 + 1;
535
p->apHash = safeMalloc( sizeof(p->apHash[0])*p->nHash );
536
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
537
if( pRow->pNext ) pRow->pNext->pPrev = pRow;
538
pRow->iRail = -1;
539
pRow->mergeOut = -1;
540
if( (pDup = hashFind(p, pRow->rid))!=0 ){
541
hasDup = 1;
542
pDup->isDup = 1;
543
}
544
hashInsert(p, pRow, 1);
545
}
546
p->mxRail = -1;
547
memset(mergeRiserFrom, 0, sizeof(mergeRiserFrom));
548
549
/* Purge merge-parents that are out-of-graph if descenders are not
550
** drawn.
551
**
552
** Each node has one primary parent and zero or more "merge" parents.
553
** A merge parent is a prior check-in from which changes were merged into
554
** the current check-in. If a merge parent is not in the visible section
555
** of this graph, then no arrows will be drawn for it, so remove it from
556
** the aParent[] array.
557
*/
558
if( (tmFlags & (TIMELINE_DISJOINT|TIMELINE_XMERGE))!=0 ){
559
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
560
for(i=1; i<pRow->nParent; i++){
561
GraphRow *pParent = hashFind(p, pRow->aParent[i]);
562
if( pParent==0 ){
563
memmove(pRow->aParent+i, pRow->aParent+i+1,
564
sizeof(pRow->aParent[0])*(pRow->nParent-i-1));
565
pRow->nParent--;
566
if( i<pRow->nNonCherrypick ){
567
pRow->nNonCherrypick--;
568
}else{
569
pRow->nCherrypick--;
570
}
571
i--;
572
}
573
}
574
}
575
}
576
577
/* Put the deepest (earliest) merge parent first in the list.
578
** An off-screen merge parent is considered deepest.
579
*/
580
for(pRow=p->pFirst; pRow; pRow=pRow->pNext ){
581
if( pRow->nParent<=1 ) continue;
582
for(i=1; i<pRow->nParent; i++){
583
GraphRow *pParent = hashFind(p, pRow->aParent[i]);
584
if( pParent ) pParent->nMergeChild++;
585
}
586
if( pRow->nCherrypick>1 ){
587
int iBest = -1;
588
int iDeepest = -1;
589
for(i=pRow->nNonCherrypick; i<pRow->nParent; i++){
590
GraphRow *pParent = hashFind(p, pRow->aParent[i]);
591
if( pParent==0 ){
592
iBest = i;
593
break;
594
}
595
if( pParent->idx>iDeepest ){
596
iDeepest = pParent->idx;
597
iBest = i;
598
}
599
}
600
i = pRow->nNonCherrypick;
601
if( iBest>i ){
602
GraphRowId x = pRow->aParent[i];
603
pRow->aParent[i] = pRow->aParent[iBest];
604
pRow->aParent[iBest] = x;
605
}
606
}
607
if( pRow->nNonCherrypick>2 ){
608
int iBest = -1;
609
int iDeepest = -1;
610
for(i=1; i<pRow->nNonCherrypick; i++){
611
GraphRow *pParent = hashFind(p, pRow->aParent[i]);
612
if( pParent==0 ){
613
iBest = i;
614
break;
615
}
616
if( pParent->idx>iDeepest ){
617
iDeepest = pParent->idx;
618
iBest = i;
619
}
620
}
621
if( iBest>1 ){
622
GraphRowId x = pRow->aParent[1];
623
pRow->aParent[1] = pRow->aParent[iBest];
624
pRow->aParent[iBest] = x;
625
}
626
}
627
}
628
629
/* If the primary parent is in a different branch, but there are
630
** other parents in the same branch, reorder the parents to make
631
** the parent from the same branch the primary parent.
632
*/
633
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
634
if( pRow->isDup ) continue;
635
if( pRow->nNonCherrypick<2 ) continue; /* Not a fork */
636
pParent = hashFind(p, pRow->aParent[0]);
637
if( pParent==0 ) continue; /* Parent off-screen */
638
if( pParent->zBranch==pRow->zBranch ) continue; /* Same branch */
639
for(i=1; i<pRow->nNonCherrypick; i++){
640
pParent = hashFind(p, pRow->aParent[i]);
641
if( pParent && pParent->zBranch==pRow->zBranch ){
642
GraphRowId t = pRow->aParent[0];
643
pRow->aParent[0] = pRow->aParent[i];
644
pRow->aParent[i] = t;
645
break;
646
}
647
}
648
}
649
650
651
/* Find the pChild pointer for each node.
652
**
653
** The pChild points to the node directly above on the same rail.
654
** The pChild must be in the same branch. Leaf nodes have a NULL
655
** pChild.
656
**
657
** In the case of a fork, choose the pChild that results in the
658
** longest rail.
659
*/
660
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
661
if( pRow->isDup ) continue;
662
if( pRow->nParent<=0 ) continue; /* Root node */
663
pParent = hashFind(p, pRow->aParent[0]);
664
if( pParent==0 ) continue; /* Parent off-screen */
665
if( pParent->zBranch!=pRow->zBranch ) continue; /* Different branch */
666
if( pParent->idx <= pRow->idx ){
667
pParent->timeWarp = 1;
668
nTimewarp++;
669
}else if( pRow->idxTop < pParent->idxTop ){
670
pParent->pChild = pRow;
671
pParent->idxTop = pRow->idxTop;
672
}
673
}
674
675
if( tmFlags & TIMELINE_FILLGAPS ){
676
/* If a node has no pChild in the graph
677
** and there is a node higher up in the graph
678
** that is in the same branch and has no in-graph parent, then
679
** make the lower node a step-child of the upper node. This will
680
** be represented on the graph by a thick dotted line without an arrowhead.
681
*/
682
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
683
if( pRow->pChild ) continue;
684
if( pRow->isLeaf ) continue;
685
for(pLoop=pRow->pPrev; pLoop; pLoop=pLoop->pPrev){
686
if( pLoop->nParent>0
687
&& pLoop->zBranch==pRow->zBranch
688
&& hashFind(p,pLoop->aParent[0])==0
689
){
690
pRow->pChild = pLoop;
691
pRow->isStepParent = 1;
692
pLoop->aParent[0] = pRow->rid;
693
break;
694
}
695
}
696
}
697
}
698
699
/* Set the idxTop values for all entries. The idxTop value is the
700
** "idx" value for the top entry in its stack of children.
701
*/
702
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
703
GraphRow *pChild = pRow->pChild;
704
if( pChild && pRow->idxTop>pChild->idxTop ){
705
pRow->idxTop = pChild->idxTop;
706
}
707
}
708
709
/* Identify rows where the primary parent is off screen. Assign
710
** each to a rail and draw descenders downward.
711
**
712
** Strive to put the main branch (usually "trunk") on far left.
713
*/
714
zMainBranch = db_main_branch();
715
zTrunk = persistBranchName(p, zMainBranch);
716
for(i=0; i<2; i++){
717
for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
718
if( i==0 && pRow->zBranch!=zTrunk ) continue;
719
if( pRow->iRail>=0 ) continue;
720
if( pRow->isDup ) continue;
721
if( pRow->nParent<0 ) continue;
722
if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){
723
pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx+riserMargin,0,0);
724
/* if( p->mxRail>=GR_MAX_RAIL ) return; */
725
mask = BIT(pRow->iRail);
726
if( !omitDescenders ){
727
int n = RISER_MARGIN;
728
pRow->bDescender = pRow->nParent>0;
729
for(pLoop=pRow; pLoop && (n--)>0; pLoop=pLoop->pNext){
730
pLoop->railInUse |= mask;
731
}
732
}
733
assignChildrenToRail(pRow, tmFlags);
734
}
735
}
736
}
737
738
/* Assign rails to all rows that are still unassigned.
739
*/
740
for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
741
GraphRowId parentRid;
742
743
if( pRow->iRail>=0 ){
744
if( pRow->pChild==0 && !pRow->timeWarp ){
745
if( !omitDescenders && count_nonbranch_children(pRow->rid)!=0 ){
746
riser_to_top(pRow);
747
}
748
}
749
continue;
750
}
751
if( pRow->isDup || pRow->nParent<0 ){
752
continue;
753
}else{
754
assert( pRow->nParent>0 );
755
parentRid = pRow->aParent[0];
756
pParent = hashFind(p, parentRid);
757
if( pParent==0 ){
758
pRow->iRail = ++p->mxRail;
759
if( p->mxRail>=GR_MAX_RAIL ){
760
pRow->iRail = p->mxRail = GR_MAX_RAIL;
761
p->bOverfull = 1;
762
}
763
pRow->railInUse = BIT(pRow->iRail);
764
continue;
765
}
766
if( pParent->idx>pRow->idx ){
767
/* Common case: Child occurs after parent and is above the
768
** parent in the timeline */
769
pRow->iRail = findFreeRail(p, pRow->idxTop, pParent->idx,
770
pParent->iRail, 0);
771
/* if( p->mxRail>=GR_MAX_RAIL ) return; */
772
pParent->aiRiser[pRow->iRail] = pRow->idx;
773
}else{
774
/* Timewarp case: Child occurs earlier in time than parent and
775
** appears below the parent in the timeline. */
776
int iDownRail = ++p->mxRail;
777
if( iDownRail<1 ) iDownRail = ++p->mxRail;
778
if( p->mxRail>GR_MAX_RAIL ){
779
iDownRail = p->mxRail = GR_MAX_RAIL;
780
p->bOverfull = 1;
781
}
782
pRow->iRail = ++p->mxRail;
783
if( p->mxRail>=GR_MAX_RAIL ){
784
pRow->iRail = p->mxRail = GR_MAX_RAIL;
785
p->bOverfull = 1;
786
}
787
pRow->railInUse = BIT(pRow->iRail);
788
pParent->aiRiser[iDownRail] = pRow->idx;
789
mask = BIT(iDownRail);
790
for(pLoop=p->pFirst; pLoop; pLoop=pLoop->pNext){
791
pLoop->railInUse |= mask;
792
}
793
}
794
}
795
mask = BIT(pRow->iRail);
796
pRow->railInUse |= mask;
797
if( pRow->pChild ){
798
assignChildrenToRail(pRow, tmFlags);
799
}else if( !omitDescenders && count_nonbranch_children(pRow->rid)!=0 ){
800
if( !pRow->timeWarp ) riser_to_top(pRow);
801
}
802
if( pParent ){
803
if( pParent->idx>pRow->idx ){
804
/* Common case: Parent is below current row in the graph */
805
for(pLoop=pParent->pPrev; pLoop && pLoop!=pRow; pLoop=pLoop->pPrev){
806
pLoop->railInUse |= mask;
807
}
808
}else{
809
/* Timewarp case: Parent is above current row in the graph */
810
for(pLoop=pParent->pNext; pLoop && pLoop!=pRow; pLoop=pLoop->pNext){
811
pLoop->railInUse |= mask;
812
}
813
}
814
}
815
}
816
817
/*
818
** Insert merge rails and merge arrows
819
*/
820
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
821
int iReuseIdx = -1;
822
int iReuseRail = -1;
823
int isCherrypick = 0;
824
for(i=1; i<pRow->nParent; i++){
825
GraphRowId parentRid = pRow->aParent[i];
826
if( i==pRow->nNonCherrypick ){
827
/* Because full merges are laid out before cherrypicks,
828
** it is ok to use a full-merge raiser for a cherrypick.
829
** See the graph on check-in 8ac66ef33b464d28 for example
830
** iReuseIdx = -1;
831
** iReuseRail = -1; */
832
isCherrypick = 1;
833
}
834
pDesc = hashFind(p, parentRid);
835
if( pDesc==0 ){
836
int iMrail = -1;
837
/* Merge from a node that is off-screen */
838
if( iReuseIdx>=p->nRow+1 ){
839
continue; /* Suppress multiple off-screen merges */
840
}
841
for(j=0; j<GR_MAX_RAIL; j++){
842
if( mergeRiserFrom[j]==parentRid ){
843
iMrail = j;
844
break;
845
}
846
}
847
if( iMrail==-1 ){
848
iMrail = findFreeRail(p, pRow->idx, p->pLast->idx, 0, 1);
849
/*if( p->mxRail>=GR_MAX_RAIL ) return;*/
850
mergeRiserFrom[iMrail] = parentRid;
851
}
852
iReuseIdx = p->nRow+1;
853
iReuseRail = iMrail;
854
mask = BIT(iMrail);
855
if( i>=pRow->nNonCherrypick ){
856
pRow->mergeIn[iMrail] = 2;
857
pRow->cherrypickDown |= mask;
858
}else{
859
pRow->mergeIn[iMrail] = 1;
860
pRow->mergeDown |= mask;
861
}
862
for(pLoop=pRow->pNext; pLoop; pLoop=pLoop->pNext){
863
pLoop->railInUse |= mask;
864
}
865
}else{
866
/* The merge parent node does exist on this graph */
867
if( iReuseIdx>pDesc->idx
868
&& pDesc->nMergeChild==1
869
){
870
/* Reuse an existing merge riser */
871
pDesc->mergeOut = iReuseRail;
872
if( isCherrypick ){
873
pDesc->cherrypickUpto = pDesc->idx;
874
}else{
875
pDesc->hasNormalOutMerge = 1;
876
pDesc->mergeUpto = pDesc->idx;
877
}
878
}else{
879
/* Create a new merge for an on-screen node */
880
createMergeRiser(p, pDesc, pRow, isCherrypick);
881
/* if( p->mxRail>=GR_MAX_RAIL ) return; */
882
if( iReuseIdx<0
883
&& pDesc->nMergeChild==1
884
&& (pDesc->iRail!=pDesc->mergeOut || pDesc->isLeaf)
885
){
886
iReuseIdx = pDesc->idx;
887
iReuseRail = pDesc->mergeOut;
888
}
889
}
890
}
891
}
892
}
893
894
/*
895
** Insert merge rails from primaries to duplicates.
896
*/
897
if( hasDup && p->mxRail<GR_MAX_RAIL ){
898
int dupRail;
899
int mxRail;
900
find_max_rail(p);
901
mxRail = p->mxRail;
902
dupRail = mxRail+1;
903
/* if( p->mxRail>=GR_MAX_RAIL ) return; */
904
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
905
if( !pRow->isDup ) continue;
906
pRow->iRail = dupRail;
907
pDesc = hashFind(p, pRow->rid);
908
assert( pDesc!=0 && pDesc!=pRow );
909
createMergeRiser(p, pDesc, pRow, 0);
910
if( pDesc->mergeOut>mxRail ) mxRail = pDesc->mergeOut;
911
}
912
if( dupRail<=mxRail ){
913
dupRail = mxRail+1;
914
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
915
if( pRow->isDup ) pRow->iRail = dupRail;
916
}
917
}
918
/* if( mxRail>=GR_MAX_RAIL ) return; */
919
}
920
921
/*
922
** Find the maximum rail number.
923
*/
924
find_max_rail(p);
925
926
/* If a leaf node has a merge riser going up on a different rail,
927
** try to move the rail of the node (and its ancestors) to be underneath
928
** the merge riser. This is an optimization that improves the
929
** appearance of graph but is not strictly necessary.
930
*/
931
if( nTimewarp==0 && p->hasOffsetMergeRiser ){
932
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
933
GraphRow *pBottom; /* Bottom row of a branch */
934
GraphRow *pRoot; /* Node off of which the branch diverges */
935
int iFrom; /* Proposed to move from this rail */
936
int iTo; /* Move the branch to this rail */
937
938
iFrom = pRow->iRail;
939
if( pRow->aiRiser[iFrom]>=0 ) continue; /* Not a leaf */
940
if( pRow->mergeOut<0 ) continue; /* No merge riser */
941
if( pRow->mergeOut==iFrom ) continue; /* Riser already aligned */
942
iTo = pRow->mergeOut;
943
944
/* Find the bottom (oldest) node in the branch */
945
pBottom = 0;
946
for(pLoop=pRow; pLoop; pLoop=pLoop->pNext){
947
if( pLoop->idxTop==pRow->idx ) pBottom = pLoop;
948
}
949
if( pBottom==0 ) continue; /* Not possible */
950
951
/* Verify that the rail we want to shift into is clear */
952
pLoop = pBottom;
953
if( pLoop->pNext ) pLoop = pLoop->pNext;
954
if( !railIsClear(pLoop, pRow->idx+1, iTo) ){
955
/* Other nodes or risers are already using the space that
956
** we propose to move the pRow branch into. */
957
continue;
958
}
959
960
/* Find the "root" of the branch. The root is a different branch
961
** from which the pRow branch emerges. There might not be a root
962
** if the pRow branch started off the bottom of the screen.
963
*/
964
for(pRoot=pBottom->pNext; pRoot; pRoot=pRoot->pNext){
965
if( pRoot->aiRiser[iFrom]==pBottom->idx ) break;
966
}
967
if( pRoot && pRoot->iRail==iTo ){
968
/* The parent branch from which this branch emerges is on the
969
** same rail as pRow. Do not shift as that would stack a child
970
** branch directly above its parent. */
971
continue;
972
}
973
974
/* All clear. Make the translation
975
*/
976
for(pLoop=pRow; pLoop && pLoop->idx<=pBottom->idx; pLoop=pLoop->pNext){
977
if( pLoop->iRail==iFrom ){
978
pLoop->iRail = iTo;
979
pLoop->aiRiser[iTo] = pLoop->aiRiser[iFrom];
980
pLoop->aiRiser[iFrom] = -1;
981
}
982
}
983
if( pRoot ){
984
pRoot->aiRiser[iTo] = pRoot->aiRiser[iFrom];
985
pRoot->aiRiser[iFrom] = -1;
986
}
987
}
988
}
989
990
/*
991
** Compute the rail mapping that tries to put the branch named
992
** pLeftBranch at the left margin. Other branches that merge
993
** with pLeftBranch are to the right with merge rails in between.
994
**
995
** aMap[X]=Y means that the X-th rail is drawn as the Y-th rail.
996
**
997
** Do not move rails around if there are timewarps, because that can
998
** seriously mess up the display of timewarps. Timewarps should be
999
** rare so this should not be a serious limitation to the algorithm.
1000
*/
1001
aMap = p->aiRailMap;
1002
for(i=0; i<=p->mxRail; i++) aMap[i] = i; /* Set up a default mapping */
1003
if( nTimewarp==0 ){
1004
int kk;
1005
/* Priority bits:
1006
**
1007
** 0x04 The preferred branch
1008
**
1009
** 0x02 A merge rail - a rail that contains merge lines into
1010
** the preferred branch. Only applies if a preferred branch
1011
** is defined. This improves the display of r=BRANCH
1012
** options to /timeline.
1013
**
1014
** 0x01 A rail that merges with the preferred branch
1015
*/
1016
u16 aPriority[GR_MAX_RAIL];
1017
int mxMatch = 0;
1018
memset(aPriority, 0, (p->mxRail+1)*sizeof(aPriority[0]));
1019
if( pLeftBranch ){
1020
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
1021
int iMatch = match_text(pLeftBranch, pRow->zBranch);
1022
if( iMatch>0 ){
1023
if( iMatch>10 ) iMatch = 10;
1024
aPriority[pRow->iRail] |= 1<<(iMatch+1);
1025
if( mxMatch<iMatch ) mxMatch = iMatch;
1026
for(i=0; i<=p->mxRail; i++){
1027
if( pRow->mergeIn[i] ) aPriority[i] |= 1;
1028
}
1029
if( pRow->mergeOut>=0 ) aPriority[pRow->mergeOut] |= 1;
1030
}
1031
}
1032
for(i=0; i<=p->mxRail; i++){
1033
if( p->mergeRail & BIT(i) ){
1034
aPriority[i] |= 2;
1035
}
1036
}
1037
}else{
1038
j = 1;
1039
aPriority[0] = 4;
1040
mxMatch = 1;
1041
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
1042
if( pRow->iRail==0 ){
1043
for(i=0; i<=p->mxRail; i++){
1044
if( pRow->mergeIn[i] ) aPriority[i] |= 1;
1045
}
1046
if( pRow->mergeOut>=0 ) aPriority[pRow->mergeOut] |= 1;
1047
}
1048
}
1049
}
1050
1051
#if 0
1052
fprintf(stderr,"mergeRail: 0x%llx\n", p->mergeRail);
1053
fprintf(stderr,"Priority:");
1054
for(i=0; i<=p->mxRail; i++){
1055
fprintf(stderr," %x.%x",
1056
aPriority[i]/4, aPriority[i]&3);
1057
}
1058
fprintf(stderr,"\n");
1059
#endif
1060
1061
j = 0;
1062
for(kk=4; kk<=1<<(mxMatch+1); kk*=2){
1063
for(i=0; i<=p->mxRail; i++){
1064
if( aPriority[i]>=kk && aPriority[i]<kk*2 ){
1065
aMap[i] = j++;
1066
}
1067
}
1068
}
1069
for(i=p->mxRail; i>=0; i--){
1070
if( aPriority[i]==3 ) aMap[i] = j++;
1071
}
1072
for(i=0; i<=p->mxRail; i++){
1073
if( aPriority[i]==1 || aPriority[i]==2 ) aMap[i] = j++;
1074
}
1075
for(i=0; i<=p->mxRail; i++){
1076
if( aPriority[i]==0 ) aMap[i] = j++;
1077
}
1078
cgi_printf("<!-- aiRailMap =");
1079
for(i=0; i<=p->mxRail; i++) cgi_printf(" %d", aMap[i]);
1080
cgi_printf(" -->\n");
1081
}
1082
1083
p->nErr = 0;
1084
}
1085

Keyboard Shortcuts

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