Fossil SCM

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

Keyboard Shortcuts

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