Fossil SCM

fossil-scm / src / name.c
Blame History Raw 2672 lines
1
/*
2
** Copyright (c) 2006 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 resolved user-supplied object names.
19
*/
20
#include "config.h"
21
#include "name.h"
22
#include <assert.h>
23
24
#if INTERFACE
25
/*
26
** An upper boundary on RIDs, provided in order to be able to
27
** distinguish real RID values from RID_CKOUT and any future
28
** RID_... values.
29
*/
30
#define RID_MAX 0x7ffffff0
31
/*
32
** A "magic" RID representing the current checkout in some contexts.
33
*/
34
#define RID_CKOUT (RID_MAX+1)
35
#endif
36
37
/*
38
** Return TRUE if the string begins with something that looks roughly
39
** like an ISO date/time string. The SQLite date/time functions will
40
** have the final say-so about whether or not the date/time string is
41
** well-formed.
42
*/
43
int fossil_isdate(const char *z){
44
if( !fossil_isdigit(z[0]) ) return 0;
45
if( !fossil_isdigit(z[1]) ) return 0;
46
if( !fossil_isdigit(z[2]) ) return 0;
47
if( !fossil_isdigit(z[3]) ) return 0;
48
if( z[4]!='-') return 0;
49
if( !fossil_isdigit(z[5]) ) return 0;
50
if( !fossil_isdigit(z[6]) ) return 0;
51
if( z[7]!='-') return 0;
52
if( !fossil_isdigit(z[8]) ) return 0;
53
if( !fossil_isdigit(z[9]) ) return 0;
54
return 1;
55
}
56
57
/*
58
** Check to see if the string might be a compact date/time that omits
59
** the punctuation. Example: "20190327084549" instead of
60
** "2019-03-27 08:45:49". If the string is of the appropriate form,
61
** then return an alternative string (in static space) that is the same
62
** string with punctuation inserted.
63
**
64
** If the bRoundUp parameter is true, then round the resulting date-time
65
** up to the largest date/time that is consistent with the input value.
66
** This is because the result will be used for an mtime<=julianday($DATE)
67
** comparison. In other words:
68
**
69
** 20250317123421 -> 2025-03-17 12:34:21.999
70
** ^^^^--- Added
71
**
72
** 202503171234 -> 2025-03-17 12:34:59.999
73
** ^^^^^^^--- Added
74
** 20250317 -> 2025-03-17 23:59:59.999
75
** ^^^^^^^^^^^^^--- Added
76
**
77
** If the bVerifyNotAHash flag is true, then a check is made to see if
78
** the input string is a hash prefix and NULL is returned if it is. If the
79
** bVerifyNotAHash flag is false, then the result is determined by syntax
80
** of the input string only, without reference to the artifact table.
81
*/
82
char *fossil_expand_datetime(const char *zIn,int bVerifyNotAHash,int bRoundUp){
83
static char zEDate[24];
84
static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' };
85
int n = (int)strlen(zIn);
86
int i, j;
87
int addZulu = 0;
88
89
/* These forms are allowed:
90
**
91
** 123456789 1234 123456789 123456789 1234
92
** (1) YYYYMMDD => YYYY-MM-DD 23:59:59.999
93
** (2) YYYYMMDDHHMM => YYYY-MM-DD HH:MM:59.999
94
** (3) YYYYMMDDHHMMSS => YYYY-MM-DD HH:MM:SS.999
95
**
96
** An optional "Z" zulu timezone designator is allowed at the end.
97
*/
98
if( n>0 && (zIn[n-1]=='Z' || zIn[n-1]=='z') ){
99
n--;
100
addZulu = 1;
101
}
102
if( n!=8 && n!=12 && n!=14 ){
103
return 0;
104
}
105
106
/* Every character must be a digit */
107
for(i=0; fossil_isdigit(zIn[i]); i++){}
108
if( i!=n && (!addZulu || i!=n+1) ) return 0;
109
110
/* Expand the date */
111
for(i=j=0; i<n; i++){
112
if( i>=4 && (i%2)==0 ){
113
zEDate[j++] = aPunct[i/2];
114
}
115
zEDate[j++] = zIn[i];
116
}
117
if( bRoundUp ){
118
if( j==10 ){
119
memcpy(&zEDate[10], " 23:59:59.999", 13);
120
j += 13;
121
}else if( j==16 ){
122
memcpy(&zEDate[16], ":59.999",7);
123
j += 7;
124
}else if( j==19 ){
125
memcpy(&zEDate[19], ".999", 4);
126
j += 4;
127
}
128
}
129
if( addZulu ){
130
zEDate[j++] = 'Z';
131
}
132
zEDate[j] = 0;
133
134
/* Check for reasonable date values.
135
** Offset references:
136
** YYYY-MM-DD HH:MM:SS
137
** 0123456789 12345678
138
*/
139
140
i = atoi(zEDate);
141
if( i<1970 || i>2100 ) return 0;
142
i = atoi(zEDate+5);
143
if( i<1 || i>12 ) return 0;
144
i = atoi(zEDate+8);
145
if( i<1 || i>31 ) return 0;
146
if( n>8 ){
147
i = atoi(zEDate+11);
148
if( i>24 ) return 0;
149
i = atoi(zEDate+14);
150
if( i>60 ) return 0;
151
if( n==14 && atoi(zEDate+17)>60 ) return 0;
152
}
153
154
/* The string is not also a hash prefix */
155
if( bVerifyNotAHash && !addZulu ){
156
if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'",zIn) ) return 0;
157
}
158
159
/* It looks like this may be a date. Return it with punctuation added. */
160
return zEDate;
161
}
162
163
/*
164
** The data-time string in the argument is going to be used as an
165
** upper bound like this: mtime<=julianday(zDate,'localtime').
166
** But if the zDate parameter omits the fractional seconds or the
167
** seconds, or the time, that might mess up the == part of the
168
** comparison. So add in missing factional seconds or seconds or time.
169
**
170
** The returned string is held in a static buffer that is overwritten
171
** with each call, or else is just a copy of its input if there are
172
** no changes.
173
**
174
** For reference:
175
**
176
** 0123456789 123456789 1234
177
** YYYY-MM-DD HH:MM:SS.SSSz
178
*/
179
const char *fossil_roundup_date(const char *zDate){
180
static char zUp[28];
181
int n = (int)strlen(zDate);
182
int addZ = 0;
183
if( n>10 && (zDate[n-1]=='z' || zDate[n-1]=='Z') ){
184
n--;
185
addZ = 1;
186
}
187
if( n>=21 && n<=23 ){ /* YYYY-MM-DD HH:MM:SS.SSS */
188
memcpy(zUp, zDate, n);
189
while( n<23 ) zUp[n++] = '9';
190
/* If milliseconds is less than 999, round up to the next millisecond */
191
if( strcmp(&zUp[20],"999")<0 ){
192
if( zUp[22]<'9' ){
193
zUp[22]++;
194
}else{
195
zUp[22] = '0';
196
if( zUp[21]<'9' ){
197
zUp[21]++;
198
}else{
199
zUp[21] = '0';
200
zUp[20]++;
201
}
202
}
203
}
204
if( addZ ) zUp[n++] = 'z';
205
zUp[n] = 0;
206
return zUp;
207
}
208
if( n==19 ){ /* YYYY-MM-DD HH:MM:SS */
209
memcpy(zUp, zDate, 19);
210
memcpy(zUp+19, ".999z", 6);
211
if( !addZ ) zUp[23] = 0;
212
return zUp;
213
}
214
if( n==16 ){ /* YYYY-MM-DD HH:MM */
215
memcpy(zUp, zDate, 16);
216
memcpy(zUp+16, ":59.999z", 8);
217
if( !addZ ) zUp[23] = 0;
218
return zUp;
219
}
220
if( n==10 ){ /* YYYY-MM-DD */
221
memcpy(zUp, zDate, 10);
222
memcpy(zUp+10, " 23:59:59.999z", 14);
223
if( !addZ ) zUp[23] = 0;
224
return zUp;
225
}
226
return zDate;
227
}
228
229
230
/*
231
** Return the RID that is the "root" of the branch that contains
232
** check-in "rid". Details depending on eType:
233
**
234
** eType==0 The check-in of the parent branch off of which
235
** the branch containing RID originally diverged.
236
**
237
** eType==1 The first check-in of the branch that contains RID.
238
**
239
** eType==2 The youngest ancestor of RID that is on the branch
240
** from which the branch containing RID diverged.
241
*/
242
int start_of_branch(int rid, int eType){
243
Stmt q;
244
int rc;
245
int ans = rid;
246
char *zBr = branch_of_rid(rid);
247
db_prepare(&q,
248
"WITH RECURSIVE"
249
" par(pid, ex, cnt) as ("
250
" SELECT pid, EXISTS(SELECT 1 FROM tagxref"
251
" WHERE tagid=%d AND tagtype>0"
252
" AND value=%Q AND rid=plink.pid), 1"
253
" FROM plink WHERE cid=%d AND isprim"
254
" UNION ALL "
255
" SELECT plink.pid, EXISTS(SELECT 1 FROM tagxref "
256
" WHERE tagid=%d AND tagtype>0"
257
" AND value=%Q AND rid=plink.pid),"
258
" 1+par.cnt"
259
" FROM plink, par"
260
" WHERE cid=par.pid AND isprim AND par.ex "
261
" LIMIT 100000 "
262
" )"
263
" SELECT pid FROM par WHERE ex>=%d ORDER BY cnt DESC LIMIT 1",
264
TAG_BRANCH, zBr, ans, TAG_BRANCH, zBr, eType%2
265
);
266
fossil_free(zBr);
267
rc = db_step(&q);
268
if( rc==SQLITE_ROW ){
269
ans = db_column_int(&q, 0);
270
}
271
db_finalize(&q);
272
if( eType==2 && ans>0 ){
273
zBr = branch_of_rid(ans);
274
ans = compute_youngest_ancestor_in_branch(rid, zBr);
275
fossil_free(zBr);
276
}
277
return ans;
278
}
279
280
/*
281
** Find the RID of the most recent object with symbolic tag zTag
282
** and having a type that matches zType.
283
**
284
** Return 0 if there are no matches.
285
**
286
** This is a tricky query to do efficiently.
287
** If the tag is very common (ex: "trunk") then
288
** we want to use the query identified below as Q1 - which searches
289
** the most recent EVENT table entries for the most recent with the tag.
290
** But if the tag is relatively scarce (anything other than "trunk", basically)
291
** then we want to do the indexed search shown below as Q2.
292
*/
293
static int most_recent_event_with_tag(const char *zTag, const char *zType){
294
return db_int(0,
295
"SELECT objid FROM ("
296
/* Q1: Begin by looking for the tag in the 30 most recent events */
297
"SELECT objid"
298
" FROM (SELECT * FROM event ORDER BY mtime DESC LIMIT 30) AS ex"
299
" WHERE type GLOB '%q'"
300
" AND EXISTS(SELECT 1 FROM tagxref, tag"
301
" WHERE tag.tagname='sym-%q'"
302
" AND tagxref.tagid=tag.tagid"
303
" AND tagxref.tagtype>0"
304
" AND tagxref.rid=ex.objid)"
305
" ORDER BY mtime DESC LIMIT 1"
306
") UNION ALL SELECT * FROM ("
307
/* Q2: If the tag is not found in the 30 most recent events, then using
308
** the tagxref table to index for the tag */
309
"SELECT event.objid"
310
" FROM tag, tagxref, event"
311
" WHERE tag.tagname='sym-%q'"
312
" AND tagxref.tagid=tag.tagid"
313
" AND tagxref.tagtype>0"
314
" AND event.objid=tagxref.rid"
315
" AND event.type GLOB '%q'"
316
" ORDER BY event.mtime DESC LIMIT 1"
317
") LIMIT 1;",
318
zType, zTag, zTag, zType
319
);
320
}
321
322
/*
323
** Find the RID for a check-in that is the most recent check-in with
324
** tag zTag that occurs on or prior to rDate.
325
**
326
** See also the performance note on most_recent_event_with_tag() which
327
** applies to this routine too.
328
*/
329
int last_checkin_with_tag_before_date(const char *zTag, double rLimit){
330
Stmt s;
331
int rid = 0;
332
if( strncmp(zTag, "tag:", 4)==0 ) zTag += 4;
333
db_prepare(&s,
334
"SELECT objid FROM ("
335
/* Q1: Begin by looking for the tag in the 30 most recent events */
336
"SELECT objid"
337
" FROM (SELECT * FROM event WHERE mtime<=:datelimit"
338
" ORDER BY mtime DESC LIMIT 30) AS ex"
339
" WHERE type='ci'"
340
" AND EXISTS(SELECT 1 FROM tagxref, tag"
341
" WHERE tag.tagname='sym-%q'"
342
" AND tagxref.tagid=tag.tagid"
343
" AND tagxref.tagtype>0"
344
" AND tagxref.rid=ex.objid)"
345
" ORDER BY mtime DESC LIMIT 1"
346
") UNION ALL SELECT * FROM ("
347
/* Q2: If the tag is not found in the 30 most recent events, then using
348
** the tagxref table to index for the tag */
349
"SELECT event.objid"
350
" FROM tag, tagxref, event"
351
" WHERE tag.tagname='sym-%q'"
352
" AND tagxref.tagid=tag.tagid"
353
" AND tagxref.tagtype>0"
354
" AND event.objid=tagxref.rid"
355
" AND event.type='ci'"
356
" AND event.mtime<=:datelimit"
357
" ORDER BY event.mtime DESC LIMIT 1"
358
") LIMIT 1;",
359
zTag, zTag
360
);
361
db_bind_double(&s, ":datelimit", rLimit);
362
if( db_step(&s)==SQLITE_ROW ){
363
rid = db_column_int(&s,0);
364
}
365
db_finalize(&s);
366
return rid;
367
}
368
369
/*
370
** Find the RID of the first check-in (chronologically) after rStart that
371
** has tag zTag.
372
**
373
** See also the performance note on most_recent_event_with_tag() which
374
** applies to this routine too.
375
*/
376
int first_checkin_with_tag_after_date(const char *zTag, double rStart){
377
Stmt s;
378
int rid = 0;
379
if( strncmp(zTag, "tag:", 4)==0 ) zTag += 4;
380
db_prepare(&s,
381
"SELECT objid FROM ("
382
/* Q1: Begin by looking for the tag in the 30 most recent events */
383
"SELECT objid"
384
" FROM (SELECT * FROM event WHERE mtime>=:startdate"
385
" ORDER BY mtime LIMIT 30) AS ex"
386
" WHERE type='ci'"
387
" AND EXISTS(SELECT 1 FROM tagxref, tag"
388
" WHERE tag.tagname='sym-%q'"
389
" AND tagxref.tagid=tag.tagid"
390
" AND tagxref.tagtype>0"
391
" AND tagxref.rid=ex.objid)"
392
" ORDER BY mtime LIMIT 1"
393
") UNION ALL SELECT * FROM ("
394
/* Q2: If the tag is not found in the 30 most recent events, then using
395
** the tagxref table to index for the tag */
396
"SELECT event.objid"
397
" FROM tag, tagxref, event"
398
" WHERE tag.tagname='sym-%q'"
399
" AND tagxref.tagid=tag.tagid"
400
" AND tagxref.tagtype>0"
401
" AND event.objid=tagxref.rid"
402
" AND event.type='ci'"
403
" AND event.mtime>=:startdate"
404
" ORDER BY event.mtime LIMIT 1"
405
") LIMIT 1;",
406
zTag, zTag
407
);
408
db_bind_double(&s, ":startdate", rStart);
409
if( db_step(&s)==SQLITE_ROW ){
410
rid = db_column_int(&s,0);
411
}
412
db_finalize(&s);
413
return rid;
414
}
415
416
/*
417
** Return true if character "c" is a character that might have been
418
** accidentally appended to the end of a URL.
419
*/
420
static int is_trailing_punct(char c){
421
return c=='.' || c=='_' || c==')' || c=='>' || c=='!' || c=='?' || c==',';
422
}
423
424
425
/*
426
** Convert a symbolic name into a RID. Acceptable forms:
427
**
428
** * artifact hash (optionally enclosed in [...])
429
** * 4-character or larger prefix of an artifact
430
** * Symbolic Name
431
** * "tag:" + symbolic name
432
** * Date or date-time
433
** * "date:" + Date or date-time
434
** * symbolic-name ":" date-time
435
** * "tip"
436
**
437
** The following additional forms are available in local checkouts:
438
**
439
** * "current"
440
** * "prev" or "previous"
441
** * "next"
442
**
443
** The following modifier prefixes may be applied to the above forms:
444
**
445
** * "root:BR" = The origin of the branch named BR.
446
** * "start:BR" = The first check-in of the branch named BR.
447
** * "merge-in:BR" = The most recent merge-in for the branch named BR.
448
**
449
** In those forms, BR may be any symbolic form but is assumed to be a
450
** check-in. Thus root:2021-02-01 would resolve to a check-in, possibly
451
** in a branch and possibly in the trunk, but never a wiki edit or
452
** forum post.
453
**
454
** Return the RID of the matching artifact. Or return 0 if the name does not
455
** match any known object. Or return -1 if the name is ambiguous.
456
**
457
** The zType parameter specifies the type of artifact: ci, t, w, e, g, f.
458
** If zType is NULL or "" or "*" then any type of artifact will serve.
459
** If zType is "br" then find the first check-in of the named branch
460
** rather than the last.
461
**
462
** zType is "ci" in most use cases since we are usually searching for
463
** a check-in. A value of "ci+" works like "ci" but adds these
464
** semantics: if zTag is "ckout" and a checkout is open, "ci+" causes
465
** RID_CKOUT to be returned, in which case g.localOpen will hold the
466
** RID of the checkout. Conversely, passing in the hash, or another
467
** symbolic name of the local checkout version, will always result in
468
** its RID being returned.
469
**
470
** Note that the input zTag for types "t" and "e" is the artifact hash of
471
** the ticket-change or technote-change artifact, not the randomly generated
472
** hexadecimal identifier assigned to tickets and events. Those identifiers
473
** live in a separate namespace.
474
*/
475
int symbolic_name_to_rid(const char *zTag, const char *zType){
476
int rid = 0;
477
int ridCkout = 0;
478
int nTag;
479
int i;
480
int startOfBranch = 0;
481
const char *zXTag; /* zTag with optional [...] removed */
482
int nXTag; /* Size of zXTag */
483
const char *zDate; /* Expanded date-time string */
484
int isCheckin = 0; /* zType==ci = 1, zType==ci+ = 2 */
485
486
if( zType==0 || zType[0]==0 ){
487
zType = "*";
488
}else if( zType[0]=='b' ){
489
zType = "ci";
490
startOfBranch = 1;
491
}
492
if( zTag==0 || zTag[0]==0 ) return 0;
493
else if( 'c'==zType[0] ){
494
if( fossil_strcmp(zType,"ci")==0 ){
495
isCheckin = 1;
496
}else if( fossil_strcmp(zType,"ci+")==0 ){
497
isCheckin = 2;
498
zType = "ci";
499
}
500
}
501
502
/* special keyword: "tip" */
503
if( fossil_strcmp(zTag, "tip")==0 && (zType[0]=='*' || isCheckin!=0) ){
504
rid = db_int(0,
505
"SELECT objid"
506
" FROM event"
507
" WHERE type='ci'"
508
" ORDER BY event.mtime DESC"
509
);
510
if( rid ) return rid;
511
}
512
513
if( g.localOpen ) {
514
ridCkout = db_lget_int("checkout",0);
515
}
516
517
/* special keywords: "prev", "previous", "current", "ckout", and
518
** "next" */
519
if( (zType[0]=='*' || isCheckin!=0) && 0<ridCkout ){
520
if( fossil_strcmp(zTag, "current")==0 ){
521
rid = ridCkout;
522
}else if( fossil_strcmp(zTag, "prev")==0
523
|| fossil_strcmp(zTag, "previous")==0 ){
524
rid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim",
525
ridCkout);
526
}else if( fossil_strcmp(zTag, "next")==0 ){
527
rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d"
528
" ORDER BY isprim DESC, mtime DESC", ridCkout);
529
}else if( isCheckin>1 && fossil_strcmp(zTag, "ckout")==0 ){
530
rid = RID_CKOUT;
531
assert(ridCkout>0);
532
g.localOpen = ridCkout;
533
}
534
if( rid ) return rid;
535
}
536
537
/* Date and times */
538
if( memcmp(zTag, "date:", 5)==0 ){
539
zDate = fossil_expand_datetime(&zTag[5],0,1);
540
if( zDate==0 ) zDate = &zTag[5];
541
rid = db_int(0,
542
"SELECT objid FROM event"
543
" WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'"
544
" ORDER BY mtime DESC LIMIT 1",
545
fossil_roundup_date(zDate), zType);
546
return rid;
547
}
548
if( fossil_isdate(zTag) ){
549
rid = db_int(0,
550
"SELECT objid FROM event"
551
" WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'"
552
" ORDER BY mtime DESC LIMIT 1",
553
fossil_roundup_date(zTag), zType);
554
if( rid) return rid;
555
}
556
557
/* Deprecated date & time formats: "local:" + date-time and
558
** "utc:" + date-time */
559
if( memcmp(zTag, "local:", 6)==0 ){
560
rid = db_int(0,
561
"SELECT objid FROM event"
562
" WHERE mtime<=julianday(%Q) AND type GLOB '%q'"
563
" ORDER BY mtime DESC LIMIT 1",
564
&zTag[6], zType);
565
return rid;
566
}
567
if( memcmp(zTag, "utc:", 4)==0 ){
568
rid = db_int(0,
569
"SELECT objid FROM event"
570
" WHERE mtime<=julianday('%qz') AND type GLOB '%q'"
571
" ORDER BY mtime DESC LIMIT 1",
572
fossil_roundup_date(&zTag[4]), zType);
573
return rid;
574
}
575
576
/* "tag:" + symbolic-name */
577
if( memcmp(zTag, "tag:", 4)==0 ){
578
rid = most_recent_event_with_tag(&zTag[4], zType);
579
if( startOfBranch ) rid = start_of_branch(rid,1);
580
return rid;
581
}
582
583
/* root:BR -> The origin of the branch named BR */
584
if( strncmp(zTag, "root:", 5)==0 ){
585
rid = symbolic_name_to_rid(zTag+5, zType);
586
return start_of_branch(rid, 0);
587
}
588
589
/* start:BR -> The first check-in on branch named BR */
590
if( strncmp(zTag, "start:", 6)==0 ){
591
rid = symbolic_name_to_rid(zTag+6, zType);
592
return start_of_branch(rid, 1);
593
}
594
595
/* merge-in:BR -> Most recent merge-in for the branch named BR */
596
if( strncmp(zTag, "merge-in:", 9)==0 ){
597
rid = symbolic_name_to_rid(zTag+9, zType);
598
return start_of_branch(rid, 2);
599
}
600
601
/* symbolic-name ":" date-time */
602
nTag = strlen(zTag);
603
for(i=0; i<nTag-8 && zTag[i]!=':'; i++){}
604
if( zTag[i]==':'
605
&& (fossil_isdate(&zTag[i+1]) || fossil_expand_datetime(&zTag[i+1],0,0)!=0)
606
){
607
char *zDate = fossil_strdup(&zTag[i+1]);
608
char *zTagBase = mprintf("%.*s", i, zTag);
609
char *zXDate;
610
int nDate = strlen(zDate);
611
if( sqlite3_strnicmp(&zDate[nDate-3],"utc",3)==0 ){
612
zDate[nDate-3] = 'z';
613
zDate[nDate-2] = 0;
614
}
615
zXDate = fossil_expand_datetime(zDate,0,1);
616
if( zXDate==0 ) zXDate = zDate;
617
rid = db_int(0,
618
"SELECT event.objid, max(event.mtime)"
619
" FROM tag, tagxref, event"
620
" WHERE tag.tagname='sym-%q' "
621
" AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
622
" AND event.objid=tagxref.rid "
623
" AND event.mtime<=julianday(%Q,fromLocal())"
624
" AND event.type GLOB '%q'",
625
zTagBase, fossil_roundup_date(zXDate), zType
626
);
627
fossil_free(zDate);
628
fossil_free(zTagBase);
629
return rid;
630
}
631
632
/* Remove optional [...] */
633
zXTag = zTag;
634
nXTag = nTag;
635
if( zXTag[0]=='[' ){
636
zXTag++;
637
nXTag--;
638
}
639
if( nXTag>0 && zXTag[nXTag-1]==']' ){
640
nXTag--;
641
}
642
643
/* artifact hash or prefix */
644
if( nXTag>=4 && nXTag<=HNAME_MAX && validate16(zXTag, nXTag) ){
645
Stmt q;
646
char zUuid[HNAME_MAX+1];
647
memcpy(zUuid, zXTag, nXTag);
648
zUuid[nXTag] = 0;
649
canonical16(zUuid, nXTag);
650
rid = 0;
651
if( zType[0]=='*' ){
652
db_prepare(&q, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUuid);
653
}else{
654
db_prepare(&q,
655
"SELECT blob.rid"
656
" FROM blob CROSS JOIN event"
657
" WHERE blob.uuid GLOB '%q*'"
658
" AND event.objid=blob.rid"
659
" AND event.type GLOB '%q'",
660
zUuid, zType
661
);
662
}
663
if( db_step(&q)==SQLITE_ROW ){
664
rid = db_column_int(&q, 0);
665
if( db_step(&q)==SQLITE_ROW ) rid = -1;
666
}
667
db_finalize(&q);
668
if( rid ) return rid;
669
}
670
671
if( zType[0]=='w' ){
672
rid = db_int(0,
673
"SELECT event.objid, max(event.mtime)"
674
" FROM tag, tagxref, event"
675
" WHERE tag.tagname='wiki-%q' "
676
" AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
677
" AND event.objid=tagxref.rid "
678
" AND event.type GLOB '%q'",
679
zTag, zType
680
);
681
}else{
682
rid = most_recent_event_with_tag(zTag, zType);
683
}
684
685
if( rid>0 ){
686
if( startOfBranch ) rid = start_of_branch(rid,1);
687
return rid;
688
}
689
690
/* Pure numeric date/time */
691
zDate = fossil_expand_datetime(zTag, 0,1);
692
if( zDate ){
693
rid = db_int(0,
694
"SELECT objid FROM event"
695
" WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'"
696
" ORDER BY mtime DESC LIMIT 1",
697
fossil_roundup_date(zDate), zType);
698
if( rid) return rid;
699
}
700
701
702
/* Undocumented: numeric tags get translated directly into the RID */
703
if( memcmp(zTag, "rid:", 4)==0 ){
704
zTag += 4;
705
for(i=0; fossil_isdigit(zTag[i]); i++){}
706
if( zTag[i]==0 ){
707
if( strcmp(zType,"*")==0 ){
708
rid = atoi(zTag);
709
}else{
710
rid = db_int(0,
711
"SELECT event.objid"
712
" FROM event"
713
" WHERE event.objid=%s"
714
" AND event.type GLOB '%q'", zTag /*safe-for-%s*/, zType);
715
}
716
}
717
return rid;
718
}
719
720
/* If nothing matches and the name ends with punctuation,
721
** then the name might have originated from a URL in plain text
722
** that was incorrectly extracted from the text. Try to remove
723
** the extra punctuation and rerun the match.
724
*/
725
if( nTag>4
726
&& is_trailing_punct(zTag[nTag-1])
727
&& !is_trailing_punct(zTag[nTag-2])
728
){
729
char *zNew = fossil_strndup(zTag, nTag-1);
730
rid = symbolic_name_to_rid(zNew,zType);
731
fossil_free(zNew);
732
}else
733
if( nTag>5
734
&& is_trailing_punct(zTag[nTag-1])
735
&& is_trailing_punct(zTag[nTag-2])
736
&& !is_trailing_punct(zTag[nTag-3])
737
){
738
char *zNew = fossil_strndup(zTag, nTag-2);
739
rid = symbolic_name_to_rid(zNew,zType);
740
fossil_free(zNew);
741
}
742
return rid;
743
}
744
745
/*
746
** Convert a symbolic name used as an argument to the a=, b=, or c=
747
** query parameters of timeline into a julianday mtime value.
748
**
749
** If pzDisplay is not null, then display text for the symbolic name might
750
** be written into *pzDisplay. But that is not guaranteed.
751
**
752
** If bRoundUp is true and the symbolic name is a timestamp with less
753
** than millisecond resolution, then the timestamp is rounding up to the
754
** largest millisecond consistent with that timestamp. If bRoundUp is
755
** false, then the resulting time is obtained by extending the timestamp
756
** with zeros (hence rounding down). Use bRoundUp==1 if the result
757
** will be used in mtime<=$RESULT and use bRoundUp==0 if the result
758
** will be used in mtime>=$RESULT.
759
*/
760
double symbolic_name_to_mtime(
761
const char *z, /* Input symbolic name */
762
const char **pzDisplay, /* Perhaps write display text here, if not NULL */
763
int bRoundUp /* Round up if true */
764
){
765
double mtime;
766
int rid;
767
const char *zDate;
768
if( z==0 ) return -1.0;
769
if( fossil_isdate(z) ){
770
mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", z);
771
if( mtime>0.0 ) return mtime;
772
}
773
zDate = fossil_expand_datetime(z, 1, bRoundUp);
774
if( zDate!=0 ){
775
mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())",
776
bRoundUp ? fossil_roundup_date(zDate) : zDate);
777
if( mtime>0.0 ){
778
if( pzDisplay ){
779
zDate = fossil_expand_datetime(z,0,0);
780
*pzDisplay = fossil_strdup(zDate);
781
}
782
return mtime;
783
}
784
}
785
rid = symbolic_name_to_rid(z, "*");
786
if( rid ){
787
mtime = mtime_of_rid(rid, 0.0);
788
}else{
789
mtime = db_double(-1.0,
790
"SELECT max(event.mtime) FROM event, tag, tagxref"
791
" WHERE tag.tagname GLOB 'event-%q*'"
792
" AND tagxref.tagid=tag.tagid AND tagxref.tagtype"
793
" AND event.objid=tagxref.rid",
794
z
795
);
796
}
797
return mtime;
798
}
799
800
/*
801
** This routine takes a user-entered string and tries to convert it to
802
** an artifact hash.
803
**
804
** We first try to treat the string as an artifact hash, or at least a
805
** unique prefix of an artifact hash. The input may be in mixed case.
806
** If we are passed such a string, this routine has the effect of
807
** converting the hash [prefix] to canonical form.
808
**
809
** If the input is not a hash or a hash prefix, then try to resolve
810
** the name as a tag. If multiple tags match, pick the latest.
811
** A caller can force this routine to skip the hash case above by
812
** prefixing the string with "tag:", a useful property when the tag
813
** may be misinterpreted as a hex ASCII string. (e.g. "decade" or "facade")
814
**
815
** If the input is not a tag, then try to match it as an ISO-8601 date
816
** string YYYY-MM-DD HH:MM:SS and pick the nearest check-in to that date.
817
** If the input is of the form "date:*" then always resolve the name as
818
** a date. The forms "utc:*" and "local:" are deprecated.
819
**
820
** Return 0 on success. Return 1 if the name cannot be resolved.
821
** Return 2 name is ambiguous.
822
*/
823
int name_to_uuid(Blob *pName, int iErrPriority, const char *zType){
824
char *zName = blob_str(pName);
825
int rid = symbolic_name_to_rid(zName, zType);
826
if( rid<0 ){
827
fossil_error(iErrPriority, "ambiguous name: %s", zName);
828
return 2;
829
}else if( rid==0 ){
830
fossil_error(iErrPriority, "cannot resolve name: %s", zName);
831
return 1;
832
}else{
833
blob_reset(pName);
834
if( RID_CKOUT==rid ) {
835
rid = g.localOpen;
836
}
837
db_blob(pName, "SELECT uuid FROM blob WHERE rid=%d", rid);
838
return 0;
839
}
840
}
841
842
/*
843
** This routine is similar to name_to_uuid() except in the form it
844
** takes its parameters and returns its value, and in that it does not
845
** treat errors as fatal. zName must be an artifact hash or prefix of
846
** a hash. zType is also as described for name_to_uuid(). If
847
** zName does not resolve, 0 is returned. If it is ambiguous, a
848
** negative value is returned. On success the rid is returned and
849
** pUuid (if it is not NULL) is set to a newly-allocated string,
850
** the full hash, which must eventually be free()d by the caller.
851
*/
852
int name_to_uuid2(const char *zName, const char *zType, char **pUuid){
853
int rid = symbolic_name_to_rid(zName, zType);
854
if((rid>0) && pUuid){
855
*pUuid = (rid<RID_MAX)
856
? db_text(NULL, "SELECT uuid FROM blob WHERE rid=%d", rid)
857
: NULL;
858
}
859
return rid;
860
}
861
862
863
/*
864
** name_collisions searches through events, blobs, and tickets for
865
** collisions of a given hash based on its length, counting only
866
** hashes greater than or equal to 4 hex ASCII characters (16 bits)
867
** in length.
868
*/
869
int name_collisions(const char *zName){
870
int c = 0; /* count of collisions for zName */
871
int nLen; /* length of zName */
872
nLen = strlen(zName);
873
if( nLen>=4 && nLen<=HNAME_MAX && validate16(zName, nLen) ){
874
c = db_int(0,
875
"SELECT"
876
" (SELECT count(*) FROM ticket"
877
" WHERE tkt_uuid GLOB '%q*') +"
878
" (SELECT count(*) FROM tag"
879
" WHERE tagname GLOB 'event-%q*') +"
880
" (SELECT count(*) FROM blob"
881
" WHERE uuid GLOB '%q*');",
882
zName, zName, zName
883
);
884
if( c<2 ) c = 0;
885
}
886
return c;
887
}
888
889
/*
890
** COMMAND: test-name-to-id
891
**
892
** Usage: %fossil test-name-to-id [--count N] [--type ARTIFACT_TYPE] NAME
893
**
894
** Convert a NAME to a full artifact ID. Repeat the conversion N
895
** times (for timing purposes) if the --count option is given.
896
*/
897
void test_name_to_id(void){
898
int i;
899
int n = 0;
900
Blob name;
901
const char *zType;
902
903
db_must_be_within_tree();
904
if( (zType = find_option("type","t",1))==0 ){
905
zType = "*";
906
}
907
for(i=2; i<g.argc; i++){
908
if( strcmp(g.argv[i],"--count")==0 && i+1<g.argc ){
909
i++;
910
n = atoi(g.argv[i]);
911
continue;
912
}
913
do{
914
blob_init(&name, g.argv[i], -1);
915
fossil_print("%s -> ", g.argv[i]);
916
if( name_to_uuid(&name, 1, zType) ){
917
fossil_print("ERROR: %s\n", g.zErrMsg);
918
fossil_error_reset();
919
}else{
920
fossil_print("%s\n", blob_buffer(&name));
921
}
922
blob_reset(&name);
923
}while( n-- > 0 );
924
}
925
}
926
927
/*
928
** Convert a name to a rid. If the name can be any of the various forms
929
** accepted:
930
**
931
** * artifact hash or prefix thereof
932
** * symbolic name
933
** * date
934
** * label:date
935
** * prev, previous
936
** * next
937
** * tip
938
**
939
** This routine is used by command-line routines to resolve command-line inputs
940
** into a rid.
941
*/
942
int name_to_typed_rid(const char *zName, const char *zType){
943
int rid;
944
945
if( zName==0 || zName[0]==0 ) return 0;
946
rid = symbolic_name_to_rid(zName, zType);
947
if( rid<0 ){
948
fossil_fatal("ambiguous name: %s", zName);
949
}else if( rid==0 ){
950
fossil_fatal("cannot resolve name: %s", zName);
951
}
952
return rid;
953
}
954
int name_to_rid(const char *zName){
955
return name_to_typed_rid(zName, "*");
956
}
957
958
/*
959
** Try to resolve zQP1 into a check-in name. If zQP1 does not exist,
960
** return 0. If zQP1 exists but cannot be resolved, then also try to
961
** resolve zQP2 if it exists. If zQP1 cannot be resolved but zQP2 does
962
** not exist, then raise an error. If both zQP1 and zQP2 exists but
963
** neither can be resolved, also raise an error.
964
**
965
** If pzPick is not a NULL pointer, then *pzPick to be the value of whichever
966
** query parameter ended up being used.
967
*/
968
int name_choice(const char *zQP1, const char *zQP2, const char **pzPick){
969
const char *zName, *zName2;
970
int rid;
971
zName = P(zQP1);
972
if( zName==0 || zName[0]==0 ) return 0;
973
rid = symbolic_name_to_rid(zName, "ci");
974
if( rid>0 ){
975
if( pzPick ) *pzPick = zName;
976
return rid;
977
}
978
if( rid<0 ){
979
fossil_fatal("ambiguous name: %s", zName);
980
}
981
zName2 = P(zQP2);
982
if( zName2==0 || zName2[0]==0 ){
983
fossil_fatal("cannot resolve name: %s", zName);
984
}
985
if( pzPick ) *pzPick = zName2;
986
return name_to_typed_rid(zName2, "ci");
987
}
988
989
/*
990
** WEBPAGE: ambiguous
991
** URL: /ambiguous?name=NAME&src=WEBPAGE
992
**
993
** The NAME given by the name parameter is ambiguous. Display a page
994
** that shows all possible choices and let the user select between them.
995
**
996
** The src= query parameter is optional. If omitted it defaults
997
** to "info".
998
*/
999
void ambiguous_page(void){
1000
Stmt q;
1001
const char *zName = P("name");
1002
const char *zSrc = PD("src","info");
1003
char *z;
1004
1005
if( zName==0 || zName[0]==0 || zSrc==0 || zSrc[0]==0 ){
1006
fossil_redirect_home();
1007
}
1008
style_header("Ambiguous Artifact ID");
1009
@ <p>The artifact hash prefix <b>%h(zName)</b> is ambiguous and might
1010
@ mean any of the following:
1011
@ <ol>
1012
z = fossil_strdup(zName);
1013
canonical16(z, strlen(z));
1014
db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
1015
while( db_step(&q)==SQLITE_ROW ){
1016
const char *zUuid = db_column_text(&q, 0);
1017
int rid = db_column_int(&q, 1);
1018
@ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
1019
@ %s(zUuid)</a> -
1020
object_description(rid, 0, 0, 0);
1021
@ </p></li>
1022
}
1023
db_finalize(&q);
1024
db_prepare(&q,
1025
" SELECT tkt_rid, tkt_uuid, title"
1026
" FROM ticket, ticketchng"
1027
" WHERE ticket.tkt_id = ticketchng.tkt_id"
1028
" AND tkt_uuid GLOB '%q*'"
1029
" GROUP BY tkt_uuid"
1030
" ORDER BY tkt_ctime DESC", z);
1031
while( db_step(&q)==SQLITE_ROW ){
1032
int rid = db_column_int(&q, 0);
1033
const char *zUuid = db_column_text(&q, 1);
1034
const char *zTitle = db_column_text(&q, 2);
1035
@ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
1036
@ %s(zUuid)</a> -
1037
@ <ul></ul>
1038
@ Ticket
1039
hyperlink_to_version(zUuid);
1040
@ - %h(zTitle).
1041
@ <ul><li>
1042
object_description(rid, 0, 0, 0);
1043
@ </li></ul>
1044
@ </p></li>
1045
}
1046
db_finalize(&q);
1047
db_prepare(&q,
1048
"SELECT rid, uuid FROM"
1049
" (SELECT tagxref.rid AS rid, substr(tagname, 7) AS uuid"
1050
" FROM tagxref, tag WHERE tagxref.tagid = tag.tagid"
1051
" AND tagname GLOB 'event-%q*') GROUP BY uuid", z);
1052
while( db_step(&q)==SQLITE_ROW ){
1053
int rid = db_column_int(&q, 0);
1054
const char* zUuid = db_column_text(&q, 1);
1055
@ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
1056
@ %s(zUuid)</a> -
1057
@ <ul><li>
1058
object_description(rid, 0, 0, 0);
1059
@ </li></ul>
1060
@ </p></li>
1061
}
1062
@ </ol>
1063
db_finalize(&q);
1064
style_finish_page();
1065
}
1066
1067
/*
1068
** Convert the name in CGI parameter zParamName into a rid and return that
1069
** rid. If the CGI parameter is missing or is not a valid artifact tag,
1070
** return 0. If the CGI parameter is ambiguous, redirect to a page that
1071
** shows all possibilities and do not return.
1072
*/
1073
int name_to_rid_www(const char *zParamName){
1074
int rid;
1075
const char *zName = P(zParamName);
1076
#ifdef FOSSIL_ENABLE_JSON
1077
if(!zName && fossil_has_json()){
1078
zName = json_find_option_cstr(zParamName,NULL,NULL);
1079
}
1080
#endif
1081
if( zName==0 || zName[0]==0 ) return 0;
1082
rid = symbolic_name_to_rid(zName, "*");
1083
if( rid<0 ){
1084
cgi_redirectf("%R/ambiguous/%T?src=%t", zName, g.zPath);
1085
rid = 0;
1086
}
1087
return rid;
1088
}
1089
1090
/*
1091
** Given an RID of a structural artifact, which is assumed to be
1092
** valid, this function returns one of the CFTYPE_xxx values
1093
** describing the record type, or 0 if the RID does not refer to an
1094
** artifact record (as determined by reading the event table).
1095
**
1096
** Note that this function never returns CFTYPE_ATTACHMENT or
1097
** CFTYPE_CLUSTER because those are not used in the event table. Thus
1098
** it cannot be used to distinguish those artifact types from
1099
** non-artifact file content.
1100
**
1101
** Potential TODO: if the rid is not found in the timeline, try to
1102
** match it to a client file and return a hypothetical new
1103
** CFTYPE_OPAQUE or CFTYPE_NONARTIFACT if a match is found.
1104
*/
1105
int whatis_rid_type(int rid){
1106
Stmt q = empty_Stmt;
1107
int type = 0;
1108
/* Check for entries on the timeline that reference this object */
1109
db_prepare(&q,
1110
"SELECT type FROM event WHERE objid=%d", rid);
1111
if( db_step(&q)==SQLITE_ROW ){
1112
switch( db_column_text(&q,0)[0] ){
1113
case 'c': type = CFTYPE_MANIFEST; break;
1114
case 'w': type = CFTYPE_WIKI; break;
1115
case 'e': type = CFTYPE_EVENT; break;
1116
case 'f': type = CFTYPE_FORUM; break;
1117
case 't': type = CFTYPE_TICKET; break;
1118
case 'g': type = CFTYPE_CONTROL; break;
1119
default: break;
1120
}
1121
}
1122
db_finalize(&q);
1123
return type;
1124
}
1125
1126
/*
1127
** A proxy for whatis_rid_type() which returns a brief string (in
1128
** static memory) describing the record type. Returns NULL if rid does
1129
** not refer to an artifact record (as determined by reading the event
1130
** table). The returned string is intended to be used in headers which
1131
** can refer to different artifact types. It is not "definitive," in
1132
** that it does not distinguish between closely-related types like
1133
** wiki creation, edit, and removal.
1134
*/
1135
char const * whatis_rid_type_label(int rid){
1136
char const * zType = 0;
1137
switch( whatis_rid_type(rid) ){
1138
case CFTYPE_MANIFEST: zType = "Check-in"; break;
1139
case CFTYPE_WIKI: zType = "Wiki-edit"; break;
1140
case CFTYPE_EVENT: zType = "Technote"; break;
1141
case CFTYPE_FORUM: zType = "Forum-post"; break;
1142
case CFTYPE_TICKET: zType = "Ticket-change"; break;
1143
case CFTYPE_CONTROL: zType = "Tag-change"; break;
1144
default: break;
1145
}
1146
return zType;
1147
}
1148
1149
/*
1150
** This is the output routine for whatis_rid(). If pOut is NULL
1151
** then write zKey and zValue on fossil_print(). But if pOut is
1152
** not NULL, append the key and value as a term of a JSON object.
1153
*/
1154
static void whatis_line(
1155
sqlite3_str *pOut,
1156
const char *zKey,
1157
const char *zValue,
1158
...
1159
){
1160
char *zArg;
1161
va_list ap;
1162
va_start(ap, zValue);
1163
zArg = sqlite3_vmprintf(zValue,ap);
1164
va_end(ap);
1165
if( pOut ){
1166
int n = sqlite3_str_length(pOut);
1167
if( n>1 && sqlite3_str_value(pOut)[n-1]!='{' ){
1168
sqlite3_str_append(pOut, ",", 1);
1169
}
1170
sqlite3_str_appendf(pOut, "%J:%J", zKey, zArg);
1171
}else{
1172
int n = 11 - (int)strlen(zKey);
1173
if( n<=0 ) n = 0;
1174
fossil_print("%s:%*s%s\n",zKey,n,"",zArg);
1175
}
1176
sqlite3_free(zArg);
1177
}
1178
1179
/*
1180
** Flag values for whatis_rid().
1181
*/
1182
#if INTERFACE
1183
#define WHATIS_VERBOSE 0x01 /* Extra output */
1184
#define WHATIS_BRIEF 0x02 /* Omit unnecessary output */
1185
#define WHATIS_REPO 0x04 /* Show repository name */
1186
#define WHATIS_OMIT_UNK 0x08 /* Do not show "unknown" lines */
1187
#define WHATIS_HASHONLY 0x10 /* Show only the hash */
1188
#define WHATIS_JSON 0x20 /* Render as JSON */
1189
#endif
1190
1191
/*
1192
** Generate a description of artifact "rid".
1193
**
1194
** Send results to stdout using fossil_print() if pOut==0 or if
1195
** WHATIS_HASHONLY is set. But without WHATIS_HASHONLY and if
1196
** pOut!=0, then write a JSON description of the object into pOut.
1197
*/
1198
void whatis_rid(int rid, int flags, sqlite3_str *pOut){
1199
Stmt q;
1200
int cnt;
1201
1202
if( pOut ) sqlite3_str_appendf(pOut, "{\"rid\":%d", rid);
1203
1204
/* Basic information about the object. */
1205
db_prepare(&q,
1206
"SELECT uuid, size, datetime(mtime,toLocal()), ipaddr"
1207
" FROM blob, rcvfrom"
1208
" WHERE rid=%d"
1209
" AND rcvfrom.rcvid=blob.rcvid",
1210
rid);
1211
if( db_step(&q)==SQLITE_ROW ){
1212
if( flags & WHATIS_HASHONLY ){
1213
fossil_print("%s\n", db_column_text(&q,0));
1214
}else if( flags & WHATIS_VERBOSE ){
1215
whatis_line(pOut, "artifact", "%s (%d)", db_column_text(&q,0), rid);
1216
whatis_line(pOut, "size", "%d bytes", db_column_int(&q,1));
1217
whatis_line(pOut, "received", "%s from %s",
1218
db_column_text(&q, 2),
1219
db_column_text(&q, 3));
1220
}else{
1221
whatis_line(pOut, "artifact","%s", db_column_text(&q,0));
1222
whatis_line(pOut, "size", "%d bytes", db_column_int(&q,1));
1223
}
1224
}else if( pOut ){
1225
sqlite3_str_appendf(pOut, ",\"artifact\":null");
1226
}
1227
db_finalize(&q);
1228
if( flags & WHATIS_HASHONLY ) return;
1229
1230
/* Report any symbolic tags on this artifact */
1231
db_prepare(&q,
1232
"SELECT substr(tagname,5)"
1233
" FROM tag JOIN tagxref ON tag.tagid=tagxref.tagid"
1234
" WHERE tagxref.rid=%d"
1235
" AND tagxref.tagtype<>0"
1236
" AND tagname GLOB 'sym-*'"
1237
" ORDER BY 1",
1238
rid
1239
);
1240
cnt = 0;
1241
while( db_step(&q)==SQLITE_ROW ){
1242
const char *zTag = (const char*)db_column_text(&q,0);
1243
if( pOut ){
1244
const char *zSep = cnt==0 ? ",tags:[" : ",";
1245
sqlite3_str_appendf(pOut, "%s%J", zSep, zTag);
1246
}else{
1247
const char *zPrefix = cnt ? ", " : "tags: ";
1248
fossil_print("%s%s", zPrefix, db_column_text(&q,0));
1249
}
1250
cnt++;
1251
}
1252
if( cnt ){
1253
if( pOut ){
1254
sqlite3_str_append(pOut, "]", 1);
1255
}else{
1256
fossil_print("\n");
1257
}
1258
}
1259
db_finalize(&q);
1260
1261
/* Report any HIDDEN, PRIVATE, CLUSTER, or CLOSED tags on this artifact */
1262
db_prepare(&q,
1263
"SELECT tagname"
1264
" FROM tag JOIN tagxref ON tag.tagid=tagxref.tagid"
1265
" WHERE tagxref.rid=%d"
1266
" AND tag.tagid IN (5,6,7,9)"
1267
" ORDER BY 1",
1268
rid
1269
);
1270
cnt = 0;
1271
while( db_step(&q)==SQLITE_ROW ){
1272
const char *zTag = (const char*)db_column_text(&q,0);
1273
if( pOut ){
1274
const char *zSep = cnt==0 ? ",\"raw-tags\":[" : ",";
1275
sqlite3_str_appendf(pOut, "%s%J", zSep, zTag);
1276
}else{
1277
const char *zPrefix = cnt ? ", " : "raw-tags: ";
1278
fossil_print("%s%s", zPrefix, db_column_text(&q,0));
1279
}
1280
cnt++;
1281
}
1282
if( cnt ){
1283
if( pOut ){
1284
sqlite3_str_append(pOut, "]", 1);
1285
}else{
1286
fossil_print("\n");
1287
}
1288
}
1289
db_finalize(&q);
1290
1291
/* Check for entries on the timeline that reference this object */
1292
db_prepare(&q,
1293
"SELECT"
1294
" type,"
1295
" datetime(mtime,toLocal()),"
1296
" coalesce(euser,user),"
1297
" coalesce(ecomment,comment),"
1298
" if(type='t',(SELECT substr(tagname,5) FROM tag"
1299
" WHERE tag.tagid=event.tagid))"
1300
"FROM event WHERE objid=%d",
1301
rid);
1302
if( db_step(&q)==SQLITE_ROW ){
1303
const char *zType;
1304
char eType = db_column_text(&q,0)[0];
1305
switch( eType ){
1306
case 'c': zType = "Check-in"; break;
1307
case 'w': zType = "Wiki-edit"; break;
1308
case 'e': zType = "Technote"; break;
1309
case 'f': zType = "Forum-post"; break;
1310
case 't': zType = "Ticket-change"; break;
1311
case 'g': zType = "Tag-change"; break;
1312
default: zType = "Unknown"; break;
1313
}
1314
whatis_line(pOut, "type", "%s by %s on %s", zType, db_column_text(&q,2),
1315
db_column_text(&q, 1));
1316
if( eType=='t' && db_column_type(&q,4)==SQLITE_TEXT ){
1317
whatis_line(pOut, "ticket-id", "%s", db_column_text(&q,4));
1318
}
1319
if( pOut ){
1320
sqlite3_str_appendf(pOut, ",\"comment\":%J", db_column_text(&q,3));
1321
}else{
1322
fossil_print("comment: ");
1323
comment_print(db_column_text(&q,3), 0, 12, -1, get_comment_format());
1324
}
1325
cnt++;
1326
}
1327
db_finalize(&q);
1328
1329
/* Check to see if this object is used as a file in a check-in */
1330
db_prepare(&q,
1331
"SELECT filename.name, blob.uuid, datetime(event.mtime,toLocal()),"
1332
" coalesce(euser,user), coalesce(ecomment,comment),"
1333
" coalesce((SELECT value FROM tagxref"
1334
" WHERE tagid=%d AND tagtype>0 AND rid=mlink.mid),'trunk')"
1335
" FROM mlink, filename, blob, event"
1336
" WHERE mlink.fid=%d"
1337
" AND filename.fnid=mlink.fnid"
1338
" AND event.objid=mlink.mid"
1339
" AND blob.rid=mlink.mid"
1340
" ORDER BY event.mtime %s /*sort*/",
1341
TAG_BRANCH, rid,
1342
(flags & WHATIS_BRIEF) ? "LIMIT 1" : "DESC");
1343
while( db_step(&q)==SQLITE_ROW ){
1344
if( flags & WHATIS_BRIEF ){
1345
whatis_line(pOut, "mtime","%s", db_column_text(&q,2));
1346
}
1347
if( pOut ){
1348
whatis_line(pOut,"file","%s", db_column_text(&q,0));
1349
whatis_line(pOut,"part-of","%s", db_column_text(&q,1));
1350
}else{
1351
fossil_print("file: %s\n", db_column_text(&q,0));
1352
fossil_print(" part of [%S] on branch %s by %s on %s\n",
1353
db_column_text(&q, 1),
1354
db_column_text(&q, 5),
1355
db_column_text(&q, 3),
1356
db_column_text(&q, 2));
1357
fossil_print(" ");
1358
comment_print(db_column_text(&q,4), 0, 12, -1, get_comment_format());
1359
}
1360
cnt++;
1361
}
1362
db_finalize(&q);
1363
1364
/* Check to see if this object is used as an attachment */
1365
db_prepare(&q,
1366
"SELECT attachment.filename,"
1367
" attachment.comment,"
1368
" attachment.user,"
1369
" datetime(attachment.mtime,toLocal()),"
1370
" attachment.target,"
1371
" CASE WHEN EXISTS(SELECT 1 FROM tag WHERE tagname=('tkt-'||target))"
1372
" THEN 'ticket'"
1373
" WHEN EXISTS(SELECT 1 FROM tag WHERE tagname=('wiki-'||target))"
1374
" THEN 'wiki' END,"
1375
" attachment.attachid,"
1376
" (SELECT uuid FROM blob WHERE rid=attachid)"
1377
" FROM attachment JOIN blob ON attachment.src=blob.uuid"
1378
" WHERE blob.rid=%d",
1379
rid
1380
);
1381
while( db_step(&q)==SQLITE_ROW ){
1382
if( pOut ){
1383
whatis_line(pOut, "attachment", "%s", db_column_text(&q,0));
1384
whatis_line(pOut, "attached-to", "%s %s",
1385
db_column_text(&q,5), db_column_text(&q,4));
1386
}else{
1387
fossil_print("attachment: %s\n", db_column_text(&q,0));
1388
fossil_print(" attached to %s %s\n",
1389
db_column_text(&q,5), db_column_text(&q,4));
1390
if( flags & WHATIS_VERBOSE ){
1391
fossil_print(" via %s (%d)\n",
1392
db_column_text(&q,7), db_column_int(&q,6));
1393
}else{
1394
fossil_print(" via %s\n",
1395
db_column_text(&q,7));
1396
}
1397
fossil_print(" by user %s on %s\n",
1398
db_column_text(&q,2), db_column_text(&q,3));
1399
fossil_print(" ");
1400
comment_print(db_column_text(&q,1), 0, 12, -1, get_comment_format());
1401
}
1402
cnt++;
1403
}
1404
db_finalize(&q);
1405
1406
/* If other information available, try to describe the object */
1407
if( cnt==0 && pOut==0 ){
1408
char *zWhere = mprintf("=%d", rid);
1409
char *zDesc;
1410
describe_artifacts(zWhere);
1411
free(zWhere);
1412
zDesc = db_text(0,
1413
"SELECT printf('%%-12s%%s %%s',type||':',summary,substr(ref,1,16))"
1414
" FROM description WHERE rid=%d", rid);
1415
fossil_print("%s\n", zDesc);
1416
fossil_free(zDesc);
1417
}
1418
1419
if( pOut ) sqlite3_str_append(pOut, "}", 1);
1420
}
1421
1422
/*
1423
** Generate a description of artifact from it symbolic name.
1424
*/
1425
void whatis_artifact(
1426
const char *zName, /* Symbolic name or full hash */
1427
const char *zFileName,/* Optional: original filename (in file mode) */
1428
const char *zType, /* Artifact type filter */
1429
int mFlags, /* WHATIS_* flags */
1430
sqlite3_str *pOut /* Write JSON here, if not NULL */
1431
){
1432
int rid = symbolic_name_to_rid(zName, zType);
1433
size_t nName;
1434
char *zC = 0;
1435
int nTkt = 0;
1436
1437
if( pOut ){
1438
mFlags &= ~(WHATIS_HASHONLY|WHATIS_REPO);
1439
zFileName = 0;
1440
}
1441
if( (nName = strlen(zName))>=4 && validate16(zName,nName) ){
1442
zC = fossil_strdup(zName);
1443
canonical16(zC, nName);
1444
nTkt = db_int(0,"SELECT count(*) FROM tag WHERE tagname GLOB 'tkt-%q*'",zC);
1445
if( nTkt>0 ){
1446
if( rid==0 ){
1447
rid = db_int(0,
1448
"SELECT srcid FROM tag, tagxref"
1449
" WHERE tag.tagname GLOB 'tkt-%q*'"
1450
" AND tagxref.tagid=tag.tagid"
1451
" AND tagxref.tagtype=1"
1452
" ORDER BY tagxref.mtime",
1453
zC
1454
);
1455
}else{
1456
rid = -1;
1457
}
1458
}
1459
}
1460
if( rid<0 ){
1461
Stmt q;
1462
int cnt = 0;
1463
if( mFlags & WHATIS_REPO ){
1464
fossil_print("\nrepository: %s\n", g.zRepositoryName);
1465
}
1466
if( zFileName ){
1467
fossil_print("%-12s%s\n", "name:", zFileName);
1468
}
1469
if( pOut ){
1470
sqlite3_str_appendall(pOut, "{\"ambiguous\":[");
1471
}else{
1472
fossil_print("%-12s%s (ambiguous)\n", "hash:", zName);
1473
}
1474
db_prepare(&q,
1475
"SELECT rid FROM blob WHERE uuid>=lower(%Q) AND uuid<(lower(%Q)||'z')",
1476
zName, zName
1477
);
1478
while( db_step(&q)==SQLITE_ROW ){
1479
if( cnt++ ){
1480
if( pOut ){
1481
sqlite3_str_append(pOut, ",", 1);
1482
}else{
1483
fossil_print("%12s---- meaning #%d ----\n", " ", cnt);
1484
}
1485
}
1486
whatis_rid(db_column_int(&q, 0), mFlags, pOut);
1487
}
1488
db_finalize(&q);
1489
if( nTkt>0 ){
1490
db_prepare(&q,
1491
"SELECT (SELECT srcid FROM tagxref"
1492
" WHERE tagxref.tagid=tag.tagid"
1493
" AND tagxref.tagtype=1"
1494
" ORDER BY mtime LIMIT 1)"
1495
" FROM tag WHERE tagname GLOB 'tkt-%q*'",
1496
zC
1497
);
1498
while( db_step(&q)==SQLITE_ROW ){
1499
if( pOut ){
1500
sqlite3_str_append(pOut, ",", 1);
1501
}else{
1502
fossil_print("%12s---- meaning #%d ----\n", " ", cnt);
1503
}
1504
whatis_rid(db_column_int(&q, 0), mFlags, pOut);
1505
}
1506
db_finalize(&q);
1507
}
1508
if( pOut ) sqlite3_str_append(pOut, "]}", 2);
1509
}else if( rid==0 ){
1510
if( (mFlags & (WHATIS_OMIT_UNK|WHATIS_HASHONLY))==0 ){
1511
/* 0123456789 12 */
1512
if( zFileName ){
1513
fossil_print("%-12s%s\n", "name:", zFileName);
1514
}
1515
if( pOut ) sqlite3_str_append(pOut, "{", 1);
1516
whatis_line(pOut, "unknown", "%s", zName);
1517
if( pOut ) sqlite3_str_append(pOut, "}", 1);
1518
}
1519
}else{
1520
if( mFlags & WHATIS_REPO ){
1521
fossil_print("\nrepository: %s\n", g.zRepositoryName);
1522
}
1523
if( zFileName ){
1524
zName = zFileName;
1525
}
1526
if( (mFlags & WHATIS_HASHONLY)==0 && pOut==0 ){
1527
whatis_line(pOut, "name", "%s", zName);
1528
}
1529
whatis_rid(rid, mFlags, pOut);
1530
}
1531
fossil_free(zC);
1532
}
1533
1534
/*
1535
** COMMAND: whatis*
1536
**
1537
** Usage: %fossil whatis NAME
1538
**
1539
** Resolve the symbol NAME into its canonical artifact hash
1540
** artifact name and provide a description of what role that artifact
1541
** plays.
1542
**
1543
** Options:
1544
** -f|--file Find artifacts with the same hash as file NAME.
1545
** If NAME is "-", read content from standard input.
1546
** -h|--hash Show only the hash of matching artifacts.
1547
** -q|--quiet Show nothing if NAME is not found
1548
** -R REPO_FILE Specifies the repository db to use. Default is
1549
** the current check-out's repository.
1550
** --type TYPE Only find artifacts of TYPE (one of: 'ci', 't',
1551
** 'w', 'g', or 'e')
1552
** -v|--verbose Provide extra information (such as the RID)
1553
*/
1554
void whatis_cmd(void){
1555
int mFlags = 0;
1556
int fileFlag;
1557
int i;
1558
const char *zType = 0;
1559
sqlite3_str *pOut = 0;
1560
db_find_and_open_repository(0,0);
1561
if( find_option("verbose","v",0)!=0 ){
1562
mFlags |= WHATIS_VERBOSE;
1563
}
1564
if( find_option("hash","h",0)!=0 ){
1565
mFlags |= WHATIS_HASHONLY;
1566
}
1567
if( g.fQuiet ){
1568
mFlags |= WHATIS_OMIT_UNK | WHATIS_REPO;
1569
}
1570
fileFlag = find_option("file","f",0)!=0;
1571
zType = find_option("type",0,1);
1572
1573
if( find_option("json",0,0)!=0 ){ /* undocumented test option */
1574
pOut = sqlite3_str_new(0);
1575
}
1576
1577
/* We should be done with options.. */
1578
verify_all_options();
1579
1580
if( g.argc<3 ) usage("NAME ...");
1581
for(i=2; i<g.argc; i++){
1582
const char *zName = g.argv[i];
1583
if( i>2 ) fossil_print("%.79c\n",'-');
1584
if( fileFlag ){
1585
Blob in;
1586
Blob hash = empty_blob;
1587
const char *zHash;
1588
/* Always follow symlinks (when applicable) */
1589
blob_read_from_file(&in, zName, ExtFILE);
1590
1591
/* First check the auxiliary hash to see if there is already an artifact
1592
** that uses the auxiliary hash name */
1593
hname_hash(&in, 1, &hash);
1594
zHash = (const char*)blob_str(&hash);
1595
if( fast_uuid_to_rid(zHash)==0 ){
1596
/* No existing artifact with the auxiliary hash name. Therefore, use
1597
** the primary hash name. */
1598
blob_reset(&hash);
1599
hname_hash(&in, 0, &hash);
1600
zHash = (const char*)blob_str(&hash);
1601
}
1602
whatis_artifact(zHash, zName, zType, mFlags, pOut);
1603
blob_reset(&hash);
1604
}else{
1605
whatis_artifact(zName, 0, zType, mFlags, pOut);
1606
}
1607
if( pOut ){
1608
fossil_print("%s\n", sqlite3_str_value(pOut));
1609
sqlite3_str_truncate(pOut, 0);
1610
}
1611
}
1612
if( pOut ) sqlite3_str_free(pOut);
1613
}
1614
1615
/*
1616
** This is an SQL function that does the rough equivalent of the
1617
** "whatis" command. The argument can be a text identifier, or a
1618
** integer RID.
1619
*/
1620
void whatis_sql_function(
1621
sqlite3_context *context,
1622
int argc,
1623
sqlite3_value **argv
1624
){
1625
sqlite3_str *pOut = sqlite3_str_new(0);
1626
int n;
1627
assert( argc==1 );
1628
if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){
1629
int rid = sqlite3_value_int(argv[0]);
1630
whatis_rid(rid, 0, pOut);
1631
}else if( sqlite3_value_type(argv[0])==SQLITE_TEXT ){
1632
const char *zName = (const char*)sqlite3_value_text(argv[0]);
1633
whatis_artifact(zName, 0, 0, 0, pOut);
1634
}else{
1635
sqlite3_str_free(pOut);
1636
return;
1637
}
1638
n = sqlite3_str_length(pOut);
1639
sqlite3_result_text64(context, sqlite3_str_finish(pOut), n,
1640
sqlite3_free, SQLITE_UTF8);
1641
sqlite3_result_subtype(context, 'J');
1642
}
1643
1644
1645
/*
1646
** COMMAND: test-whatis-all
1647
**
1648
** Usage: %fossil test-whatis-all
1649
**
1650
** Show "whatis" information about every artifact in the repository
1651
*/
1652
void test_whatis_all_cmd(void){
1653
Stmt q;
1654
int cnt = 0;
1655
db_find_and_open_repository(0,0);
1656
db_prepare(&q, "SELECT rid FROM blob ORDER BY rid");
1657
while( db_step(&q)==SQLITE_ROW ){
1658
if( cnt++ ) fossil_print("%.79c\n", '-');
1659
whatis_rid(db_column_int(&q,0), 1, 0);
1660
}
1661
db_finalize(&q);
1662
}
1663
1664
1665
/*
1666
** COMMAND: test-ambiguous
1667
**
1668
** Usage: %fossil test-ambiguous [--minsize N]
1669
**
1670
** Show a list of ambiguous artifact hash abbreviations of N characters or
1671
** more where N defaults to 4. Change N to a different value using
1672
** the "--minsize N" command-line option.
1673
*/
1674
void test_ambiguous_cmd(void){
1675
Stmt q, ins;
1676
int i;
1677
int minSize = 4;
1678
const char *zMinsize;
1679
char zPrev[100];
1680
db_find_and_open_repository(0,0);
1681
zMinsize = find_option("minsize",0,1);
1682
if( zMinsize && atoi(zMinsize)>0 ) minSize = atoi(zMinsize);
1683
db_multi_exec("CREATE TEMP TABLE dups(uuid, cnt)");
1684
db_prepare(&ins,"INSERT INTO dups(uuid) VALUES(substr(:uuid,1,:cnt))");
1685
db_prepare(&q,
1686
"SELECT uuid FROM blob "
1687
"UNION "
1688
"SELECT substr(tagname,7) FROM tag WHERE tagname GLOB 'event-*' "
1689
"UNION "
1690
"SELECT tkt_uuid FROM ticket "
1691
"ORDER BY 1"
1692
);
1693
zPrev[0] = 0;
1694
while( db_step(&q)==SQLITE_ROW ){
1695
const char *zUuid = db_column_text(&q, 0);
1696
for(i=0; zUuid[i]==zPrev[i] && zUuid[i]!=0; i++){}
1697
if( i>=minSize ){
1698
db_bind_int(&ins, ":cnt", i);
1699
db_bind_text(&ins, ":uuid", zUuid);
1700
db_step(&ins);
1701
db_reset(&ins);
1702
}
1703
sqlite3_snprintf(sizeof(zPrev), zPrev, "%s", zUuid);
1704
}
1705
db_finalize(&ins);
1706
db_finalize(&q);
1707
db_prepare(&q, "SELECT uuid FROM dups ORDER BY length(uuid) DESC, uuid");
1708
while( db_step(&q)==SQLITE_ROW ){
1709
fossil_print("%s\n", db_column_text(&q, 0));
1710
}
1711
db_finalize(&q);
1712
}
1713
1714
/*
1715
** Schema for the description table
1716
*/
1717
static const char zDescTab[] =
1718
@ CREATE TEMP TABLE IF NOT EXISTS description(
1719
@ rid INTEGER PRIMARY KEY, -- RID of the object
1720
@ uuid TEXT, -- hash of the object
1721
@ ctime DATETIME, -- Time of creation
1722
@ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts
1723
@ type TEXT, -- file, check-in, wiki, ticket, etc.
1724
@ rcvid INT, -- When the artifact was received
1725
@ summary TEXT, -- Summary comment for the object
1726
@ ref TEXT -- hash of an object to link against
1727
@ );
1728
@ CREATE INDEX IF NOT EXISTS desctype
1729
@ ON description(summary) WHERE summary='unknown';
1730
;
1731
1732
/*
1733
** Attempt to describe all phantom artifacts. The artifacts are
1734
** already loaded into the description table and have summary='unknown'.
1735
** This routine attempts to generate a better summary, and possibly
1736
** fill in the ref field.
1737
*/
1738
static void describe_unknown_artifacts(){
1739
/* Try to figure out the origin of unknown artifacts */
1740
db_multi_exec(
1741
"REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
1742
" SELECT description.rid, description.uuid, isPrivate, type,\n"
1743
" CASE WHEN plink.isprim THEN '' ELSE 'merge ' END ||\n"
1744
" 'parent of check-in', blob.uuid\n"
1745
" FROM description, plink, blob\n"
1746
" WHERE description.summary='unknown'\n"
1747
" AND plink.pid=description.rid\n"
1748
" AND blob.rid=plink.cid;"
1749
);
1750
db_multi_exec(
1751
"REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
1752
" SELECT description.rid, description.uuid, isPrivate, type,\n"
1753
" 'child of check-in', blob.uuid\n"
1754
" FROM description, plink, blob\n"
1755
" WHERE description.summary='unknown'\n"
1756
" AND plink.cid=description.rid\n"
1757
" AND blob.rid=plink.pid;"
1758
);
1759
db_multi_exec(
1760
"REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
1761
" SELECT description.rid, description.uuid, isPrivate, type,\n"
1762
" 'check-in referenced by \"'||tag.tagname ||'\" tag',\n"
1763
" blob.uuid\n"
1764
" FROM description, tagxref, tag, blob\n"
1765
" WHERE description.summary='unknown'\n"
1766
" AND tagxref.origid=description.rid\n"
1767
" AND tag.tagid=tagxref.tagid\n"
1768
" AND blob.rid=tagxref.srcid;"
1769
);
1770
db_multi_exec(
1771
"REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
1772
" SELECT description.rid, description.uuid, isPrivate, type,\n"
1773
" 'file \"'||filename.name||'\"',\n"
1774
" blob.uuid\n"
1775
" FROM description, mlink, filename, blob\n"
1776
" WHERE description.summary='unknown'\n"
1777
" AND mlink.fid=description.rid\n"
1778
" AND blob.rid=mlink.mid\n"
1779
" AND filename.fnid=mlink.fnid;"
1780
);
1781
if( !db_exists("SELECT 1 FROM description WHERE summary='unknown'") ){
1782
return;
1783
}
1784
add_content_sql_commands(g.db);
1785
db_multi_exec(
1786
"REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
1787
" SELECT description.rid, description.uuid, isPrivate, type,\n"
1788
" 'referenced by cluster', blob.uuid\n"
1789
" FROM description, tagxref, blob\n"
1790
" WHERE description.summary='unknown'\n"
1791
" AND tagxref.tagid=(SELECT tagid FROM tag WHERE tagname='cluster')\n"
1792
" AND blob.rid=tagxref.rid\n"
1793
" AND CAST(content(blob.uuid) AS text)"
1794
" GLOB ('*M '||description.uuid||'*');"
1795
);
1796
}
1797
1798
/*
1799
** Create the description table if it does not already exists.
1800
** Populate fields of this table with descriptions for all artifacts
1801
** whose RID matches the SQL expression in zWhere.
1802
*/
1803
void describe_artifacts(const char *zWhere){
1804
db_multi_exec("%s", zDescTab/*safe-for-%s*/);
1805
1806
/* Describe check-ins */
1807
db_multi_exec(
1808
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1809
"SELECT blob.rid, blob.uuid, blob.rcvid, event.mtime, 'checkin',\n"
1810
" 'check-in to '\n"
1811
" || coalesce((SELECT value FROM tagxref WHERE tagid=%d"
1812
" AND tagtype>0 AND tagxref.rid=blob.rid),'trunk')\n"
1813
" || ' by ' || coalesce(event.euser,event.user)\n"
1814
" || ' on ' || strftime('%%Y-%%m-%%d %%H:%%M',event.mtime)\n"
1815
" FROM event, blob\n"
1816
" WHERE (event.objid %s) AND event.type='ci'\n"
1817
" AND event.objid=blob.rid;",
1818
TAG_BRANCH, zWhere /*safe-for-%s*/
1819
);
1820
1821
/* Describe files */
1822
db_multi_exec(
1823
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1824
"SELECT blob.rid, blob.uuid, blob.rcvid, event.mtime,"
1825
" 'file', 'file '||filename.name\n"
1826
" FROM mlink, blob, event, filename\n"
1827
" WHERE (mlink.fid %s)\n"
1828
" AND mlink.mid=event.objid\n"
1829
" AND filename.fnid=mlink.fnid\n"
1830
" AND mlink.fid=blob.rid;",
1831
zWhere /*safe-for-%s*/
1832
);
1833
1834
/* Describe tags */
1835
db_multi_exec(
1836
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1837
"SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'tag',\n"
1838
" 'tag '||substr((SELECT uuid FROM blob WHERE rid=tagxref.rid),1,16)\n"
1839
" FROM tagxref, blob\n"
1840
" WHERE (tagxref.srcid %s) AND tagxref.srcid!=tagxref.rid\n"
1841
" AND tagxref.srcid=blob.rid;",
1842
zWhere /*safe-for-%s*/
1843
);
1844
1845
/* Cluster artifacts */
1846
db_multi_exec(
1847
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1848
"SELECT blob.rid, blob.uuid, blob.rcvid, rcvfrom.mtime,"
1849
" 'cluster', 'cluster'\n"
1850
" FROM tagxref, blob, rcvfrom\n"
1851
" WHERE (tagxref.rid %s)\n"
1852
" AND tagxref.tagid=(SELECT tagid FROM tag WHERE tagname='cluster')\n"
1853
" AND blob.rid=tagxref.rid"
1854
" AND rcvfrom.rcvid=blob.rcvid;",
1855
zWhere /*safe-for-%s*/
1856
);
1857
1858
/* Ticket change artifacts */
1859
db_multi_exec(
1860
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1861
"SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'ticket',\n"
1862
" 'ticket '||substr(tag.tagname,5,21)\n"
1863
" FROM tagxref, tag, blob\n"
1864
" WHERE (tagxref.rid %s)\n"
1865
" AND tag.tagid=tagxref.tagid\n"
1866
" AND tag.tagname GLOB 'tkt-*'"
1867
" AND blob.rid=tagxref.rid;",
1868
zWhere /*safe-for-%s*/
1869
);
1870
1871
/* Wiki edit artifacts */
1872
db_multi_exec(
1873
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1874
"SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'wiki',\n"
1875
" printf('wiki \"%%s\"',substr(tag.tagname,6))\n"
1876
" FROM tagxref, tag, blob\n"
1877
" WHERE (tagxref.rid %s)\n"
1878
" AND tag.tagid=tagxref.tagid\n"
1879
" AND tag.tagname GLOB 'wiki-*'"
1880
" AND blob.rid=tagxref.rid;",
1881
zWhere /*safe-for-%s*/
1882
);
1883
1884
/* Event edit artifacts */
1885
db_multi_exec(
1886
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1887
"SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'event',\n"
1888
" 'event '||substr(tag.tagname,7)\n"
1889
" FROM tagxref, tag, blob\n"
1890
" WHERE (tagxref.rid %s)\n"
1891
" AND tag.tagid=tagxref.tagid\n"
1892
" AND tag.tagname GLOB 'event-*'"
1893
" AND blob.rid=tagxref.rid;",
1894
zWhere /*safe-for-%s*/
1895
);
1896
1897
/* Attachments */
1898
db_multi_exec(
1899
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1900
"SELECT blob.rid, blob.uuid, blob.rcvid, attachment.mtime,"
1901
" 'attach-control',\n"
1902
" 'attachment-control for '||attachment.filename\n"
1903
" FROM attachment, blob\n"
1904
" WHERE (attachment.attachid %s)\n"
1905
" AND blob.rid=attachment.attachid",
1906
zWhere /*safe-for-%s*/
1907
);
1908
db_multi_exec(
1909
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1910
"SELECT blob.rid, blob.uuid, blob.rcvid, attachment.mtime, 'attachment',\n"
1911
" 'attachment '||attachment.filename\n"
1912
" FROM attachment, blob\n"
1913
" WHERE (blob.rid %s)\n"
1914
" AND blob.rid NOT IN (SELECT rid FROM description)\n"
1915
" AND blob.uuid=attachment.src",
1916
zWhere /*safe-for-%s*/
1917
);
1918
1919
/* Forum posts */
1920
if( db_table_exists("repository","forumpost") ){
1921
db_multi_exec(
1922
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1923
"SELECT postblob.rid, postblob.uuid, postblob.rcvid,"
1924
" forumpost.fmtime, 'forumpost',\n"
1925
" CASE WHEN fpid=froot THEN 'forum-post '\n"
1926
" ELSE 'forum-reply-to ' END || substr(rootblob.uuid,1,14)\n"
1927
" FROM forumpost, blob AS postblob, blob AS rootblob\n"
1928
" WHERE (forumpost.fpid %s)\n"
1929
" AND postblob.rid=forumpost.fpid"
1930
" AND rootblob.rid=forumpost.froot",
1931
zWhere /*safe-for-%s*/
1932
);
1933
}
1934
1935
/* Mark all other artifacts as "unknown" for now */
1936
db_multi_exec(
1937
"INSERT OR IGNORE INTO description(rid,uuid,rcvid,type,summary)\n"
1938
"SELECT blob.rid, blob.uuid,blob.rcvid,\n"
1939
" CASE WHEN EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)\n"
1940
" THEN 'phantom' ELSE '' END,\n"
1941
" 'unknown'\n"
1942
" FROM blob\n"
1943
" WHERE (blob.rid %s)\n"
1944
" AND (blob.rid NOT IN (SELECT rid FROM description));",
1945
zWhere /*safe-for-%s*/
1946
);
1947
1948
/* Mark private elements */
1949
db_multi_exec(
1950
"UPDATE description SET isPrivate=1 WHERE rid IN private"
1951
);
1952
1953
if( db_exists("SELECT 1 FROM description WHERE summary='unknown'") ){
1954
describe_unknown_artifacts();
1955
}
1956
}
1957
1958
/*
1959
** Print the content of the description table on stdout.
1960
**
1961
** The description table is computed using the WHERE clause zWhere if
1962
** the zWhere parameter is not NULL. If zWhere is NULL, then this
1963
** routine assumes that the description table already exists and is
1964
** populated and merely prints the contents.
1965
*/
1966
int describe_artifacts_to_stdout(const char *zWhere, const char *zLabel){
1967
Stmt q;
1968
int cnt = 0;
1969
if( zWhere!=0 ) describe_artifacts(zWhere);
1970
db_prepare(&q,
1971
"SELECT uuid, summary, coalesce(ref,''), isPrivate\n"
1972
" FROM description\n"
1973
" ORDER BY ctime, type;"
1974
);
1975
while( db_step(&q)==SQLITE_ROW ){
1976
if( zLabel ){
1977
fossil_print("%s\n", zLabel);
1978
zLabel = 0;
1979
}
1980
fossil_print(" %.16s %s %s", db_column_text(&q,0),
1981
db_column_text(&q,1), db_column_text(&q,2));
1982
if( db_column_int(&q,3) ) fossil_print(" (private)");
1983
fossil_print("\n");
1984
cnt++;
1985
}
1986
db_finalize(&q);
1987
if( zWhere!=0 ) db_multi_exec("DELETE FROM description;");
1988
return cnt;
1989
}
1990
1991
/*
1992
** COMMAND: test-describe-artifacts
1993
**
1994
** Usage: %fossil test-describe-artifacts [--from S] [--count N]
1995
**
1996
** Display a one-line description of every artifact.
1997
*/
1998
void test_describe_artifacts_cmd(void){
1999
int iFrom = 0;
2000
int iCnt = 1000000;
2001
const char *z;
2002
char *zRange;
2003
db_find_and_open_repository(0,0);
2004
z = find_option("from",0,1);
2005
if( z ) iFrom = atoi(z);
2006
z = find_option("count",0,1);
2007
if( z ) iCnt = atoi(z);
2008
zRange = mprintf("BETWEEN %d AND %d", iFrom, iFrom+iCnt-1);
2009
describe_artifacts_to_stdout(zRange, 0);
2010
}
2011
2012
/*
2013
** WEBPAGE: bloblist
2014
**
2015
** Return a page showing all artifacts in the repository. Query parameters:
2016
**
2017
** n=N Show N artifacts
2018
** s=S Start with artifact number S
2019
** priv Show only unpublished or private artifacts
2020
** phan Show only phantom artifacts
2021
** hclr Color code hash types (SHA1 vs SHA3)
2022
** recent Show the most recent N artifacts
2023
*/
2024
void bloblist_page(void){
2025
Stmt q;
2026
int s = atoi(PD("s","0"));
2027
int n = atoi(PD("n","5000"));
2028
int mx = db_int(0, "SELECT max(rid) FROM blob");
2029
int privOnly = PB("priv");
2030
int phantomOnly = PB("phan");
2031
int hashClr = PB("hclr");
2032
int bRecent = PB("recent");
2033
int bUnclst = PB("unclustered");
2034
char *zRange;
2035
char *zSha1Bg;
2036
char *zSha3Bg;
2037
2038
login_check_credentials();
2039
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2040
cgi_check_for_malice();
2041
style_header("List Of Artifacts");
2042
style_submenu_element("250 Largest", "bigbloblist");
2043
if( bRecent==0 || n!=250 ){
2044
style_submenu_element("Recent","bloblist?n=250&recent");
2045
}
2046
if( bUnclst==0 ){
2047
style_submenu_element("Unclustered","bloblist?unclustered");
2048
}
2049
if( g.perm.Admin ){
2050
style_submenu_element("Xfer Log", "rcvfromlist");
2051
}
2052
if( !phantomOnly ){
2053
style_submenu_element("Phantoms", "bloblist?phan");
2054
}
2055
style_submenu_element("Clusters","clusterlist");
2056
if( g.perm.Private || g.perm.Admin ){
2057
if( !privOnly ){
2058
style_submenu_element("Private", "bloblist?priv");
2059
}
2060
}else{
2061
privOnly = 0;
2062
}
2063
if( g.perm.Write ){
2064
style_submenu_element("Artifact Stats", "artifact_stats");
2065
}
2066
if( !privOnly && !phantomOnly && mx>n && P("s")==0 && !bRecent && !bUnclst ){
2067
int i;
2068
@ <p>Select a range of artifacts to view:</p>
2069
@ <ul>
2070
for(i=1; i<=mx; i+=n){
2071
@ <li> %z(href("%R/bloblist?s=%d&n=%d",i,n))
2072
@ %d(i)..%d(i+n-1<mx?i+n-1:mx)</a>
2073
}
2074
@ <li> %z(href("%R/bloblist?n=250&recent"))250 most recent</a>
2075
@ <li> %z(href("%R/bloblist?unclustered"))All unclustered</a>
2076
@ </ul>
2077
style_finish_page();
2078
return;
2079
}
2080
if( phantomOnly || privOnly || mx>n ){
2081
style_submenu_element("Index", "bloblist");
2082
}
2083
if( privOnly ){
2084
@ <h2>Private Artifacts</h2>
2085
zRange = mprintf("IN private");
2086
}else if( phantomOnly ){
2087
@ <h2>Phantom Artifacts</h2>
2088
zRange = mprintf("IN phantom");
2089
}else if( bUnclst ){
2090
@ <h2>Unclustered Artifacts</h2>
2091
zRange = mprintf("IN unclustered");
2092
}else if( bRecent ){
2093
@ <h2>%d(n) Most Recent Artifacts</h2>
2094
zRange = mprintf(">=(SELECT rid FROM blob"
2095
" ORDER BY rid DESC LIMIT 1 OFFSET %d)",n);
2096
}else{
2097
zRange = mprintf("BETWEEN %d AND %d", s, s+n-1);
2098
}
2099
describe_artifacts(zRange);
2100
fossil_free(zRange);
2101
db_prepare(&q,
2102
/* 0 1 2 3 4 5 6 */
2103
"SELECT rid, uuid, summary, isPrivate, type='phantom', ref, rcvid, "
2104
" datetime(rcvfrom.mtime)"
2105
" FROM description LEFT JOIN rcvfrom USING(rcvid)"
2106
" ORDER BY rid %s",
2107
((bRecent||bUnclst)?"DESC":"ASC")/*safe-for-%s*/
2108
);
2109
if( skin_detail_boolean("white-foreground") ){
2110
zSha1Bg = "#714417";
2111
zSha3Bg = "#177117";
2112
}else{
2113
zSha1Bg = "#ebffb0";
2114
zSha3Bg = "#b0ffb0";
2115
}
2116
@ <table cellpadding="2" cellspacing="0" border="1">
2117
@ <tr><th>RID<th>Hash<th>Received<th>Description<th>Ref<th>Remarks
2118
while( db_step(&q)==SQLITE_ROW ){
2119
int rid = db_column_int(&q,0);
2120
const char *zUuid = db_column_text(&q, 1);
2121
const char *zDesc = db_column_text(&q, 2);
2122
int isPriv = db_column_int(&q,3);
2123
int isPhantom = db_column_int(&q,4);
2124
const char *zRef = db_column_text(&q,5);
2125
const char *zDate = db_column_text(&q,7);
2126
if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){
2127
/* Don't show private artifacts to users without Private (x) permission */
2128
continue;
2129
}
2130
if( hashClr ){
2131
const char *zClr = db_column_bytes(&q,1)>40 ? zSha3Bg : zSha1Bg;
2132
@ <tr style='background-color:%s(zClr);'><td align="right">%d(rid)</td>
2133
}else{
2134
@ <tr><td align="right">%d(rid)</td>
2135
}
2136
@ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
2137
if( g.perm.Admin ){
2138
int rcvid = db_column_int(&q, 6);
2139
@ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%h(zDate)</a>
2140
}else{
2141
@ <td>%h(zDate)
2142
}
2143
@ <td align="left">%h(zDesc)</td>
2144
if( zRef && zRef[0] ){
2145
@ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a>
2146
}else{
2147
@ <td>&nbsp;
2148
}
2149
if( isPriv || isPhantom ){
2150
if( isPriv==0 ){
2151
@ <td>phantom</td>
2152
}else if( isPhantom==0 ){
2153
@ <td>private</td>
2154
}else{
2155
@ <td>private,phantom</td>
2156
}
2157
}else{
2158
@ <td>&nbsp;
2159
}
2160
@ </tr>
2161
}
2162
@ </table>
2163
db_finalize(&q);
2164
style_finish_page();
2165
}
2166
2167
/*
2168
** Output HTML that shows a table of all public phantoms.
2169
*/
2170
void table_of_public_phantoms(void){
2171
Stmt q;
2172
char *zRange;
2173
double rNow;
2174
zRange = mprintf("IN (SELECT rid FROM phantom EXCEPT"
2175
" SELECT rid FROM private)");
2176
describe_artifacts(zRange);
2177
fossil_free(zRange);
2178
db_prepare(&q,
2179
"SELECT rid, uuid, summary, ref,"
2180
" (SELECT mtime FROM blob, rcvfrom"
2181
" WHERE blob.uuid=ref AND rcvfrom.rcvid=blob.rcvid)"
2182
" FROM description ORDER BY rid"
2183
);
2184
rNow = db_double(0.0, "SELECT julianday('now')");
2185
@ <table cellpadding="2" cellspacing="0" border="1">
2186
@ <tr><th>RID<th>Description<th>Source<th>Age
2187
while( db_step(&q)==SQLITE_ROW ){
2188
int rid = db_column_int(&q,0);
2189
const char *zUuid = db_column_text(&q, 1);
2190
const char *zDesc = db_column_text(&q, 2);
2191
const char *zRef = db_column_text(&q,3);
2192
double mtime = db_column_double(&q,4);
2193
@ <tr><td valign="top">%d(rid)</td>
2194
@ <td valign="top" align="left">%h(zUuid)<br>%h(zDesc)</td>
2195
if( zRef && zRef[0] ){
2196
@ <td valign="top">%z(href("%R/info/%!S",zRef))%!S(zRef)</a>
2197
if( mtime>0 ){
2198
char *zAge = human_readable_age(rNow - mtime);
2199
@ <td valign="top">%h(zAge)
2200
fossil_free(zAge);
2201
}else{
2202
@ <td>&nbsp;
2203
}
2204
}else{
2205
@ <td>&nbsp;<td>&nbsp;
2206
}
2207
@ </tr>
2208
}
2209
@ </table>
2210
db_finalize(&q);
2211
}
2212
2213
/*
2214
** WEBPAGE: phantoms
2215
**
2216
** Show a list of all "phantom" artifacts that are not marked as "private".
2217
**
2218
** A "phantom" artifact is an artifact whose hash named appears in some
2219
** artifact but whose content is unknown. For example, if a manifest
2220
** references a particular SHA3 hash of a file, but that SHA3 hash is
2221
** not on the shunning list and is not in the database, then the file
2222
** is a phantom. We know it exists, but we do not know its content.
2223
**
2224
** Whenever a sync occurs, both each party looks at its phantom list
2225
** and for every phantom that is not also marked private, it asks the
2226
** other party to send it the content. This mechanism helps keep all
2227
** repositories synced up.
2228
**
2229
** This page is similar to the /bloblist page in that it lists artifacts.
2230
** But this page is a special case in that it only shows phantoms that
2231
** are not private. In other words, this page shows all phantoms that
2232
** generate extra network traffic on every sync request.
2233
*/
2234
void phantom_list_page(void){
2235
login_check_credentials();
2236
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2237
style_header("Public Phantom Artifacts");
2238
if( g.perm.Admin ){
2239
style_submenu_element("Xfer Log", "rcvfromlist");
2240
style_submenu_element("Artifact List", "bloblist");
2241
}
2242
if( g.perm.Write ){
2243
style_submenu_element("Artifact Stats", "artifact_stats");
2244
}
2245
table_of_public_phantoms();
2246
style_finish_page();
2247
}
2248
2249
/*
2250
** WEBPAGE: bigbloblist
2251
**
2252
** Return a page showing the largest artifacts in the repository in order
2253
** of decreasing size.
2254
**
2255
** n=N Show the top N artifacts (default: 250)
2256
*/
2257
void bigbloblist_page(void){
2258
Stmt q;
2259
int n = atoi(PD("n","250"));
2260
2261
login_check_credentials();
2262
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2263
if( g.perm.Admin ){
2264
style_submenu_element("Xfer Log", "rcvfromlist");
2265
}
2266
if( g.perm.Write ){
2267
style_submenu_element("Artifact Stats", "artifact_stats");
2268
}
2269
style_submenu_element("All Artifacts", "bloblist");
2270
style_header("%d Largest Artifacts", n);
2271
db_multi_exec(
2272
"CREATE TEMP TABLE toshow(rid INTEGER PRIMARY KEY);"
2273
"INSERT INTO toshow(rid)"
2274
" SELECT rid FROM blob"
2275
" ORDER BY length(content) DESC"
2276
" LIMIT %d;", n
2277
);
2278
describe_artifacts("IN toshow");
2279
db_prepare(&q,
2280
"SELECT description.rid, description.uuid, description.summary,"
2281
" length(blob.content), coalesce(delta.srcid,''),"
2282
" datetime(description.ctime)"
2283
" FROM description, blob LEFT JOIN delta ON delta.rid=blob.rid"
2284
" WHERE description.rid=blob.rid"
2285
" ORDER BY length(content) DESC"
2286
);
2287
@ <table cellpadding="2" cellspacing="0" border="1" \
2288
@ class='sortable' data-column-types='NnnttT' data-init-sort='0'>
2289
@ <thead><tr><th align="right">Size<th align="right">RID
2290
@ <th align="right">From<th>Hash<th>Description<th>Date</tr></thead>
2291
@ <tbody>
2292
while( db_step(&q)==SQLITE_ROW ){
2293
int rid = db_column_int(&q,0);
2294
const char *zUuid = db_column_text(&q, 1);
2295
const char *zDesc = db_column_text(&q, 2);
2296
int sz = db_column_int(&q,3);
2297
const char *zSrcId = db_column_text(&q,4);
2298
const char *zDate = db_column_text(&q,5);
2299
@ <tr><td align="right">%d(sz)</td>
2300
@ <td align="right">%d(rid)</td>
2301
@ <td align="right">%s(zSrcId)</td>
2302
@ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
2303
@ <td align="left">%h(zDesc)</td>
2304
@ <td align="left">%z(href("%R/timeline?c=%T",zDate))%s(zDate)</a></td>
2305
@ </tr>
2306
}
2307
@ </tbody></table>
2308
db_finalize(&q);
2309
style_table_sorter();
2310
style_finish_page();
2311
}
2312
2313
/*
2314
** WEBPAGE: deltachain
2315
**
2316
** Usage: /deltachain/RID
2317
**
2318
** The RID query parameter is required. Generate a page with a table
2319
** showing storage characteristics of RID and other artifacts that are
2320
** derived from RID via delta.
2321
*/
2322
void deltachain_page(void){
2323
Stmt q;
2324
int id = atoi(PD("name","0"));
2325
int top;
2326
i64 nStored = 0;
2327
i64 nExpanded = 0;
2328
2329
login_check_credentials();
2330
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2331
top = db_int(id,
2332
"WITH RECURSIVE chain(aa,bb) AS (\n"
2333
" SELECT rid, srcid FROM delta WHERE rid=%d\n"
2334
" UNION ALL\n"
2335
" SELECT bb, delta.srcid"
2336
" FROM chain LEFT JOIN delta ON delta.rid=bb"
2337
" WHERE bb IS NOT NULL\n"
2338
")\n"
2339
"SELECT aa FROM chain WHERE bb IS NULL",
2340
id
2341
);
2342
style_header("Delta Chain Containing Artifact %d", id);
2343
db_multi_exec(
2344
"CREATE TEMP TABLE toshow(rid INT, gen INT);\n"
2345
"WITH RECURSIVE tx(id,px) AS (\n"
2346
" VALUES(%d,0)\n"
2347
" UNION ALL\n"
2348
" SELECT delta.rid, px+1 FROM tx, delta where delta.srcid=tx.id\n"
2349
" ORDER BY 2\n"
2350
") "
2351
"INSERT INTO toshow(rid,gen) SELECT id,px FROM tx;",
2352
top
2353
);
2354
db_multi_exec("CREATE INDEX toshow_rid ON toshow(rid);");
2355
describe_artifacts("IN (SELECT rid FROM toshow)");
2356
db_prepare(&q,
2357
"SELECT description.rid, description.uuid, description.summary,"
2358
" length(blob.content), coalesce(delta.srcid,''),"
2359
" datetime(description.ctime), toshow.gen, blob.size"
2360
" FROM description, toshow, blob LEFT JOIN delta ON delta.rid=blob.rid"
2361
" WHERE description.rid=blob.rid"
2362
" AND toshow.rid=description.rid"
2363
" ORDER BY toshow.gen, description.ctime"
2364
);
2365
@ <table cellpadding="2" cellspacing="0" border="1" \
2366
@ class='sortable' data-column-types='nNnnttT' data-init-sort='0'>
2367
@ <thead><tr><th align="right">Level</th>
2368
@ <th align="right">Size<th align="right">RID
2369
@ <th align="right">From<th>Hash<th>Description<th>Date</tr></thead>
2370
@ <tbody>
2371
while( db_step(&q)==SQLITE_ROW ){
2372
int rid = db_column_int(&q,0);
2373
const char *zUuid = db_column_text(&q, 1);
2374
const char *zDesc = db_column_text(&q, 2);
2375
int sz = db_column_int(&q,3);
2376
const char *zSrcId = db_column_text(&q,4);
2377
const char *zDate = db_column_text(&q,5);
2378
int gen = db_column_int(&q,6);
2379
nExpanded += db_column_int(&q,7);
2380
nStored += sz;
2381
@ <tr><td align="right">%d(gen)</td>
2382
@ <td align="right">%d(sz)</td>
2383
if( rid==id ){
2384
@ <td align="right"><b>%d(rid)</b></td>
2385
}else{
2386
@ <td align="right">%d(rid)</td>
2387
}
2388
@ <td align="right">%s(zSrcId)</td>
2389
@ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
2390
@ <td align="left">%h(zDesc)</td>
2391
@ <td align="left">%z(href("%R/timeline?c=%T",zDate))%s(zDate)</a></td>
2392
@ </tr>
2393
}
2394
@ </tbody></table>
2395
db_finalize(&q);
2396
style_table_sorter();
2397
@ <p>
2398
@ <table border="0" cellspacing="0" cellpadding="0">
2399
@ <tr><td>Bytes of content</td><td>&nbsp;&nbsp;&nbsp;</td>
2400
@ <td align="right">%,lld(nExpanded)</td></tr>
2401
@ <tr><td>Bytes stored in repository</td><td></td>
2402
@ <td align="right">%,lld(nStored)</td>
2403
@ </table>
2404
@ </p>
2405
style_finish_page();
2406
}
2407
2408
/*
2409
** COMMAND: test-unsent
2410
**
2411
** Usage: %fossil test-unsent
2412
**
2413
** Show all artifacts in the unsent table
2414
*/
2415
void test_unsent_cmd(void){
2416
db_find_and_open_repository(0,0);
2417
describe_artifacts_to_stdout("IN unsent", 0);
2418
}
2419
2420
/*
2421
** COMMAND: test-unclustered
2422
**
2423
** Usage: %fossil test-unclustered
2424
**
2425
** Show all artifacts in the unclustered table
2426
*/
2427
void test_unclusterd_cmd(void){
2428
db_find_and_open_repository(0,0);
2429
describe_artifacts_to_stdout("IN unclustered", 0);
2430
}
2431
2432
/*
2433
** COMMAND: test-phantoms
2434
**
2435
** Usage: %fossil test-phantoms
2436
**
2437
** Show all phantom artifacts. A phantom artifact is one for which there
2438
** is no content. Options:
2439
**
2440
** --count Show only a count of the number of phantoms.
2441
** --delta Show all delta-phantoms. A delta-phantom is a
2442
** artifact for which there is a delta but the delta
2443
** source is a phantom.
2444
** --list Just list the phantoms. Do not try to describe them.
2445
*/
2446
void test_phantoms_cmd(void){
2447
int bDelta;
2448
int bList;
2449
int bCount;
2450
unsigned nPhantom = 0;
2451
unsigned nDeltaPhantom = 0;
2452
db_find_and_open_repository(0,0);
2453
bDelta = find_option("delta", 0, 0)!=0;
2454
bList = find_option("list", 0, 0)!=0;
2455
bCount = find_option("count", 0, 0)!=0;
2456
verify_all_options();
2457
if( bList || bCount ){
2458
Stmt q1, q2;
2459
db_prepare(&q1, "SELECT rid, uuid FROM blob WHERE size<0");
2460
while( db_step(&q1)==SQLITE_ROW ){
2461
int rid = db_column_int(&q1, 0);
2462
nPhantom++;
2463
if( !bCount ){
2464
fossil_print("%S (%d)\n", db_column_text(&q1,1), rid);
2465
}
2466
db_prepare(&q2,
2467
"WITH RECURSIVE deltasof(rid) AS ("
2468
" SELECT rid FROM delta WHERE srcid=%d"
2469
" UNION"
2470
" SELECT delta.rid FROM deltasof, delta"
2471
" WHERE delta.srcid=deltasof.rid)"
2472
"SELECT deltasof.rid, blob.uuid FROM deltasof LEFT JOIN blob"
2473
" ON blob.rid=deltasof.rid", rid
2474
);
2475
while( db_step(&q2)==SQLITE_ROW ){
2476
nDeltaPhantom++;
2477
if( !bCount ){
2478
fossil_print(" %S (%d)\n", db_column_text(&q2,1),
2479
db_column_int(&q2,0));
2480
}
2481
}
2482
db_finalize(&q2);
2483
}
2484
db_finalize(&q1);
2485
if( nPhantom ){
2486
fossil_print("Phantoms: %u Delta-phantoms: %u\n",
2487
nPhantom, nDeltaPhantom);
2488
}
2489
}else if( bDelta ){
2490
describe_artifacts_to_stdout(
2491
"IN (WITH RECURSIVE delta_phantom(rid) AS (\n"
2492
" SELECT delta.rid FROM blob, delta\n"
2493
" WHERE blob.size<0 AND delta.srcid=blob.rid\n"
2494
" UNION\n"
2495
" SELECT delta.rid FROM delta_phantom, delta\n"
2496
" WHERE delta.srcid=delta_phantom.rid)\n"
2497
" SELECT rid FROM delta_phantom)", 0);
2498
}else{
2499
describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0);
2500
}
2501
}
2502
2503
/* Maximum number of collision examples to remember */
2504
#define MAX_COLLIDE 25
2505
2506
/*
2507
** Generate a report on the number of collisions in artifact hashes
2508
** generated by the SQL given in the argument.
2509
*/
2510
static void collision_report(const char *zSql){
2511
int i, j;
2512
int nHash = 0;
2513
Stmt q;
2514
char zPrev[HNAME_MAX+1];
2515
struct {
2516
int cnt;
2517
char *azHit[MAX_COLLIDE];
2518
char z[HNAME_MAX+1];
2519
} aCollide[HNAME_MAX+1];
2520
memset(aCollide, 0, sizeof(aCollide));
2521
memset(zPrev, 0, sizeof(zPrev));
2522
db_prepare(&q,"%s",zSql/*safe-for-%s*/);
2523
while( db_step(&q)==SQLITE_ROW ){
2524
const char *zUuid = db_column_text(&q,0);
2525
int n = db_column_bytes(&q,0);
2526
int i;
2527
nHash++;
2528
for(i=0; zPrev[i] && zPrev[i]==zUuid[i]; i++){}
2529
if( i>0 && i<=HNAME_MAX ){
2530
if( i>=4 && aCollide[i].cnt<MAX_COLLIDE ){
2531
aCollide[i].azHit[aCollide[i].cnt] = mprintf("%.*s", i, zPrev);
2532
}
2533
aCollide[i].cnt++;
2534
if( aCollide[i].z[0]==0 ) memcpy(aCollide[i].z, zPrev, n+1);
2535
}
2536
memcpy(zPrev, zUuid, n+1);
2537
}
2538
db_finalize(&q);
2539
@ <table border=1><thead>
2540
@ <tr><th>Length<th>Instances<th>First Instance</tr>
2541
@ </thead><tbody>
2542
for(i=1; i<=HNAME_MAX; i++){
2543
if( aCollide[i].cnt==0 ) continue;
2544
@ <tr><td>%d(i)<td>%d(aCollide[i].cnt)<td>%h(aCollide[i].z)</tr>
2545
}
2546
@ </tbody></table>
2547
@ <p>Total number of hashes: %d(nHash)</p>
2548
for(i=HNAME_MAX; i>=4; i--){
2549
if( aCollide[i].cnt==0 ) continue;
2550
if( aCollide[i].cnt>200 ) break;
2551
if( aCollide[i].cnt<25 ){
2552
@ <p>Collisions of length %d(i):
2553
}else{
2554
@ <p>First 25 collisions of length %d(i):
2555
}
2556
for(j=0; j<aCollide[i].cnt && j<MAX_COLLIDE; j++){
2557
char *zId = aCollide[i].azHit[j];
2558
if( zId==0 ) continue;
2559
@ %z(href("%R/ambiguous/%s",zId))%h(zId)</a>
2560
}
2561
}
2562
for(i=4; i<count(aCollide); i++){
2563
for(j=0; j<aCollide[i].cnt && j<MAX_COLLIDE; j++){
2564
fossil_free(aCollide[i].azHit[j]);
2565
}
2566
}
2567
}
2568
2569
/*
2570
** WEBPAGE: hash-collisions
2571
**
2572
** Show the number of hash collisions for hash prefixes of various lengths.
2573
*/
2574
void hash_collisions_webpage(void){
2575
login_check_credentials();
2576
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2577
style_header("Hash Prefix Collisions");
2578
style_submenu_element("Activity Reports", "reports");
2579
style_submenu_element("Stats", "stat");
2580
@ <h1>Hash Prefix Collisions on Check-ins</h1>
2581
collision_report("SELECT (SELECT uuid FROM blob WHERE rid=objid)"
2582
" FROM event WHERE event.type='ci'"
2583
" ORDER BY 1");
2584
@ <h1>Hash Prefix Collisions on All Artifacts</h1>
2585
collision_report("SELECT uuid FROM blob ORDER BY 1");
2586
style_finish_page();
2587
}
2588
2589
/*
2590
** WEBPAGE: clusterlist
2591
**
2592
** Show information about all cluster artifacts in the database.
2593
*/
2594
void clusterlist_page(void){
2595
Stmt q;
2596
int cnt = 1;
2597
sqlite3_int64 szTotal = 0;
2598
sqlite3_int64 szCTotal = 0;
2599
login_check_credentials();
2600
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2601
style_header("All Cluster Artifacts");
2602
style_submenu_element("All Artifactst", "bloblist");
2603
if( g.perm.Admin ){
2604
style_submenu_element("Xfer Log", "rcvfromlist");
2605
}
2606
style_submenu_element("Phantoms", "bloblist?phan");
2607
if( g.perm.Write ){
2608
style_submenu_element("Artifact Stats", "artifact_stats");
2609
}
2610
2611
db_prepare(&q,
2612
"SELECT blob.uuid, "
2613
" blob.size, "
2614
" octet_length(blob.content), "
2615
" datetime(rcvfrom.mtime),"
2616
" user.login,"
2617
" rcvfrom.ipaddr"
2618
" FROM tagxref JOIN blob ON tagxref.rid=blob.rid"
2619
" LEFT JOIN rcvfrom ON blob.rcvid=rcvfrom.rcvid"
2620
" LEFT JOIN user ON user.uid=rcvfrom.uid"
2621
" WHERE tagxref.tagid=%d"
2622
" ORDER BY rcvfrom.mtime, blob.uuid",
2623
TAG_CLUSTER
2624
);
2625
@ <table cellpadding="2" cellspacing="0" border="1">
2626
@ <tr><th>&nbsp;
2627
@ <th>Hash
2628
@ <th>Date&nbsp;Received
2629
@ <th>Size
2630
@ <th>Compressed&nbsp;Size
2631
if( g.perm.Admin ){
2632
@ <th>User<th>IP-Address
2633
}
2634
while( db_step(&q)==SQLITE_ROW ){
2635
const char *zUuid = db_column_text(&q, 0);
2636
sqlite3_int64 sz = db_column_int64(&q, 1);
2637
sqlite3_int64 szC = db_column_int64(&q, 2);
2638
const char *zDate = db_column_text(&q, 3);
2639
const char *zUser = db_column_text(&q, 4);
2640
const char *zIp = db_column_text(&q, 5);
2641
szTotal += sz;
2642
szCTotal += szC;
2643
@ <tr><td align="right">%d(cnt++)
2644
@ <td><a href="%R/info/%S(zUuid)">%S(zUuid)</a>
2645
if( zDate ){
2646
@ <td>%h(zDate)
2647
}else{
2648
@ <td>&nbsp;
2649
}
2650
@ <td align="right">%,lld(sz)
2651
@ <td align="right">%,lld(szC)
2652
if( g.perm.Admin ){
2653
if( zUser ){
2654
@ <td>%h(zUser)
2655
}else{
2656
@ <td>&nbsp;
2657
}
2658
if( zIp ){
2659
@ <td>%h(zIp)
2660
}else{
2661
@ <td>&nbsp;
2662
}
2663
}
2664
@ </tr>
2665
}
2666
@ </table>
2667
db_finalize(&q);
2668
@ <p>Total size of all clusters: %,lld(szTotal) bytes,
2669
@ %,lld(szCTotal) bytes compressed</p>
2670
style_finish_page();
2671
}
2672

Keyboard Shortcuts

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