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 **   drh@hwaci.com
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 /*
25 ** Return TRUE if the string begins with something that looks roughly
26 ** like an ISO date/time string.  The SQLite date/time functions will
27 ** have the final say-so about whether or not the date/time string is
28 ** well-formed.
29 */
fossil_isdate(const char * z)30 int fossil_isdate(const char *z){
31   if( !fossil_isdigit(z[0]) ) return 0;
32   if( !fossil_isdigit(z[1]) ) return 0;
33   if( !fossil_isdigit(z[2]) ) return 0;
34   if( !fossil_isdigit(z[3]) ) return 0;
35   if( z[4]!='-') return 0;
36   if( !fossil_isdigit(z[5]) ) return 0;
37   if( !fossil_isdigit(z[6]) ) return 0;
38   if( z[7]!='-') return 0;
39   if( !fossil_isdigit(z[8]) ) return 0;
40   if( !fossil_isdigit(z[9]) ) return 0;
41   return 1;
42 }
43 
44 /*
45 ** Check to see if the string might be a compact date/time that omits
46 ** the punctuation.  Example:  "20190327084549" instead of
47 ** "2019-03-27 08:45:49".  If the string is of the appropriate form,
48 ** then return an alternative string (in static space) that is the same
49 ** string with punctuation inserted.
50 **
51 ** If the bVerifyNotAHash flag is true, then a check is made to see if
52 ** the string is a hash prefix and NULL is returned if it is.  If the
53 ** bVerifyNotAHash flag is false, then the result is determined by syntax
54 ** of the input string only, without reference to the artifact table.
55 */
fossil_expand_datetime(const char * zIn,int bVerifyNotAHash)56 char *fossil_expand_datetime(const char *zIn, int bVerifyNotAHash){
57   static char zEDate[20];
58   static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' };
59   int n = (int)strlen(zIn);
60   int i, j;
61 
62   /* Only three forms allowed:
63   **   (1)  YYYYMMDD
64   **   (2)  YYYYMMDDHHMM
65   **   (3)  YYYYMMDDHHMMSS
66   */
67   if( n!=8 && n!=12 && n!=14 ) return 0;
68 
69   /* Every character must be a digit */
70   for(i=0; fossil_isdigit(zIn[i]); i++){}
71   if( i!=n ) return 0;
72 
73   /* Expand the date */
74   for(i=j=0; zIn[i]; i++){
75     if( i>=4 && (i%2)==0 ){
76       zEDate[j++] = aPunct[i/2];
77     }
78     zEDate[j++] = zIn[i];
79   }
80   zEDate[j] = 0;
81 
82   /* Check for reasonable date values.
83   ** Offset references:
84   **    YYYY-MM-DD HH:MM:SS
85   **    0123456789 12345678
86   */
87 
88   i = atoi(zEDate);
89   if( i<1970 || i>2100 ) return 0;
90   i = atoi(zEDate+5);
91   if( i<1 || i>12 ) return 0;
92   i = atoi(zEDate+8);
93   if( i<1 || i>31 ) return 0;
94   if( n>8 ){
95     i = atoi(zEDate+11);
96     if( i>24 ) return 0;
97     i = atoi(zEDate+14);
98     if( i>60 ) return 0;
99     if( n==14 && atoi(zEDate+17)>60 ) return 0;
100   }
101 
102   /* The string is not also a hash prefix */
103   if( bVerifyNotAHash ){
104     if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'",zIn) ) return 0;
105   }
106 
107   /* It looks like this may be a date.  Return it with punctuation added. */
108   return zEDate;
109 }
110 
111 /*
112 ** The data-time string in the argument is going to be used as an
113 ** upper bound like this:    mtime<=julianday(zDate,'localtime').
114 ** But if the zDate parameter omits the fractional seconds or the
115 ** seconds, or the time, that might mess up the == part of the
116 ** comparison.  So add in missing factional seconds or seconds or time.
117 **
118 ** The returned string is held in a static buffer that is overwritten
119 ** with each call, or else is just a copy of its input if there are
120 ** no changes.
121 */
fossil_roundup_date(const char * zDate)122 const char *fossil_roundup_date(const char *zDate){
123   static char zUp[24];
124   int n = (int)strlen(zDate);
125   if( n==19 ){  /* YYYY-MM-DD HH:MM:SS */
126     memcpy(zUp, zDate, 19);
127     memcpy(zUp+19, ".999", 5);
128     return zUp;
129   }
130   if( n==16 ){ /* YYYY-MM-DD HH:MM */
131     memcpy(zUp, zDate, 16);
132     memcpy(zUp+16, ":59.999", 8);
133     return zUp;
134   }
135   if( n==10 ){ /* YYYY-MM-DD */
136     memcpy(zUp, zDate, 10);
137     memcpy(zUp+10, " 23:59:59.999", 14);
138     return zUp;
139   }
140   return zDate;
141 }
142 
143 
144 /*
145 ** Return the RID that is the "root" of the branch that contains
146 ** check-in "rid".  Details depending on eType:
147 **
148 **    eType==0    The check-in of the parent branch off of which
149 **                the branch containing RID originally diverged.
150 **
151 **    eType==1    The first check-in of the branch that contains RID.
152 **
153 **    eType==2    The youngest ancestor of RID that is on the branch
154 **                from which the branch containing RID diverged.
155 */
start_of_branch(int rid,int eType)156 int start_of_branch(int rid, int eType){
157   Stmt q;
158   int rc;
159   int ans = rid;
160   char *zBr = branch_of_rid(rid);
161   db_prepare(&q,
162     "SELECT pid, EXISTS(SELECT 1 FROM tagxref"
163                        " WHERE tagid=%d AND tagtype>0"
164                        "   AND value=%Q AND rid=plink.pid)"
165     "  FROM plink"
166     " WHERE cid=:cid AND isprim",
167     TAG_BRANCH, zBr
168   );
169   fossil_free(zBr);
170   do{
171     db_reset(&q);
172     db_bind_int(&q, ":cid", ans);
173     rc = db_step(&q);
174     if( rc!=SQLITE_ROW ) break;
175     if( eType==1 && db_column_int(&q,1)==0 ) break;
176     ans = db_column_int(&q, 0);
177   }while( db_column_int(&q, 1)==1 && ans>0 );
178   db_finalize(&q);
179   if( eType==2 && ans>0 ){
180     zBr = branch_of_rid(ans);
181     ans = compute_youngest_ancestor_in_branch(rid, zBr);
182     fossil_free(zBr);
183   }
184   return ans;
185 }
186 
187 /*
188 ** Find the RID of the most recent object with symbolic tag zTag
189 ** and having a type that matches zType.
190 **
191 ** Return 0 if there are no matches.
192 **
193 ** This is a tricky query to do efficiently.
194 ** If the tag is very common (ex: "trunk") then
195 ** we want to use the query identified below as Q1 - which searching
196 ** the most recent EVENT table entries for the most recent with the tag.
197 ** But if the tag is relatively scarce (anything other than "trunk", basically)
198 ** then we want to do the indexed search show below as Q2.
199 */
most_recent_event_with_tag(const char * zTag,const char * zType)200 static int most_recent_event_with_tag(const char *zTag, const char *zType){
201   return db_int(0,
202     "SELECT objid FROM ("
203       /* Q1:  Begin by looking for the tag in the 30 most recent events */
204       "SELECT objid"
205        " FROM (SELECT * FROM event ORDER BY mtime DESC LIMIT 30) AS ex"
206       " WHERE type GLOB '%q'"
207         " AND EXISTS(SELECT 1 FROM tagxref, tag"
208                      " WHERE tag.tagname='sym-%q'"
209                        " AND tagxref.tagid=tag.tagid"
210                        " AND tagxref.tagtype>0"
211                        " AND tagxref.rid=ex.objid)"
212       " ORDER BY mtime DESC LIMIT 1"
213     ") UNION ALL SELECT * FROM ("
214       /* Q2: If the tag is not found in the 30 most recent events, then using
215       ** the tagxref table to index for the tag */
216       "SELECT event.objid"
217        " FROM tag, tagxref, event"
218       " WHERE tag.tagname='sym-%q'"
219         " AND tagxref.tagid=tag.tagid"
220         " AND tagxref.tagtype>0"
221         " AND event.objid=tagxref.rid"
222         " AND event.type GLOB '%q'"
223       " ORDER BY event.mtime DESC LIMIT 1"
224     ") LIMIT 1;",
225     zType, zTag, zTag, zType
226   );
227 }
228 
229 /*
230 ** Return true if character "c" is a character that might have been
231 ** accidentally appended to the end of a URL.
232 */
is_trailing_punct(char c)233 static int is_trailing_punct(char c){
234   return c=='.' || c=='_' || c==')' || c=='>' || c=='!' || c=='?' || c==',';
235 }
236 
237 
238 /*
239 ** Convert a symbolic name into a RID.  Acceptable forms:
240 **
241 **   *  artifact hash (optionally enclosed in [...])
242 **   *  4-character or larger prefix of a artifact
243 **   *  Symbolic Name
244 **   *  "tag:" + symbolic name
245 **   *  Date or date-time
246 **   *  "date:" + Date or date-time
247 **   *  symbolic-name ":" date-time
248 **   *  "tip"
249 **
250 ** The following additional forms are available in local checkouts:
251 **
252 **   *  "current"
253 **   *  "prev" or "previous"
254 **   *  "next"
255 **
256 ** The following modifier prefixes may be applied to the above forms:
257 **
258 **   *  "root:BR" = The origin of the branch named BR.
259 **   *  "merge-in:BR" = The most recent merge-in for the branch named BR.
260 **
261 ** In those forms, BR may be any symbolic form but is assumed to be a
262 ** checkin. Thus root:2021-02-01 would resolve to a checkin, possibly
263 ** in a branch and possibly in the trunk, but never a wiki edit or
264 ** forum post.
265 **
266 ** Return the RID of the matching artifact.  Or return 0 if the name does not
267 ** match any known object.  Or return -1 if the name is ambiguous.
268 **
269 ** The zType parameter specifies the type of artifact: ci, t, w, e, g, f.
270 ** If zType is NULL or "" or "*" then any type of artifact will serve.
271 ** If zType is "br" then find the first check-in of the named branch
272 ** rather than the last.
273 **
274 ** zType is "ci" in most use cases since we are usually searching for
275 ** a check-in.
276 **
277 ** Note that the input zTag for types "t" and "e" is the artifact hash of
278 ** the ticket-change or technote-change artifact, not the randomly generated
279 ** hexadecimal identifier assigned to tickets and events.  Those identifiers
280 ** live in a separate namespace.
281 */
symbolic_name_to_rid(const char * zTag,const char * zType)282 int symbolic_name_to_rid(const char *zTag, const char *zType){
283   int vid;
284   int rid = 0;
285   int nTag;
286   int i;
287   int startOfBranch = 0;
288   const char *zXTag;     /* zTag with optional [...] removed */
289   int nXTag;             /* Size of zXTag */
290   const char *zDate;     /* Expanded date-time string */
291 
292   if( zType==0 || zType[0]==0 ){
293     zType = "*";
294   }else if( zType[0]=='b' ){
295     zType = "ci";
296     startOfBranch = 1;
297   }
298   if( zTag==0 || zTag[0]==0 ) return 0;
299 
300   /* special keyword: "tip" */
301   if( fossil_strcmp(zTag, "tip")==0 && (zType[0]=='*' || zType[0]=='c') ){
302     rid = db_int(0,
303       "SELECT objid"
304       "  FROM event"
305       " WHERE type='ci'"
306       " ORDER BY event.mtime DESC"
307     );
308     if( rid ) return rid;
309   }
310 
311   /* special keywords: "prev", "previous", "current", and "next" */
312   if( g.localOpen && (vid=db_lget_int("checkout",0))!=0 ){
313     if( fossil_strcmp(zTag, "current")==0 ){
314       rid = vid;
315     }else if( fossil_strcmp(zTag, "prev")==0
316               || fossil_strcmp(zTag, "previous")==0 ){
317       rid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", vid);
318     }else if( fossil_strcmp(zTag, "next")==0 ){
319       rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d"
320                       "  ORDER BY isprim DESC, mtime DESC", vid);
321     }
322     if( rid ) return rid;
323   }
324 
325   /* Date and times */
326   if( memcmp(zTag, "date:", 5)==0 ){
327     zDate = fossil_expand_datetime(&zTag[5],0);
328     if( zDate==0 ) zDate = &zTag[5];
329     rid = db_int(0,
330       "SELECT objid FROM event"
331       " WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'"
332       " ORDER BY mtime DESC LIMIT 1",
333       fossil_roundup_date(zDate), zType);
334     return rid;
335   }
336   if( fossil_isdate(zTag) ){
337     rid = db_int(0,
338       "SELECT objid FROM event"
339       " WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'"
340       " ORDER BY mtime DESC LIMIT 1",
341       fossil_roundup_date(zTag), zType);
342     if( rid) return rid;
343   }
344 
345   /* Deprecated date & time formats:   "local:" + date-time and
346   ** "utc:" + date-time */
347   if( memcmp(zTag, "local:", 6)==0 ){
348     rid = db_int(0,
349       "SELECT objid FROM event"
350       " WHERE mtime<=julianday(%Q) AND type GLOB '%q'"
351       " ORDER BY mtime DESC LIMIT 1",
352       &zTag[6], zType);
353     return rid;
354   }
355   if( memcmp(zTag, "utc:", 4)==0 ){
356     rid = db_int(0,
357       "SELECT objid FROM event"
358       " WHERE mtime<=julianday('%qz') AND type GLOB '%q'"
359       " ORDER BY mtime DESC LIMIT 1",
360       fossil_roundup_date(&zTag[4]), zType);
361     return rid;
362   }
363 
364   /* "tag:" + symbolic-name */
365   if( memcmp(zTag, "tag:", 4)==0 ){
366     rid = most_recent_event_with_tag(&zTag[4], zType);
367     if( startOfBranch ) rid = start_of_branch(rid,1);
368     return rid;
369   }
370 
371   /* root:BR -> The origin of the branch named BR */
372   if( strncmp(zTag, "root:", 5)==0 ){
373     rid = symbolic_name_to_rid(zTag+5, zType);
374     return start_of_branch(rid, 0);
375   }
376   /* merge-in:BR -> Most recent merge-in for the branch name BR */
377   if( strncmp(zTag, "merge-in:", 9)==0 ){
378     rid = symbolic_name_to_rid(zTag+9, zType);
379     return start_of_branch(rid, 2);
380   }
381 
382   /* symbolic-name ":" date-time */
383   nTag = strlen(zTag);
384   for(i=0; i<nTag-8 && zTag[i]!=':'; i++){}
385   if( zTag[i]==':'
386    && (fossil_isdate(&zTag[i+1]) || fossil_expand_datetime(&zTag[i+1],0)!=0)
387   ){
388     char *zDate = mprintf("%s", &zTag[i+1]);
389     char *zTagBase = mprintf("%.*s", i, zTag);
390     char *zXDate;
391     int nDate = strlen(zDate);
392     if( sqlite3_strnicmp(&zDate[nDate-3],"utc",3)==0 ){
393       zDate[nDate-3] = 'z';
394       zDate[nDate-2] = 0;
395     }
396     zXDate = fossil_expand_datetime(zDate,0);
397     if( zXDate==0 ) zXDate = zDate;
398     rid = db_int(0,
399       "SELECT event.objid, max(event.mtime)"
400       "  FROM tag, tagxref, event"
401       " WHERE tag.tagname='sym-%q' "
402       "   AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
403       "   AND event.objid=tagxref.rid "
404       "   AND event.mtime<=julianday(%Q,fromLocal())"
405       "   AND event.type GLOB '%q'",
406       zTagBase, fossil_roundup_date(zXDate), zType
407     );
408     fossil_free(zDate);
409     fossil_free(zTagBase);
410     return rid;
411   }
412 
413   /* Remove optional [...] */
414   zXTag = zTag;
415   nXTag = nTag;
416   if( zXTag[0]=='[' ){
417     zXTag++;
418     nXTag--;
419   }
420   if( nXTag>0 && zXTag[nXTag-1]==']' ){
421     nXTag--;
422   }
423 
424   /* artifact hash or prefix */
425   if( nXTag>=4 && nXTag<=HNAME_MAX && validate16(zXTag, nXTag) ){
426     Stmt q;
427     char zUuid[HNAME_MAX+1];
428     memcpy(zUuid, zXTag, nXTag);
429     zUuid[nXTag] = 0;
430     canonical16(zUuid, nXTag);
431     rid = 0;
432     if( zType[0]=='*' ){
433       db_prepare(&q, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUuid);
434     }else{
435       db_prepare(&q,
436         "SELECT blob.rid"
437         "  FROM blob CROSS JOIN event"
438         " WHERE blob.uuid GLOB '%q*'"
439         "   AND event.objid=blob.rid"
440         "   AND event.type GLOB '%q'",
441         zUuid, zType
442       );
443     }
444     if( db_step(&q)==SQLITE_ROW ){
445       rid = db_column_int(&q, 0);
446       if( db_step(&q)==SQLITE_ROW ) rid = -1;
447     }
448     db_finalize(&q);
449     if( rid ) return rid;
450   }
451 
452   if( zType[0]=='w' ){
453     rid = db_int(0,
454       "SELECT event.objid, max(event.mtime)"
455       "  FROM tag, tagxref, event"
456       " WHERE tag.tagname='wiki-%q' "
457       "   AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
458       "   AND event.objid=tagxref.rid "
459       "   AND event.type GLOB '%q'",
460       zTag, zType
461     );
462   }else{
463     rid = most_recent_event_with_tag(zTag, zType);
464   }
465 
466   if( rid>0 ){
467     if( startOfBranch ) rid = start_of_branch(rid,1);
468     return rid;
469   }
470 
471   /* Pure numeric date/time */
472   zDate = fossil_expand_datetime(zTag, 0);
473   if( zDate ){
474     rid = db_int(0,
475       "SELECT objid FROM event"
476       " WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'"
477       " ORDER BY mtime DESC LIMIT 1",
478       fossil_roundup_date(zDate), zType);
479     if( rid) return rid;
480   }
481 
482 
483   /* Undocumented:  numeric tags get translated directly into the RID */
484   if( memcmp(zTag, "rid:", 4)==0 ){
485     zTag += 4;
486     for(i=0; fossil_isdigit(zTag[i]); i++){}
487     if( zTag[i]==0 ){
488       if( strcmp(zType,"*")==0 ){
489         rid = atoi(zTag);
490       }else{
491         rid = db_int(0,
492           "SELECT event.objid"
493           "  FROM event"
494           " WHERE event.objid=%s"
495           "   AND event.type GLOB '%q'", zTag /*safe-for-%s*/, zType);
496       }
497     }
498     return rid;
499   }
500 
501   /* If nothing matches and the name ends with punctuation,
502   ** then the name might have originated from a URL in plain text
503   ** that was incorrectly extracted from the text.  Try to remove
504   ** the extra punctuation and rerun the match.
505   */
506   if( nTag>4
507    && is_trailing_punct(zTag[nTag-1])
508    && !is_trailing_punct(zTag[nTag-2])
509   ){
510     char *zNew = fossil_strndup(zTag, nTag-1);
511     rid = symbolic_name_to_rid(zNew,zType);
512     fossil_free(zNew);
513   }else
514   if( nTag>5
515    && is_trailing_punct(zTag[nTag-1])
516    && is_trailing_punct(zTag[nTag-2])
517    && !is_trailing_punct(zTag[nTag-3])
518   ){
519     char *zNew = fossil_strndup(zTag, nTag-2);
520     rid = symbolic_name_to_rid(zNew,zType);
521     fossil_free(zNew);
522   }
523   return rid;
524 }
525 
526 /*
527 ** This routine takes a user-entered string and tries to convert it to
528 ** an artifact hash.
529 **
530 ** We first try to treat the string as an artifact hash, or at least a
531 ** unique prefix of an artifact hash. The input may be in mixed case.
532 ** If we are passed such a string, this routine has the effect of
533 ** converting the hash [prefix] to canonical form.
534 **
535 ** If the input is not a hash or a hash prefix, then try to resolve
536 ** the name as a tag.  If multiple tags match, pick the latest.
537 ** A caller can force this routine to skip the hash case above by
538 ** prefixing the string with "tag:", a useful property when the tag
539 ** may be misinterpreted as a hex ASCII string. (e.g. "decade" or "facade")
540 **
541 ** If the input is not a tag, then try to match it as an ISO-8601 date
542 ** string YYYY-MM-DD HH:MM:SS and pick the nearest check-in to that date.
543 ** If the input is of the form "date:*" then always resolve the name as
544 ** a date. The forms "utc:*" and "local:" are deprecated.
545 **
546 ** Return 0 on success.  Return 1 if the name cannot be resolved.
547 ** Return 2 name is ambiguous.
548 */
name_to_uuid(Blob * pName,int iErrPriority,const char * zType)549 int name_to_uuid(Blob *pName, int iErrPriority, const char *zType){
550   char *zName = blob_str(pName);
551   int rid = symbolic_name_to_rid(zName, zType);
552   if( rid<0 ){
553     fossil_error(iErrPriority, "ambiguous name: %s", zName);
554     return 2;
555   }else if( rid==0 ){
556     fossil_error(iErrPriority, "not found: %s", zName);
557     return 1;
558   }else{
559     blob_reset(pName);
560     db_blob(pName, "SELECT uuid FROM blob WHERE rid=%d", rid);
561     return 0;
562   }
563 }
564 
565 /*
566 ** This routine is similar to name_to_uuid() except in the form it
567 ** takes its parameters and returns its value, and in that it does not
568 ** treat errors as fatal. zName must be an artifact hash or prefix of
569 ** a hash. zType is also as described for name_to_uuid(). If
570 ** zName does not resolve, 0 is returned. If it is ambiguous, a
571 ** negative value is returned. On success the rid is returned and
572 ** pUuid (if it is not NULL) is set to a newly-allocated string,
573 ** the full hash, which must eventually be free()d by the caller.
574 */
name_to_uuid2(const char * zName,const char * zType,char ** pUuid)575 int name_to_uuid2(const char *zName, const char *zType, char **pUuid){
576   int rid = symbolic_name_to_rid(zName, zType);
577   if((rid>0) && pUuid){
578     *pUuid = db_text(NULL, "SELECT uuid FROM blob WHERE rid=%d", rid);
579   }
580   return rid;
581 }
582 
583 
584 /*
585 ** name_collisions searches through events, blobs, and tickets for
586 ** collisions of a given hash based on its length, counting only
587 ** hashes greater than or equal to 4 hex ASCII characters (16 bits)
588 ** in length.
589 */
name_collisions(const char * zName)590 int name_collisions(const char *zName){
591   int c = 0;         /* count of collisions for zName */
592   int nLen;          /* length of zName */
593   nLen = strlen(zName);
594   if( nLen>=4 && nLen<=HNAME_MAX && validate16(zName, nLen) ){
595     c = db_int(0,
596       "SELECT"
597       " (SELECT count(*) FROM ticket"
598       "   WHERE tkt_uuid GLOB '%q*') +"
599       " (SELECT count(*) FROM tag"
600       "   WHERE tagname GLOB 'event-%q*') +"
601       " (SELECT count(*) FROM blob"
602       "   WHERE uuid GLOB '%q*');",
603       zName, zName, zName
604     );
605     if( c<2 ) c = 0;
606   }
607   return c;
608 }
609 
610 /*
611 ** COMMAND: test-name-to-id
612 **
613 ** Usage:  %fossil test-name-to-id [--count N] NAME
614 **
615 ** Convert a NAME to a full artifact ID.  Repeat the conversion N
616 ** times (for timing purposes) if the --count option is given.
617 */
test_name_to_id(void)618 void test_name_to_id(void){
619   int i;
620   int n = 0;
621   Blob name;
622   db_must_be_within_tree();
623   for(i=2; i<g.argc; i++){
624     if( strcmp(g.argv[i],"--count")==0 && i+1<g.argc ){
625       i++;
626       n = atoi(g.argv[i]);
627       continue;
628     }
629     do{
630       blob_init(&name, g.argv[i], -1);
631       fossil_print("%s -> ", g.argv[i]);
632       if( name_to_uuid(&name, 1, "*") ){
633         fossil_print("ERROR: %s\n", g.zErrMsg);
634         fossil_error_reset();
635       }else{
636         fossil_print("%s\n", blob_buffer(&name));
637       }
638       blob_reset(&name);
639     }while( n-- > 0 );
640   }
641 }
642 
643 /*
644 ** Convert a name to a rid.  If the name can be any of the various forms
645 ** accepted:
646 **
647 **   * artifact hash or prefix thereof
648 **   * symbolic name
649 **   * date
650 **   * label:date
651 **   * prev, previous
652 **   * next
653 **   * tip
654 **
655 ** This routine is used by command-line routines to resolve command-line inputs
656 ** into a rid.
657 */
name_to_typed_rid(const char * zName,const char * zType)658 int name_to_typed_rid(const char *zName, const char *zType){
659   int rid;
660 
661   if( zName==0 || zName[0]==0 ) return 0;
662   rid = symbolic_name_to_rid(zName, zType);
663   if( rid<0 ){
664     fossil_fatal("ambiguous name: %s", zName);
665   }else if( rid==0 ){
666     fossil_fatal("not found: %s", zName);
667   }
668   return rid;
669 }
name_to_rid(const char * zName)670 int name_to_rid(const char *zName){
671   return name_to_typed_rid(zName, "*");
672 }
673 
674 /*
675 ** WEBPAGE: ambiguous
676 ** URL: /ambiguous?name=NAME&src=WEBPAGE
677 **
678 ** The NAME given by the name parameter is ambiguous.  Display a page
679 ** that shows all possible choices and let the user select between them.
680 **
681 ** The src= query parameter is optional.  If omitted it defaults
682 ** to "info".
683 */
ambiguous_page(void)684 void ambiguous_page(void){
685   Stmt q;
686   const char *zName = P("name");
687   const char *zSrc = PD("src","info");
688   char *z;
689 
690   if( zName==0 || zName[0]==0 || zSrc==0 || zSrc[0]==0 ){
691     fossil_redirect_home();
692   }
693   style_header("Ambiguous Artifact ID");
694   @ <p>The artifact hash prefix <b>%h(zName)</b> is ambiguous and might
695   @ mean any of the following:
696   @ <ol>
697   z = mprintf("%s", zName);
698   canonical16(z, strlen(z));
699   db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
700   while( db_step(&q)==SQLITE_ROW ){
701     const char *zUuid = db_column_text(&q, 0);
702     int rid = db_column_int(&q, 1);
703     @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
704     @ %s(zUuid)</a> -
705     object_description(rid, 0, 0, 0);
706     @ </p></li>
707   }
708   db_finalize(&q);
709   db_prepare(&q,
710     "   SELECT tkt_rid, tkt_uuid, title"
711     "     FROM ticket, ticketchng"
712     "    WHERE ticket.tkt_id = ticketchng.tkt_id"
713     "      AND tkt_uuid GLOB '%q*'"
714     " GROUP BY tkt_uuid"
715     " ORDER BY tkt_ctime DESC", z);
716   while( db_step(&q)==SQLITE_ROW ){
717     int rid = db_column_int(&q, 0);
718     const char *zUuid = db_column_text(&q, 1);
719     const char *zTitle = db_column_text(&q, 2);
720     @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
721     @ %s(zUuid)</a> -
722     @ <ul></ul>
723     @ Ticket
724     hyperlink_to_version(zUuid);
725     @ - %h(zTitle).
726     @ <ul><li>
727     object_description(rid, 0, 0, 0);
728     @ </li></ul>
729     @ </p></li>
730   }
731   db_finalize(&q);
732   db_prepare(&q,
733     "SELECT rid, uuid FROM"
734     "  (SELECT tagxref.rid AS rid, substr(tagname, 7) AS uuid"
735     "     FROM tagxref, tag WHERE tagxref.tagid = tag.tagid"
736     "      AND tagname GLOB 'event-%q*') GROUP BY uuid", z);
737   while( db_step(&q)==SQLITE_ROW ){
738     int rid = db_column_int(&q, 0);
739     const char* zUuid = db_column_text(&q, 1);
740     @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
741     @ %s(zUuid)</a> -
742     @ <ul><li>
743     object_description(rid, 0, 0, 0);
744     @ </li></ul>
745     @ </p></li>
746   }
747   @ </ol>
748   db_finalize(&q);
749   style_finish_page();
750 }
751 
752 /*
753 ** Convert the name in CGI parameter zParamName into a rid and return that
754 ** rid.  If the CGI parameter is missing or is not a valid artifact tag,
755 ** return 0.  If the CGI parameter is ambiguous, redirect to a page that
756 ** shows all possibilities and do not return.
757 */
name_to_rid_www(const char * zParamName)758 int name_to_rid_www(const char *zParamName){
759   int rid;
760   const char *zName = P(zParamName);
761 #ifdef FOSSIL_ENABLE_JSON
762   if(!zName && fossil_has_json()){
763     zName = json_find_option_cstr(zParamName,NULL,NULL);
764   }
765 #endif
766   if( zName==0 || zName[0]==0 ) return 0;
767   rid = symbolic_name_to_rid(zName, "*");
768   if( rid<0 ){
769     cgi_redirectf("%R/ambiguous/%T?src=%t", zName, g.zPath);
770     rid = 0;
771   }
772   return rid;
773 }
774 
775 /*
776 ** Given an RID of a structural artifact, which is assumed to be
777 ** valid, this function returns one of the CFTYPE_xxx values
778 ** describing the record type, or 0 if the RID does not refer to an
779 ** artifact record (as determined by reading the event table).
780 **
781 ** Note that this function never returns CFTYPE_ATTACHMENT or
782 ** CFTYPE_CLUSTER because those are not used in the event table. Thus
783 ** it cannot be used to distinguish those artifact types from
784 ** non-artifact file content.
785 **
786 ** Potential TODO: if the rid is not found in the timeline, try to
787 ** match it to a client file and return a hypothetical new
788 ** CFTYPE_OPAQUE or CFTYPE_NONARTIFACT if a match is found.
789 */
whatis_rid_type(int rid)790 int whatis_rid_type(int rid){
791   Stmt q = empty_Stmt;
792   int type = 0;
793     /* Check for entries on the timeline that reference this object */
794   db_prepare(&q,
795      "SELECT type FROM event WHERE objid=%d", rid);
796   if( db_step(&q)==SQLITE_ROW ){
797     switch( db_column_text(&q,0)[0] ){
798       case 'c':  type = CFTYPE_MANIFEST; break;
799       case 'w':  type = CFTYPE_WIKI;     break;
800       case 'e':  type = CFTYPE_EVENT;    break;
801       case 'f':  type = CFTYPE_FORUM;    break;
802       case 't':  type = CFTYPE_TICKET;   break;
803       case 'g':  type = CFTYPE_CONTROL;  break;
804       default:   break;
805     }
806   }
807   db_finalize(&q);
808   return type;
809 }
810 
811 /*
812 ** A proxy for whatis_rid_type() which returns a brief string (in
813 ** static memory) describing the record type. Returns NULL if rid does
814 ** not refer to an artifact record (as determined by reading the event
815 ** table). The returned string is intended to be used in headers which
816 ** can refer to different artifact types. It is not "definitive," in
817 ** that it does not distinguish between closely-related types like
818 ** wiki creation, edit, and removal.
819 */
whatis_rid_type_label(int rid)820 char const * whatis_rid_type_label(int rid){
821   char const * zType = 0;
822   switch( whatis_rid_type(rid) ){
823     case CFTYPE_MANIFEST: zType = "Check-in";      break;
824     case CFTYPE_WIKI:     zType = "Wiki-edit";     break;
825     case CFTYPE_EVENT:    zType = "Technote";      break;
826     case CFTYPE_FORUM:    zType = "Forum-post";    break;
827     case CFTYPE_TICKET:   zType = "Ticket-change"; break;
828     case CFTYPE_CONTROL:  zType = "Tag-change";    break;
829     default:   break;
830   }
831   return zType;
832 }
833 
834 /*
835 ** Generate a description of artifact "rid"
836 */
whatis_rid(int rid,int verboseFlag)837 void whatis_rid(int rid, int verboseFlag){
838   Stmt q;
839   int cnt;
840 
841   /* Basic information about the object. */
842   db_prepare(&q,
843      "SELECT uuid, size, datetime(mtime,toLocal()), ipaddr"
844      "  FROM blob, rcvfrom"
845      " WHERE rid=%d"
846      "   AND rcvfrom.rcvid=blob.rcvid",
847      rid);
848   if( db_step(&q)==SQLITE_ROW ){
849     if( verboseFlag ){
850       fossil_print("artifact:   %s (%d)\n", db_column_text(&q,0), rid);
851       fossil_print("size:       %d bytes\n", db_column_int(&q,1));
852       fossil_print("received:   %s from %s\n",
853          db_column_text(&q, 2),
854          db_column_text(&q, 3));
855     }else{
856       fossil_print("artifact:   %s\n", db_column_text(&q,0));
857       fossil_print("size:       %d bytes\n", db_column_int(&q,1));
858     }
859   }
860   db_finalize(&q);
861 
862   /* Report any symbolic tags on this artifact */
863   db_prepare(&q,
864     "SELECT substr(tagname,5)"
865     "  FROM tag JOIN tagxref ON tag.tagid=tagxref.tagid"
866     " WHERE tagxref.rid=%d"
867     "   AND tagxref.tagtype<>0"
868     "   AND tagname GLOB 'sym-*'"
869     " ORDER BY 1",
870     rid
871   );
872   cnt = 0;
873   while( db_step(&q)==SQLITE_ROW ){
874     const char *zPrefix = cnt++ ? ", " : "tags:       ";
875     fossil_print("%s%s", zPrefix, db_column_text(&q,0));
876   }
877   if( cnt ) fossil_print("\n");
878   db_finalize(&q);
879 
880   /* Report any HIDDEN, PRIVATE, CLUSTER, or CLOSED tags on this artifact */
881   db_prepare(&q,
882     "SELECT tagname"
883     "  FROM tag JOIN tagxref ON tag.tagid=tagxref.tagid"
884     " WHERE tagxref.rid=%d"
885     "   AND tag.tagid IN (5,6,7,9)"
886     " ORDER BY 1",
887     rid
888   );
889   cnt = 0;
890   while( db_step(&q)==SQLITE_ROW ){
891     const char *zPrefix = cnt++ ? ", " : "raw-tags:   ";
892     fossil_print("%s%s", zPrefix, db_column_text(&q,0));
893   }
894   if( cnt ) fossil_print("\n");
895   db_finalize(&q);
896 
897   /* Check for entries on the timeline that reference this object */
898   db_prepare(&q,
899      "SELECT type, datetime(mtime,toLocal()),"
900      "       coalesce(euser,user), coalesce(ecomment,comment)"
901      "  FROM event WHERE objid=%d", rid);
902   if( db_step(&q)==SQLITE_ROW ){
903     const char *zType;
904     switch( db_column_text(&q,0)[0] ){
905       case 'c':  zType = "Check-in";       break;
906       case 'w':  zType = "Wiki-edit";      break;
907       case 'e':  zType = "Technote";       break;
908       case 'f':  zType = "Forum-post";     break;
909       case 't':  zType = "Ticket-change";  break;
910       case 'g':  zType = "Tag-change";     break;
911       default:   zType = "Unknown";        break;
912     }
913     fossil_print("type:       %s by %s on %s\n", zType, db_column_text(&q,2),
914                  db_column_text(&q, 1));
915     fossil_print("comment:    ");
916     comment_print(db_column_text(&q,3), 0, 12, -1, get_comment_format());
917     cnt++;
918   }
919   db_finalize(&q);
920 
921   /* Check to see if this object is used as a file in a check-in */
922   db_prepare(&q,
923     "SELECT filename.name, blob.uuid, datetime(event.mtime,toLocal()),"
924     "       coalesce(euser,user), coalesce(ecomment,comment)"
925     "  FROM mlink, filename, blob, event"
926     " WHERE mlink.fid=%d"
927     "   AND filename.fnid=mlink.fnid"
928     "   AND event.objid=mlink.mid"
929     "   AND blob.rid=mlink.mid"
930     " ORDER BY event.mtime DESC /*sort*/",
931     rid);
932   while( db_step(&q)==SQLITE_ROW ){
933     fossil_print("file:       %s\n", db_column_text(&q,0));
934     fossil_print("            part of [%S] by %s on %s\n",
935       db_column_text(&q, 1),
936       db_column_text(&q, 3),
937       db_column_text(&q, 2));
938     fossil_print("            ");
939     comment_print(db_column_text(&q,4), 0, 12, -1, get_comment_format());
940     cnt++;
941   }
942   db_finalize(&q);
943 
944   /* Check to see if this object is used as an attachment */
945   db_prepare(&q,
946     "SELECT attachment.filename,"
947     "       attachment.comment,"
948     "       attachment.user,"
949     "       datetime(attachment.mtime,toLocal()),"
950     "       attachment.target,"
951     "       CASE WHEN EXISTS(SELECT 1 FROM tag WHERE tagname=('tkt-'||target))"
952     "            THEN 'ticket'"
953     "       WHEN EXISTS(SELECT 1 FROM tag WHERE tagname=('wiki-'||target))"
954     "            THEN 'wiki' END,"
955     "       attachment.attachid,"
956     "       (SELECT uuid FROM blob WHERE rid=attachid)"
957     "  FROM attachment JOIN blob ON attachment.src=blob.uuid"
958     " WHERE blob.rid=%d",
959     rid
960   );
961   while( db_step(&q)==SQLITE_ROW ){
962     fossil_print("attachment: %s\n", db_column_text(&q,0));
963     fossil_print("            attached to %s %s\n",
964                  db_column_text(&q,5), db_column_text(&q,4));
965     if( verboseFlag ){
966       fossil_print("            via %s (%d)\n",
967                    db_column_text(&q,7), db_column_int(&q,6));
968     }else{
969       fossil_print("            via %s\n",
970                    db_column_text(&q,7));
971     }
972     fossil_print("            by user %s on %s\n",
973                  db_column_text(&q,2), db_column_text(&q,3));
974     fossil_print("            ");
975     comment_print(db_column_text(&q,1), 0, 12, -1, get_comment_format());
976     cnt++;
977   }
978   db_finalize(&q);
979 
980   /* If other information available, try to describe the object */
981   if( cnt==0 ){
982     char *zWhere = mprintf("=%d", rid);
983     char *zDesc;
984     describe_artifacts(zWhere);
985     free(zWhere);
986     zDesc = db_text(0,
987        "SELECT printf('%%-12s%%s %%s',type||':',summary,substr(ref,1,16))"
988        "  FROM description WHERE rid=%d", rid);
989     fossil_print("%s\n", zDesc);
990     fossil_free(zDesc);
991   }
992 }
993 
994 /*
995 ** COMMAND: whatis*
996 **
997 ** Usage: %fossil whatis NAME
998 **
999 ** Resolve the symbol NAME into its canonical artifact hash
1000 ** artifact name and provide a description of what role that artifact
1001 ** plays.
1002 **
1003 ** Options:
1004 **
1005 **    --type TYPE          Only find artifacts of TYPE (one of: 'ci', 't',
1006 **                         'w', 'g', or 'e')
1007 **    -v|--verbose         Provide extra information (such as the RID)
1008 */
whatis_cmd(void)1009 void whatis_cmd(void){
1010   int rid;
1011   const char *zName;
1012   int verboseFlag;
1013   int i;
1014   const char *zType = 0;
1015   db_find_and_open_repository(0,0);
1016   verboseFlag = find_option("verbose","v",0)!=0;
1017   zType = find_option("type",0,1);
1018 
1019   /* We should be done with options.. */
1020   verify_all_options();
1021 
1022   if( g.argc<3 ) usage("NAME ...");
1023   for(i=2; i<g.argc; i++){
1024     zName = g.argv[i];
1025     if( i>2 ) fossil_print("%.79c\n",'-');
1026     rid = symbolic_name_to_rid(zName, zType);
1027     if( rid<0 ){
1028       Stmt q;
1029       int cnt = 0;
1030       fossil_print("name:       %s (ambiguous)\n", zName);
1031       db_prepare(&q,
1032          "SELECT rid FROM blob WHERE uuid>=lower(%Q) AND uuid<(lower(%Q)||'z')",
1033          zName, zName
1034       );
1035       while( db_step(&q)==SQLITE_ROW ){
1036         if( cnt++ ) fossil_print("%12s---- meaning #%d ----\n", " ", cnt);
1037         whatis_rid(db_column_int(&q, 0), verboseFlag);
1038       }
1039       db_finalize(&q);
1040     }else if( rid==0 ){
1041                  /* 0123456789 12 */
1042       fossil_print("unknown:    %s\n", zName);
1043     }else{
1044       fossil_print("name:       %s\n", zName);
1045       whatis_rid(rid, verboseFlag);
1046     }
1047   }
1048 }
1049 
1050 /*
1051 ** COMMAND: test-whatis-all
1052 **
1053 ** Usage: %fossil test-whatis-all
1054 **
1055 ** Show "whatis" information about every artifact in the repository
1056 */
test_whatis_all_cmd(void)1057 void test_whatis_all_cmd(void){
1058   Stmt q;
1059   int cnt = 0;
1060   db_find_and_open_repository(0,0);
1061   db_prepare(&q, "SELECT rid FROM blob ORDER BY rid");
1062   while( db_step(&q)==SQLITE_ROW ){
1063     if( cnt++ ) fossil_print("%.79c\n", '-');
1064     whatis_rid(db_column_int(&q,0), 1);
1065   }
1066   db_finalize(&q);
1067 }
1068 
1069 
1070 /*
1071 ** COMMAND: test-ambiguous
1072 **
1073 ** Usage: %fossil test-ambiguous [--minsize N]
1074 **
1075 ** Show a list of ambiguous artifact hash abbreviations of N characters or
1076 ** more where N defaults to 4.  Change N to a different value using
1077 ** the "--minsize N" command-line option.
1078 */
test_ambiguous_cmd(void)1079 void test_ambiguous_cmd(void){
1080   Stmt q, ins;
1081   int i;
1082   int minSize = 4;
1083   const char *zMinsize;
1084   char zPrev[100];
1085   db_find_and_open_repository(0,0);
1086   zMinsize = find_option("minsize",0,1);
1087   if( zMinsize && atoi(zMinsize)>0 ) minSize = atoi(zMinsize);
1088   db_multi_exec("CREATE TEMP TABLE dups(uuid, cnt)");
1089   db_prepare(&ins,"INSERT INTO dups(uuid) VALUES(substr(:uuid,1,:cnt))");
1090   db_prepare(&q,
1091     "SELECT uuid FROM blob "
1092     "UNION "
1093     "SELECT substr(tagname,7) FROM tag WHERE tagname GLOB 'event-*' "
1094     "UNION "
1095     "SELECT tkt_uuid FROM ticket "
1096     "ORDER BY 1"
1097   );
1098   zPrev[0] = 0;
1099   while( db_step(&q)==SQLITE_ROW ){
1100     const char *zUuid = db_column_text(&q, 0);
1101     for(i=0; zUuid[i]==zPrev[i] && zUuid[i]!=0; i++){}
1102     if( i>=minSize ){
1103       db_bind_int(&ins, ":cnt", i);
1104       db_bind_text(&ins, ":uuid", zUuid);
1105       db_step(&ins);
1106       db_reset(&ins);
1107     }
1108     sqlite3_snprintf(sizeof(zPrev), zPrev, "%s", zUuid);
1109   }
1110   db_finalize(&ins);
1111   db_finalize(&q);
1112   db_prepare(&q, "SELECT uuid FROM dups ORDER BY length(uuid) DESC, uuid");
1113   while( db_step(&q)==SQLITE_ROW ){
1114     fossil_print("%s\n", db_column_text(&q, 0));
1115   }
1116   db_finalize(&q);
1117 }
1118 
1119 /*
1120 ** Schema for the description table
1121 */
1122 static const char zDescTab[] =
1123 @ CREATE TEMP TABLE IF NOT EXISTS description(
1124 @   rid INTEGER PRIMARY KEY,       -- RID of the object
1125 @   uuid TEXT,                     -- hash of the object
1126 @   ctime DATETIME,                -- Time of creation
1127 @   isPrivate BOOLEAN DEFAULT 0,   -- True for unpublished artifacts
1128 @   type TEXT,                     -- file, checkin, wiki, ticket, etc.
1129 @   rcvid INT,                     -- When the artifact was received
1130 @   summary TEXT,                  -- Summary comment for the object
1131 @   ref TEXT                       -- hash of an object to link against
1132 @ );
1133 @ CREATE INDEX IF NOT EXISTS desctype
1134 @   ON description(summary) WHERE summary='unknown';
1135 ;
1136 
1137 /*
1138 ** Attempt to describe all phantom artifacts.  The artifacts are
1139 ** already loaded into the description table and have summary='unknown'.
1140 ** This routine attempts to generate a better summary, and possibly
1141 ** fill in the ref field.
1142 */
describe_unknown_artifacts()1143 static void describe_unknown_artifacts(){
1144   /* Try to figure out the origin of unknown artifacts */
1145   db_multi_exec(
1146     "REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
1147     "  SELECT description.rid, description.uuid, isPrivate, type,\n"
1148     "         CASE WHEN plink.isprim THEN '' ELSE 'merge ' END ||\n"
1149     "         'parent of check-in', blob.uuid\n"
1150     "    FROM description, plink, blob\n"
1151     "   WHERE description.summary='unknown'\n"
1152     "     AND plink.pid=description.rid\n"
1153     "     AND blob.rid=plink.cid;"
1154   );
1155   db_multi_exec(
1156     "REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
1157     "  SELECT description.rid, description.uuid, isPrivate, type,\n"
1158     "         'child of check-in', blob.uuid\n"
1159     "    FROM description, plink, blob\n"
1160     "   WHERE description.summary='unknown'\n"
1161     "     AND plink.cid=description.rid\n"
1162     "     AND blob.rid=plink.pid;"
1163   );
1164   db_multi_exec(
1165     "REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
1166     "  SELECT description.rid, description.uuid, isPrivate, type,\n"
1167     "         'check-in referenced by \"'||tag.tagname ||'\" tag',\n"
1168     "         blob.uuid\n"
1169     "    FROM description, tagxref, tag, blob\n"
1170     "   WHERE description.summary='unknown'\n"
1171     "     AND tagxref.origid=description.rid\n"
1172     "     AND tag.tagid=tagxref.tagid\n"
1173     "     AND blob.rid=tagxref.srcid;"
1174   );
1175   db_multi_exec(
1176     "REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
1177     "  SELECT description.rid, description.uuid, isPrivate, type,\n"
1178     "         'file \"'||filename.name||'\"',\n"
1179     "         blob.uuid\n"
1180     "    FROM description, mlink, filename, blob\n"
1181     "   WHERE description.summary='unknown'\n"
1182     "     AND mlink.fid=description.rid\n"
1183     "     AND blob.rid=mlink.mid\n"
1184     "     AND filename.fnid=mlink.fnid;"
1185   );
1186   if( !db_exists("SELECT 1 FROM description WHERE summary='unknown'") ){
1187     return;
1188   }
1189   add_content_sql_commands(g.db);
1190   db_multi_exec(
1191     "REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n"
1192     "  SELECT description.rid, description.uuid, isPrivate, type,\n"
1193     "         'referenced by cluster', blob.uuid\n"
1194     "    FROM description, tagxref, blob\n"
1195     "   WHERE description.summary='unknown'\n"
1196     "     AND tagxref.tagid=(SELECT tagid FROM tag WHERE tagname='cluster')\n"
1197     "     AND blob.rid=tagxref.rid\n"
1198     "     AND CAST(content(blob.uuid) AS text)"
1199     "                   GLOB ('*M '||description.uuid||'*');"
1200   );
1201 }
1202 
1203 /*
1204 ** Create the description table if it does not already exists.
1205 ** Populate fields of this table with descriptions for all artifacts
1206 ** whose RID matches the SQL expression in zWhere.
1207 */
describe_artifacts(const char * zWhere)1208 void describe_artifacts(const char *zWhere){
1209   db_multi_exec("%s", zDescTab/*safe-for-%s*/);
1210 
1211   /* Describe check-ins */
1212   db_multi_exec(
1213     "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1214     "SELECT blob.rid, blob.uuid, blob.rcvid, event.mtime, 'checkin',\n"
1215           " 'check-in to '\n"
1216           " ||  coalesce((SELECT value FROM tagxref WHERE tagid=%d"
1217                      "   AND tagtype>0 AND tagxref.rid=blob.rid),'trunk')\n"
1218           " || ' by ' || coalesce(event.euser,event.user)\n"
1219           " || ' on ' || strftime('%%Y-%%m-%%d %%H:%%M',event.mtime)\n"
1220     "  FROM event, blob\n"
1221     " WHERE (event.objid %s) AND event.type='ci'\n"
1222     "   AND event.objid=blob.rid;",
1223     TAG_BRANCH, zWhere /*safe-for-%s*/
1224   );
1225 
1226   /* Describe files */
1227   db_multi_exec(
1228     "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1229     "SELECT blob.rid, blob.uuid, blob.rcvid, event.mtime,"
1230     "       'file', 'file '||filename.name\n"
1231     "  FROM mlink, blob, event, filename\n"
1232     " WHERE (mlink.fid %s)\n"
1233     "   AND mlink.mid=event.objid\n"
1234     "   AND filename.fnid=mlink.fnid\n"
1235     "   AND mlink.fid=blob.rid;",
1236     zWhere /*safe-for-%s*/
1237   );
1238 
1239   /* Describe tags */
1240   db_multi_exec(
1241    "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1242     "SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'tag',\n"
1243     "     'tag '||substr((SELECT uuid FROM blob WHERE rid=tagxref.rid),1,16)\n"
1244     "  FROM tagxref, blob\n"
1245     " WHERE (tagxref.srcid %s) AND tagxref.srcid!=tagxref.rid\n"
1246     "   AND tagxref.srcid=blob.rid;",
1247     zWhere /*safe-for-%s*/
1248   );
1249 
1250   /* Cluster artifacts */
1251   db_multi_exec(
1252     "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1253     "SELECT blob.rid, blob.uuid, blob.rcvid, rcvfrom.mtime,"
1254     "       'cluster', 'cluster'\n"
1255     "  FROM tagxref, blob, rcvfrom\n"
1256     " WHERE (tagxref.rid %s)\n"
1257     "   AND tagxref.tagid=(SELECT tagid FROM tag WHERE tagname='cluster')\n"
1258     "   AND blob.rid=tagxref.rid"
1259     "   AND rcvfrom.rcvid=blob.rcvid;",
1260     zWhere /*safe-for-%s*/
1261   );
1262 
1263   /* Ticket change artifacts */
1264   db_multi_exec(
1265     "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1266     "SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'ticket',\n"
1267     "       'ticket '||substr(tag.tagname,5,21)\n"
1268     "  FROM tagxref, tag, blob\n"
1269     " WHERE (tagxref.rid %s)\n"
1270     "   AND tag.tagid=tagxref.tagid\n"
1271     "   AND tag.tagname GLOB 'tkt-*'"
1272     "   AND blob.rid=tagxref.rid;",
1273     zWhere /*safe-for-%s*/
1274   );
1275 
1276   /* Wiki edit artifacts */
1277   db_multi_exec(
1278     "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1279     "SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'wiki',\n"
1280     "       printf('wiki \"%%s\"',substr(tag.tagname,6))\n"
1281     "  FROM tagxref, tag, blob\n"
1282     " WHERE (tagxref.rid %s)\n"
1283     "   AND tag.tagid=tagxref.tagid\n"
1284     "   AND tag.tagname GLOB 'wiki-*'"
1285     "   AND blob.rid=tagxref.rid;",
1286     zWhere /*safe-for-%s*/
1287   );
1288 
1289   /* Event edit artifacts */
1290   db_multi_exec(
1291     "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1292     "SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'event',\n"
1293     "       'event '||substr(tag.tagname,7)\n"
1294     "  FROM tagxref, tag, blob\n"
1295     " WHERE (tagxref.rid %s)\n"
1296     "   AND tag.tagid=tagxref.tagid\n"
1297     "   AND tag.tagname GLOB 'event-*'"
1298     "   AND blob.rid=tagxref.rid;",
1299     zWhere /*safe-for-%s*/
1300   );
1301 
1302   /* Attachments */
1303   db_multi_exec(
1304     "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1305     "SELECT blob.rid, blob.uuid, blob.rcvid, attachment.mtime,"
1306     "       'attach-control',\n"
1307     "       'attachment-control for '||attachment.filename\n"
1308     "  FROM attachment, blob\n"
1309     " WHERE (attachment.attachid %s)\n"
1310     "   AND blob.rid=attachment.attachid",
1311     zWhere /*safe-for-%s*/
1312   );
1313   db_multi_exec(
1314     "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1315     "SELECT blob.rid, blob.uuid, blob.rcvid, attachment.mtime, 'attachment',\n"
1316     "       'attachment '||attachment.filename\n"
1317     "  FROM attachment, blob\n"
1318     " WHERE (blob.rid %s)\n"
1319     "   AND blob.rid NOT IN (SELECT rid FROM description)\n"
1320     "   AND blob.uuid=attachment.src",
1321     zWhere /*safe-for-%s*/
1322   );
1323 
1324   /* Forum posts */
1325   if( db_table_exists("repository","forumpost") ){
1326     db_multi_exec(
1327       "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n"
1328       "SELECT postblob.rid, postblob.uuid, postblob.rcvid,"
1329       "       forumpost.fmtime, 'forumpost',\n"
1330       "       CASE WHEN fpid=froot THEN 'forum-post '\n"
1331       "            ELSE 'forum-reply-to ' END || substr(rootblob.uuid,1,14)\n"
1332       "  FROM forumpost, blob AS postblob, blob AS rootblob\n"
1333       " WHERE (forumpost.fpid %s)\n"
1334       "   AND postblob.rid=forumpost.fpid"
1335       "   AND rootblob.rid=forumpost.froot",
1336       zWhere /*safe-for-%s*/
1337     );
1338   }
1339 
1340   /* Mark all other artifacts as "unknown" for now */
1341   db_multi_exec(
1342     "INSERT OR IGNORE INTO description(rid,uuid,rcvid,type,summary)\n"
1343     "SELECT blob.rid, blob.uuid,blob.rcvid,\n"
1344     "       CASE WHEN EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)\n"
1345            " THEN 'phantom' ELSE '' END,\n"
1346     "       'unknown'\n"
1347     "  FROM blob\n"
1348     " WHERE (blob.rid %s)\n"
1349     "   AND (blob.rid NOT IN (SELECT rid FROM description));",
1350     zWhere /*safe-for-%s*/
1351   );
1352 
1353   /* Mark private elements */
1354   db_multi_exec(
1355    "UPDATE description SET isPrivate=1 WHERE rid IN private"
1356   );
1357 
1358   if( db_exists("SELECT 1 FROM description WHERE summary='unknown'") ){
1359     describe_unknown_artifacts();
1360   }
1361 }
1362 
1363 /*
1364 ** Print the content of the description table on stdout.
1365 **
1366 ** The description table is computed using the WHERE clause zWhere if
1367 ** the zWhere parameter is not NULL.  If zWhere is NULL, then this
1368 ** routine assumes that the description table already exists and is
1369 ** populated and merely prints the contents.
1370 */
describe_artifacts_to_stdout(const char * zWhere,const char * zLabel)1371 int describe_artifacts_to_stdout(const char *zWhere, const char *zLabel){
1372   Stmt q;
1373   int cnt = 0;
1374   if( zWhere!=0 ) describe_artifacts(zWhere);
1375   db_prepare(&q,
1376     "SELECT uuid, summary, coalesce(ref,''), isPrivate\n"
1377     "  FROM description\n"
1378     " ORDER BY ctime, type;"
1379   );
1380   while( db_step(&q)==SQLITE_ROW ){
1381     if( zLabel ){
1382       fossil_print("%s\n", zLabel);
1383       zLabel = 0;
1384     }
1385     fossil_print("  %.16s %s %s", db_column_text(&q,0),
1386            db_column_text(&q,1), db_column_text(&q,2));
1387     if( db_column_int(&q,3) ) fossil_print(" (private)");
1388     fossil_print("\n");
1389     cnt++;
1390   }
1391   db_finalize(&q);
1392   if( zWhere!=0 ) db_multi_exec("DELETE FROM description;");
1393   return cnt;
1394 }
1395 
1396 /*
1397 ** COMMAND: test-describe-artifacts
1398 **
1399 ** Usage: %fossil test-describe-artifacts [--from S] [--count N]
1400 **
1401 ** Display a one-line description of every artifact.
1402 */
test_describe_artifacts_cmd(void)1403 void test_describe_artifacts_cmd(void){
1404   int iFrom = 0;
1405   int iCnt = 1000000;
1406   const char *z;
1407   char *zRange;
1408   db_find_and_open_repository(0,0);
1409   z = find_option("from",0,1);
1410   if( z ) iFrom = atoi(z);
1411   z = find_option("count",0,1);
1412   if( z ) iCnt = atoi(z);
1413   zRange = mprintf("BETWEEN %d AND %d", iFrom, iFrom+iCnt-1);
1414   describe_artifacts_to_stdout(zRange, 0);
1415 }
1416 
1417 /*
1418 ** WEBPAGE: bloblist
1419 **
1420 ** Return a page showing all artifacts in the repository.  Query parameters:
1421 **
1422 **   n=N         Show N artifacts
1423 **   s=S         Start with artifact number S
1424 **   priv        Show only unpublished or private artifacts
1425 **   phan        Show only phantom artifacts
1426 **   hclr        Color code hash types (SHA1 vs SHA3)
1427 */
bloblist_page(void)1428 void bloblist_page(void){
1429   Stmt q;
1430   int s = atoi(PD("s","0"));
1431   int n = atoi(PD("n","5000"));
1432   int mx = db_int(0, "SELECT max(rid) FROM blob");
1433   int privOnly = PB("priv");
1434   int phantomOnly = PB("phan");
1435   int hashClr = PB("hclr");
1436   char *zRange;
1437   char *zSha1Bg;
1438   char *zSha3Bg;
1439 
1440   login_check_credentials();
1441   if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1442   style_header("List Of Artifacts");
1443   style_submenu_element("250 Largest", "bigbloblist");
1444   if( g.perm.Admin ){
1445     style_submenu_element("Artifact Log", "rcvfromlist");
1446   }
1447   if( !phantomOnly ){
1448     style_submenu_element("Phantoms", "bloblist?phan");
1449   }
1450   if( g.perm.Private || g.perm.Admin ){
1451     if( !privOnly ){
1452       style_submenu_element("Private", "bloblist?priv");
1453     }
1454   }else{
1455     privOnly = 0;
1456   }
1457   if( g.perm.Write ){
1458     style_submenu_element("Artifact Stats", "artifact_stats");
1459   }
1460   if( !privOnly && !phantomOnly && mx>n && P("s")==0 ){
1461     int i;
1462     @ <p>Select a range of artifacts to view:</p>
1463     @ <ul>
1464     for(i=1; i<=mx; i+=n){
1465       @ <li> %z(href("%R/bloblist?s=%d&n=%d",i,n))
1466       @ %d(i)..%d(i+n-1<mx?i+n-1:mx)</a>
1467     }
1468     @ </ul>
1469     style_finish_page();
1470     return;
1471   }
1472   if( phantomOnly || privOnly || mx>n ){
1473     style_submenu_element("Index", "bloblist");
1474   }
1475   if( privOnly ){
1476     zRange = mprintf("IN private");
1477   }else if( phantomOnly ){
1478     zRange = mprintf("IN phantom");
1479   }else{
1480     zRange = mprintf("BETWEEN %d AND %d", s, s+n-1);
1481   }
1482   describe_artifacts(zRange);
1483   fossil_free(zRange);
1484   db_prepare(&q,
1485     "SELECT rid, uuid, summary, isPrivate, type='phantom', rcvid, ref"
1486     "  FROM description ORDER BY rid"
1487   );
1488   if( skin_detail_boolean("white-foreground") ){
1489     zSha1Bg = "#714417";
1490     zSha3Bg = "#177117";
1491   }else{
1492     zSha1Bg = "#ebffb0";
1493     zSha3Bg = "#b0ffb0";
1494   }
1495   @ <table cellpadding="2" cellspacing="0" border="1">
1496   if( g.perm.Admin ){
1497     @ <tr><th>RID<th>Hash<th>Rcvid<th>Description<th>Ref<th>Remarks
1498   }else{
1499     @ <tr><th>RID<th>Hash<th>Description<th>Ref<th>Remarks
1500   }
1501   while( db_step(&q)==SQLITE_ROW ){
1502     int rid = db_column_int(&q,0);
1503     const char *zUuid = db_column_text(&q, 1);
1504     const char *zDesc = db_column_text(&q, 2);
1505     int isPriv = db_column_int(&q,3);
1506     int isPhantom = db_column_int(&q,4);
1507     const char *zRef = db_column_text(&q,6);
1508     if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){
1509       /* Don't show private artifacts to users without Private (x) permission */
1510       continue;
1511     }
1512     if( hashClr ){
1513       const char *zClr = db_column_bytes(&q,1)>40 ? zSha3Bg : zSha1Bg;
1514       @ <tr style='background-color:%s(zClr);'><td align="right">%d(rid)</td>
1515     }else{
1516       @ <tr><td align="right">%d(rid)</td>
1517     }
1518     @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
1519     if( g.perm.Admin ){
1520       int rcvid = db_column_int(&q,5);
1521       if( rcvid<=0 ){
1522         @ <td>&nbsp;
1523       }else{
1524         @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%d(rcvid)</a>
1525       }
1526     }
1527     @ <td align="left">%h(zDesc)</td>
1528     if( zRef && zRef[0] ){
1529       @ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a>
1530     }else{
1531       @ <td>&nbsp;
1532     }
1533     if( isPriv || isPhantom ){
1534       if( isPriv==0 ){
1535         @ <td>phantom</td>
1536       }else if( isPhantom==0 ){
1537         @ <td>private</td>
1538       }else{
1539         @ <td>private,phantom</td>
1540       }
1541     }else{
1542       @ <td>&nbsp;
1543     }
1544     @ </tr>
1545   }
1546   @ </table>
1547   db_finalize(&q);
1548   style_finish_page();
1549 }
1550 
1551 /*
1552 ** Output HTML that shows a table of all public phantoms.
1553 */
table_of_public_phantoms(void)1554 void table_of_public_phantoms(void){
1555   Stmt q;
1556   char *zRange;
1557   double rNow;
1558   zRange = mprintf("IN (SELECT rid FROM phantom EXCEPT"
1559                    " SELECT rid FROM private)");
1560   describe_artifacts(zRange);
1561   fossil_free(zRange);
1562   db_prepare(&q,
1563     "SELECT rid, uuid, summary, ref,"
1564     "  (SELECT mtime FROM blob, rcvfrom"
1565     "    WHERE blob.uuid=ref AND rcvfrom.rcvid=blob.rcvid)"
1566     "  FROM description ORDER BY rid"
1567   );
1568   rNow = db_double(0.0, "SELECT julianday('now')");
1569   @ <table cellpadding="2" cellspacing="0" border="1">
1570   @ <tr><th>RID<th>Description<th>Source<th>Age
1571   while( db_step(&q)==SQLITE_ROW ){
1572     int rid = db_column_int(&q,0);
1573     const char *zUuid = db_column_text(&q, 1);
1574     const char *zDesc = db_column_text(&q, 2);
1575     const char *zRef = db_column_text(&q,3);
1576     double mtime = db_column_double(&q,4);
1577     @ <tr><td valign="top">%d(rid)</td>
1578     @ <td valign="top" align="left">%h(zUuid)<br>%h(zDesc)</td>
1579     if( zRef && zRef[0] ){
1580       @ <td valign="top">%z(href("%R/info/%!S",zRef))%!S(zRef)</a>
1581       if( mtime>0 ){
1582         char *zAge = human_readable_age(rNow - mtime);
1583         @ <td valign="top">%h(zAge)
1584         fossil_free(zAge);
1585       }else{
1586         @ <td>&nbsp;
1587       }
1588     }else{
1589       @ <td>&nbsp;<td>&nbsp;
1590     }
1591     @ </tr>
1592   }
1593   @ </table>
1594   db_finalize(&q);
1595 }
1596 
1597 /*
1598 ** WEBPAGE: phantoms
1599 **
1600 ** Show a list of all "phantom" artifacts that are not marked as "private".
1601 **
1602 ** A "phantom" artifact is an artifact whose hash named appears in some
1603 ** artifact but whose content is unknown.  For example, if a manifest
1604 ** references a particular SHA3 hash of a file, but that SHA3 hash is
1605 ** not on the shunning list and is not in the database, then the file
1606 ** is a phantom.  We know it exists, but we do not know its content.
1607 **
1608 ** Whenever a sync occurs, both each party looks at its phantom list
1609 ** and for every phantom that is not also marked private, it asks the
1610 ** other party to send it the content.  This mechanism helps keep all
1611 ** repositories synced up.
1612 **
1613 ** This page is similar to the /bloblist page in that it lists artifacts.
1614 ** But this page is a special case in that it only shows phantoms that
1615 ** are not private.  In other words, this page shows all phantoms that
1616 ** generate extra network traffic on every sync request.
1617 */
phantom_list_page(void)1618 void phantom_list_page(void){
1619   login_check_credentials();
1620   if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1621   style_header("Public Phantom Artifacts");
1622   if( g.perm.Admin ){
1623     style_submenu_element("Artifact Log", "rcvfromlist");
1624     style_submenu_element("Artifact List", "bloblist");
1625   }
1626   if( g.perm.Write ){
1627     style_submenu_element("Artifact Stats", "artifact_stats");
1628   }
1629   table_of_public_phantoms();
1630   style_finish_page();
1631 }
1632 
1633 /*
1634 ** WEBPAGE: bigbloblist
1635 **
1636 ** Return a page showing the largest artifacts in the repository in order
1637 ** of decreasing size.
1638 **
1639 **   n=N         Show the top N artifacts
1640 */
bigbloblist_page(void)1641 void bigbloblist_page(void){
1642   Stmt q;
1643   int n = atoi(PD("n","250"));
1644 
1645   login_check_credentials();
1646   if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1647   if( g.perm.Admin ){
1648     style_submenu_element("Artifact Log", "rcvfromlist");
1649   }
1650   if( g.perm.Write ){
1651     style_submenu_element("Artifact Stats", "artifact_stats");
1652   }
1653   style_submenu_element("All Artifacts", "bloblist");
1654   style_header("%d Largest Artifacts", n);
1655   db_multi_exec(
1656     "CREATE TEMP TABLE toshow(rid INTEGER PRIMARY KEY);"
1657     "INSERT INTO toshow(rid)"
1658     "  SELECT rid FROM blob"
1659     "   ORDER BY length(content) DESC"
1660     "   LIMIT %d;", n
1661   );
1662   describe_artifacts("IN toshow");
1663   db_prepare(&q,
1664     "SELECT description.rid, description.uuid, description.summary,"
1665     "       length(blob.content), coalesce(delta.srcid,''),"
1666     "       datetime(description.ctime)"
1667     "  FROM description, blob LEFT JOIN delta ON delta.rid=blob.rid"
1668     " WHERE description.rid=blob.rid"
1669     " ORDER BY length(content) DESC"
1670   );
1671   @ <table cellpadding="2" cellspacing="0" border="1" \
1672   @  class='sortable' data-column-types='NnnttT' data-init-sort='0'>
1673   @ <thead><tr><th align="right">Size<th align="right">RID
1674   @ <th align="right">Delta From<th>Hash<th>Description<th>Date</tr></thead>
1675   @ <tbody>
1676   while( db_step(&q)==SQLITE_ROW ){
1677     int rid = db_column_int(&q,0);
1678     const char *zUuid = db_column_text(&q, 1);
1679     const char *zDesc = db_column_text(&q, 2);
1680     int sz = db_column_int(&q,3);
1681     const char *zSrcId = db_column_text(&q,4);
1682     const char *zDate = db_column_text(&q,5);
1683     @ <tr><td align="right">%d(sz)</td>
1684     @ <td align="right">%d(rid)</td>
1685     @ <td align="right">%s(zSrcId)</td>
1686     @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
1687     @ <td align="left">%h(zDesc)</td>
1688     @ <td align="left">%z(href("%R/timeline?c=%T",zDate))%s(zDate)</a></td>
1689     @ </tr>
1690   }
1691   @ </tbody></table>
1692   db_finalize(&q);
1693   style_table_sorter();
1694   style_finish_page();
1695 }
1696 
1697 /*
1698 ** COMMAND: test-unsent
1699 **
1700 ** Usage: %fossil test-unsent
1701 **
1702 ** Show all artifacts in the unsent table
1703 */
test_unsent_cmd(void)1704 void test_unsent_cmd(void){
1705   db_find_and_open_repository(0,0);
1706   describe_artifacts_to_stdout("IN unsent", 0);
1707 }
1708 
1709 /*
1710 ** COMMAND: test-unclustered
1711 **
1712 ** Usage: %fossil test-unclustered
1713 **
1714 ** Show all artifacts in the unclustered table
1715 */
test_unclusterd_cmd(void)1716 void test_unclusterd_cmd(void){
1717   db_find_and_open_repository(0,0);
1718   describe_artifacts_to_stdout("IN unclustered", 0);
1719 }
1720 
1721 /*
1722 ** COMMAND: test-phantoms
1723 **
1724 ** Usage: %fossil test-phantoms
1725 **
1726 ** Show all phantom artifacts
1727 */
test_phatoms_cmd(void)1728 void test_phatoms_cmd(void){
1729   db_find_and_open_repository(0,0);
1730   describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0);
1731 }
1732 
1733 /* Maximum number of collision examples to remember */
1734 #define MAX_COLLIDE 25
1735 
1736 /*
1737 ** Generate a report on the number of collisions in artifact hashes
1738 ** generated by the SQL given in the argument.
1739 */
collision_report(const char * zSql)1740 static void collision_report(const char *zSql){
1741   int i, j, kk;
1742   int nHash = 0;
1743   Stmt q;
1744   char zPrev[HNAME_MAX+1];
1745   struct {
1746     int cnt;
1747     char *azHit[MAX_COLLIDE];
1748     char z[HNAME_MAX+1];
1749   } aCollide[HNAME_MAX+1];
1750   memset(aCollide, 0, sizeof(aCollide));
1751   memset(zPrev, 0, sizeof(zPrev));
1752   db_prepare(&q,"%s",zSql/*safe-for-%s*/);
1753   while( db_step(&q)==SQLITE_ROW ){
1754     const char *zUuid = db_column_text(&q,0);
1755     int n = db_column_bytes(&q,0);
1756     int i;
1757     nHash++;
1758     for(i=0; zPrev[i] && zPrev[i]==zUuid[i]; i++){}
1759     if( i>0 && i<=HNAME_MAX ){
1760       if( i>=4 && aCollide[i].cnt<MAX_COLLIDE ){
1761         aCollide[i].azHit[aCollide[i].cnt] = mprintf("%.*s", i, zPrev);
1762       }
1763       aCollide[i].cnt++;
1764       if( aCollide[i].z[0]==0 ) memcpy(aCollide[i].z, zPrev, n+1);
1765     }
1766     memcpy(zPrev, zUuid, n+1);
1767   }
1768   db_finalize(&q);
1769   @ <table border=1><thead>
1770   @ <tr><th>Length<th>Instances<th>First Instance</tr>
1771   @ </thead><tbody>
1772   for(i=1; i<=HNAME_MAX; i++){
1773     if( aCollide[i].cnt==0 ) continue;
1774     @ <tr><td>%d(i)<td>%d(aCollide[i].cnt)<td>%h(aCollide[i].z)</tr>
1775   }
1776   @ </tbody></table>
1777   @ <p>Total number of hashes: %d(nHash)</p>
1778   kk = 0;
1779   for(i=HNAME_MAX; i>=4; i--){
1780     if( aCollide[i].cnt==0 ) continue;
1781     if( aCollide[i].cnt>200 ) break;
1782     kk += aCollide[i].cnt;
1783     if( aCollide[i].cnt<25 ){
1784       @ <p>Collisions of length %d(i):
1785     }else{
1786       @ <p>First 25 collisions of length %d(i):
1787     }
1788     for(j=0; j<aCollide[i].cnt && j<MAX_COLLIDE; j++){
1789       char *zId = aCollide[i].azHit[j];
1790       if( zId==0 ) continue;
1791       @ %z(href("%R/ambiguous/%s",zId))%h(zId)</a>
1792     }
1793   }
1794   for(i=4; i<count(aCollide); i++){
1795     for(j=0; j<aCollide[i].cnt && j<MAX_COLLIDE; j++){
1796       fossil_free(aCollide[i].azHit[j]);
1797     }
1798   }
1799 }
1800 
1801 /*
1802 ** WEBPAGE: hash-collisions
1803 **
1804 ** Show the number of hash collisions for hash prefixes of various lengths.
1805 */
hash_collisions_webpage(void)1806 void hash_collisions_webpage(void){
1807   login_check_credentials();
1808   if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1809   style_header("Hash Prefix Collisions");
1810   style_submenu_element("Activity Reports", "reports");
1811   style_submenu_element("Stats", "stat");
1812   @ <h1>Hash Prefix Collisions on Check-ins</h1>
1813   collision_report("SELECT (SELECT uuid FROM blob WHERE rid=objid)"
1814                    "  FROM event WHERE event.type='ci'"
1815                    " ORDER BY 1");
1816   @ <h1>Hash Prefix Collisions on All Artifacts</h1>
1817   collision_report("SELECT uuid FROM blob ORDER BY 1");
1818   style_finish_page();
1819 }
1820