| | @@ -22,10 +22,28 @@ |
| 22 | 22 | #include "add.h" |
| 23 | 23 | #include <assert.h> |
| 24 | 24 | #include <dirent.h> |
| 25 | 25 | #include "cygsup.h" |
| 26 | 26 | |
| 27 | +/* |
| 28 | +** WARNING: For Fossil version 1.x this value was always zero. For Fossil |
| 29 | +** 2.x, it will probably always be one. When this value is zero, |
| 30 | +** files in the checkout will not be moved by the "mv" command and |
| 31 | +** files in the checkout will not be removed by the "rm" command. |
| 32 | +** |
| 33 | +** If the FOSSIL_ENABLE_LEGACY_MV_RM compile-time option is used, |
| 34 | +** the "mv-rm-files" setting will be consulted instead of using |
| 35 | +** this value. |
| 36 | +** |
| 37 | +** To retain the Fossil version 1.x behavior when using Fossil 2.x, |
| 38 | +** the FOSSIL_ENABLE_LEGACY_MV_RM compile-time option must be used |
| 39 | +** -AND- the "mv-rm-files" setting must be set to zero. |
| 40 | +*/ |
| 41 | +#ifndef FOSSIL_MV_RM_FILE |
| 42 | +#define FOSSIL_MV_RM_FILE (0) |
| 43 | +#endif |
| 44 | + |
| 27 | 45 | /* |
| 28 | 46 | ** This routine returns the names of files in a working checkout that |
| 29 | 47 | ** are created by Fossil itself, and hence should not be added, deleted, |
| 30 | 48 | ** or merge, and should be omitted from "clean" and "extras" lists. |
| 31 | 49 | ** |
| | @@ -331,10 +349,58 @@ |
| 331 | 349 | glob_free(pClean); |
| 332 | 350 | |
| 333 | 351 | add_files_in_sfile(vid); |
| 334 | 352 | db_end_transaction(0); |
| 335 | 353 | } |
| 354 | + |
| 355 | +/* |
| 356 | +** This function adds a file to list of files to delete from disk after |
| 357 | +** the other actions required for the parent operation have completed |
| 358 | +** successfully. The first time it is called for the current process, |
| 359 | +** it creates a temporary table named "fremove", to keep track of these |
| 360 | +** files. |
| 361 | +*/ |
| 362 | +static void add_file_to_remove( |
| 363 | + const char *zOldName /* The old name of the file on disk. */ |
| 364 | +){ |
| 365 | + static int tableCreated = 0; |
| 366 | + Blob fullOldName; |
| 367 | + if( !tableCreated ){ |
| 368 | + db_multi_exec("CREATE TEMP TABLE fremove(x TEXT PRIMARY KEY %s)", |
| 369 | + filename_collation()); |
| 370 | + tableCreated = 1; |
| 371 | + } |
| 372 | + file_canonical_name(zOldName, &fullOldName, 0); |
| 373 | + db_multi_exec("INSERT INTO fremove VALUES('%q');", blob_str(&fullOldName)); |
| 374 | + blob_reset(&fullOldName); |
| 375 | +} |
| 376 | + |
| 377 | +/* |
| 378 | +** This function deletes files from the checkout, using the file names |
| 379 | +** contained in the temporary table "fremove". The temporary table is |
| 380 | +** created on demand by the add_file_to_remove() function. |
| 381 | +** |
| 382 | +** If dryRunFlag is non-zero, no files will be removed; however, their |
| 383 | +** names will still be output. |
| 384 | +** |
| 385 | +** The temporary table "fremove" is dropped after being processed. |
| 386 | +*/ |
| 387 | +static void process_files_to_remove( |
| 388 | + int dryRunFlag /* Zero to actually operate on the file-system. */ |
| 389 | +){ |
| 390 | + Stmt remove; |
| 391 | + db_prepare(&remove, "SELECT x FROM fremove ORDER BY x;"); |
| 392 | + while( db_step(&remove)==SQLITE_ROW ){ |
| 393 | + const char *zOldName = db_column_text(&remove, 0); |
| 394 | + if( !dryRunFlag ){ |
| 395 | + file_delete(zOldName); |
| 396 | + } |
| 397 | + fossil_print("DELETED_FILE %s\n", zOldName); |
| 398 | + } |
| 399 | + db_finalize(&remove); |
| 400 | + db_multi_exec("DROP TABLE fremove;"); |
| 401 | +} |
| 336 | 402 | |
| 337 | 403 | /* |
| 338 | 404 | ** COMMAND: rm |
| 339 | 405 | ** COMMAND: delete |
| 340 | 406 | ** COMMAND: forget* |
| | @@ -341,28 +407,55 @@ |
| 341 | 407 | ** |
| 342 | 408 | ** Usage: %fossil rm|delete|forget FILE1 ?FILE2 ...? |
| 343 | 409 | ** |
| 344 | 410 | ** Remove one or more files or directories from the repository. |
| 345 | 411 | ** |
| 346 | | -** This command does NOT remove the files from disk. It just marks the |
| 347 | | -** files as no longer being part of the project. In other words, future |
| 348 | | -** changes to the named files will not be versioned. |
| 412 | +** The 'rm' and 'delete' commands do NOT normally remove the files from |
| 413 | +** disk. They just mark the files as no longer being part of the project. |
| 414 | +** In other words, future changes to the named files will not be versioned. |
| 415 | +** However, the default behavior of this command may be overridden via the |
| 416 | +** command line options listed below and/or the 'mv-rm-files' setting. |
| 417 | +** |
| 418 | +** The 'forget' command never removes files from disk, even when the command |
| 419 | +** line options and/or the 'mv-rm-files' setting would otherwise require it |
| 420 | +** to do so. |
| 421 | +** |
| 422 | +** WARNING: If the "--hard" option is specified -OR- the "mv-rm-files" |
| 423 | +** setting is non-zero, files WILL BE removed from disk as well. |
| 424 | +** This does NOT apply to the 'forget' command. |
| 349 | 425 | ** |
| 350 | 426 | ** Options: |
| 427 | +** --soft Skip removing files from the checkout. |
| 428 | +** This supersedes the --hard option. |
| 429 | +** --hard Remove files from the checkout. |
| 351 | 430 | ** --case-sensitive <BOOL> Override the case-sensitive setting. |
| 431 | +** -n|--dry-run If given, display instead of run actions. |
| 352 | 432 | ** |
| 353 | 433 | ** See also: addremove, add |
| 354 | 434 | */ |
| 355 | 435 | void delete_cmd(void){ |
| 356 | 436 | int i; |
| 437 | + int removeFiles; |
| 438 | + int dryRunFlag; |
| 357 | 439 | Stmt loop; |
| 440 | + |
| 441 | + dryRunFlag = find_option("dry-run","n",0)!=0; |
| 358 | 442 | |
| 359 | 443 | /* We should be done with options.. */ |
| 360 | 444 | verify_all_options(); |
| 361 | 445 | |
| 362 | 446 | db_must_be_within_tree(); |
| 363 | 447 | db_begin_transaction(); |
| 448 | + if( g.argv[1][0]=='f' ){ /* i.e. "forget" */ |
| 449 | + removeFiles = 0; |
| 450 | + }else{ |
| 451 | +#if FOSSIL_ENABLE_LEGACY_MV_RM |
| 452 | + removeFiles = db_get_boolean("mv-rm-files",0); |
| 453 | +#else |
| 454 | + removeFiles = FOSSIL_MV_RM_FILE; |
| 455 | +#endif |
| 456 | + } |
| 364 | 457 | db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY %s)", |
| 365 | 458 | filename_collation()); |
| 366 | 459 | for(i=2; i<g.argc; i++){ |
| 367 | 460 | Blob treeName; |
| 368 | 461 | char *zTreeName; |
| | @@ -382,17 +475,21 @@ |
| 382 | 475 | } |
| 383 | 476 | |
| 384 | 477 | db_prepare(&loop, "SELECT x FROM sfile"); |
| 385 | 478 | while( db_step(&loop)==SQLITE_ROW ){ |
| 386 | 479 | fossil_print("DELETED %s\n", db_column_text(&loop, 0)); |
| 480 | + if( removeFiles ) add_file_to_remove(db_column_text(&loop, 0)); |
| 387 | 481 | } |
| 388 | 482 | db_finalize(&loop); |
| 389 | | - db_multi_exec( |
| 390 | | - "UPDATE vfile SET deleted=1 WHERE pathname IN sfile;" |
| 391 | | - "DELETE FROM vfile WHERE rid=0 AND deleted;" |
| 392 | | - ); |
| 483 | + if( !dryRunFlag ){ |
| 484 | + db_multi_exec( |
| 485 | + "UPDATE vfile SET deleted=1 WHERE pathname IN sfile;" |
| 486 | + "DELETE FROM vfile WHERE rid=0 AND deleted;" |
| 487 | + ); |
| 488 | + } |
| 393 | 489 | db_end_transaction(0); |
| 490 | + if( removeFiles ) process_files_to_remove(dryRunFlag); |
| 394 | 491 | } |
| 395 | 492 | |
| 396 | 493 | /* |
| 397 | 494 | ** Capture the command-line --case-sensitive option. |
| 398 | 495 | */ |
| | @@ -593,11 +690,12 @@ |
| 593 | 690 | ** The original name of the file is zOrig. The new filename is zNew. |
| 594 | 691 | */ |
| 595 | 692 | static void mv_one_file( |
| 596 | 693 | int vid, |
| 597 | 694 | const char *zOrig, |
| 598 | | - const char *zNew |
| 695 | + const char *zNew, |
| 696 | + int dryRunFlag |
| 599 | 697 | ){ |
| 600 | 698 | int x = db_int(-1, "SELECT deleted FROM vfile WHERE pathname=%Q %s", |
| 601 | 699 | zNew, filename_collation()); |
| 602 | 700 | if( x>=0 ){ |
| 603 | 701 | if( x==0 ){ |
| | @@ -607,14 +705,75 @@ |
| 607 | 705 | fossil_fatal("cannot rename '%s' to '%s' since the delete of '%s' has " |
| 608 | 706 | "not yet been committed", zOrig, zNew, zNew); |
| 609 | 707 | } |
| 610 | 708 | } |
| 611 | 709 | fossil_print("RENAME %s %s\n", zOrig, zNew); |
| 612 | | - db_multi_exec( |
| 613 | | - "UPDATE vfile SET pathname='%q' WHERE pathname='%q' %s AND vid=%d", |
| 614 | | - zNew, zOrig, filename_collation(), vid |
| 615 | | - ); |
| 710 | + if( !dryRunFlag ){ |
| 711 | + db_multi_exec( |
| 712 | + "UPDATE vfile SET pathname='%q' WHERE pathname='%q' %s AND vid=%d", |
| 713 | + zNew, zOrig, filename_collation(), vid |
| 714 | + ); |
| 715 | + } |
| 716 | +} |
| 717 | + |
| 718 | +/* |
| 719 | +** This function adds a file to list of files to move on disk after the |
| 720 | +** other actions required for the parent operation have completed |
| 721 | +** successfully. The first time it is called for the current process, |
| 722 | +** it creates a temporary table named "fmove", to keep track of these |
| 723 | +** files. |
| 724 | +*/ |
| 725 | +static void add_file_to_move( |
| 726 | + const char *zOldName, /* The old name of the file on disk. */ |
| 727 | + const char *zNewName /* The new name of the file on disk. */ |
| 728 | +){ |
| 729 | + static int tableCreated = 0; |
| 730 | + Blob fullOldName; |
| 731 | + Blob fullNewName; |
| 732 | + if( !tableCreated ){ |
| 733 | + db_multi_exec("CREATE TEMP TABLE fmove(x TEXT PRIMARY KEY %s, y TEXT %s)", |
| 734 | + filename_collation(), filename_collation()); |
| 735 | + tableCreated = 1; |
| 736 | + } |
| 737 | + file_canonical_name(zOldName, &fullOldName, 0); |
| 738 | + file_canonical_name(zNewName, &fullNewName, 0); |
| 739 | + db_multi_exec("INSERT INTO fmove VALUES('%q','%q');", |
| 740 | + blob_str(&fullOldName), blob_str(&fullNewName)); |
| 741 | + blob_reset(&fullNewName); |
| 742 | + blob_reset(&fullOldName); |
| 743 | +} |
| 744 | + |
| 745 | +/* |
| 746 | +** This function moves files within the checkout, using the file names |
| 747 | +** contained in the temporary table "fmove". The temporary table is |
| 748 | +** created on demand by the add_file_to_move() function. |
| 749 | +** |
| 750 | +** If dryRunFlag is non-zero, no files will be moved; however, their |
| 751 | +** names will still be output. |
| 752 | +** |
| 753 | +** The temporary table "fmove" is dropped after being processed. |
| 754 | +*/ |
| 755 | +static void process_files_to_move( |
| 756 | + int dryRunFlag /* Zero to actually operate on the file-system. */ |
| 757 | +){ |
| 758 | + Stmt move; |
| 759 | + db_prepare(&move, "SELECT x, y FROM fmove ORDER BY x;"); |
| 760 | + while( db_step(&move)==SQLITE_ROW ){ |
| 761 | + const char *zOldName = db_column_text(&move, 0); |
| 762 | + const char *zNewName = db_column_text(&move, 1); |
| 763 | + if( !dryRunFlag ){ |
| 764 | + if( file_wd_islink(zOldName) ){ |
| 765 | + symlink_copy(zOldName, zNewName); |
| 766 | + }else{ |
| 767 | + file_copy(zOldName, zNewName); |
| 768 | + } |
| 769 | + file_delete(zOldName); |
| 770 | + } |
| 771 | + fossil_print("MOVED_FILE %s\n", zOldName); |
| 772 | + } |
| 773 | + db_finalize(&move); |
| 774 | + db_multi_exec("DROP TABLE fmove;"); |
| 616 | 775 | } |
| 617 | 776 | |
| 618 | 777 | /* |
| 619 | 778 | ** COMMAND: mv |
| 620 | 779 | ** COMMAND: rename* |
| | @@ -623,27 +782,44 @@ |
| 623 | 782 | ** or: %fossil mv|rename OLDNAME... DIR |
| 624 | 783 | ** |
| 625 | 784 | ** Move or rename one or more files or directories within the repository tree. |
| 626 | 785 | ** You can either rename a file or directory or move it to another subdirectory. |
| 627 | 786 | ** |
| 628 | | -** This command does NOT rename or move the files on disk. This command merely |
| 629 | | -** records the fact that filenames have changed so that appropriate notations |
| 630 | | -** can be made at the next commit/check-in. |
| 787 | +** The 'mv' command does NOT normally rename or move the files on disk. |
| 788 | +** This command merely records the fact that file names have changed so |
| 789 | +** that appropriate notations can be made at the next commit/check-in. |
| 790 | +** However, the default behavior of this command may be overridden via |
| 791 | +** command line options listed below and/or the 'mv-rm-files' setting. |
| 792 | +** |
| 793 | +** The 'rename' command never renames or moves files on disk, even when the |
| 794 | +** command line options and/or the 'mv-rm-files' setting would otherwise |
| 795 | +** require it to do so. |
| 796 | +** |
| 797 | +** WARNING: If the "--hard" option is specified -OR- the "mv-rm-files" |
| 798 | +** setting is non-zero, files WILL BE renamed or moved on disk |
| 799 | +** as well. This does NOT apply to the 'rename' command. |
| 631 | 800 | ** |
| 632 | 801 | ** Options: |
| 802 | +** --soft Skip moving files within the checkout. |
| 803 | +** This supersedes the --hard option. |
| 804 | +** --hard Move files within the checkout. |
| 633 | 805 | ** --case-sensitive <BOOL> Override the case-sensitive setting. |
| 806 | +** -n|--dry-run If given, display instead of run actions. |
| 634 | 807 | ** |
| 635 | 808 | ** See also: changes, status |
| 636 | 809 | */ |
| 637 | 810 | void mv_cmd(void){ |
| 638 | 811 | int i; |
| 639 | 812 | int vid; |
| 813 | + int moveFiles; |
| 814 | + int dryRunFlag; |
| 640 | 815 | char *zDest; |
| 641 | 816 | Blob dest; |
| 642 | 817 | Stmt q; |
| 643 | 818 | |
| 644 | 819 | db_must_be_within_tree(); |
| 820 | + dryRunFlag = find_option("dry-run","n",0)!=0; |
| 645 | 821 | |
| 646 | 822 | /* We should be done with options.. */ |
| 647 | 823 | verify_all_options(); |
| 648 | 824 | |
| 649 | 825 | vid = db_lget_int("checkout", 0); |
| | @@ -653,10 +829,19 @@ |
| 653 | 829 | if( g.argc<4 ){ |
| 654 | 830 | usage("OLDNAME NEWNAME"); |
| 655 | 831 | } |
| 656 | 832 | zDest = g.argv[g.argc-1]; |
| 657 | 833 | db_begin_transaction(); |
| 834 | + if( g.argv[1][0]=='r' ){ /* i.e. "rename" */ |
| 835 | + moveFiles = 0; |
| 836 | + }else{ |
| 837 | +#if FOSSIL_ENABLE_LEGACY_MV_RM |
| 838 | + moveFiles = db_get_boolean("mv-rm-files",0); |
| 839 | +#else |
| 840 | + moveFiles = FOSSIL_MV_RM_FILE; |
| 841 | +#endif |
| 842 | + } |
| 658 | 843 | file_tree_name(zDest, &dest, 1); |
| 659 | 844 | db_multi_exec( |
| 660 | 845 | "UPDATE vfile SET origname=pathname WHERE origname IS NULL;" |
| 661 | 846 | ); |
| 662 | 847 | db_multi_exec( |
| | @@ -711,18 +896,20 @@ |
| 711 | 896 | } |
| 712 | 897 | db_prepare(&q, "SELECT f, t FROM mv ORDER BY f"); |
| 713 | 898 | while( db_step(&q)==SQLITE_ROW ){ |
| 714 | 899 | const char *zFrom = db_column_text(&q, 0); |
| 715 | 900 | const char *zTo = db_column_text(&q, 1); |
| 716 | | - mv_one_file(vid, zFrom, zTo); |
| 901 | + mv_one_file(vid, zFrom, zTo, dryRunFlag); |
| 902 | + if( moveFiles ) add_file_to_move(zFrom, zTo); |
| 717 | 903 | } |
| 718 | 904 | db_finalize(&q); |
| 719 | 905 | db_end_transaction(0); |
| 906 | + if( moveFiles ) process_files_to_move(dryRunFlag); |
| 720 | 907 | } |
| 721 | 908 | |
| 722 | 909 | /* |
| 723 | 910 | ** Function for stash_apply to be able to restore a file and indicate |
| 724 | 911 | ** newly ADDED state. |
| 725 | 912 | */ |
| 726 | 913 | int stash_add_files_in_sfile(int vid){ |
| 727 | 914 | return add_files_in_sfile(vid); |
| 728 | 915 | } |
| 729 | 916 | |