| | @@ -22,11 +22,11 @@ |
| 22 | 22 | #include "config.h" |
| 23 | 23 | #include "purge.h" |
| 24 | 24 | #include <assert.h> |
| 25 | 25 | |
| 26 | 26 | /* |
| 27 | | -** SQL code used to initialize the schema of a bundle. |
| 27 | +** SQL code used to initialize the schema of the graveyard. |
| 28 | 28 | ** |
| 29 | 29 | ** The purgeevent table contains one entry for each purge event. For each |
| 30 | 30 | ** purge event, multiple artifacts might have been removed. Each removed |
| 31 | 31 | ** artifact is stored as an entry in the purgeitem table. |
| 32 | 32 | ** |
| | @@ -52,10 +52,19 @@ |
| 52 | 52 | @ desc TEXT, -- Brief description of this artifact |
| 53 | 53 | @ data BLOB -- Compressed artifact content |
| 54 | 54 | @ ); |
| 55 | 55 | ; |
| 56 | 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 | + |
| 57 | 66 | /* |
| 58 | 67 | ** This routine purges multiple artifacts from the repository, transfering |
| 59 | 68 | ** those artifacts into the PURGEITEM table. |
| 60 | 69 | ** |
| 61 | 70 | ** Prior to invoking this routine, the caller must create a (TEMP) table |
| | @@ -81,17 +90,17 @@ |
| 81 | 90 | ** (j) TICKETCHNG |
| 82 | 91 | ** (7) If any ticket artifacts were removed (6j) then rebuild the |
| 83 | 92 | ** corresponding ticket entries. Possibly remove entries from |
| 84 | 93 | ** the ticket table. |
| 85 | 94 | ** |
| 86 | | -** Stops 1-4 (saving the purged artifacts into the graveyard) are only |
| 95 | +** Steps 1-4 (saving the purged artifacts into the graveyard) are only |
| 87 | 96 | ** undertaken if the moveToGraveyard flag is true. |
| 88 | 97 | */ |
| 89 | 98 | int purge_artifact_list( |
| 90 | 99 | const char *zTab, /* TEMP table containing list of RIDS to be purged */ |
| 91 | 100 | const char *zNote, /* Text of the purgeevent.pnotes field */ |
| 92 | | - int moveToGraveyard /* Move purged artifacts into the graveyard */ |
| 101 | + unsigned purgeFlags /* zero or more PURGE_* flags */ |
| 93 | 102 | ){ |
| 94 | 103 | int peid = 0; /* New purgeevent ID */ |
| 95 | 104 | Stmt q; /* General-use prepared statement */ |
| 96 | 105 | char *z; |
| 97 | 106 | |
| | @@ -98,10 +107,20 @@ |
| 98 | 107 | assert( g.repositoryOpen ); /* Main database must already be open */ |
| 99 | 108 | db_begin_transaction(); |
| 100 | 109 | z = sqlite3_mprintf("IN \"%w\"", zTab); |
| 101 | 110 | describe_artifacts(z); |
| 102 | 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 | + } |
| 103 | 122 | |
| 104 | 123 | /* Make sure we are not removing a manifest that is the baseline of some |
| 105 | 124 | ** manifest that is being left behind. This step is not strictly necessary. |
| 106 | 125 | ** is is just a safety check. */ |
| 107 | 126 | if( purge_baseline_out_from_under_delta(zTab) ){ |
| | @@ -121,11 +140,11 @@ |
| 121 | 140 | } |
| 122 | 141 | db_finalize(&q); |
| 123 | 142 | |
| 124 | 143 | /* Construct the graveyard and copy the artifacts to be purged into the |
| 125 | 144 | ** graveyard */ |
| 126 | | - if( moveToGraveyard ){ |
| 145 | + if( purgeFlags & PURGE_MOVETO_GRAVEYARD ){ |
| 127 | 146 | db_multi_exec(zPurgeInit /*works-like:"%w%w"*/, |
| 128 | 147 | db_name("repository"), db_name("repository")); |
| 129 | 148 | db_multi_exec( |
| 130 | 149 | "INSERT INTO purgeevent(ctime,pnotes) VALUES(now(),%Q)", zNote |
| 131 | 150 | ); |
| | @@ -189,10 +208,17 @@ |
| 189 | 208 | db_finalize(&q); |
| 190 | 209 | /* db_multi_exec("DROP TABLE \"%w_tickets\"", zTab); */ |
| 191 | 210 | |
| 192 | 211 | /* Mission accomplished */ |
| 193 | 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 | + } |
| 194 | 220 | return peid; |
| 195 | 221 | } |
| 196 | 222 | |
| 197 | 223 | /* |
| 198 | 224 | ** The TEMP table named zTab contains RIDs for a set of check-ins. |
| | @@ -220,11 +246,11 @@ |
| 220 | 246 | |
| 221 | 247 | |
| 222 | 248 | /* |
| 223 | 249 | ** The TEMP table named zTab contains the RIDs for a set of check-in |
| 224 | 250 | ** artifacts. Expand this set (by adding new entries to zTab) to include |
| 225 | | -** all other artifacts that are used the set of check-ins in |
| 251 | +** all other artifacts that are used by the check-ins in |
| 226 | 252 | ** the original list. |
| 227 | 253 | ** |
| 228 | 254 | ** If the bExclusive flag is true, then the set is only expanded by |
| 229 | 255 | ** artifacts that are used exclusively by the check-ins in the set. |
| 230 | 256 | ** When bExclusive is false, then all artifacts used by the check-ins |
| | @@ -422,64 +448,113 @@ |
| 422 | 448 | db_finalize(&q); |
| 423 | 449 | if( iSrc>0 ) bag_remove(&busy, iSrc); |
| 424 | 450 | } |
| 425 | 451 | |
| 426 | 452 | /* |
| 427 | | -** COMMAND: purge |
| 453 | +** COMMAND: purge* |
| 428 | 454 | ** |
| 429 | 455 | ** The purge command removes content from a repository and stores that content |
| 430 | 456 | ** in a "graveyard". The graveyard exists so that content can be recovered |
| 431 | | -** using the "fossil purge undo" command. |
| 457 | +** using the "fossil purge undo" command. The "fossil purge obliterate" |
| 458 | +** command empties the graveyard, making the content unrecoverable. |
| 459 | +** |
| 460 | +** ==== WARNING: This command can potentially destroy historical data and ==== |
| 461 | +** ==== leave your repository in a goofy state. Know what you are doing! ==== |
| 462 | +** ==== Make a backup of your repository before using this command! ==== |
| 463 | +** |
| 464 | +** ==== FURTHER WARNING: This command is a work-in-progress and may yet ==== |
| 465 | +** ==== contains bugs. ==== |
| 466 | +** |
| 467 | +** fossil purge artifacts UUID... ?OPTIONS? |
| 468 | +** |
| 469 | +** Move arbitrary artifacts identified by the UUID list into the |
| 470 | +** graveyard. |
| 432 | 471 | ** |
| 433 | 472 | ** fossil purge cat UUID... |
| 434 | 473 | ** |
| 435 | 474 | ** Write the content of one or more artifacts in the graveyard onto |
| 436 | 475 | ** standard output. |
| 437 | 476 | ** |
| 438 | | -** fossil purge ?checkins? TAGS... ?OPTIONS? |
| 477 | +** fossil purge checkins TAGS... ?OPTIONS? |
| 439 | 478 | ** |
| 440 | | -** Move the check-ins identified by TAGS and all of their descendants |
| 441 | | -** out of the repository and into the graveyard. The "checkins" |
| 442 | | -** subcommand keyword is optional and can be omitted as long as TAGS |
| 443 | | -** does not conflict with any other subcommand. |
| 444 | | -** |
| 479 | +** Move the check-ins or branches identified by TAGS and all of |
| 480 | +** their descendants out of the repository and into the graveyard. |
| 445 | 481 | ** If TAGS includes a branch name then it means all the check-ins |
| 446 | 482 | ** on the most recent occurrence of that branch. |
| 447 | 483 | ** |
| 448 | | -** --explain Make no changes, but show what would happen. |
| 449 | | -** --dry-run Make no changes. |
| 484 | +** fossil purge files NAME ... ?OPTIONS? |
| 485 | +** |
| 486 | +** Move all instances of files called NAME into the graveyard. |
| 487 | +** NAME should be the name of the file relative to the root of the |
| 488 | +** repository. If NAME is a directory, then all files within that |
| 489 | +** directory are moved. |
| 450 | 490 | ** |
| 451 | 491 | ** fossil purge list|ls ?-l? |
| 452 | 492 | ** |
| 453 | 493 | ** Show the graveyard of prior purges. The -l option gives more |
| 454 | 494 | ** detail in the output. |
| 455 | 495 | ** |
| 456 | | -** fossil purge obliterate ID... |
| 496 | +** fossil purge obliterate ID... ?--force? |
| 457 | 497 | ** |
| 458 | 498 | ** Remove one or more purge events from the graveyard. Once a purge |
| 459 | | -** event is obliterated, it can no longer be undone. |
| 499 | +** event is obliterated, it can no longer be undone. The --force |
| 500 | +** option suppresses the confirmation prompt. |
| 501 | +** |
| 502 | +** fossil purge tickets NAME ... ?OPTIONS? |
| 503 | +** |
| 504 | +** TBD... |
| 460 | 505 | ** |
| 461 | 506 | ** fossil purge undo ID |
| 462 | 507 | ** |
| 463 | 508 | ** Restore the content previously removed by purge ID. |
| 464 | 509 | ** |
| 510 | +** fossil purge wiki NAME ... ?OPTIONS? |
| 511 | +** |
| 512 | +** TBD... |
| 513 | +** |
| 514 | +** COMMON OPTIONS: |
| 515 | +** |
| 516 | +** --explain Make no changes, but show what would happen. |
| 517 | +** --dry-run An alias for --explain |
| 518 | +** |
| 465 | 519 | ** SUMMARY: |
| 520 | +** fossil purge artifacts UUID.. [OPTIONS] |
| 466 | 521 | ** fossil purge cat UUID... |
| 467 | | -** fossil purge [checkins] TAGS... [--explain] |
| 522 | +** fossil purge checkins TAGS... [OPTIONS] |
| 523 | +** fossil purge files FILENAME... [OPTIONS] |
| 468 | 524 | ** fossil purge list |
| 469 | 525 | ** fossil purge obliterate ID... |
| 526 | +** fossil purge tickets NAME... [OPTIONS] |
| 470 | 527 | ** fossil purge undo ID |
| 528 | +** fossil purge wiki NAME... [OPTIONS] |
| 471 | 529 | */ |
| 472 | 530 | void purge_cmd(void){ |
| 531 | + int purgeFlags = PURGE_MOVETO_GRAVEYARD | PURGE_PRINT_SUMMARY; |
| 473 | 532 | const char *zSubcmd; |
| 474 | 533 | int n; |
| 534 | + int i; |
| 475 | 535 | Stmt q; |
| 536 | + |
| 476 | 537 | if( g.argc<3 ) usage("SUBCOMMAND ?ARGS?"); |
| 477 | 538 | zSubcmd = g.argv[2]; |
| 478 | 539 | db_find_and_open_repository(0,0); |
| 479 | 540 | n = (int)strlen(zSubcmd); |
| 480 | | - if( strncmp(zSubcmd, "cat", n)==0 ){ |
| 541 | + if( find_option("explain",0,0)!=0 || find_option("dry-run",0,0)!=0 ){ |
| 542 | + purgeFlags |= PURGE_EXPLAIN_ONLY; |
| 543 | + } |
| 544 | + if( strncmp(zSubcmd, "artifacts", n)==0 ){ |
| 545 | + verify_all_options(); |
| 546 | + db_begin_transaction(); |
| 547 | + db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)"); |
| 548 | + for(i=3; i<g.argc; i++){ |
| 549 | + int r = name_to_typed_rid(g.argv[i], ""); |
| 550 | + db_multi_exec("INSERT OR IGNORE INTO ok(rid) VALUES(%d);", r); |
| 551 | + } |
| 552 | + describe_artifacts_to_stdout("IN ok", 0); |
| 553 | + purge_artifact_list("ok", "", purgeFlags); |
| 554 | + db_end_transaction(0); |
| 555 | + }else if( strncmp(zSubcmd, "cat", n)==0 ){ |
| 481 | 556 | int i, piid; |
| 482 | 557 | Blob content; |
| 483 | 558 | if( g.argc<4 ) usage("cat UUID..."); |
| 484 | 559 | for(i=3; i<g.argc; i++){ |
| 485 | 560 | piid = db_int(0, "SELECT piid FROM purgeitem WHERE uuid LIKE '%q%%'", |
| | @@ -487,12 +562,45 @@ |
| 487 | 562 | if( piid==0 ) fossil_fatal("no such item: %s", g.argv[3]); |
| 488 | 563 | purge_extract_item(piid, &content); |
| 489 | 564 | blob_write_to_file(&content, "-"); |
| 490 | 565 | blob_reset(&content); |
| 491 | 566 | } |
| 492 | | - /* The "checkins" subcommand goes here in alphabetical order, but it must |
| 493 | | - ** be moved to the end since it is the default case */ |
| 567 | + }else if( strncmp(zSubcmd, "checkins", n)==0 ){ |
| 568 | + int vid; |
| 569 | + if( find_option("explain",0,0)!=0 || find_option("dry-run",0,0)!=0 ){ |
| 570 | + purgeFlags |= PURGE_EXPLAIN_ONLY; |
| 571 | + } |
| 572 | + verify_all_options(); |
| 573 | + db_begin_transaction(); |
| 574 | + if( g.argc<=3 ) usage("checkins TAGS... [OPTIONS]"); |
| 575 | + db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)"); |
| 576 | + for(i=3; i<g.argc; i++){ |
| 577 | + int r = name_to_typed_rid(g.argv[i], "br"); |
| 578 | + compute_descendants(r, 1000000000); |
| 579 | + } |
| 580 | + vid = db_lget_int("checkout",0); |
| 581 | + if( db_exists("SELECT 1 FROM ok WHERE rid=%d",vid) ){ |
| 582 | + fossil_fatal("cannot purge the current checkout"); |
| 583 | + } |
| 584 | + find_checkin_associates("ok", 1); |
| 585 | + purge_artifact_list("ok", "", purgeFlags); |
| 586 | + db_end_transaction(0); |
| 587 | + }else if( strncmp(zSubcmd, "files", n)==0 ){ |
| 588 | + verify_all_options(); |
| 589 | + db_begin_transaction(); |
| 590 | + db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)"); |
| 591 | + for(i=3; i<g.argc; i++){ |
| 592 | + db_multi_exec( |
| 593 | + "INSERT OR IGNORE INTO ok(rid) " |
| 594 | + " SELECT fid FROM mlink, filename" |
| 595 | + " WHERE mlink.fnid=filename.fnid" |
| 596 | + " AND (filename.name=%Q OR filename.name GLOB '%q/*')", |
| 597 | + g.argv[i], g.argv[i] |
| 598 | + ); |
| 599 | + } |
| 600 | + purge_artifact_list("ok", "", purgeFlags); |
| 601 | + db_end_transaction(0); |
| 494 | 602 | }else if( strncmp(zSubcmd, "list", n)==0 || strcmp(zSubcmd,"ls")==0 ){ |
| 495 | 603 | int showDetail = find_option("l","l",0)!=0; |
| 496 | 604 | if( !db_table_exists("repository","purgeevent") ) return; |
| 497 | 605 | db_prepare(&q, "SELECT peid, datetime(ctime,'unixepoch',toLocal())" |
| 498 | 606 | " FROM purgeevent"); |
| | @@ -503,11 +611,23 @@ |
| 503 | 611 | } |
| 504 | 612 | } |
| 505 | 613 | db_finalize(&q); |
| 506 | 614 | }else if( strncmp(zSubcmd, "obliterate", n)==0 ){ |
| 507 | 615 | int i; |
| 616 | + int bForce = find_option("force","f",0)!=0; |
| 508 | 617 | if( g.argc<4 ) usage("obliterate ID..."); |
| 618 | + if( !bForce ){ |
| 619 | + Blob ans; |
| 620 | + char cReply; |
| 621 | + prompt_user( |
| 622 | + "Obliterating the graveyard will permanently delete information.\n" |
| 623 | + "Changes cannot be undone. Continue (y/N)? ", &ans); |
| 624 | + cReply = blob_str(&ans)[0]; |
| 625 | + if( cReply!='y' && cReply!='Y' ){ |
| 626 | + fossil_exit(1); |
| 627 | + } |
| 628 | + } |
| 509 | 629 | db_begin_transaction(); |
| 510 | 630 | for(i=3; i<g.argc; i++){ |
| 511 | 631 | int peid = atoi(g.argv[i]); |
| 512 | 632 | if( !db_exists("SELECT 1 FROM purgeevent WHERE peid=%d",peid) ){ |
| 513 | 633 | fossil_fatal("no such purge event: %s", g.argv[i]); |
| | @@ -517,66 +637,43 @@ |
| 517 | 637 | "DELETE FROM purgeitem WHERE peid=%d;", |
| 518 | 638 | peid, peid |
| 519 | 639 | ); |
| 520 | 640 | } |
| 521 | 641 | db_end_transaction(0); |
| 642 | + }else if( strncmp(zSubcmd, "tickets", n)==0 ){ |
| 643 | + fossil_fatal("not yet implemented...."); |
| 522 | 644 | }else if( strncmp(zSubcmd, "undo", n)==0 ){ |
| 523 | 645 | int peid; |
| 524 | 646 | if( g.argc!=4 ) usage("undo ID"); |
| 525 | 647 | peid = atoi(g.argv[3]); |
| 526 | | - db_begin_transaction(); |
| 527 | | - db_multi_exec( |
| 528 | | - "CREATE TEMP TABLE ix(" |
| 529 | | - " piid INTEGER PRIMARY KEY," |
| 530 | | - " srcid INTEGER" |
| 531 | | - ");" |
| 532 | | - "CREATE INDEX ixsrcid ON ix(srcid);" |
| 533 | | - "INSERT INTO ix(piid,srcid) " |
| 534 | | - " SELECT piid, coalesce(srcid,0) FROM purgeitem WHERE peid=%d;", |
| 535 | | - peid |
| 536 | | - ); |
| 537 | | - db_multi_exec( |
| 538 | | - "DELETE FROM shun" |
| 539 | | - " WHERE uuid IN (SELECT uuid FROM purgeitem WHERE peid=%d);", |
| 540 | | - peid |
| 541 | | - ); |
| 542 | | - manifest_crosslink_begin(); |
| 543 | | - purge_item_resurrect(0, 0); |
| 544 | | - manifest_crosslink_end(0); |
| 545 | | - db_multi_exec("DELETE FROM purgeevent WHERE peid=%d", peid); |
| 546 | | - db_multi_exec("DELETE FROM purgeitem WHERE peid=%d", peid); |
| 547 | | - db_end_transaction(0); |
| 548 | | - }else{ |
| 549 | | - /* The "checkins" command is the default and so must occur last */ |
| 550 | | - int explainOnly = find_option("explain",0,0)!=0; |
| 551 | | - int dryRun = find_option("dry-run",0,0)!=0; |
| 552 | | - int i; |
| 553 | | - int vid; |
| 554 | | - int nCkin; |
| 555 | | - int nArtifact; |
| 556 | | - verify_all_options(); |
| 557 | | - db_begin_transaction(); |
| 558 | | - i = strncmp(zSubcmd,"checkins",n)==0 ? 3 : 2; |
| 559 | | - if( i>=g.argc ) usage("[checkin] TAGS... [--explain]"); |
| 560 | | - db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)"); |
| 561 | | - for(; i<g.argc; i++){ |
| 562 | | - int r = name_to_typed_rid(g.argv[i], "br"); |
| 563 | | - compute_descendants(r, 1000000000); |
| 564 | | - } |
| 565 | | - vid = db_lget_int("checkout",0); |
| 566 | | - if( db_exists("SELECT 1 FROM ok WHERE rid=%d",vid) ){ |
| 567 | | - fossil_fatal("cannot purge the current checkout"); |
| 568 | | - } |
| 569 | | - nCkin = db_int(0, "SELECT count(*) FROM ok"); |
| 570 | | - find_checkin_associates("ok", 1); |
| 571 | | - nArtifact = db_int(0, "SELECT count(*) FROM ok"); |
| 572 | | - if( explainOnly ){ |
| 573 | | - describe_artifacts_to_stdout("IN ok", 0); |
| 574 | | - }else{ |
| 575 | | - int peid = purge_artifact_list("ok","",1); |
| 576 | | - fossil_print("%d check-ins and %d artifacts purged.\n", nCkin, nArtifact); |
| 577 | | - fossil_print("undoable using \"%s purge undo %d\".\n", |
| 578 | | - g.nameOfExe, peid); |
| 579 | | - } |
| 580 | | - db_end_transaction(explainOnly||dryRun); |
| 648 | + if( (purgeFlags & PURGE_EXPLAIN_ONLY)==0 ){ |
| 649 | + db_begin_transaction(); |
| 650 | + db_multi_exec( |
| 651 | + "CREATE TEMP TABLE ix(" |
| 652 | + " piid INTEGER PRIMARY KEY," |
| 653 | + " srcid INTEGER" |
| 654 | + ");" |
| 655 | + "CREATE INDEX ixsrcid ON ix(srcid);" |
| 656 | + "INSERT INTO ix(piid,srcid) " |
| 657 | + " SELECT piid, coalesce(srcid,0) FROM purgeitem WHERE peid=%d;", |
| 658 | + peid |
| 659 | + ); |
| 660 | + db_multi_exec( |
| 661 | + "DELETE FROM shun" |
| 662 | + " WHERE uuid IN (SELECT uuid FROM purgeitem WHERE peid=%d);", |
| 663 | + peid |
| 664 | + ); |
| 665 | + manifest_crosslink_begin(); |
| 666 | + purge_item_resurrect(0, 0); |
| 667 | + manifest_crosslink_end(0); |
| 668 | + db_multi_exec("DELETE FROM purgeevent WHERE peid=%d", peid); |
| 669 | + db_multi_exec("DELETE FROM purgeitem WHERE peid=%d", peid); |
| 670 | + db_end_transaction(0); |
| 671 | + } |
| 672 | + }else if( strncmp(zSubcmd, "wiki", n)==0 ){ |
| 673 | + fossil_fatal("not yet implemented...."); |
| 674 | + }else{ |
| 675 | + fossil_fatal("unknown subcommand \"%s\".\n" |
| 676 | + "should be one of: cat, checkins, files, list, obliterate," |
| 677 | + " tickets, undo, wiki", zSubcmd); |
| 581 | 678 | } |
| 582 | 679 | } |
| 583 | 680 | |