Fossil

Check-in [8745d0d579]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Merge tagview branch into mainline
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 8745d0d579e1b95e8fb766be7ef69eb7ed361744
User & Date: eric 2008-09-06 13:29:29.000
Context
2008-09-07
08:32
Remove small glitch that prevent fossil to be built with BSD make. ... (check-in: f3fb059eb6 user: cle tags: trunk)
2008-09-06
13:29
Merge tagview branch into mainline ... (check-in: 8745d0d579 user: eric tags: trunk)
13:16
Merge mainline into tagview branch ... (check-in: a55a0a49a3 user: eric tags: eric-tagview-rework, trunk)
2008-08-30
13:20
Add a much larger and more complete file-suffix to mimetype translation table to the "doc" method. ... (check-in: 8e66784522 user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/cgi.c.
198
199
200
201
202
203
204

205
206
207

208
209
210
211
212
213
214
  const char *zValue,   /* Value of the cookie.  Automatically escaped */
  const char *zPath,    /* Path cookie applies to.  NULL means "/" */
  int lifetime          /* Expiration of the cookie in seconds from now */
){
  if( zPath==0 ) zPath = g.zTop;
  if( lifetime>0 ){
    lifetime += (int)time(0);

    blob_appendf(&extraHeader,
       "Set-Cookie: %s=%t; Path=%s; expires=%s; Version=1\r\n",
        zName, zValue, zPath, cgi_rfc822_datestamp(lifetime));

  }else{
    blob_appendf(&extraHeader,
       "Set-Cookie: %s=%t; Path=%s; Version=1\r\n",
       zName, zValue, zPath);
  }
}








>

|
|
>







198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
  const char *zValue,   /* Value of the cookie.  Automatically escaped */
  const char *zPath,    /* Path cookie applies to.  NULL means "/" */
  int lifetime          /* Expiration of the cookie in seconds from now */
){
  if( zPath==0 ) zPath = g.zTop;
  if( lifetime>0 ){
    lifetime += (int)time(0);
    char * zDate = cgi_rfc822_datestamp(lifetime);
    blob_appendf(&extraHeader,
       "Set-Cookie: %s=%t; Path=%s; expires=%z; Version=1\r\n",
        zName, zValue, zPath, zDate);
    if( zDate[0] ) free( zDate );
  }else{
    blob_appendf(&extraHeader,
       "Set-Cookie: %s=%t; Path=%s; Version=1\r\n",
       zName, zValue, zPath);
  }
}

284
285
286
287
288
289
290

291

292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311

312

313
314
315
316
317
318
319
    iReplyStatus = 304;
    zReplyStatus = "Not Modified";
  }
#endif

  if( g.fullHttpReply ){
    fprintf(g.httpOut, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus);

    fprintf(g.httpOut, "Date: %s\r\n", cgi_rfc822_datestamp(time(0)));

    fprintf(g.httpOut, "Connection: close\r\n");
  }else{
    fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus);
  }

  if( blob_size(&extraHeader)>0 ){
    fprintf(g.httpOut, "%s", blob_buffer(&extraHeader));
  }

  if( g.isConst ){
    /* constant means that the input URL will _never_ generate anything
    ** else. In the case of attachments, the contents won't change because
    ** an attempt to change them generates a new attachment number. In the
    ** case of most /getfile calls for specific versions, the only way the
    ** content changes is if someone breaks the SCM. And if that happens, a
    ** stale cache is the least of the problem. So we provide an Expires
    ** header set to a reasonable period (default: one week).
    */
    /*time_t expires = time(0) + atoi(db_config("constant_expires","604800"));*/
    time_t expires = time(0) + 604800;

    fprintf(g.httpOut, "Expires: %s\r\n", cgi_rfc822_datestamp(expires));

  }

  /* Content intended for logged in users should only be cached in
  ** the browser, not some shared location.
  */
  fprintf(g.httpOut, "Cache-control: private\r\n");








>
|
>




















>
|
>







286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
    iReplyStatus = 304;
    zReplyStatus = "Not Modified";
  }
#endif

  if( g.fullHttpReply ){
    fprintf(g.httpOut, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus);
    char * zDate = cgi_rfc822_datestamp(time(0));
    fprintf(g.httpOut, "Date: %s\r\n", zDate );
    if( zDate[0] ) free( zDate );
    fprintf(g.httpOut, "Connection: close\r\n");
  }else{
    fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus);
  }

  if( blob_size(&extraHeader)>0 ){
    fprintf(g.httpOut, "%s", blob_buffer(&extraHeader));
  }

  if( g.isConst ){
    /* constant means that the input URL will _never_ generate anything
    ** else. In the case of attachments, the contents won't change because
    ** an attempt to change them generates a new attachment number. In the
    ** case of most /getfile calls for specific versions, the only way the
    ** content changes is if someone breaks the SCM. And if that happens, a
    ** stale cache is the least of the problem. So we provide an Expires
    ** header set to a reasonable period (default: one week).
    */
    /*time_t expires = time(0) + atoi(db_config("constant_expires","604800"));*/
    time_t expires = time(0) + 604800;
    char * zDate = cgi_rfc822_datestamp(expires);
    fprintf(g.httpOut, "Expires: %s\r\n", zDate );
    if( zDate[0] ) free( zDate );
  }

  /* Content intended for logged in users should only be cached in
  ** the browser, not some shared location.
  */
  fprintf(g.httpOut, "Cache-control: private\r\n");

1265
1266
1267
1268
1269
1270
1271


1272
1273
1274
1275
1276
1277
1278
     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0};


/*
** Returns an RFC822-formatted time string suitable for HTTP headers, among
** other things.
** Returned timezone is always GMT as required by HTTP/1.1 specification.


**
** See http://www.faqs.org/rfcs/rfc822.html, section 5
** and http://www.faqs.org/rfcs/rfc2616.html, section 3.3.
*/
char *cgi_rfc822_datestamp(time_t now){
  struct tm *pTm;
  pTm = gmtime(&now);







>
>







1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0};


/*
** Returns an RFC822-formatted time string suitable for HTTP headers, among
** other things.
** Returned timezone is always GMT as required by HTTP/1.1 specification.
** The returned string is allocated with malloc() and must be freed
** with free().
**
** See http://www.faqs.org/rfcs/rfc822.html, section 5
** and http://www.faqs.org/rfcs/rfc2616.html, section 3.3.
*/
char *cgi_rfc822_datestamp(time_t now){
  struct tm *pTm;
  pTm = gmtime(&now);
Changes to src/info.c.
913
914
915
916
917
918
919

920
921
922
923
924
925













926



927
928
929
930
931
932
933
** The argument is a UUID which might be a baseline or a file or
** a ticket changes or a wiki editor or something else. 
**
** Figure out what the UUID is and jump to it.
*/
void info_page(void){
  const char *zName;

  int rid, nName;
  
  zName = P("name");
  if( zName==0 ) fossil_redirect_home();
  nName = strlen(zName);
  if( nName<4 || nName>UUID_SIZE || !validate16(zName, nName) ){













    fossil_redirect_home();



  }
  if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%s*'", zName) ){
    tktview_page();
    return;
  }
  rid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%s*'", zName);
  if( rid==0 ){







>






>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>







913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
** The argument is a UUID which might be a baseline or a file or
** a ticket changes or a wiki editor or something else. 
**
** Figure out what the UUID is and jump to it.
*/
void info_page(void){
  const char *zName;
  Blob uuid;
  int rid, nName;
  
  zName = P("name");
  if( zName==0 ) fossil_redirect_home();
  nName = strlen(zName);
  if( nName<4 || nName>UUID_SIZE || !validate16(zName, nName) ){
    switch( sym_tag_to_uuid(zName, &uuid) ){
      case 1: {
        /* got one UUID, use it */
        zName = blob_str(&uuid);
        break;
      }
      case 2: {
        /* go somewhere to show the multiple UUIDs */
        tagview_page();
        return;
        break;
      }
      default: {
        fossil_redirect_home();
        break;
      }
    }
  }
  if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%s*'", zName) ){
    tktview_page();
    return;
  }
  rid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%s*'", zName);
  if( rid==0 ){
Changes to src/name.c.
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
** Return the number of errors.
*/
int name_to_uuid(Blob *pName, int iErrPriority){
  int rc;
  int sz;
  sz = blob_size(pName);
  if( sz>UUID_SIZE || sz<4 || !validate16(blob_buffer(pName), sz) ){
    Stmt q;
    Blob uuid;
    static const char prefix[] = "tag:";
    static const int preflen = sizeof(prefix)-1;
    const char *zName = blob_str(pName);

    if( strncmp(zName, prefix, preflen)==0 ){
      zName += preflen;
    }

    db_prepare(&q,
      "SELECT (SELECT uuid FROM blob WHERE rid=objid)"
      "  FROM tagxref JOIN event ON rid=objid"
      " WHERE tagid=(SELECT tagid FROM tag WHERE tagname='sym-'||%Q)"
      "   AND tagtype>0"
      "   AND value IS NULL"
      " ORDER BY event.mtime DESC",
      zName
    );
    blob_zero(&uuid);
    if( db_step(&q)==SQLITE_ROW ){
      db_column_blob(&q, 0, &uuid);
    }
    db_finalize(&q);
    if( blob_size(&uuid)==0 ){
      fossil_error(iErrPriority, "not a valid object name: %s", zName);
      blob_reset(&uuid);
      return 1;
    }else{
      blob_reset(pName);
      *pName = uuid;







<









<
<
<
<
<
<
<
<
<
<
<
|
<
<







42
43
44
45
46
47
48

49
50
51
52
53
54
55
56
57











58


59
60
61
62
63
64
65
** Return the number of errors.
*/
int name_to_uuid(Blob *pName, int iErrPriority){
  int rc;
  int sz;
  sz = blob_size(pName);
  if( sz>UUID_SIZE || sz<4 || !validate16(blob_buffer(pName), sz) ){

    Blob uuid;
    static const char prefix[] = "tag:";
    static const int preflen = sizeof(prefix)-1;
    const char *zName = blob_str(pName);

    if( strncmp(zName, prefix, preflen)==0 ){
      zName += preflen;
    }












    sym_tag_to_uuid(zName, &uuid);


    if( blob_size(&uuid)==0 ){
      fossil_error(iErrPriority, "not a valid object name: %s", zName);
      blob_reset(&uuid);
      return 1;
    }else{
      blob_reset(pName);
      *pName = uuid;
109
110
111
112
113
114
115













































116
117
118
119
120
121
122
      }
    }
  }else{
    rc = 0;
  }
  return rc;
}














































/*
** COMMAND:  test-name-to-uuid
**
** Convert a name to a full UUID.
*/
void test_name_to_uuid(void){







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
      }
    }
  }else{
    rc = 0;
  }
  return rc;
}

/*
** This routine takes a name which might be a tag and attempts to
** produce a UUID. The UUID (if any) is returned in the blob pointed
** to by the second argument.
**
** Return as follows:
**      0   Name is not a tag
**      1   A single UUID was found
**      2   More than one UUID was found, so this is presumably a
**          propagating tag. The return UUID is the most recent,
**          which is most likely to be the one wanted.
*/
int tag_to_uuid(const char *pName, Blob *pUuid,const char *pPrefix){
  Stmt q;
  int count = 0;
  db_prepare(&q,
    "SELECT (SELECT uuid FROM blob WHERE rid=objid)"
    "  FROM tagxref JOIN event ON rid=objid"
    " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q||%Q)"
    "   AND tagtype>0"
    "   AND value IS NULL"
    " ORDER BY event.mtime DESC",
    pPrefix,
    pName
  );
  blob_zero(pUuid);
  while( db_step(&q)==SQLITE_ROW ){
    count++;
    if(count>1){
      break;
    }
    db_column_blob(&q, 0, pUuid);
  }
  db_finalize(&q);
  return count;
}

/*
** This routine takes a name which might be a symbolic tag and
** attempts to produce a UUID. See tag_to_uuid.
*/
int sym_tag_to_uuid(const char *pName, Blob *pUuid){
    return tag_to_uuid(pName,pUuid,"sym-");
}

/*
** COMMAND:  test-name-to-uuid
**
** Convert a name to a full UUID.
*/
void test_name_to_uuid(void){
Changes to src/schema.c.
295
296
297
298
299
300
301



302
303
304
305
306
307
308
# define TAG_BGCOLOR    1     /* Set the background color for display */
# define TAG_COMMENT    2     /* The check-in comment */
# define TAG_USER       3     /* User who made a checking */
# define TAG_HIDDEN     4     /* Do not display or sync */
# define TAG_PRIVATE    5     /* Display but do not sync */
# define TAG_CLUSTER    6     /* A cluster */
#endif




/*
** The schema for the locate FOSSIL database file found at the root
** of very check-out.  This database contains the complete state of
** the checkout.
*/
const char zLocalSchema[] =







>
>
>







295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# define TAG_BGCOLOR    1     /* Set the background color for display */
# define TAG_COMMENT    2     /* The check-in comment */
# define TAG_USER       3     /* User who made a checking */
# define TAG_HIDDEN     4     /* Do not display or sync */
# define TAG_PRIVATE    5     /* Display but do not sync */
# define TAG_CLUSTER    6     /* A cluster */
#endif
#if EXPORT_INTERFACE
# define MAX_INT_TAG    6     /* The largest pre-assigned tag id */
#endif

/*
** The schema for the locate FOSSIL database file found at the root
** of very check-out.  This database contains the complete state of
** the checkout.
*/
const char zLocalSchema[] =
Changes to src/style.c.
324
325
326
327
328
329
330






331
332
333
334
335
336
337
@   font-size: 0.8em;
@   margin-top: 12px;
@   padding: 5px 10px 5px 10px;
@   text-align: right;
@   background-color: #558195;
@   color: white;
@ }






@ 
@ /* <verbatim> blocks */
@ pre.verbatim {
@    background-color: #f5f5f5;
@    padding: 0.5em;
@}
@







>
>
>
>
>
>







324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
@   font-size: 0.8em;
@   margin-top: 12px;
@   padding: 5px 10px 5px 10px;
@   text-align: right;
@   background-color: #558195;
@   color: white;
@ }
@
@ /* Make the links in the footer less ugly... */
@ div.footer a { color: white; }
@ div.footer a:link { color: white; }
@ div.footer a:visited { color: white; }
@ div.footer a:hover { background-color: white; color: #558195; }
@ 
@ /* <verbatim> blocks */
@ pre.verbatim {
@    background-color: #f5f5f5;
@    padding: 0.5em;
@}
@
Changes to src/tagview.c.
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
  zSql = mprintf( 
    "SELECT "
    "   linktagid(t.tagid) AS 'Tag ID',"
    "   linktagname(t.tagname) AS 'Name',"
    "   DATETIME(tx.mtime) AS 'Timestamp',"
    "   linkuuid(b.uuid) AS 'Version'"
    "  FROM tag t, tagxref tx, blob b "
    " WHERE t.tagid=tx.tagid AND tx.srcid=b.rid"
    "   AND tx.tagtype!=0 %s "
    TAGVIEW_DEFAULT_FILTER
    " ORDER BY tx.mtime DESC %s",
    zLikeClause, zLimit
  );
  db_generic_query_view(zSql, 1);
  free(zSql);
  if( zLikeClause[0] ) free(zLikeClause);
  if( zLimit[0] ) free(zLimit);
}

/*
** A small search form which forwards to ?like=SEARCH_STRING
*/
static void tagview_page_search_miniform(void){
  char const * like = P("like");
  @ <div style='font-size:smaller'>
  @ <form action='/tagview' method='post'>
  @ Search for tags: 
  @ <input type='text' name='like' value='%h((like?like:""))' size='10'/>
  @ <input type='submit'/>
  @ </form>
  @ </div>
}








|

















|







58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
  zSql = mprintf( 
    "SELECT "
    "   linktagid(t.tagid) AS 'Tag ID',"
    "   linktagname(t.tagname) AS 'Name',"
    "   DATETIME(tx.mtime) AS 'Timestamp',"
    "   linkuuid(b.uuid) AS 'Version'"
    "  FROM tag t, tagxref tx, blob b "
    " WHERE t.tagid=tx.tagid AND tx.rid=b.rid"
    "   AND tx.tagtype!=0 %s "
    TAGVIEW_DEFAULT_FILTER
    " ORDER BY tx.mtime DESC %s",
    zLikeClause, zLimit
  );
  db_generic_query_view(zSql, 1);
  free(zSql);
  if( zLikeClause[0] ) free(zLikeClause);
  if( zLimit[0] ) free(zLimit);
}

/*
** A small search form which forwards to ?like=SEARCH_STRING
*/
static void tagview_page_search_miniform(void){
  char const * like = P("like");
  @ <div style='font-size:smaller'>
  @ <form action='tagview' method='post'>
  @ Search for tags: 
  @ <input type='text' name='like' value='%h((like?like:""))' size='10'/>
  @ <input type='submit'/>
  @ </form>
  @ </div>
}

103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
  @ <h2>Tag #%d(tagid):</h2>
  zSql = mprintf( 
    "SELECT DISTINCT"
    "       linktagname(t.tagname) AS 'Tag Name',"
    "       DATETIME(tx.mtime) AS 'Timestamp',"
    "       linkuuid(b.uuid) AS 'Version'"
    "  FROM tag t, tagxref tx, blob b"
    " WHERE t.tagid=%d AND t.tagid=tx.tagid AND tx.srcid=b.rid "
    TAGVIEW_DEFAULT_FILTER
    " ORDER BY tx.mtime DESC",
    tagid
  );
  db_generic_query_view(zSql, 1);
  free(zSql);
}

/*
** Lists all tags matching the given tag name.
*/
static void tagview_page_tag_by_name( char const * tagname ){
  char *zSql;
  @ <h2>Tag '%s(tagname)':</h2>
  zSql = mprintf( 
    "SELECT DISTINCT"
    "       linktagid(t.tagid) AS 'Tag ID',"
    "       DATETIME(tx.mtime) AS 'Timestamp',"
    "       linkuuid(b.uuid) AS 'Version'"
    "  FROM tag t, tagxref tx, blob b "
    " WHERE t.tagname='%q' AND t.tagid=tx.tagid AND tx.srcid=b.rid "
    TAGVIEW_DEFAULT_FILTER
    " ORDER BY tx.mtime DESC",
    tagname);
  db_generic_query_view(zSql, 1);
  free(zSql);
}


/*
** WEBPAGE: /tagview
*/
void tagview_page(void){
  char const * check = 0;
  login_check_credentials();
  if( !g.okRdWiki ){
    login_needed();
  }
  style_header("Tags");
  login_anonymous_available();







|




















|







<

|

|







103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138

139
140
141
142
143
144
145
146
147
148
149
  @ <h2>Tag #%d(tagid):</h2>
  zSql = mprintf( 
    "SELECT DISTINCT"
    "       linktagname(t.tagname) AS 'Tag Name',"
    "       DATETIME(tx.mtime) AS 'Timestamp',"
    "       linkuuid(b.uuid) AS 'Version'"
    "  FROM tag t, tagxref tx, blob b"
    " WHERE t.tagid=%d AND t.tagid=tx.tagid AND tx.rid=b.rid "
    TAGVIEW_DEFAULT_FILTER
    " ORDER BY tx.mtime DESC",
    tagid
  );
  db_generic_query_view(zSql, 1);
  free(zSql);
}

/*
** Lists all tags matching the given tag name.
*/
static void tagview_page_tag_by_name( char const * tagname ){
  char *zSql;
  @ <h2>Tag '%s(tagname)':</h2>
  zSql = mprintf( 
    "SELECT DISTINCT"
    "       linktagid(t.tagid) AS 'Tag ID',"
    "       DATETIME(tx.mtime) AS 'Timestamp',"
    "       linkuuid(b.uuid) AS 'Version'"
    "  FROM tag t, tagxref tx, blob b "
    " WHERE t.tagname='%q' AND t.tagid=tx.tagid AND tx.rid=b.rid "
    TAGVIEW_DEFAULT_FILTER
    " ORDER BY tx.mtime DESC",
    tagname);
  db_generic_query_view(zSql, 1);
  free(zSql);
}


/*
** WEBP AGE: /tagview
*/
void old_tagview_page(void){
  char const * check = 0;
  login_check_credentials();
  if( !g.okRdWiki ){
    login_needed();
  }
  style_header("Tags");
  login_anonymous_available();
159
160
161
162
163
164
165







































































































  }else{
    tagview_page_default();
  }
  style_footer();
}

#undef TAGVIEW_DEFAULT_FILTER














































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
  }else{
    tagview_page_default();
  }
  style_footer();
}

#undef TAGVIEW_DEFAULT_FILTER

/*
** Generate a timeline for the chosen tag
*/
void tagview_print_timeline(char const *pName, char const *pPrefix){
  char *zSql;
  Stmt q;
  zSql = mprintf("%s AND EXISTS (SELECT 1"
         " FROM tagxref"
         "  WHERE tagxref.rid = event.objid"
         "  AND tagxref.tagid = (SELECT tagid FROM tag"
         "      WHERE tagname = %Q||%Q))"
         " ORDER BY 3 desc",
         timeline_query_for_www(), pPrefix, pName);
  db_prepare(&q, zSql);
  free(zSql);
  www_print_timeline(&q);
  db_finalize(&q);
}

/*
** WEBPAGE: /tagview
*/
void tagview_page(void){
  char const *zName = 0;
  int zTcount = 0;
  login_check_credentials();
  if( !g.okRead ){
    login_needed();
  }
  login_anonymous_available();
  if( 0 != (zName = P("name")) ){
    Blob uuid;
    style_header("Tagged Baselines");
    @ <h2>%s(zName):</h2>
    if( sym_tag_to_uuid(zName, &uuid) > 0){
      tagview_print_timeline(zName, "sym-");
    }else if( tag_to_uuid(zName, &uuid, "") > 0){
      tagview_print_timeline(zName, "");
    }else{
      @ There is no artifact with this tag.
    }
  }else{
    Stmt q;
    const char *prefix = "sym-";
    int preflen = strlen(prefix);
    style_header("Tags");
    db_prepare(&q,
      "SELECT tagname"
      "  FROM tag"
      " WHERE EXISTS(SELECT 1 FROM tagxref"
      "               WHERE tagid=tag.tagid"
      "                 AND tagtype>0)"
      " AND tagid > %d"
      " AND tagname NOT GLOB 'wiki-*'"
      " AND tagname NOT GLOB 'tkt-*'"
      " ORDER BY tagname",
      MAX_INT_TAG
    );
    @ <ul>
    while( db_step(&q)==SQLITE_ROW ){
      zTcount++;
      const char *name = db_column_text(&q, 0);
      if( g.okHistory ){
        if( strncmp(name, prefix, preflen)==0 ){
          @ <li><a href=%s(g.zBaseURL)/tagview?name=%s(name+preflen)>
          @ %s(name+preflen)</a>
        }else{
          @ <li><a href=%s(g.zBaseURL)/tagview?name=%s(name)>
          @ %s(name)</a>
        }
      }else{
        if( strncmp(name, prefix, preflen)==0 ){
          @ <li><strong>%s(name+preflen)</strong>
        }else{
          @ <li><strong>%s(name)</strong>
        }
      }
      if( strncmp(name, prefix, preflen)==0 ){
        @ (symbolic label)
      }
      @ </li>
    }
    @ </ul>
    if( zTcount == 0) {
      @ There are no relevant tags.
    }
    db_finalize(&q);
  }
  /*
   * Put in dummy functions since www_print_timeline has generated calls to
   * them. Some browsers don't seem to care, but better to be safe.
   * Actually, it would be nice to use the functions on this page, but at
   * the moment it looks to be too difficult.
   */
  @ <script>
  @ function xin(id){
  @ }
  @ function xout(id){
  @ }
  @ </script>
  style_footer();
}