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