Fossil SCM

fossil-scm / src / tag.c
Blame History Raw 993 lines
1
/*
2
** Copyright (c) 2007 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 manage tags
19
*/
20
#include "config.h"
21
#include "tag.h"
22
#include <assert.h>
23
24
/*
25
** Propagate the tag given by tagid to the children of pid.
26
**
27
** This routine assumes that tagid is a tag that should be
28
** propagated and that the tag is already present in pid.
29
**
30
** If tagtype is 2 then the tag is being propagated from an
31
** ancestor node. If tagtype is 0 it means a propagating tag is
32
** being blocked.
33
*/
34
static void tag_propagate(
35
int pid, /* Propagate the tag to children of this node */
36
int tagid, /* Tag to propagate */
37
int tagType, /* 2 for a propagating tag. 0 for an antitag */
38
int origId, /* Artifact of tag, when tagType==2 */
39
const char *zValue, /* Value of the tag. Might be NULL */
40
double mtime /* Timestamp on the tag */
41
){
42
PQueue queue; /* Queue of check-ins to be tagged */
43
Stmt s; /* Query the children of :pid to which to propagate */
44
Stmt ins; /* INSERT INTO tagxref */
45
Stmt eventupdate; /* UPDATE event */
46
47
assert( tagType==0 || tagType==2 );
48
pqueuex_init(&queue);
49
pqueuex_insert(&queue, pid, 0.0);
50
51
/* Query for children of :pid to which to propagate the tag.
52
** Three returns: (1) rid of the child. (2) timestamp of child.
53
** (3) True to propagate or false to block.
54
*/
55
db_prepare(&s,
56
"SELECT cid, plink.mtime,"
57
" coalesce(srcid=0 AND tagxref.mtime<:mtime, %d) AS doit"
58
" FROM plink LEFT JOIN tagxref ON cid=rid AND tagid=%d"
59
" WHERE pid=:pid AND isprim",
60
tagType==2, tagid
61
);
62
db_bind_double(&s, ":mtime", mtime);
63
64
if( tagType==2 ){
65
/* Set the propagated tag marker on check-in :rid */
66
db_prepare(&ins,
67
"REPLACE INTO tagxref(tagid, tagtype, srcid, origid, value, mtime, rid)"
68
"VALUES(%d,2,0,%d,%Q,:mtime,:rid)",
69
tagid, origId, zValue
70
);
71
db_bind_double(&ins, ":mtime", mtime);
72
}else{
73
/* Remove all references to the tag from check-in :rid */
74
zValue = 0;
75
db_prepare(&ins,
76
"DELETE FROM tagxref WHERE tagid=%d AND rid=:rid", tagid
77
);
78
}
79
if( tagid==TAG_BGCOLOR ){
80
db_prepare(&eventupdate,
81
"UPDATE event SET bgcolor=%Q WHERE objid=:rid", zValue
82
);
83
}
84
while( (pid = pqueuex_extract(&queue))!=0 ){
85
db_bind_int(&s, ":pid", pid);
86
while( db_step(&s)==SQLITE_ROW ){
87
int doit = db_column_int(&s, 2);
88
if( doit ){
89
int cid = db_column_int(&s, 0);
90
double mtime = db_column_double(&s, 1);
91
pqueuex_insert(&queue, cid, mtime);
92
db_bind_int(&ins, ":rid", cid);
93
db_step(&ins);
94
db_reset(&ins);
95
if( tagid==TAG_BGCOLOR ){
96
db_bind_int(&eventupdate, ":rid", cid);
97
db_step(&eventupdate);
98
db_reset(&eventupdate);
99
}
100
if( tagid==TAG_BRANCH ){
101
leaf_eventually_check(cid);
102
}
103
}
104
}
105
db_reset(&s);
106
}
107
pqueuex_clear(&queue);
108
db_finalize(&ins);
109
db_finalize(&s);
110
if( tagid==TAG_BGCOLOR ){
111
db_finalize(&eventupdate);
112
}
113
}
114
115
/*
116
** Propagate all propagatable tags in pid to the children of pid.
117
*/
118
void tag_propagate_all(int pid){
119
Stmt q;
120
db_prepare(&q,
121
"SELECT tagid, tagtype, mtime, value, origid FROM tagxref"
122
" WHERE rid=%d",
123
pid
124
);
125
while( db_step(&q)==SQLITE_ROW ){
126
int tagid = db_column_int(&q, 0);
127
int tagtype = db_column_int(&q, 1);
128
double mtime = db_column_double(&q, 2);
129
const char *zValue = db_column_text(&q, 3);
130
int origid = db_column_int(&q, 4);
131
if( tagtype==1 ) tagtype = 0;
132
tag_propagate(pid, tagid, tagtype, origid, zValue, mtime);
133
}
134
db_finalize(&q);
135
}
136
137
/*
138
** Get a tagid for the given TAG. Create a new tag if necessary
139
** if createFlag is 1.
140
*/
141
int tag_findid(const char *zTag, int createFlag){
142
int id;
143
id = db_int(0, "SELECT tagid FROM tag WHERE tagname=%Q", zTag);
144
if( id==0 && createFlag ){
145
db_multi_exec("INSERT INTO tag(tagname) VALUES(%Q)", zTag);
146
id = db_last_insert_rowid();
147
}
148
return id;
149
}
150
151
/*
152
** Insert a tag into the database.
153
**
154
** Also translate zTag into a tagid and return the tagid. (In other words
155
** if zTag is "bgcolor" then return TAG_BGCOLOR.)
156
*/
157
int tag_insert(
158
const char *zTag, /* Name of the tag (w/o the "+" or "-" prefix */
159
int tagtype, /* 0:cancel 1:singleton 2:propagated */
160
const char *zValue, /* Value if the tag is really a property */
161
int srcId, /* Artifact that contains this tag */
162
double mtime, /* Timestamp. Use default if <=0.0 */
163
int rid /* Artifact to which the tag is to attached */
164
){
165
Stmt s;
166
const char *zCol;
167
int tagid = tag_findid(zTag, 1);
168
int rc;
169
170
if( mtime<=0.0 ){
171
mtime = db_double(0.0, "SELECT julianday('now')");
172
}
173
db_prepare(&s,
174
"SELECT 1 FROM tagxref"
175
" WHERE tagid=%d"
176
" AND rid=%d"
177
" AND mtime>=:mtime",
178
tagid, rid
179
);
180
db_bind_double(&s, ":mtime", mtime);
181
rc = db_step(&s);
182
db_finalize(&s);
183
if( rc==SQLITE_ROW ){
184
/* Another entry that is more recent already exists. Do nothing */
185
return tagid;
186
}
187
db_prepare(&s,
188
"REPLACE INTO tagxref(tagid,tagtype,srcId,origid,value,mtime,rid)"
189
" VALUES(%d,%d,%d,%d,%Q,:mtime,%d)",
190
tagid, tagtype, srcId, rid, zValue, rid
191
);
192
db_bind_double(&s, ":mtime", mtime);
193
db_step(&s);
194
db_finalize(&s);
195
if( tagid==TAG_BRANCH ) leaf_eventually_check(rid);
196
if( tagtype==0 ){
197
zValue = 0;
198
}
199
zCol = 0;
200
switch( tagid ){
201
case TAG_BGCOLOR: {
202
zCol = "bgcolor";
203
break;
204
}
205
case TAG_COMMENT: {
206
zCol = "ecomment";
207
break;
208
}
209
case TAG_USER: {
210
zCol = "euser";
211
break;
212
}
213
case TAG_PRIVATE: {
214
db_multi_exec(
215
"INSERT OR IGNORE INTO private(rid) VALUES(%d);",
216
rid
217
);
218
}
219
}
220
if( zCol ){
221
db_multi_exec("UPDATE event SET \"%w\"=%Q WHERE objid=%d",
222
zCol, zValue, rid);
223
if( tagid==TAG_COMMENT && zValue!=0 ){
224
char *zCopy = fossil_strdup(zValue);
225
backlink_extract(zCopy, MT_NONE, rid, BKLNK_COMMENT, mtime, 1);
226
free(zCopy);
227
}
228
}
229
if( tagid==TAG_DATE ){
230
db_multi_exec("UPDATE event "
231
" SET mtime=julianday(%Q),"
232
" omtime=coalesce(omtime,mtime)"
233
" WHERE objid=%d",
234
zValue, rid);
235
}
236
if( tagid==TAG_PARENT && tagtype==1 ){
237
manifest_reparent_checkin(rid, zValue);
238
}
239
if( tagtype==1 ) tagtype = 0;
240
tag_propagate(rid, tagid, tagtype, rid, zValue, mtime);
241
return tagid;
242
}
243
244
245
/*
246
** COMMAND: test-tag
247
**
248
** Usage: %fossil test-tag (+|*|-)TAGNAME ARTIFACT-ID ?VALUE?
249
**
250
** Add a tag or anti-tag to the rebuildable tables of the local repository.
251
** No tag artifact is created so the new tag is erased the next
252
** time the repository is rebuilt. This routine is for testing
253
** use only.
254
*/
255
void testtag_cmd(void){
256
const char *zTag;
257
const char *zValue;
258
int rid;
259
int tagtype;
260
db_must_be_within_tree();
261
if( g.argc!=4 && g.argc!=5 ){
262
usage("TAGNAME ARTIFACT-ID ?VALUE?");
263
}
264
zTag = g.argv[2];
265
switch( zTag[0] ){
266
case '+': tagtype = 1; break;
267
case '*': tagtype = 2; break;
268
case '-': tagtype = 0; break;
269
default:
270
fossil_fatal("tag should begin with '+', '*', or '-'");
271
return;
272
}
273
rid = name_to_rid(g.argv[3]);
274
if( rid==0 ){
275
fossil_fatal("no such object: %s", g.argv[3]);
276
}
277
g.markPrivate = content_is_private(rid);
278
zValue = g.argc==5 ? g.argv[4] : 0;
279
db_begin_transaction();
280
tag_insert(zTag, tagtype, zValue, -1, 0.0, rid);
281
db_end_transaction(0);
282
}
283
284
/*
285
** OR this value into the tagtype argument to tag_add_artifact to
286
** cause the tag to be displayed on standard output rather than be
287
** inserted. Used for --dry-run options and debugging.
288
*/
289
#if INTERFACE
290
#define TAG_ADD_DRYRUN 0x04
291
#endif
292
293
/*
294
** Add a control record to the repository that either creates
295
** or cancels a tag.
296
**
297
** tagtype should normally be 0, 1, or 2. But if the TAG_ADD_DRYRUN bit
298
** is also set, then simply print the text of the tag on standard output
299
** (for testing purposes) rather than create the tag.
300
*/
301
void tag_add_artifact(
302
const char *zPrefix, /* Prefix to prepend to tag name */
303
const char *zTagname, /* The tag to add or cancel */
304
const char *zObjName, /* Name of object attached to */
305
const char *zValue, /* Value for the tag. Might be NULL */
306
int tagtype, /* 0:cancel 1:singleton 2:propagated */
307
const char *zDateOvrd, /* Override date string */
308
const char *zUserOvrd /* Override user name */
309
){
310
int rid;
311
int nrid;
312
char *zDate;
313
Blob uuid;
314
Blob ctrl;
315
Blob cksum;
316
static const char zTagtype[] = { '-', '+', '*' };
317
int dryRun = 0;
318
319
if( tagtype & TAG_ADD_DRYRUN ){
320
tagtype &= ~TAG_ADD_DRYRUN;
321
dryRun = 1;
322
}
323
assert( tagtype>=0 && tagtype<=2 );
324
user_select();
325
blob_zero(&uuid);
326
blob_append(&uuid, zObjName, -1);
327
if( name_to_uuid(&uuid, 9, "*") ){
328
fossil_fatal("%s", g.zErrMsg);
329
return;
330
}
331
rid = name_to_rid(blob_str(&uuid));
332
g.markPrivate = content_is_private(rid);
333
blob_zero(&ctrl);
334
335
#if 0
336
if( validate16(zTagname, strlen(zTagname)) ){
337
fossil_fatal(
338
"invalid tag name \"%s\" - might be confused with"
339
" a hexadecimal artifact ID",
340
zTagname
341
);
342
}
343
#endif
344
zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now");
345
blob_appendf(&ctrl, "D %s\n", zDate);
346
blob_appendf(&ctrl, "T %c%s%F %b",
347
zTagtype[tagtype], zPrefix, zTagname, &uuid);
348
if( tagtype>0 && zValue && zValue[0] ){
349
blob_appendf(&ctrl, " %F\n", zValue);
350
}else{
351
blob_appendf(&ctrl, "\n");
352
}
353
blob_appendf(&ctrl, "U %F\n", zUserOvrd ? zUserOvrd : login_name());
354
md5sum_blob(&ctrl, &cksum);
355
blob_appendf(&ctrl, "Z %b\n", &cksum);
356
if( dryRun ){
357
fossil_print("%s", blob_str(&ctrl));
358
blob_reset(&ctrl);
359
}else{
360
nrid = content_put(&ctrl);
361
manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS);
362
}
363
assert( blob_is_reset(&ctrl) );
364
if( g.localOpen ){
365
manifest_to_disk(rid);
366
}
367
}
368
369
/*
370
** If zTag is NULL or valid for use as a tag for the `tag add` and
371
** `tag cancel` commands, returns without side effects, else emits a
372
** fatal error message. We reject certain prefixes to avoid that
373
** clients cause undue grief by improperly tagging artifacts as being,
374
** e.g., wiki pages or tickets.
375
**
376
** Note that we intentionally allow the "sym-" prefix, partly for
377
** historical compatibility and partly because it can be applied
378
** properly, whereas the other reserved name types have special
379
** meanings for fossil and cannot be sensibly manually manipulated.
380
*/
381
static void tag_cmd_tagname_check(const char *zTag){
382
if(zTag && *zTag &&
383
(strncmp(zTag,"wiki-",5)==0
384
|| strncmp(zTag,"tkt-",4)==0
385
|| strncmp(zTag,"event-",6)==0)){
386
fossil_fatal("Invalid prefix for tag name: %s", zTag);
387
}
388
}
389
390
/*
391
** COMMAND: tag
392
**
393
** Usage: %fossil tag SUBCOMMAND ...
394
**
395
** Run various subcommands to control tags and properties.
396
**
397
** > fossil tag add ?OPTIONS? TAGNAME ARTIFACT-ID ?VALUE?
398
**
399
** Add a new tag or property to an artifact referenced by
400
** ARTIFACT-ID. For check-ins, the tag will be usable instead
401
** of a CHECK-IN in commands such as update and merge. If the
402
** --propagate flag is present and ARTIFACT-ID refers to a
403
** wiki page, forum post, technote, or check-in, the tag
404
** propagates to all descendants of that artifact.
405
**
406
** Options:
407
** --date-override DATETIME Set date and time added
408
** -n|--dry-run Display the tag text, but do not
409
** actually insert it into the database
410
** --propagate Propagating tag
411
** --raw Raw tag name. Ignored for
412
** non-CHECK-IN artifacts.
413
** --user-override USER Name USER when adding the tag
414
**
415
** The --date-override and --user-override options support
416
** importing history from other SCM systems. DATETIME has
417
** the form 'YYYY-MM-DD HH:MM:SS'.
418
**
419
** Note that fossil uses some tag prefixes internally and this
420
** command will reject tags with these prefixes to avoid
421
** causing problems or confusion: "wiki-", "tkt-", "event-".
422
**
423
** > fossil tag cancel ?--raw? TAGNAME ARTIFACT-ID
424
**
425
** Remove the tag TAGNAME from the artifact referenced by
426
** ARTIFACT-ID, and also remove the propagation of the tag to
427
** any descendants. Use the -n|--dry-run option to see
428
** what would have happened. Certain tag name prefixes are
429
** forbidden, as documented for the 'add' subcommand.
430
**
431
** Options:
432
** --date-override DATETIME Set date and time deleted
433
** -n|--dry-run Display the control artifact, but do
434
** not insert it into the database
435
** --raw Raw tag name. Ignored for
436
** non-CHECK-IN artifacts.
437
** --user-override USER Name USER when deleting the tag
438
**
439
** > fossil tag find ?OPTIONS? TAGNAME
440
**
441
** List all objects that use TAGNAME.
442
**
443
** Options:
444
** -n|--limit N Limit to N results
445
** --raw Interprets tag as a raw name instead of a
446
** branch name and matches any type of artifact.
447
** Changes the output to include only the
448
** hashes of matching objects.
449
** -t|--type TYPE One of: ci (check-in), w (wiki),
450
** e (event/technote), f (forum post),
451
** t (ticket). Default is all types. Ignored
452
** if --raw is used.
453
**
454
** > fossil tag list|ls ?OPTIONS? ?ARTIFACT-ID?
455
**
456
** List all tags or, if ARTIFACT-ID is supplied, all tags and
457
** their values for that artifact. The tagtype option accepts
458
** one of: propagated, singleton, cancel. For historical
459
** scripting compatibility, the internal tag types "wiki-",
460
** "tkt-", and "event-" (technote) are elided by default
461
** unless the --raw or --prefix options are used.
462
**
463
** Options:
464
** -v|--inverse Inverse the meaning of --tagtype TYPE
465
** --prefix List only tags with the given prefix
466
** Fossil-internal prefixes include "sym-"
467
** (branch name), "wiki-", "event-"
468
** (technote), and "tkt-" (ticket). The
469
** prefix is stripped from the resulting
470
** list unless --raw is provided. Ignored if
471
** ARTIFACT-ID is provided.
472
** --raw List raw names of tags
473
** --sep SEP Separator when concatenating values
474
** --tagtype TYPE List only tags of type TYPE, which must
475
** be one of: cancel, singleton, propagated
476
** --values List tag values
477
** If --sep is supplied, list all values of a tag on
478
** the same line, separated by SEP; otherwise list
479
** each value on its own line.
480
**
481
** The option --raw allows the manipulation of all types of tags
482
** used for various internal purposes in fossil. It also shows
483
** "cancel" tags for the "find" and "list" subcommands. You should
484
** not use this option to make changes unless you are sure what
485
** you are doing.
486
**
487
** If you need to use a tagname that might be confused with
488
** a hexadecimal baseline or artifact ID, you can explicitly
489
** disambiguate it by prefixing it with "tag:". For instance:
490
**
491
** fossil update decaf
492
**
493
** will be taken as an artifact or baseline ID and fossil will
494
** probably complain that no such revision was found. However
495
**
496
** fossil update tag:decaf
497
**
498
** will assume that "decaf" is a tag/branch name.
499
**
500
*/
501
void tag_cmd(void){
502
int n;
503
504
db_find_and_open_repository(0, 0);
505
if( g.argc<3 ){
506
goto tag_cmd_usage;
507
}
508
n = strlen(g.argv[2]);
509
if( n==0 ){
510
goto tag_cmd_usage;
511
}
512
513
if( strncmp(g.argv[2],"add",n)==0 ){
514
char *zValue;
515
int dryRun = 0;
516
int fRaw = find_option("raw","",0)!=0;
517
const char *zPrefix = "";
518
int fPropagate = find_option("propagate","",0)!=0;
519
const char *zDateOvrd = find_option("date-override",0,1);
520
const char *zUserOvrd = find_option("user-override",0,1);
521
const char *zTag;
522
const char *zObjId;
523
int objType;
524
if( find_option("dry-run","n",0)!=0 ) dryRun = TAG_ADD_DRYRUN;
525
if( g.argc!=5 && g.argc!=6 ){
526
usage("add ?options? TAGNAME ARTIFACT-ID ?VALUE?");
527
}
528
zTag = g.argv[3];
529
tag_cmd_tagname_check(zTag);
530
zObjId = g.argv[4];
531
zValue = g.argc==6 ? g.argv[5] : 0;
532
objType = whatis_rid_type(symbolic_name_to_rid(zObjId, 0));
533
switch(objType){
534
case 0:
535
fossil_fatal("Cannot resolve artifact ID: %s", zObjId);
536
break;
537
case CFTYPE_MANIFEST:
538
zPrefix = fRaw ? "" : "sym-";
539
break;
540
default: break;
541
}
542
db_begin_transaction();
543
tag_add_artifact(zPrefix, zTag, zObjId, zValue,
544
1+fPropagate+dryRun,zDateOvrd,zUserOvrd);
545
db_end_transaction(0);
546
}else
547
548
if( strncmp(g.argv[2],"branch",n)==0 ){
549
fossil_fatal("the \"fossil tag branch\" command is discontinued\n"
550
"Use the \"fossil branch new\" command instead.");
551
}else
552
553
if( strncmp(g.argv[2],"cancel",n)==0 ){
554
int dryRun = 0;
555
int fRaw = find_option("raw","",0)!=0;
556
const char *zPrefix = "";
557
const char *zDateOvrd = find_option("date-override",0,1);
558
const char *zUserOvrd = find_option("user-override",0,1);
559
const char *zTag;
560
const char *zObjId;
561
int objType;
562
if( find_option("dry-run","n",0)!=0 ) dryRun = TAG_ADD_DRYRUN;
563
if( g.argc!=5 ){
564
usage("cancel ?options? TAGNAME ARTIFACT-ID");
565
}
566
zTag = g.argv[3];
567
tag_cmd_tagname_check(zTag);
568
zObjId = g.argv[4];
569
objType = whatis_rid_type(symbolic_name_to_rid(zObjId, 0));
570
switch(objType){
571
case 0:
572
fossil_fatal("Cannot resolve artifact ID: %s", zObjId);
573
break;
574
case CFTYPE_MANIFEST:
575
zPrefix = fRaw ? "" : "sym-";
576
break;
577
default: break;
578
}
579
db_begin_transaction();
580
tag_add_artifact(zPrefix, zTag, zObjId, 0, dryRun,
581
zDateOvrd, zUserOvrd);
582
db_end_transaction(0);
583
}else
584
585
if( strncmp(g.argv[2],"find",n)==0 ){
586
Stmt q;
587
int fRaw = find_option("raw","",0)!=0;
588
const char *zFindLimit = find_option("limit","n",1);
589
const int nFindLimit = zFindLimit ? atoi(zFindLimit) : -2000;
590
const char *zType = find_option("type","t",1);
591
Blob sql = empty_blob;
592
const char *zTag;
593
if( zType==0 || zType[0]==0 ) zType = "*";
594
if( g.argc!=4 ){
595
usage("find ?--raw? ?-t|--type TYPE? ?-n|--limit #? TAGNAME");
596
}
597
zTag = g.argv[3];
598
if( fRaw ){
599
blob_append_sql(&sql,
600
"SELECT blob.uuid FROM tagxref, blob"
601
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
602
" AND tagxref.tagtype>0"
603
" AND blob.rid=tagxref.rid",
604
zTag
605
);
606
if( nFindLimit>0 ){
607
blob_append_sql(&sql, " LIMIT %d", nFindLimit);
608
}
609
db_prepare(&q, "%s", blob_sql_text(&sql));
610
blob_reset(&sql);
611
while( db_step(&q)==SQLITE_ROW ){
612
fossil_print("%s\n", db_column_text(&q, 0));
613
}
614
db_finalize(&q);
615
}else{
616
blob_append_sql(&sql,
617
"%s"
618
" AND event.type GLOB '%q'"
619
" AND blob.rid IN ("
620
" SELECT rid FROM tagxref"
621
" WHERE tagtype>0 AND tagid IN ("
622
" SELECT tagid FROM tag WHERE tagname IN "
623
" ('%q','sym-%q','wiki-%q','tkt-%q','event-%q')"
624
" )"
625
")"
626
" ORDER BY event.mtime DESC /*sort*/",
627
timeline_query_for_tty(), zType, zTag, zTag, zTag, zTag, zTag
628
);
629
db_prepare(&q, "%s", blob_sql_text(&sql));
630
blob_reset(&sql);
631
print_timeline(&q, nFindLimit, 79, 0, 0);
632
db_finalize(&q);
633
}
634
}else
635
636
if(( strncmp(g.argv[2],"list",n)==0 )||( strncmp(g.argv[2],"ls",n)==0 )){
637
Stmt q;
638
const int fRaw = find_option("raw","",0)!=0;
639
const char *zTagType = find_option("tagtype","t",1);
640
const int fInverse = find_option("inverse","v",0)!=0;
641
const char *zTagPrefix = find_option("prefix","",1);
642
int nTagType = fRaw ? -1 : 0;
643
int fValues = find_option("values","",0)!=0;
644
const char *zSep = find_option("sep","",1);
645
646
647
if( zTagType!=0 ){
648
int l = strlen(zTagType);
649
if( strncmp(zTagType,"cancel",l)==0 ){
650
nTagType = 0;
651
}else if( strncmp(zTagType,"singleton",l)==0 ){
652
nTagType = 1;
653
}else if( strncmp(zTagType,"propagated",l)==0 ){
654
nTagType = 2;
655
}else{
656
fossil_fatal("unrecognized tag type");
657
}
658
}
659
if( g.argc==3 ){
660
const int nTagPrefix = zTagPrefix ? (int)strlen(zTagPrefix) : 0;
661
if( !fValues ){
662
db_prepare(&q,
663
"SELECT tagname FROM tag"
664
" WHERE EXISTS(SELECT 1 FROM tagxref"
665
" WHERE tagid=tag.tagid"
666
" AND tagtype%s%d)"
667
" AND CASE WHEN %Q IS NULL THEN 1 ELSE tagname GLOB %Q||'*' "
668
" END ORDER BY tagname COLLATE uintnocase",
669
zTagType!=0 ? (fInverse!=0?"<>":"=") : ">"/*safe-for-%s*/,
670
nTagType, zTagPrefix, zTagPrefix
671
);
672
}else{
673
if( zSep ){
674
db_prepare(&q,
675
/* work around group_concat() with DISTINCT and custom separator */
676
"SELECT tagname,"
677
" rtrim(replace(group_concat(DISTINCT value||'@!' "
678
" ORDER BY value ASC), '@!,', %Q),'@!')"
679
" FROM tagxref, tag"
680
" WHERE tagxref.tagid=tag.tagid AND tagtype%s%d"
681
" AND CASE WHEN %Q IS NULL THEN 1 ELSE tagname GLOB %Q||'*' END"
682
" GROUP BY tagname"
683
" ORDER BY tagname COLLATE uintnocase",
684
( zSep && strlen(zSep)>0 ) ? zSep : ",",
685
zTagType!=0 ? (fInverse!=0?"<>":"=") : ">"/*safe-for-%s*/,
686
nTagType, zTagPrefix, zTagPrefix
687
);
688
}else{
689
db_prepare(&q,
690
"SELECT DISTINCT tagname, value"
691
" FROM tagxref, tag"
692
" WHERE tagxref.tagid=tag.tagid AND tagtype%s%d"
693
" AND CASE WHEN %Q IS NULL THEN 1 ELSE tagname GLOB %Q||'*' END"
694
" ORDER BY tagname, value COLLATE uintnocase",
695
zTagType!=0 ? (fInverse!=0?"<>":"=") : ">"/*safe-for-%s*/,
696
nTagType, zTagPrefix, zTagPrefix
697
);
698
}
699
}
700
while( db_step(&q)==SQLITE_ROW ){
701
const char *zName = db_column_text(&q, 0);
702
const char *zValue = db_column_text(&q, 1);
703
int nWidth = fValues ? 20 : 0;
704
const char *zzValue = (fValues && zValue) ? mprintf(" %s", zValue) : "";
705
if( fRaw ){
706
fossil_print("%-*s%s\n", nWidth, zName, zzValue);
707
}else if( nTagPrefix>0 ){
708
assert(db_column_bytes(&q,0)>=nTagPrefix);
709
fossil_print("%-*s%s\n", nWidth, &zName[nTagPrefix], zzValue);
710
}else if( strncmp(zName, "sym-", 4)==0 ){
711
fossil_print("%-*s%s\n", nWidth, &zName[4], zzValue);
712
}
713
}
714
db_finalize(&q);
715
}else if( g.argc==4 ){
716
char const *zObjId = g.argv[3];
717
const int rid = name_to_rid(zObjId);
718
const int objType = whatis_rid_type(rid);
719
int nTagOffset = 0;
720
721
zTagPrefix = 0;
722
if(objType<=0){
723
fossil_fatal("Cannot resolve artifact ID: %s", zObjId);
724
}else if(fRaw==0){
725
/* Figure out the tag prefix to strip */
726
switch(objType){
727
case CFTYPE_MANIFEST: zTagPrefix = "sym-"; break;
728
case CFTYPE_WIKI: zTagPrefix = "wiki-"; break;
729
case CFTYPE_TICKET: zTagPrefix = "tkt-"; break;
730
case CFTYPE_EVENT: zTagPrefix = "event-"; break;
731
default: break;
732
}
733
if(zTagPrefix!=0){
734
nTagOffset = (int)strlen(zTagPrefix);
735
}
736
}
737
db_prepare(&q,
738
"SELECT tagname, value FROM tagxref, tag"
739
" WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
740
" AND tagtype%s%d"
741
" ORDER BY tagname COLLATE uintnocase",
742
rid,
743
zTagType!=0 ? (fInverse!=0?"<>":"=") : ">"/*safe-for-%s*/,
744
nTagType
745
);
746
while( db_step(&q)==SQLITE_ROW ){
747
const char *zName = db_column_text(&q, 0);
748
const char *zValue = db_column_text(&q, 1);
749
if( zTagPrefix && strncmp(zName, zTagPrefix, nTagOffset)==0 ){
750
zName += nTagOffset;
751
}
752
if( zValue && zValue[0] ){
753
fossil_print("%s=%s\n", zName, zValue);
754
}else{
755
fossil_print("%s\n", zName);
756
}
757
}
758
db_finalize(&q);
759
}else{
760
usage("list ?OPTIONS? ?CHECK-IN?");
761
}
762
}else
763
{
764
goto tag_cmd_usage;
765
}
766
767
/* Cleanup */
768
return;
769
770
tag_cmd_usage:
771
usage("add|cancel|find|list ...");
772
}
773
774
/*
775
** COMMAND: reparent*
776
**
777
** Usage: %fossil reparent [OPTIONS] CHECK-IN PARENT ...
778
**
779
** Create a "parent" tag that causes CHECK-IN to be interpreted as a
780
** child of PARENT. If multiple PARENTs are listed, then the first is
781
** the primary parent and others are merge ancestors.
782
**
783
** This is an experts-only command. It is used to patch up a repository
784
** that has been damaged by a shun or that has been pieced together from
785
** two or more separate repositories. You should never need to reparent
786
** during normal operations.
787
**
788
** Reparenting is accomplished by adding a parent tag. So to undo the
789
** reparenting operation, simply delete the tag.
790
**
791
** --test Make database entries but do not add the tag artifact.
792
** So the reparent operation will be undone by the next
793
** "fossil rebuild" command.
794
** -n|--dry-run Print the tag that would have been created but do not
795
** actually change the database in any way.
796
** --date-override DATETIME Set the change time on the control artifact
797
** --user-override USER Set the user name on the control artifact
798
*/
799
void reparent_cmd(void){
800
int bTest = find_option("test","",0)!=0;
801
int rid;
802
int i;
803
Blob value;
804
char *zUuid;
805
int dryRun = 0;
806
const char *zDateOvrd; /* The change time on the control artifact */
807
const char *zUserOvrd; /* The user name on the control artifact */
808
809
if( find_option("dry-run","n",0)!=0 ) dryRun = TAG_ADD_DRYRUN;
810
zDateOvrd = find_option("date-override",0,1);
811
zUserOvrd = find_option("user-override",0,1);
812
db_find_and_open_repository(0, 0);
813
verify_all_options();
814
if( g.argc<4 ){
815
usage("[OPTIONS] CHECK-IN PARENT ...");
816
}
817
rid = name_to_typed_rid(g.argv[2], "ci");
818
blob_init(&value, 0, 0);
819
for(i=3; i<g.argc; i++){
820
int pid = name_to_typed_rid(g.argv[i], "ci");
821
if( i>3 ) blob_append(&value, " ", 1);
822
zUuid = rid_to_uuid(pid);
823
blob_append(&value, zUuid, strlen(zUuid));
824
fossil_free(zUuid);
825
}
826
if( bTest && !dryRun ){
827
tag_insert("parent", 1, blob_str(&value), -1, 0.0, rid);
828
}else{
829
zUuid = rid_to_uuid(rid);
830
tag_add_artifact("","parent",zUuid,blob_str(&value),1|dryRun,
831
zDateOvrd,zUserOvrd);
832
}
833
}
834
835
836
/*
837
** WEBPAGE: taglist
838
**
839
** List all non-propagating symbolic tags.
840
*/
841
void taglist_page(void){
842
Stmt q;
843
844
login_check_credentials();
845
if( !g.perm.Read ){
846
login_needed(g.anon.Read);
847
}
848
cgi_check_for_malice();
849
login_anonymous_available();
850
style_header("Tags");
851
style_adunit_config(ADUNIT_RIGHT_OK);
852
style_submenu_element("Timeline", "tagtimeline");
853
@ <h2>Non-propagating tags:</h2>
854
@ <table class='sortable' data-column-types='ktn' data-init-sort='2'>
855
@ <thead><tr>
856
@ <th>Tag Name</th>
857
@ <th>Most Recent</th>
858
@ <th>Count</th>
859
@ </tr></thead><tbody>
860
861
db_prepare(&q,
862
"SELECT substr(tagname,5),\n"
863
"row_number()OVER(ORDER BY tagname COLLATE uintnocase),\n"
864
"substr(datetime(max(event.mtime)),1,16),\n"
865
"count(*)\n"
866
"FROM tagxref JOIN tag USING(tagid)\n"
867
" JOIN event ON event.objid=tagxref.rid\n"
868
"WHERE tagname like 'sym-%%'\n"
869
"AND tagxref.tagtype=1\n"
870
"GROUP BY 1\n"
871
"ORDER BY 3 DESC;\n"
872
);
873
while( db_step(&q)==SQLITE_ROW ){
874
const char *zName = db_column_text(&q, 0);
875
int rn = db_column_int(&q, 1);
876
const char *zDate = db_column_text(&q, 2);
877
int cnt = db_column_int(&q, 3);
878
@ <tr><td data-sortkey="%06x(rn)">\
879
if( g.perm.Hyperlink ){
880
@ %z(chref("taglink","%R/timeline?t=%T",zName))%h(zName)</a></td>\
881
}else{
882
@ <span class="tagDsp">%h(zName)</span></td>\
883
}
884
@ <td>&nbsp;&nbsp;&nbsp;%h(zDate)&nbsp;&nbsp;&nbsp;</td>\
885
@ <td align="center">%d(cnt)</td></tr>
886
}
887
@ </table>
888
db_finalize(&q);
889
style_table_sorter();
890
style_finish_page();
891
}
892
893
/*
894
** WEBPAGE: /tagtimeline
895
**
896
** Render a timeline with all check-ins that contain non-propagating
897
** symbolic tags.
898
**
899
** Query parameters:
900
**
901
** ng No graph
902
** nohidden Hide check-ins with "hidden" tag
903
** onlyhidden Show only check-ins with "hidden" tag
904
** brbg Background color by branch name
905
** ubg Background color by user name
906
*/
907
void tagtimeline_page(void){
908
Blob sql = empty_blob;
909
Stmt q;
910
int tmFlags; /* Timeline display flags */
911
int fNoHidden = PB("nohidden")!=0; /* The "nohidden" query parameter */
912
int fOnlyHidden = PB("onlyhidden")!=0; /* The "onlyhidden" query parameter */
913
914
login_check_credentials();
915
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
916
917
style_header("Tagged Check-ins");
918
style_submenu_element("List", "taglist");
919
login_anonymous_available();
920
timeline_ss_submenu();
921
@ <h2>Check-ins with non-propagating tags:</h2>
922
blob_append(&sql, timeline_query_for_www(), -1);
923
blob_append_sql(&sql,
924
"AND blob.rid IN (SELECT rid FROM tagxref"
925
" WHERE tagtype=1 AND srcid>0"
926
" AND tagid IN (SELECT tagid FROM tag "
927
" WHERE tagname GLOB 'sym-*'))");
928
if( fNoHidden || fOnlyHidden ){
929
const char* zUnaryOp = fNoHidden ? "NOT" : "";
930
blob_append_sql(&sql,
931
" AND %s EXISTS(SELECT 1 FROM tagxref"
932
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
933
zUnaryOp/*safe-for-%s*/, TAG_HIDDEN);
934
}
935
db_prepare(&q, "%s ORDER BY event.mtime DESC /*sort*/", blob_sql_text(&sql));
936
blob_reset(&sql);
937
/* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too
938
** many descenders to (off-screen) parents. */
939
tmFlags = TIMELINE_XMERGE | TIMELINE_FILLGAPS | TIMELINE_NOSCROLL;
940
if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH;
941
if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR;
942
if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR;
943
www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, 0);
944
db_finalize(&q);
945
@ <br>
946
style_finish_page();
947
}
948
949
/*
950
** Returns true if the given blob.rid value has the given tag ID
951
** applied to it, else false.
952
*/
953
int rid_has_tag(int rid, int tagId){
954
return db_exists(
955
"SELECT tag.tagid FROM tagxref, tag"
956
" WHERE tagxref.rid=%d AND tagtype>0 "
957
" AND tag.tagid=%d"
958
" AND tagxref.tagid=tag.tagid",
959
rid, tagId
960
);
961
}
962
963
964
/*
965
** Returns tagxref.rowid if the given blob.rid has a tagxref.rid entry
966
** of an active (non-cancelled) tag matching the given rid and tag
967
** name string, else returns 0. Note that this function does not
968
** distinguish between a non-existent tag and a cancelled tag.
969
**
970
** Design note: the return value is the tagxref.rowid because that
971
** gives us an easy way to fetch the value of the tag later on, if
972
** needed.
973
*/
974
int rid_has_active_tag_name(int rid, const char *zTagName){
975
static Stmt q = empty_Stmt_m;
976
int rc;
977
978
assert( 0 != zTagName );
979
if( !q.pStmt ){
980
db_static_prepare(&q,
981
"SELECT x.rowid FROM tagxref x, tag t"
982
" WHERE x.rid=$rid AND x.tagtype>0 "
983
" AND x.tagid=t.tagid"
984
" AND t.tagname=$tagname"
985
);
986
}
987
db_bind_int(&q, "$rid", rid);
988
db_bind_text(&q, "$tagname", zTagName);
989
rc = (SQLITE_ROW==db_step(&q)) ? db_column_int(&q, 0) : 0;
990
db_reset(&q);
991
return rc;
992
}
993

Keyboard Shortcuts

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