Fossil SCM

fossil-scm / src / purge.c
Blame History Raw 663 lines
1
/*
2
** Copyright (c) 2014 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 implement the "purge" command and
19
** related functionality for removing check-ins from a repository. It also
20
** manages the graveyard of purged content.
21
*/
22
#include "config.h"
23
#include "purge.h"
24
#include <assert.h>
25
26
/*
27
** SQL code used to initialize the schema of the graveyard.
28
**
29
** The purgeevent table contains one entry for each purge event. For each
30
** purge event, multiple artifacts might have been removed. Each removed
31
** artifact is stored as an entry in the purgeitem table.
32
**
33
** The purgeevent and purgeitem tables are not synced, even by the
34
** "fossil config" command. They exist only as a backup in case of a
35
** mistaken purge or for content recovery in case there is a bug in the
36
** purge command.
37
*/
38
static const char zPurgeInit[] =
39
@ CREATE TABLE IF NOT EXISTS "%w".purgeevent(
40
@ peid INTEGER PRIMARY KEY, -- Unique ID for the purge event
41
@ ctime DATETIME, -- When purge occurred. Seconds since 1970.
42
@ pnotes TEXT -- Human-readable notes about the purge event
43
@ );
44
@ CREATE TABLE IF NOT EXISTS "%w".purgeitem(
45
@ piid INTEGER PRIMARY KEY, -- ID for the purge item
46
@ peid INTEGER REFERENCES purgeevent ON DELETE CASCADE, -- Purge event
47
@ orid INTEGER, -- Original RID before purged
48
@ uuid TEXT NOT NULL, -- hash of the purged artifact
49
@ srcid INTEGER, -- Basis purgeitem for delta compression
50
@ isPrivate BOOLEAN, -- True if artifact was originally private
51
@ sz INT NOT NULL, -- Uncompressed size of the purged artifact
52
@ desc TEXT, -- Brief description of this artifact
53
@ data BLOB -- Compressed artifact content
54
@ );
55
;
56
57
/*
58
** Flags for the purge_artifact_list() function.
59
*/
60
#if INTERFACE
61
#define PURGE_MOVETO_GRAVEYARD 0x0001 /* Move artifacts in graveyard */
62
#define PURGE_EXPLAIN_ONLY 0x0002 /* Show what would have happened */
63
#define PURGE_PRINT_SUMMARY 0x0004 /* Print a summary report at end */
64
#endif
65
66
/*
67
** This routine purges multiple artifacts from the repository, transferring
68
** those artifacts into the PURGEITEM table.
69
**
70
** Prior to invoking this routine, the caller must create a (TEMP) table
71
** named zTab that contains the RID of every artifact to be purged.
72
**
73
** This routine does the following:
74
**
75
** (1) Create the purgeevent and purgeitem tables, if required
76
** (2) Create a new purgeevent
77
** (3) Make sure no DELTA table entries depend on purged artifacts
78
** (4) Create new purgeitem entries for each purged artifact
79
** (5) Remove purged artifacts from the BLOB table
80
** (6) Remove references to purged artifacts in the following tables:
81
** (a) EVENT
82
** (b) PRIVATE
83
** (c) MLINK
84
** (d) PLINK
85
** (e) LEAF
86
** (f) UNCLUSTERED
87
** (g) UNSENT
88
** (h) BACKLINK
89
** (i) ATTACHMENT
90
** (j) TICKETCHNG
91
** (7) If any ticket artifacts were removed (6j) then rebuild the
92
** corresponding ticket entries. Possibly remove entries from
93
** the ticket table.
94
**
95
** Steps 1-4 (saving the purged artifacts into the graveyard) are only
96
** undertaken if the moveToGraveyard flag is true.
97
*/
98
int purge_artifact_list(
99
const char *zTab, /* TEMP table containing list of RIDS to be purged */
100
const char *zNote, /* Text of the purgeevent.pnotes field */
101
unsigned purgeFlags /* zero or more PURGE_* flags */
102
){
103
int peid = 0; /* New purgeevent ID */
104
Stmt q; /* General-use prepared statement */
105
char *z;
106
107
assert( g.repositoryOpen ); /* Main database must already be open */
108
db_begin_transaction();
109
z = sqlite3_mprintf("IN \"%w\"", zTab);
110
describe_artifacts(z);
111
sqlite3_free(z);
112
describe_artifacts_to_stdout(0, 0);
113
114
/* The explain-only flags causes this routine to list the artifacts
115
** that would have been purged but to not actually make any changes
116
** to the repository.
117
*/
118
if( purgeFlags & PURGE_EXPLAIN_ONLY ){
119
db_end_transaction(0);
120
return 0;
121
}
122
123
/* Make sure we are not removing a manifest that is the baseline of some
124
** manifest that is being left behind. This step is not strictly necessary.
125
** It is just a safety check. */
126
if( purge_baseline_out_from_under_delta(zTab) ){
127
fossil_panic("attempt to purge a baseline manifest without also purging "
128
"all of its deltas");
129
}
130
131
/* Make sure that no delta that is left behind requires a purged artifact
132
** as its basis. If such artifacts exist, go ahead and undelta them now.
133
*/
134
db_prepare(&q, "SELECT rid FROM delta WHERE srcid IN \"%w\""
135
" AND rid NOT IN \"%w\"", zTab, zTab);
136
while( db_step(&q)==SQLITE_ROW ){
137
int rid = db_column_int(&q, 0);
138
content_undelta(rid);
139
verify_before_commit(rid);
140
}
141
db_finalize(&q);
142
143
/* Construct the graveyard and copy the artifacts to be purged into the
144
** graveyard */
145
if( purgeFlags & PURGE_MOVETO_GRAVEYARD ){
146
db_multi_exec(zPurgeInit /*works-like:"%w%w"*/,
147
"repository", "repository");
148
db_multi_exec(
149
"INSERT INTO purgeevent(ctime,pnotes) VALUES(now(),%Q)", zNote
150
);
151
peid = db_last_insert_rowid();
152
db_prepare(&q, "SELECT rid FROM delta WHERE rid IN \"%w\""
153
" AND srcid NOT IN \"%w\"", zTab, zTab);
154
while( db_step(&q)==SQLITE_ROW ){
155
int rid = db_column_int(&q, 0);
156
content_undelta(rid);
157
}
158
db_finalize(&q);
159
db_multi_exec(
160
"INSERT INTO purgeitem(peid,orid,uuid,sz,isPrivate,desc,data)"
161
" SELECT %d, rid, uuid, size,"
162
" EXISTS(SELECT 1 FROM private WHERE private.rid=blob.rid),"
163
" (SELECT summary FROM description WHERE rid=blob.rid),"
164
" content"
165
" FROM blob WHERE rid IN \"%w\"",
166
peid, zTab
167
);
168
db_multi_exec(
169
"UPDATE purgeitem"
170
" SET srcid=(SELECT piid FROM purgeitem px, delta"
171
" WHERE px.orid=delta.srcid"
172
" AND delta.rid=purgeitem.orid)"
173
" WHERE peid=%d",
174
peid
175
);
176
}
177
178
/* Remove the artifacts being purged. Also remove all references to those
179
** artifacts from the secondary tables. */
180
db_multi_exec("DELETE FROM blob WHERE rid IN \"%w\"", zTab);
181
db_multi_exec("DELETE FROM delta WHERE rid IN \"%w\"", zTab);
182
db_multi_exec("DELETE FROM delta WHERE srcid IN \"%w\"", zTab);
183
db_multi_exec("DELETE FROM event WHERE objid IN \"%w\"", zTab);
184
db_multi_exec("DELETE FROM private WHERE rid IN \"%w\"", zTab);
185
db_multi_exec("DELETE FROM mlink WHERE mid IN \"%w\"", zTab);
186
db_multi_exec("DELETE FROM plink WHERE pid IN \"%w\"", zTab);
187
db_multi_exec("DELETE FROM plink WHERE cid IN \"%w\"", zTab);
188
db_multi_exec("DELETE FROM leaf WHERE rid IN \"%w\"", zTab);
189
db_multi_exec("DELETE FROM phantom WHERE rid IN \"%w\"", zTab);
190
db_multi_exec("DELETE FROM unclustered WHERE rid IN \"%w\"", zTab);
191
db_multi_exec("DELETE FROM unsent WHERE rid IN \"%w\"", zTab);
192
db_multi_exec("DELETE FROM tagxref"
193
" WHERE rid IN \"%w\""
194
" OR srcid IN \"%w\""
195
" OR origid IN \"%w\"", zTab, zTab, zTab);
196
db_multi_exec("DELETE FROM backlink WHERE srctype=0 AND srcid IN \"%w\"",
197
zTab);
198
db_multi_exec(
199
"CREATE TEMP TABLE \"%w_tickets\" AS"
200
" SELECT DISTINCT tkt_uuid FROM ticket WHERE tkt_id IN"
201
" (SELECT tkt_id FROM ticketchng WHERE tkt_rid IN \"%w\")",
202
zTab, zTab);
203
db_multi_exec("DELETE FROM ticketchng WHERE tkt_rid IN \"%w\"", zTab);
204
db_prepare(&q, "SELECT tkt_uuid FROM \"%w_tickets\"", zTab);
205
while( db_step(&q)==SQLITE_ROW ){
206
ticket_rebuild_entry(db_column_text(&q, 0));
207
}
208
db_finalize(&q);
209
/* db_multi_exec("DROP TABLE \"%w_tickets\"", zTab); */
210
211
/* Mission accomplished */
212
db_end_transaction(0);
213
214
if( purgeFlags & PURGE_PRINT_SUMMARY ){
215
fossil_print("%d artifacts purged\n",
216
db_int(0, "SELECT count(*) FROM \"%w\";", zTab));
217
fossil_print("undoable using \"%s purge undo %d\".\n",
218
g.nameOfExe, peid);
219
}
220
return peid;
221
}
222
223
/*
224
** The TEMP table named zTab contains RIDs for a set of check-ins.
225
**
226
** Check to see if any check-in in zTab is a baseline manifest for some
227
** delta manifest that is not in zTab. Return true if zTab contains a
228
** baseline for a delta that is not in zTab.
229
**
230
** This is a database integrity preservation check. The check-ins in zTab
231
** are about to be deleted or otherwise made inaccessible. This routine
232
** is checking to ensure that purging the check-ins in zTab will not delete
233
** a baseline manifest out from under a delta.
234
*/
235
int purge_baseline_out_from_under_delta(const char *zTab){
236
if( !db_table_has_column("repository","plink","baseid") ){
237
/* Skip this check if the current database is an older schema that
238
** does not contain the PLINK.BASEID field. */
239
return 0;
240
}else{
241
return db_int(0,
242
"SELECT 1 FROM plink WHERE baseid IN \"%w\" AND cid NOT IN \"%w\"",
243
zTab, zTab);
244
}
245
}
246
247
248
/*
249
** The TEMP table named zTab contains the RIDs for a set of check-in
250
** artifacts. Expand this set (by adding new entries to zTab) to include
251
** all other artifacts that are used by the check-ins in
252
** the original list.
253
**
254
** If the bExclusive flag is true, then the set is only expanded by
255
** artifacts that are used exclusively by the check-ins in the set.
256
** When bExclusive is false, then all artifacts used by the check-ins
257
** are added even if those artifacts are also used by other check-ins
258
** not in the set.
259
**
260
** The "fossil publish" command with the (undocumented) --test and
261
** --exclusive options can be used for interactive testing of this
262
** function.
263
*/
264
void find_checkin_associates(const char *zTab, int bExclusive){
265
db_begin_transaction();
266
267
/* Compute the set of files that need to be added to zTab */
268
db_multi_exec("CREATE TEMP TABLE \"%w_files\"(fid INTEGER PRIMARY KEY)",zTab);
269
db_multi_exec(
270
"INSERT OR IGNORE INTO \"%w_files\"(fid)"
271
" SELECT fid FROM mlink WHERE fid!=0 AND mid IN \"%w\"",
272
zTab, zTab
273
);
274
if( bExclusive ){
275
/* But take out all files that are referenced by check-ins not in zTab */
276
db_multi_exec(
277
"DELETE FROM \"%w_files\""
278
" WHERE fid IN (SELECT fid FROM mlink"
279
" WHERE fid IN \"%w_files\""
280
" AND mid NOT IN \"%w\")",
281
zTab, zTab, zTab
282
);
283
}
284
285
/* Compute the set of tags that need to be added to zTag */
286
db_multi_exec("CREATE TEMP TABLE \"%w_tags\"(tid INTEGER PRIMARY KEY)",zTab);
287
db_multi_exec(
288
"INSERT OR IGNORE INTO \"%w_tags\"(tid)"
289
" SELECT DISTINCT srcid FROM tagxref WHERE rid in \"%w\" AND srcid!=0",
290
zTab, zTab
291
);
292
if( bExclusive ){
293
/* But take out tags that references some check-ins in zTab and other
294
** check-ins not in zTab. The current Fossil implementation never creates
295
** such tags, so the following should usually be a no-op. But the file
296
** format specification allows such tags, so we should check for them.
297
*/
298
db_multi_exec(
299
"DELETE FROM \"%w_tags\""
300
" WHERE tid IN (SELECT srcid FROM tagxref"
301
" WHERE srcid IN \"%w_tags\""
302
" AND rid NOT IN \"%w\")",
303
zTab, zTab, zTab
304
);
305
}
306
307
/* Transfer the extra artifacts into zTab */
308
db_multi_exec(
309
"INSERT OR IGNORE INTO \"%w\" SELECT fid FROM \"%w_files\";"
310
"INSERT OR IGNORE INTO \"%w\" SELECT tid FROM \"%w_tags\";"
311
"DROP TABLE \"%w_files\";"
312
"DROP TABLE \"%w_tags\";",
313
zTab, zTab, zTab, zTab, zTab, zTab
314
);
315
316
db_end_transaction(0);
317
}
318
319
/*
320
** Display the content of a single purge event.
321
*/
322
static void purge_list_event_content(int peid){
323
Stmt q;
324
sqlite3_int64 sz = 0;
325
db_prepare(&q, "SELECT piid, substr(uuid,1,16), srcid, isPrivate,"
326
" length(data), desc"
327
" FROM purgeitem WHERE peid=%d", peid);
328
while( db_step(&q)==SQLITE_ROW ){
329
fossil_print(" %5d %s %4s %c %10d %s\n",
330
db_column_int(&q,0),
331
db_column_text(&q,1),
332
db_column_text(&q,2),
333
db_column_int(&q,3) ? 'P' : ' ',
334
db_column_int(&q,4),
335
db_column_text(&q,5));
336
sz += db_column_int(&q,4);
337
}
338
db_finalize(&q);
339
fossil_print("%.11c%16s%.8c%10lld\n", ' ', "Total:", ' ', sz);
340
}
341
342
/*
343
** Extract the content for purgeitem number piid into a Blob. Return
344
** the number of errors.
345
*/
346
static int purge_extract_item(
347
int piid, /* ID of the item to extract */
348
Blob *pOut /* Write the content into this blob */
349
){
350
Stmt q;
351
int srcid;
352
Blob h1, x;
353
static Bag busy;
354
355
db_prepare(&q, "SELECT uuid, srcid, data FROM purgeitem"
356
" WHERE piid=%d", piid);
357
if( db_step(&q)!=SQLITE_ROW ){
358
db_finalize(&q);
359
fossil_fatal("missing purge-item %d", piid);
360
}
361
if( bag_find(&busy, piid) ) return 1;
362
srcid = db_column_int(&q, 1);
363
blob_zero(pOut);
364
blob_zero(&x);
365
db_column_blob(&q, 2, &x);
366
blob_uncompress(&x, pOut);
367
blob_reset(&x);
368
if( srcid>0 ){
369
Blob baseline, out;
370
bag_insert(&busy, piid);
371
purge_extract_item(srcid, &baseline);
372
blob_zero(&out);
373
blob_delta_apply(&baseline, pOut, &out);
374
blob_reset(pOut);
375
*pOut = out;
376
blob_reset(&baseline);
377
}
378
bag_remove(&busy, piid);
379
blob_zero(&h1);
380
db_column_blob(&q, 0, &h1);
381
if( hname_verify_hash(pOut, blob_buffer(&h1), blob_size(&h1))==0 ){
382
fossil_fatal("incorrect artifact hash on %b", &h1);
383
}
384
blob_reset(&h1);
385
db_finalize(&q);
386
return 0;
387
}
388
389
/*
390
** There is a TEMP table ix(piid,srcid) containing a set of purgeitems
391
** that need to be transferred to the BLOB table. This routine does
392
** all items that have srcid=iSrc. The pBasis blob holds the content
393
** of the source document if iSrc>0.
394
*/
395
static void purge_item_resurrect(int iSrc, Blob *pBasis){
396
Stmt q;
397
static Bag busy;
398
assert( pBasis!=0 || iSrc==0 );
399
if( iSrc>0 ){
400
if( bag_find(&busy, iSrc) ){
401
fossil_fatal("delta loop while uncompressing purged artifacts");
402
}
403
bag_insert(&busy, iSrc);
404
}
405
db_prepare(&q,
406
"SELECT uuid, data, isPrivate, ix.piid"
407
" FROM ix, purgeitem"
408
" WHERE ix.srcid=%d"
409
" AND ix.piid=purgeitem.piid;",
410
iSrc
411
);
412
while( db_step(&q)==SQLITE_ROW ){
413
Blob h1, c1, c2;
414
int isPriv, rid;
415
blob_zero(&h1);
416
db_column_blob(&q, 0, &h1);
417
blob_zero(&c1);
418
db_column_blob(&q, 1, &c1);
419
blob_uncompress(&c1, &c1);
420
blob_zero(&c2);
421
if( pBasis ){
422
blob_delta_apply(pBasis, &c1, &c2);
423
blob_reset(&c1);
424
}else{
425
c2 = c1;
426
}
427
if( hname_verify_hash(&c2, blob_buffer(&h1), blob_size(&h1))==0 ){
428
fossil_fatal("incorrect hash on %b", &h1);
429
}
430
isPriv = db_column_int(&q, 2);
431
rid = content_put_ex(&c2, blob_str(&h1), 0, 0, isPriv);
432
if( rid==0 ){
433
fossil_fatal("%s", g.zErrMsg);
434
}else{
435
if( !isPriv ) content_make_public(rid);
436
content_get(rid, &c1);
437
manifest_crosslink(rid, &c1, MC_NO_ERRORS);
438
}
439
purge_item_resurrect(db_column_int(&q,3), &c2);
440
blob_reset(&c2);
441
}
442
db_finalize(&q);
443
if( iSrc>0 ) bag_remove(&busy, iSrc);
444
}
445
446
/*
447
** COMMAND: purge*
448
**
449
** The purge command removes content from a repository and stores that content
450
** in a "graveyard". The graveyard exists so that content can be recovered
451
** using the "fossil purge undo" command. The "fossil purge obliterate"
452
** command empties the graveyard, making the content unrecoverable.
453
**
454
** WARNING: This command can potentially destroy historical data and
455
** leave your repository in a goofy state. Know what you are doing!
456
** Make a backup of your repository before using this command!
457
**
458
** FURTHER WARNING: This command is a work-in-progress and may yet
459
** contain bugs.
460
**
461
** > fossil purge artifacts HASH... ?OPTIONS?
462
**
463
** Move arbitrary artifacts identified by the HASH list into the
464
** graveyard.
465
**
466
** > fossil purge cat HASH...
467
**
468
** Write the content of one or more artifacts in the graveyard onto
469
** standard output.
470
**
471
** > fossil purge checkins TAGS... ?OPTIONS?
472
**
473
** Move the check-ins or branches identified by TAGS and all of
474
** their descendants out of the repository and into the graveyard.
475
** If TAGS includes a branch name then it means all the check-ins
476
** on the most recent occurrence of that branch.
477
**
478
** > fossil purge files NAME ... ?OPTIONS?
479
**
480
** Move all instances of files called NAME into the graveyard.
481
** NAME should be the name of the file relative to the root of the
482
** repository. If NAME is a directory, then all files within that
483
** directory are moved.
484
**
485
** > fossil purge list|ls ?-l?
486
**
487
** Show the graveyard of prior purges. The -l option gives more
488
** detail in the output.
489
**
490
** > fossil purge obliterate ID... ?--force?
491
**
492
** Remove one or more purge events from the graveyard. Once a purge
493
** event is obliterated, it can no longer be undone. The --force
494
** option suppresses the confirmation prompt.
495
**
496
** > fossil purge tickets NAME ... ?OPTIONS?
497
**
498
** TBD...
499
**
500
** > fossil purge undo ID
501
**
502
** Restore the content previously removed by purge ID.
503
**
504
** > fossil purge wiki NAME ... ?OPTIONS?
505
**
506
** TBD...
507
**
508
** COMMON OPTIONS:
509
**
510
** --explain Make no changes, but show what would happen
511
** --dry-run An alias for --explain
512
*/
513
void purge_cmd(void){
514
int purgeFlags = PURGE_MOVETO_GRAVEYARD | PURGE_PRINT_SUMMARY;
515
const char *zSubcmd;
516
int n;
517
int i;
518
Stmt q;
519
520
if( g.argc<3 ) usage("SUBCOMMAND ?ARGS?");
521
zSubcmd = g.argv[2];
522
db_find_and_open_repository(0,0);
523
n = (int)strlen(zSubcmd);
524
if( find_option("explain",0,0)!=0 || find_option("dry-run",0,0)!=0 ){
525
purgeFlags |= PURGE_EXPLAIN_ONLY;
526
}
527
if( strncmp(zSubcmd, "artifacts", n)==0 ){
528
verify_all_options();
529
db_begin_transaction();
530
db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)");
531
for(i=3; i<g.argc; i++){
532
int r = name_to_typed_rid(g.argv[i], "");
533
db_multi_exec("INSERT OR IGNORE INTO ok(rid) VALUES(%d);", r);
534
}
535
describe_artifacts_to_stdout("IN ok", 0);
536
purge_artifact_list("ok", "", purgeFlags);
537
db_end_transaction(0);
538
}else if( strncmp(zSubcmd, "cat", n)==0 ){
539
int i, piid;
540
Blob content;
541
if( g.argc<4 ) usage("cat HASH...");
542
for(i=3; i<g.argc; i++){
543
piid = db_int(0, "SELECT piid FROM purgeitem WHERE uuid LIKE '%q%%'",
544
g.argv[i]);
545
if( piid==0 ) fossil_fatal("no such item: %s", g.argv[3]);
546
purge_extract_item(piid, &content);
547
blob_write_to_file(&content, "-");
548
blob_reset(&content);
549
}
550
}else if( strncmp(zSubcmd, "checkins", n)==0 ){
551
int vid;
552
if( find_option("explain",0,0)!=0 || find_option("dry-run",0,0)!=0 ){
553
purgeFlags |= PURGE_EXPLAIN_ONLY;
554
}
555
verify_all_options();
556
db_begin_transaction();
557
if( g.argc<=3 ) usage("checkins TAGS... [OPTIONS]");
558
db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)");
559
for(i=3; i<g.argc; i++){
560
int r = name_to_typed_rid(g.argv[i], "br");
561
compute_descendants(r, 1000000000);
562
}
563
vid = db_lget_int("checkout",0);
564
if( db_exists("SELECT 1 FROM ok WHERE rid=%d",vid) ){
565
fossil_fatal("cannot purge the current check-out");
566
}
567
find_checkin_associates("ok", 1);
568
purge_artifact_list("ok", "", purgeFlags);
569
db_end_transaction(0);
570
}else if( strncmp(zSubcmd, "files", n)==0 ){
571
verify_all_options();
572
db_begin_transaction();
573
db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)");
574
for(i=3; i<g.argc; i++){
575
db_multi_exec(
576
"INSERT OR IGNORE INTO ok(rid) "
577
" SELECT fid FROM mlink, filename"
578
" WHERE mlink.fnid=filename.fnid"
579
" AND (filename.name=%Q OR filename.name GLOB '%q/*')",
580
g.argv[i], g.argv[i]
581
);
582
}
583
purge_artifact_list("ok", "", purgeFlags);
584
db_end_transaction(0);
585
}else if( strncmp(zSubcmd, "list", n)==0 || strcmp(zSubcmd,"ls")==0 ){
586
int showDetail = find_option("l","l",0)!=0;
587
if( !db_table_exists("repository","purgeevent") ) return;
588
db_prepare(&q, "SELECT peid, datetime(ctime,'unixepoch',toLocal())"
589
" FROM purgeevent");
590
while( db_step(&q)==SQLITE_ROW ){
591
fossil_print("%4d on %s\n", db_column_int(&q,0), db_column_text(&q,1));
592
if( showDetail ){
593
purge_list_event_content(db_column_int(&q,0));
594
}
595
}
596
db_finalize(&q);
597
}else if( strncmp(zSubcmd, "obliterate", n)==0 ){
598
int i;
599
int bForce = find_option("force","f",0)!=0;
600
if( g.argc<4 ) usage("obliterate ID...");
601
if( !bForce ){
602
Blob ans;
603
char cReply;
604
prompt_user(
605
"Obliterating the graveyard will permanently delete information.\n"
606
"Changes cannot be undone. Continue (y/N)? ", &ans);
607
cReply = blob_str(&ans)[0];
608
if( cReply!='y' && cReply!='Y' ){
609
fossil_exit(1);
610
}
611
}
612
db_begin_transaction();
613
for(i=3; i<g.argc; i++){
614
int peid = atoi(g.argv[i]);
615
if( !db_exists("SELECT 1 FROM purgeevent WHERE peid=%d",peid) ){
616
fossil_fatal("no such purge event: %s", g.argv[i]);
617
}
618
db_multi_exec(
619
"DELETE FROM purgeevent WHERE peid=%d;"
620
"DELETE FROM purgeitem WHERE peid=%d;",
621
peid, peid
622
);
623
}
624
db_end_transaction(0);
625
}else if( strncmp(zSubcmd, "tickets", n)==0 ){
626
fossil_fatal("not yet implemented....");
627
}else if( strncmp(zSubcmd, "undo", n)==0 ){
628
int peid;
629
if( g.argc!=4 ) usage("undo ID");
630
peid = atoi(g.argv[3]);
631
if( (purgeFlags & PURGE_EXPLAIN_ONLY)==0 ){
632
db_begin_transaction();
633
db_multi_exec(
634
"CREATE TEMP TABLE ix("
635
" piid INTEGER PRIMARY KEY,"
636
" srcid INTEGER"
637
");"
638
"CREATE INDEX ixsrcid ON ix(srcid);"
639
"INSERT INTO ix(piid,srcid) "
640
" SELECT piid, coalesce(srcid,0) FROM purgeitem WHERE peid=%d;",
641
peid
642
);
643
db_multi_exec(
644
"DELETE FROM shun"
645
" WHERE uuid IN (SELECT uuid FROM purgeitem WHERE peid=%d);",
646
peid
647
);
648
manifest_crosslink_begin();
649
purge_item_resurrect(0, 0);
650
manifest_crosslink_end(0);
651
db_multi_exec("DELETE FROM purgeevent WHERE peid=%d", peid);
652
db_multi_exec("DELETE FROM purgeitem WHERE peid=%d", peid);
653
db_end_transaction(0);
654
}
655
}else if( strncmp(zSubcmd, "wiki", n)==0 ){
656
fossil_fatal("not yet implemented....");
657
}else{
658
fossil_fatal("unknown subcommand \"%s\".\n"
659
"should be one of: cat, checkins, files, list, obliterate,"
660
" tickets, undo, wiki", zSubcmd);
661
}
662
}
663

Keyboard Shortcuts

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