Fossil SCM

fossil-scm / src / descendants.c
Blame History Raw 726 lines
1
/*
2
** Copyright (c) 2007 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 used to find descendants of a version
19
** or leaves of a version tree.
20
*/
21
#include "config.h"
22
#include "descendants.h"
23
#include <assert.h>
24
25
26
/*
27
** Create a temporary table named "leaves" if it does not
28
** already exist. Load this table with the RID of all
29
** check-ins that are leaves which are descended from
30
** check-in iBase.
31
**
32
** A "leaf" is a check-in that has no children in the same branch.
33
** There is a separate permanent table LEAF that contains all leaves
34
** in the tree. This routine is used to compute a subset of that
35
** table consisting of leaves that are descended from a single check-in.
36
**
37
** The closeMode flag determines behavior associated with the "closed"
38
** tag:
39
**
40
** closeMode==0 Show all leaves regardless of the "closed" tag.
41
**
42
** closeMode==1 Show only leaves without the "closed" tag.
43
**
44
** closeMode==2 Show only leaves with the "closed" tag.
45
**
46
** The default behavior is to ignore closed leaves (closeMode==0). To
47
** Show all leaves, use closeMode==1. To show only closed leaves, use
48
** closeMode==2.
49
*/
50
void compute_leaves(int iBase, int closeMode){
51
52
/* Create the LEAVES table if it does not already exist. Make sure
53
** it is empty.
54
*/
55
db_multi_exec(
56
"CREATE TEMP TABLE IF NOT EXISTS leaves("
57
" rid INTEGER PRIMARY KEY"
58
");"
59
"DELETE FROM leaves;"
60
);
61
62
if( iBase>0 ){
63
Bag seen; /* Descendants seen */
64
Bag pending; /* Unpropagated descendants */
65
Stmt q1; /* Query to find children of a check-in */
66
Stmt isBr; /* Query to check to see if a check-in starts a new branch */
67
Stmt ins; /* INSERT statement for a new record */
68
69
/* Initialize the bags. */
70
bag_init(&seen);
71
bag_init(&pending);
72
bag_insert(&pending, iBase);
73
74
/* This query returns all non-branch-merge children of check-in :rid.
75
**
76
** If a child is a merge of a fork within the same branch, it is
77
** returned. Only merge children in different branches are excluded.
78
*/
79
db_prepare(&q1,
80
"SELECT cid FROM plink"
81
" WHERE pid=:rid"
82
" AND (isprim"
83
" OR coalesce((SELECT value FROM tagxref"
84
" WHERE tagid=%d AND rid=plink.pid), 'trunk')"
85
"=coalesce((SELECT value FROM tagxref"
86
" WHERE tagid=%d AND rid=plink.cid), 'trunk'))",
87
TAG_BRANCH, TAG_BRANCH
88
);
89
90
/* This query returns a single row if check-in :rid is the first
91
** check-in of a new branch.
92
*/
93
db_prepare(&isBr,
94
"SELECT 1 FROM tagxref"
95
" WHERE rid=:rid AND tagid=%d AND tagtype=2"
96
" AND srcid>0",
97
TAG_BRANCH
98
);
99
100
/* This statement inserts check-in :rid into the LEAVES table.
101
*/
102
db_prepare(&ins, "INSERT OR IGNORE INTO leaves VALUES(:rid)");
103
104
while( bag_count(&pending) ){
105
int rid = bag_first(&pending);
106
int cnt = 0;
107
bag_remove(&pending, rid);
108
db_bind_int(&q1, ":rid", rid);
109
while( db_step(&q1)==SQLITE_ROW ){
110
int cid = db_column_int(&q1, 0);
111
if( bag_insert(&seen, cid) ){
112
bag_insert(&pending, cid);
113
}
114
db_bind_int(&isBr, ":rid", cid);
115
if( db_step(&isBr)==SQLITE_DONE ){
116
cnt++;
117
}
118
db_reset(&isBr);
119
}
120
db_reset(&q1);
121
if( cnt==0 && !is_a_leaf(rid) ){
122
cnt++;
123
}
124
if( cnt==0 ){
125
db_bind_int(&ins, ":rid", rid);
126
db_step(&ins);
127
db_reset(&ins);
128
}
129
}
130
db_finalize(&ins);
131
db_finalize(&isBr);
132
db_finalize(&q1);
133
bag_clear(&pending);
134
bag_clear(&seen);
135
}else{
136
db_multi_exec(
137
"INSERT INTO leaves"
138
" SELECT leaf.rid FROM leaf"
139
);
140
}
141
if( closeMode==1 ){
142
db_multi_exec(
143
"DELETE FROM leaves WHERE rid IN"
144
" (SELECT leaves.rid FROM leaves, tagxref"
145
" WHERE tagxref.rid=leaves.rid "
146
" AND tagxref.tagid=%d"
147
" AND tagxref.tagtype>0)",
148
TAG_CLOSED
149
);
150
}else if( closeMode==2 ){
151
db_multi_exec(
152
"DELETE FROM leaves WHERE rid NOT IN"
153
" (SELECT leaves.rid FROM leaves, tagxref"
154
" WHERE tagxref.rid=leaves.rid "
155
" AND tagxref.tagid=%d"
156
" AND tagxref.tagtype>0)",
157
TAG_CLOSED
158
);
159
}
160
}
161
162
/*
163
** If RID refers to a check-in, return the mtime of that check-in - the
164
** Julian day number of when the check-in occurred.
165
*/
166
double mtime_of_rid(int rid, double mtime){
167
static Stmt q;
168
db_static_prepare(&q,"SELECT mtime FROM event WHERE objid=:rid");
169
db_bind_int(&q, ":rid", rid);
170
if( db_step(&q)==SQLITE_ROW ){
171
mtime = db_column_double(&q,0);
172
}
173
db_reset(&q);
174
return mtime;
175
}
176
177
178
179
/*
180
** Load the record ID rid and up to |N|-1 closest ancestors into
181
** the "ok" table. If N is zero, no limit. If ridBackTo is not zero
182
** then stop the search upon reaching the ancestor with rid==ridBackTo.
183
*/
184
void compute_ancestors(int rid, int N, int directOnly, int ridBackTo){
185
if( !N ){
186
N = -1;
187
}else if( N<0 ){
188
N = -N;
189
}
190
if( directOnly ){
191
/* Direct mode means to show primary parents only */
192
db_multi_exec(
193
"WITH RECURSIVE "
194
" ancestor(rid, mtime) AS ("
195
" SELECT %d, mtime FROM event WHERE objid=%d "
196
" UNION "
197
" SELECT plink.pid, event.mtime"
198
" FROM ancestor, plink, event"
199
" WHERE plink.cid=ancestor.rid"
200
" AND event.objid=plink.pid"
201
" AND plink.isPrim"
202
" ORDER BY mtime DESC LIMIT %d"
203
" )"
204
"INSERT INTO ok"
205
" SELECT rid FROM ancestor;",
206
rid, rid, N
207
);
208
}else{
209
/* If not in directMode, also include merge parents, including
210
** cherrypick merges. Except, terminate searches at the cherrypick
211
** merge parent itself. In other words, include:
212
** (1) Primary parents
213
** (2) Merge parents
214
** (3) Cherrypick merge parents.
215
** (4) All ancestores of 1 and 2 but not of 3.
216
*/
217
double rLimitMtime = 0.0;
218
if( ridBackTo ){
219
rLimitMtime = mtime_of_rid(ridBackTo, 0.0);
220
}
221
db_multi_exec(
222
"WITH RECURSIVE\n"
223
" parent(pid,cid,isCP) AS (\n"
224
" SELECT plink.pid, plink.cid, 0 AS xisCP FROM plink\n"
225
" UNION ALL\n"
226
" SELECT parentid, childid, 1 FROM cherrypick WHERE NOT isExclude\n"
227
" ),\n"
228
" ancestor(rid, mtime, isCP) AS (\n"
229
" SELECT %d, mtime, 0 FROM event WHERE objid=%d\n"
230
" UNION\n"
231
" SELECT parent.pid, event.mtime, parent.isCP\n"
232
" FROM ancestor, parent, event\n"
233
" WHERE parent.cid=ancestor.rid\n"
234
" AND event.objid=parent.pid\n"
235
" AND NOT ancestor.isCP\n"
236
" AND (event.mtime>=%.17g OR parent.pid=%d)\n"
237
" ORDER BY mtime DESC LIMIT %d\n"
238
" )\n"
239
"INSERT OR IGNORE INTO ok SELECT rid FROM ancestor;",
240
rid, rid, rLimitMtime, ridBackTo, N
241
);
242
if( ridBackTo && db_changes()>1 ){
243
db_multi_exec("INSERT OR IGNORE INTO ok VALUES(%d)", ridBackTo);
244
}
245
}
246
}
247
248
/*
249
** Compute the youngest ancestor of record ID rid that is a member of
250
** branch zBranch.
251
*/
252
int compute_youngest_ancestor_in_branch(int rid, const char *zBranch){
253
return db_int(0,
254
"WITH RECURSIVE "
255
" ancestor(rid, mtime) AS ("
256
" SELECT %d, mtime FROM event WHERE objid=%d "
257
" UNION "
258
" SELECT plink.pid, event.mtime"
259
" FROM ancestor, plink, event"
260
" WHERE plink.cid=ancestor.rid"
261
" AND event.objid=plink.pid"
262
" ORDER BY mtime DESC"
263
" )"
264
" SELECT ancestor.rid FROM ancestor"
265
" WHERE EXISTS(SELECT 1 FROM tagxref"
266
" WHERE tagid=%d AND tagxref.rid=ancestor.rid"
267
" AND value=%Q AND tagtype>0)"
268
" ORDER BY mtime DESC"
269
" LIMIT 1",
270
rid, rid, TAG_BRANCH, zBranch
271
);
272
}
273
274
/*
275
** Compute all direct ancestors (merge ancestors do not count)
276
** for the check-in rid and put them in a table named "ancestor".
277
** Label each generation with consecutive integers going backwards
278
** in time such that rid has the smallest generation number and the oldest
279
** direct ancestor as the largest generation number.
280
*/
281
void compute_direct_ancestors(int rid){
282
db_multi_exec(
283
"CREATE TEMP TABLE IF NOT EXISTS ancestor(rid INTEGER UNIQUE NOT NULL,"
284
" generation INTEGER PRIMARY KEY);"
285
"DELETE FROM ancestor;"
286
"WITH RECURSIVE g(x,i) AS ("
287
" VALUES(%d,1)"
288
" UNION ALL"
289
" SELECT plink.pid, g.i+1 FROM plink, g"
290
" WHERE plink.cid=g.x AND plink.isprim)"
291
"INSERT INTO ancestor(rid,generation) SELECT x,i FROM g;",
292
rid
293
);
294
}
295
296
/*
297
** Compute the "mtime" of the file given whose blob.rid is "fid" that
298
** is part of check-in "vid". The mtime will be the mtime on vid or
299
** some ancestor of vid where fid first appears.
300
*/
301
int mtime_of_manifest_file(
302
int vid, /* The check-in that contains fid */
303
int fid, /* The id of the file whose check-in time is sought */
304
i64 *pMTime /* Write result here */
305
){
306
static int prevVid = -1;
307
static Stmt q;
308
309
if( prevVid!=vid ){
310
prevVid = vid;
311
db_multi_exec("CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
312
"DELETE FROM ok;");
313
compute_ancestors(vid, 100000000, 1, 0);
314
}
315
db_static_prepare(&q,
316
"SELECT (max(event.mtime)-2440587.5)*86400 FROM mlink, event"
317
" WHERE mlink.mid=event.objid"
318
" AND +mlink.mid IN ok"
319
" AND mlink.fid=:fid");
320
db_bind_int(&q, ":fid", fid);
321
if( db_step(&q)!=SQLITE_ROW ){
322
db_reset(&q);
323
return 1;
324
}
325
*pMTime = db_column_int64(&q, 0);
326
db_reset(&q);
327
return 0;
328
}
329
330
/*
331
** Load the record ID rid and up to |N|-1 closest descendants into
332
** the "ok" table. If N is zero, no limit.
333
*/
334
void compute_descendants(int rid, int N){
335
if( !N ){
336
N = -1;
337
}else if( N<0 ){
338
N = -N;
339
}
340
db_multi_exec(
341
"WITH RECURSIVE\n"
342
" dx(rid,mtime) AS (\n"
343
" SELECT %d, 0\n"
344
" UNION\n"
345
" SELECT plink.cid, plink.mtime FROM dx, plink\n"
346
" WHERE plink.pid=dx.rid\n"
347
" ORDER BY 2\n"
348
" )\n"
349
"INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d",
350
rid, N
351
);
352
}
353
354
/*
355
** COMMAND: descendants*
356
**
357
** Usage: %fossil descendants ?CHECKIN? ?OPTIONS?
358
**
359
** Find all leaf descendants of the check-in specified or if the argument
360
** is omitted, of the check-in currently checked out.
361
**
362
** Options:
363
** -R|--repository REPO Extract info from repository REPO
364
** -W|--width N Width of lines (default is to auto-detect).
365
** Must be greater than 20 or else 0 for no
366
** limit, resulting in a one line per entry.
367
**
368
** See also: [[finfo]], [[info]], [[leaves]]
369
*/
370
void descendants_cmd(void){
371
Stmt q;
372
int base, width;
373
const char *zWidth;
374
375
db_find_and_open_repository(0,0);
376
zWidth = find_option("width","W",1);
377
if( zWidth ){
378
width = atoi(zWidth);
379
if( (width!=0) && (width<=20) ){
380
fossil_fatal("-W|--width value must be >20 or 0");
381
}
382
}else{
383
width = -1;
384
}
385
386
/* We should be done with options.. */
387
verify_all_options();
388
389
if( g.argc==2 ){
390
base = db_lget_int("checkout", 0);
391
}else{
392
base = name_to_typed_rid(g.argv[2], "ci");
393
}
394
if( base==0 ) return;
395
compute_leaves(base, 0);
396
db_prepare(&q,
397
"%s"
398
" AND event.objid IN (SELECT rid FROM leaves)"
399
" ORDER BY event.mtime DESC",
400
timeline_query_for_tty()
401
);
402
print_timeline(&q, 0, width, 0, 0);
403
db_finalize(&q);
404
}
405
406
/*
407
** COMMAND: leaves*
408
**
409
** Usage: %fossil leaves ?OPTIONS?
410
**
411
** Find leaves of all branches. By default show only open leaves.
412
** The -a|--all flag causes all leaves (closed and open) to be shown.
413
** The -c|--closed flag shows only closed leaves.
414
**
415
** The --recompute flag causes the content of the "leaf" table in the
416
** repository database to be recomputed.
417
**
418
** Options:
419
** -a|--all Show ALL leaves
420
** --bybranch Order output by branch name
421
** -c|--closed Show only closed leaves
422
** -m|--multiple Show only cases with multiple leaves on a single branch
423
** --recompute Recompute the "leaf" table in the repository DB
424
** -W|--width N Width of lines (default is to auto-detect). Must be
425
** more than 39 or else 0 no limit, resulting in a single
426
** line per entry.
427
**
428
** See also: [[descendants]], [[finfo]], [[info]], [[branch]]
429
*/
430
void leaves_cmd(void){
431
Stmt q;
432
Blob sql;
433
int showAll = find_option("all", "a", 0)!=0;
434
int showClosed = find_option("closed", "c", 0)!=0;
435
int recomputeFlag = find_option("recompute",0,0)!=0;
436
int byBranch = find_option("bybranch",0,0)!=0;
437
int multipleFlag = find_option("multiple","m",0)!=0;
438
const char *zWidth = find_option("width","W",1);
439
char *zLastBr = 0;
440
int n, width;
441
char zLineNo[10];
442
const char *zMainBranch = db_main_branch();
443
444
if( multipleFlag ) byBranch = 1;
445
if( zWidth ){
446
width = atoi(zWidth);
447
if( (width!=0) && (width<=39) ){
448
fossil_fatal("-W|--width value must be >39 or 0");
449
}
450
}else{
451
width = -1;
452
}
453
db_find_and_open_repository(0,0);
454
455
/* We should be done with options.. */
456
verify_all_options();
457
458
if( recomputeFlag ) leaf_rebuild();
459
blob_zero(&sql);
460
blob_append(&sql, timeline_query_for_tty(), -1);
461
if( !multipleFlag ){
462
/* The usual case - show all leaves */
463
blob_append_sql(&sql, " AND blob.rid IN leaf");
464
}else{
465
/* Show only leaves where two are more occur in the same branch */
466
db_multi_exec(
467
"CREATE TEMP TABLE openLeaf(rid INTEGER PRIMARY KEY);"
468
"INSERT INTO openLeaf(rid)"
469
" SELECT rid FROM leaf"
470
" WHERE NOT EXISTS("
471
" SELECT 1 FROM tagxref"
472
" WHERE tagid=%d AND tagtype>0 AND rid=leaf.rid);",
473
TAG_CLOSED
474
);
475
db_multi_exec(
476
"CREATE TEMP TABLE ambiguousBranch(brname TEXT);"
477
"INSERT INTO ambiguousBranch(brname)"
478
" SELECT (SELECT value FROM tagxref WHERE tagid=%d AND rid=openLeaf.rid)"
479
" FROM openLeaf"
480
" GROUP BY 1 HAVING count(*)>1;",
481
TAG_BRANCH
482
);
483
db_multi_exec(
484
"CREATE TEMP TABLE ambiguousLeaf(rid INTEGER PRIMARY KEY);\n"
485
"INSERT INTO ambiguousLeaf(rid)\n"
486
" SELECT rid FROM openLeaf\n"
487
" WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=openLeaf.rid)"
488
" IN (SELECT brname FROM ambiguousBranch);",
489
TAG_BRANCH
490
);
491
blob_append_sql(&sql, " AND blob.rid IN ambiguousLeaf");
492
}
493
if( showClosed ){
494
blob_append_sql(&sql," AND %z", leaf_is_closed_sql("blob.rid"));
495
}else if( !showAll ){
496
blob_append_sql(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid"));
497
}
498
if( byBranch ){
499
db_prepare(&q, "%s ORDER BY nullif(branch,'trunk') COLLATE nocase,"
500
" event.mtime DESC",
501
blob_sql_text(&sql));
502
}else{
503
db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql));
504
}
505
blob_reset(&sql);
506
n = 0;
507
while( db_step(&q)==SQLITE_ROW ){
508
const char *zId = db_column_text(&q, 1);
509
const char *zDate = db_column_text(&q, 2);
510
const char *zCom = db_column_text(&q, 3);
511
const char *zBr = db_column_text(&q, 7);
512
char *z = 0;
513
char * zBranchPoint = 0;
514
515
if( byBranch && fossil_strcmp(zBr, zLastBr)!=0 ){
516
fossil_print("*** %s ***\n", zBr);
517
fossil_free(zLastBr);
518
zLastBr = fossil_strdup(zBr);
519
if( multipleFlag ) n = 0;
520
}
521
n++;
522
sqlite3_snprintf(sizeof(zLineNo), zLineNo, "(%d)", n);
523
fossil_print("%6s ", zLineNo);
524
if(0!=fossil_strcmp(zBr,zMainBranch)){
525
int ridOfRoot;
526
z = mprintf("root:%s", zId);
527
ridOfRoot = symbolic_name_to_rid(z, "ci");
528
if(ridOfRoot>0){
529
zBranchPoint = mprintf(" (branched from: [%.*z])", hash_digits(0),
530
rid_to_uuid(ridOfRoot));
531
}
532
fossil_free(z);
533
}
534
z = mprintf("%s [%S] %s%s", zDate, zId, zCom,
535
zBranchPoint ? zBranchPoint : "");
536
comment_print(z, zCom, 7, width, get_comment_format());
537
fossil_free(z);
538
fossil_free(zBranchPoint);
539
}
540
fossil_free(zLastBr);
541
db_finalize(&q);
542
}
543
544
/*
545
** WEBPAGE: leaves
546
**
547
** Show leaf check-ins in a timeline. By default only open leaves
548
** are listed.
549
**
550
** A "leaf" is a check-in with no children in the same branch. A
551
** "closed leaf" is a leaf that has a "closed" tag. An "open leaf"
552
** is a leaf without a "closed" tag.
553
**
554
** Query parameters:
555
**
556
** all Show all leaves
557
** closed Show only closed leaves
558
** ng No graph
559
** nohidden Hide check-ins with "hidden" tag
560
** onlyhidden Show only check-ins with "hidden" tag
561
** brbg Background color by branch name
562
** ubg Background color by user name
563
*/
564
void leaves_page(void){
565
Blob sql;
566
Stmt q;
567
int showAll = P("all")!=0;
568
int showClosed = P("closed")!=0;
569
int fNg = PB("ng")!=0; /* Flag for the "ng" query parameter */
570
int fNoHidden = PB("nohidden")!=0; /* "nohidden" query parameter */
571
int fOnlyHidden = PB("onlyhidden")!=0; /* "onlyhidden" query parameter */
572
int fBrBg = PB("brbg")!=0; /* Flag for the "brbg" query parameter */
573
int fUBg = PB("ubg")!=0; /* Flag for the "ubg" query parameter */
574
HQuery url; /* URL to /leaves plus query parameters */
575
int tmFlags; /* Timeline display flags */
576
577
login_check_credentials();
578
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
579
url_initialize(&url, "leaves");
580
if( fNg ) url_add_parameter(&url, "ng", "");
581
if( fNoHidden ) url_add_parameter(&url, "nohidden", "");
582
if( fOnlyHidden ) url_add_parameter(&url, "onlyhidden", "");
583
if( fBrBg ) url_add_parameter(&url, "brbg", "");
584
if( fUBg ) url_add_parameter(&url, "ubg", "");
585
if( !showAll ){
586
style_submenu_element("All", "%s", url_render(&url, "all", "", 0, 0));
587
}
588
if( !showClosed ){
589
style_submenu_element("Closed", "%s", url_render(&url, "closed", "", 0, 0));
590
}
591
if( showClosed || showAll ){
592
style_submenu_element("Open", "%s", url_render(&url, 0, 0, 0, 0));
593
}
594
url_reset(&url);
595
cgi_check_for_malice();
596
style_set_current_feature("leaves");
597
style_header("Leaves");
598
login_anonymous_available();
599
timeline_ss_submenu();
600
#if 0
601
style_sidebox_begin("Nomenclature:", "33%");
602
@ <ol>
603
@ <li> A <div class="sideboxDescribed">leaf</div>
604
@ is a check-in with no descendants in the same branch.</li>
605
@ <li> An <div class="sideboxDescribed">open leaf</div>
606
@ is a leaf that does not have a "closed" tag
607
@ and is thus assumed to still be in use.</li>
608
@ <li> A <div class="sideboxDescribed">closed leaf</div>
609
@ has a "closed" tag and is thus assumed to
610
@ be historical and no longer in active use.</li>
611
@ </ol>
612
style_sidebox_end();
613
#endif
614
615
if( showAll ){
616
@ <h1>All leaves, both open and closed:</h1>
617
}else if( showClosed ){
618
@ <h1>Closed leaves:</h1>
619
}else{
620
@ <h1>Open leaves:</h1>
621
}
622
blob_zero(&sql);
623
blob_append(&sql, timeline_query_for_www(), -1);
624
blob_append_sql(&sql, " AND blob.rid IN leaf");
625
if( showClosed ){
626
blob_append_sql(&sql," AND %z", leaf_is_closed_sql("blob.rid"));
627
}else if( !showAll ){
628
blob_append_sql(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid"));
629
}
630
if( fNoHidden || fOnlyHidden ){
631
const char* zUnaryOp = fNoHidden ? "NOT" : "";
632
blob_append_sql(&sql,
633
" AND %s EXISTS(SELECT 1 FROM tagxref"
634
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
635
zUnaryOp/*safe-for-%s*/, TAG_HIDDEN);
636
}
637
db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql));
638
blob_reset(&sql);
639
/* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too
640
** many descenders to (off-screen) parents. */
641
tmFlags = TIMELINE_LEAFONLY | TIMELINE_DISJOINT | TIMELINE_NOSCROLL;
642
if( fNg==0 ) tmFlags |= TIMELINE_GRAPH;
643
if( fBrBg ) tmFlags |= TIMELINE_BRCOLOR;
644
if( fUBg ) tmFlags |= TIMELINE_UCOLOR;
645
www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, 0);
646
db_finalize(&q);
647
@ <br>
648
style_finish_page();
649
}
650
651
#if INTERFACE
652
/* Flag parameters to compute_uses_file() */
653
#define USESFILE_DELETE 0x01 /* Include the check-ins where file deleted */
654
655
#endif
656
657
/*
658
** Append a new VALUES term.
659
*/
660
static void uses_file_append_term(Blob *pSql, int *pnCnt, int rid){
661
if( *pnCnt==0 ){
662
blob_append_sql(pSql, "(%d)", rid);
663
*pnCnt = 4;
664
}else if( (*pnCnt)%10==9 ){
665
blob_append_sql(pSql, ",\n (%d)", rid);
666
}else{
667
blob_append_sql(pSql, ",(%d)", rid);
668
}
669
++*pnCnt;
670
}
671
672
673
/*
674
** Add to table zTab the record ID (rid) of every check-in that contains
675
** the file fid.
676
*/
677
void compute_uses_file(const char *zTab, int fid, int usesFlags){
678
Bag seen;
679
Bag pending;
680
Blob ins = BLOB_INITIALIZER;
681
int nIns = 0;
682
Stmt q;
683
int rid;
684
685
bag_init(&seen);
686
bag_init(&pending);
687
blob_append_sql(&ins, "INSERT OR IGNORE INTO \"%w\" VALUES", zTab);
688
db_prepare(&q, "SELECT mid FROM mlink WHERE fid=%d", fid);
689
while( db_step(&q)==SQLITE_ROW ){
690
int mid = db_column_int(&q, 0);
691
bag_insert(&pending, mid);
692
bag_insert(&seen, mid);
693
uses_file_append_term(&ins, &nIns, mid);
694
}
695
db_finalize(&q);
696
697
db_prepare(&q, "SELECT mid FROM mlink WHERE pid=%d", fid);
698
while( db_step(&q)==SQLITE_ROW ){
699
int mid = db_column_int(&q, 0);
700
bag_insert(&seen, mid);
701
if( usesFlags & USESFILE_DELETE ){
702
uses_file_append_term(&ins, &nIns, mid);
703
}
704
}
705
db_finalize(&q);
706
db_prepare(&q, "SELECT cid FROM plink WHERE pid=:rid AND isprim");
707
708
while( (rid = bag_first(&pending))!=0 ){
709
bag_remove(&pending, rid);
710
db_bind_int(&q, ":rid", rid);
711
while( db_step(&q)==SQLITE_ROW ){
712
int mid = db_column_int(&q, 0);
713
if( bag_find(&seen, mid) ) continue;
714
bag_insert(&seen, mid);
715
bag_insert(&pending, mid);
716
uses_file_append_term(&ins, &nIns, mid);
717
}
718
db_reset(&q);
719
}
720
db_finalize(&q);
721
db_exec_sql(blob_str(&ins));
722
blob_reset(&ins);
723
bag_clear(&seen);
724
bag_clear(&pending);
725
}
726

Keyboard Shortcuts

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