Fossil SCM

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

Keyboard Shortcuts

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