|
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> %z(href("%R/info/%!S",zUuid))%S(zUuid)</a> </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> |
|
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> |
|
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> |
|
1989
|
} |
|
1990
|
}else{ |
|
1991
|
@ <td> <td> |
|
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> %z(href("%R/info/%!S",zUuid))%S(zUuid)</a> </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> %z(href("%R/info/%!S",zUuid))%S(zUuid)</a> </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> </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> |
|
2413
|
@ <th>Hash |
|
2414
|
@ <th>Date Received |
|
2415
|
@ <th>Size |
|
2416
|
@ <th>Compressed 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> |
|
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> |
|
2443
|
} |
|
2444
|
if( zIp ){ |
|
2445
|
@ <td>%h(zIp) |
|
2446
|
}else{ |
|
2447
|
@ <td> |
|
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
|
|