| | @@ -37,11 +37,11 @@ |
| 37 | 37 | */ |
| 38 | 38 | static const char zPurgeInit[] = |
| 39 | 39 | @ CREATE TABLE IF NOT EXISTS "%w".purgeevent( |
| 40 | 40 | @ peid INTEGER PRIMARY KEY, -- Unique ID for the purge event |
| 41 | 41 | @ ctime DATETIME, -- Julian day number when purge occurred |
| 42 | | -@ pnotes TEXT, -- Human-readable notes about the purge event |
| 42 | +@ pnotes TEXT -- Human-readable notes about the purge event |
| 43 | 43 | @ ); |
| 44 | 44 | @ CREATE TABLE IF NOT EXISTS "%w".purgeitem( |
| 45 | 45 | @ peid INTEGER REFERENCES purgeevent ON DELETE CASCADE, -- Purge event |
| 46 | 46 | @ uuid TEXT NOT NULL, -- SHA1 hash of the purged artifact |
| 47 | 47 | @ sz INT NOT NULL, -- Uncompressed size of the purged artifact |
| | @@ -76,26 +76,32 @@ |
| 76 | 76 | ** (j) TICKETCHNG |
| 77 | 77 | ** (7) If any ticket artifacts were removed (6j) then rebuild the |
| 78 | 78 | ** corresponding ticket entries. Possibly remove entries from |
| 79 | 79 | ** the ticket table. |
| 80 | 80 | */ |
| 81 | | -void purge_artifact_list( |
| 81 | +int purge_artifact_list( |
| 82 | 82 | const char *zTab, /* TEMP table containing list of RIDS to be purged */ |
| 83 | 83 | const char *zNote /* Text of the purgeevent.pnotes field */ |
| 84 | 84 | ){ |
| 85 | 85 | int peid = 0; /* New purgeevent ID */ |
| 86 | 86 | Stmt q; /* General-use prepared statement */ |
| 87 | 87 | |
| 88 | 88 | assert( g.repositoryOpen ); /* Main database must already be open */ |
| 89 | + add_content_sql_commands(g.db); |
| 89 | 90 | db_begin_transaction(); |
| 91 | + if( purge_baseline_out_from_under_delta(zTab) ){ |
| 92 | + fossil_fatal("attempt to purge a baseline manifest without also purging " |
| 93 | + "all of its deltas"); |
| 94 | + } |
| 90 | 95 | db_multi_exec(zPurgeInit /*works-like:"%w%w"*/, |
| 91 | 96 | db_name("repository"), db_name("repository")); |
| 92 | 97 | db_multi_exec( |
| 93 | 98 | "INSERT INTO purgeevent(ctime,pnotes) VALUES(now(),%Q)", zNote |
| 94 | 99 | ); |
| 95 | 100 | peid = db_last_insert_rowid(); |
| 96 | | - db_prepare(&q, "SELECT rid FROM delta WHERE srcid IN \"%w\"", zTab); |
| 101 | + db_prepare(&q, "SELECT rid FROM delta WHERE srcid IN \"%w\"" |
| 102 | + " AND rid NOT IN \"%w\"", zTab, zTab); |
| 97 | 103 | while( db_step(&q)==SQLITE_ROW ){ |
| 98 | 104 | int rid = db_column_int(&q, 0); |
| 99 | 105 | content_undelta(rid); |
| 100 | 106 | verify_before_commit(rid); |
| 101 | 107 | } |
| | @@ -133,18 +139,200 @@ |
| 133 | 139 | ticket_rebuild_entry(db_column_text(&q, 0)); |
| 134 | 140 | } |
| 135 | 141 | db_finalize(&q); |
| 136 | 142 | db_multi_exec("DROP TABLE \"%w_tickets\"", zTab); |
| 137 | 143 | db_end_transaction(0); |
| 144 | + return peid; |
| 145 | +} |
| 146 | + |
| 147 | +/* |
| 148 | +** The TEMP table named zTab contains RIDs for a set of checkins. |
| 149 | +** |
| 150 | +** Check to see if any checkin in zTab is a baseline manifest for some |
| 151 | +** delta manifest that is not in zTab. Return true if zTab contains a |
| 152 | +** baseline for a delta that is not in zTab. |
| 153 | +** |
| 154 | +** This is a database integrity preservation check. The checkins in zTab |
| 155 | +** are about to be deleted or otherwise made inaccessible. This routine |
| 156 | +** is checking to ensure that purging the checkins in zTab will not delete |
| 157 | +** a baseline manifest out from under a delta. |
| 158 | +*/ |
| 159 | +int purge_baseline_out_from_under_delta(const char *zTab){ |
| 160 | + return db_int(0, |
| 161 | + "SELECT 1 FROM plink WHERE baseid IN \"%w\" AND cid NOT IN \"%w\"", |
| 162 | + zTab, zTab); |
| 138 | 163 | } |
| 164 | + |
| 139 | 165 | |
| 140 | 166 | /* |
| 141 | 167 | ** The TEMP table named zTab contains the RIDs for a set of checkin |
| 142 | 168 | ** artifacts. Expand this set (by adding new entries to zTab) to include |
| 143 | | -** all other facts that are used exclusively by the set of checkins in |
| 169 | +** all other artifacts that are used exclusively by the set of checkins in |
| 144 | 170 | ** the original list. |
| 145 | 171 | */ |
| 146 | | -void purge_checkin_associates(const char *zTab){ |
| 172 | +void find_checkin_associates(const char *zTab){ |
| 147 | 173 | db_begin_transaction(); |
| 148 | | - |
| 174 | + |
| 175 | + /* Compute the set of files that need to be added to zTab */ |
| 176 | + db_multi_exec("CREATE TEMP TABLE \"%w_files\"(fid INTEGER PRIMARY KEY)",zTab); |
| 177 | + db_multi_exec( |
| 178 | + "INSERT OR IGNORE INTO \"%w_files\"(fid)" |
| 179 | + " SELECT fid FROM mlink WHERE fid!=0 AND mid IN \"%w\"", |
| 180 | + zTab, zTab |
| 181 | + ); |
| 182 | + /* But take out all files that are referenced by check-ins not in zTab */ |
| 183 | + db_multi_exec( |
| 184 | + "DELETE FROM \"%w_files\"" |
| 185 | + " WHERE fid IN (SELECT fid FROM mlink" |
| 186 | + " WHERE fid IN \"%w_files\"" |
| 187 | + " AND mid NOT IN \"%w\")", |
| 188 | + zTab, zTab, zTab |
| 189 | + ); |
| 190 | + |
| 191 | + /* Compute the set of tags that need to be added to zTag */ |
| 192 | + db_multi_exec("CREATE TEMP TABLE \"%w_tags\"(tid INTEGER PRIMARY KEY)",zTab); |
| 193 | + db_multi_exec( |
| 194 | + "INSERT OR IGNORE INTO \"%w_tags\"(tid)" |
| 195 | + " SELECT DISTINCT srcid FROM tagxref WHERE rid in \"%w\" AND srcid!=0", |
| 196 | + zTab, zTab |
| 197 | + ); |
| 198 | + /* But take out tags that references some check-ins in zTab and other |
| 199 | + ** check-ins not in zTab. The current Fossil implementation never creates |
| 200 | + ** such tags, so the following should usually be a no-op. But the file |
| 201 | + ** format specification allows such tags, so we should check for them. |
| 202 | + */ |
| 203 | + db_multi_exec( |
| 204 | + "DELETE FROM \"%w_tags\"" |
| 205 | + " WHERE tid IN (SELECT srcid FROM tagxref" |
| 206 | + " WHERE srcid IN \"%w_tags\"" |
| 207 | + " AND rid NOT IN \"%w\")", |
| 208 | + zTab, zTab, zTab |
| 209 | + ); |
| 210 | + |
| 211 | + /* Transfer the extra artifacts into zTab */ |
| 212 | + db_multi_exec( |
| 213 | + "INSERT OR IGNORE INTO \"%w\" SELECT fid FROM \"%w_files\";" |
| 214 | + "INSERT OR IGNORE INTO \"%w\" SELECT tid FROM \"%w_tags\";" |
| 215 | + "DROP TABLE \"%w_files\";" |
| 216 | + "DROP TABLE \"%w_tags\";", |
| 217 | + zTab, zTab, zTab, zTab, zTab, zTab |
| 218 | + ); |
| 219 | + |
| 149 | 220 | db_end_transaction(0); |
| 150 | 221 | } |
| 222 | + |
| 223 | +/* |
| 224 | +** Display the content of a single purge event. |
| 225 | +*/ |
| 226 | +static void purge_event_content(int peid){ |
| 227 | + Stmt q; |
| 228 | + sqlite3_int64 sz1 = 0; |
| 229 | + sqlite3_int64 sz2 = 0; |
| 230 | + db_prepare(&q, "SELECT uuid, sz, length(data) FROM purgeitem WHERE peid=%d", |
| 231 | + peid); |
| 232 | + while( db_step(&q)==SQLITE_ROW ){ |
| 233 | + fossil_print(" %s %10d %10d\n", |
| 234 | + db_column_text(&q,0), |
| 235 | + db_column_int(&q,1), |
| 236 | + db_column_int(&q,2)); |
| 237 | + sz1 += db_column_int(&q,1); |
| 238 | + sz2 += db_column_int(&q,2); |
| 239 | + } |
| 240 | + db_finalize(&q); |
| 241 | + fossil_print(" %40s %10lld %10lld\n", "", sz1, sz2); |
| 242 | +} |
| 243 | + |
| 244 | +/* |
| 245 | +** COMMAND: purge |
| 246 | +** |
| 247 | +** The purge command is used to remove content from a repository into a |
| 248 | +** "graveyard" and also to show manage the graveyard and optionally restored |
| 249 | +** content into the repository from the graveyard. |
| 250 | +** |
| 251 | +** fossil purge list|ls [-l] |
| 252 | +** |
| 253 | +** Show the graveyard of prior purges. |
| 254 | +** |
| 255 | +** fossil purge undo ID |
| 256 | +** |
| 257 | +** Restore the content previously removed by purge ID. |
| 258 | +** |
| 259 | +** fossil purge [checkin] TAGS... [--explain] |
| 260 | +** |
| 261 | +** Move the checkins identified by TAGS and all of their descendants |
| 262 | +** out of the repository and into the graveyard. If the --explain option |
| 263 | +** is included, then the repository and graveyard are unchanged and |
| 264 | +** an explaination of what would have happened is shown instead. |
| 265 | +** |
| 266 | +** SUMMARY: |
| 267 | +** fossil purge [checkin] TAGS... [--explain] |
| 268 | +** fossil purge list |
| 269 | +** fossil purge undo ID |
| 270 | +*/ |
| 271 | +void purge_cmd(void){ |
| 272 | + const char *zSubcmd; |
| 273 | + int n; |
| 274 | + Stmt q; |
| 275 | + if( g.argc<3 ) usage("SUBCOMMAND ?ARGS?"); |
| 276 | + zSubcmd = g.argv[2]; |
| 277 | + db_find_and_open_repository(0,0); |
| 278 | + n = (int)strlen(zSubcmd); |
| 279 | + if( strncmp(zSubcmd, "list", n)==0 || strcmp(zSubcmd,"ls")==0 ){ |
| 280 | + int showDetail = find_option("l","l",0)!=0; |
| 281 | + if( db_int(-1,"PRAGMA table_info('purgeevent')")<0 ) return; |
| 282 | + db_prepare(&q, "SELECT peid, datetime(ctime) FROM purgeevent"); |
| 283 | + while( db_step(&q)==SQLITE_ROW ){ |
| 284 | + fossil_print("%4d on %s\n", db_column_int(&q,0), db_column_text(&q,1)); |
| 285 | + if( showDetail ){ |
| 286 | + purge_event_content(db_column_int(&q,0)); |
| 287 | + } |
| 288 | + } |
| 289 | + db_finalize(&q); |
| 290 | + }else if( strncmp(zSubcmd, "undo", n)==0 ){ |
| 291 | + fossil_print("Not yet implemented...\n"); |
| 292 | + }else{ |
| 293 | + int explainOnly = find_option("explain",0,0)!=0; |
| 294 | + int dryRun = find_option("dry-run",0,0)!=0; |
| 295 | + const char *zTag; |
| 296 | + int i; |
| 297 | + int vid; |
| 298 | + int nCkin; |
| 299 | + int nArtifact; |
| 300 | + verify_all_options(); |
| 301 | + db_begin_transaction(); |
| 302 | + i = strncmp(zSubcmd,"checkin",n)==0 ? 3 : 2; |
| 303 | + if( i>=g.argc ) usage("[checkin] TAGS... [--explain]"); |
| 304 | + db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)"); |
| 305 | + for(; i<g.argc; i++){ |
| 306 | + int r = symbolic_name_to_rid(g.argv[i], "ci"); |
| 307 | + if( r>0 ){ |
| 308 | + compute_descendants(r, 1000000000); |
| 309 | + }else if( r==0 ){ |
| 310 | + fossil_fatal("not found: %s", g.argv[i]); |
| 311 | + }else{ |
| 312 | + fossil_fatal("ambiguous: %s\n", g.argv[i]); |
| 313 | + } |
| 314 | + } |
| 315 | + vid = db_lget_int("checkout",0); |
| 316 | + if( db_exists("SELECT 1 FROM ok WHERE rid=%d",vid) ){ |
| 317 | + fossil_fatal("cannot purge the current checkout"); |
| 318 | + } |
| 319 | + nCkin = db_int(0, "SELECT count(*) FROM ok"); |
| 320 | + find_checkin_associates("ok"); |
| 321 | + nArtifact = db_int(0, "SELECT count(*) FROM ok"); |
| 322 | + if( explainOnly ){ |
| 323 | + i = 0; |
| 324 | + db_prepare(&q, "SELECT rid FROM ok"); |
| 325 | + while( db_step(&q)==SQLITE_ROW ){ |
| 326 | + if( i++ > 0 ) fossil_print("%.78c\n",'-'); |
| 327 | + whatis_rid(db_column_int(&q,0), 0); |
| 328 | + } |
| 329 | + db_finalize(&q); |
| 330 | + }else{ |
| 331 | + int peid = purge_artifact_list("ok",""); |
| 332 | + fossil_print("%d checkins and %d artifacts purged.\n", nCkin, nArtifact); |
| 333 | + fossil_print("undoable using \"%s purge undo %d\".\n", |
| 334 | + g.nameOfExe, peid); |
| 335 | + } |
| 336 | + db_end_transaction(explainOnly||dryRun); |
| 337 | + } |
| 338 | +} |
| 151 | 339 | |