Fossil SCM

Make it possible to undo the deletion of files less than 10MiB by the clean command. This branch is an alternative to the 'undo-clean' branch. Needs tests.

mistachkin 2015-06-27 04:03 enhancedUndo
Commit 7794b21695e7dc8952e54555fefd9099ebd84650
2 files changed +46 -19 +25 -4
+46 -19
--- src/checkin.c
+++ src/checkin.c
@@ -644,11 +644,12 @@
644644
** Files and subdirectories whose names begin with "." are automatically
645645
** ignored unless the --dotfiles option is used.
646646
**
647647
** The --verily option ignores the keep-glob and ignore-glob settings
648648
** and turns on --force, --dotfiles, and --emptydirs. Use the --verily
649
-** option when you really want to clean up everything.
649
+** option when you really want to clean up everything. Extreme care
650
+** should be exercised when using the --verily option.
650651
**
651652
** Options:
652653
** --allckouts Check for empty directories within any checkouts
653654
** that may be nested within the current one. This
654655
** option should be used with great care because the
@@ -657,24 +658,32 @@
657658
** not be checked.
658659
** --case-sensitive <BOOL> override case-sensitive setting
659660
** --dirsonly Only remove empty directories. No files will
660661
** be removed. Using this option will automatically
661662
** enable the --emptydirs option as well.
663
+** --disable-undo WARNING: This option disables use of the undo
664
+** mechanism for this clean operation and should be
665
+** used with extreme caution.
662666
** --dotfiles Include files beginning with a dot (".").
663667
** --emptydirs Remove any empty directories that are not
664668
** explicitly exempted via the empty-dirs setting
665669
** or another applicable setting or command line
666670
** argument. Matching files, if any, are removed
667671
** prior to checking for any empty directories;
668672
** therefore, directories that contain only files
669673
** that were removed will be removed as well.
670674
** -f|--force Remove files without prompting.
671
-** -x|--verily Remove everything that is not a managed file or
672
-** the repository itself. Implies -f --emptydirs
673
-** --dotfiles. Disregard keep-glob and ignore-glob.
674
-** --clean <CSG> Never prompt for files matching this
675
-** comma separated list of glob patterns.
675
+** -x|--verily WARNING: Remove everything that is not a managed
676
+** file or the repository itself. This option
677
+** implies the --force, --emptydirs, and --dotfiles
678
+** options. Furthermore, it completely disregards
679
+** the keep-glob and ignore-glob settings. However,
680
+** it does honor the --ignore and --keep options.
681
+** --clean <CSG> WARNING: Never prompt to delete any files matching
682
+** this comma separated list of glob patterns. Also,
683
+** deletions of any files matching this pattern list
684
+** cannot be undone.
676685
** --ignore <CSG> Ignore files matching patterns from the
677686
** comma separated list of glob patterns.
678687
** --keep <CSG> Keep files matching this comma separated
679688
** list of glob patterns.
680689
** -n|--dry-run Delete nothing, but display what would have been
@@ -685,23 +694,30 @@
685694
** See also: addremove, extras, status
686695
*/
687696
void clean_cmd(void){
688697
int allFileFlag, allDirFlag, dryRunFlag, verboseFlag;
689698
int emptyDirsFlag, dirsOnlyFlag;
699
+ int disableUndo;
690700
unsigned scanFlags = 0;
691701
int verilyFlag = 0;
692702
const char *zIgnoreFlag, *zKeepFlag, *zCleanFlag;
693703
Glob *pIgnore, *pKeep, *pClean;
694704
int nRoot;
695705
706
+#ifndef UNDO_SIZE_LIMIT /* TODO: Setting? */
707
+#define UNDO_SIZE_LIMIT (10*1024*1024) /* 10MiB */
708
+#endif
709
+
710
+ undo_capture_command_line();
696711
dryRunFlag = find_option("dry-run","n",0)!=0;
697712
if( !dryRunFlag ){
698713
dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
699714
}
700715
if( !dryRunFlag ){
701716
dryRunFlag = find_option("whatif",0,0)!=0;
702717
}
718
+ disableUndo = find_option("disable-undo",0,0)!=0;
703719
allFileFlag = allDirFlag = find_option("force","f",0)!=0;
704720
dirsOnlyFlag = find_option("dirsonly",0,0)!=0;
705721
emptyDirsFlag = find_option("emptydirs","d",0)!=0 || dirsOnlyFlag;
706722
if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL;
707723
if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP;
@@ -734,10 +750,11 @@
734750
nRoot = (int)strlen(g.zLocalRoot);
735751
g.allowSymlinks = 1; /* Find symlinks too */
736752
if( !dirsOnlyFlag ){
737753
Stmt q;
738754
Blob repo;
755
+ if( !dryRunFlag && !disableUndo ) undo_begin();
739756
locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore, 0);
740757
db_prepare(&q,
741758
"SELECT %Q || x FROM sfile"
742759
" WHERE x NOT IN (%s)"
743760
" ORDER BY 1",
@@ -755,23 +772,32 @@
755772
" or \"keep-glob\")\n", zName+nRoot);
756773
}
757774
continue;
758775
}
759776
if( !allFileFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){
760
- Blob ans;
761
- char cReply;
762
- char *prompt = mprintf("Remove unmanaged file \"%s\" (a=all/y/N)? ",
763
- zName+nRoot);
764
- prompt_user(prompt, &ans);
765
- cReply = blob_str(&ans)[0];
766
- if( cReply=='a' || cReply=='A' ){
767
- allFileFlag = 1;
768
- }else if( cReply!='y' && cReply!='Y' ){
769
- blob_reset(&ans);
770
- continue;
771
- }
772
- blob_reset(&ans);
777
+ int undoRc = UNDO_NONE;
778
+ if( !disableUndo ){
779
+ undoRc = undo_maybe_save(zName+nRoot, UNDO_SIZE_LIMIT);
780
+ }
781
+ if( undoRc!=UNDO_SAVED_OK ){
782
+ Blob ans;
783
+ char cReply;
784
+ char *prompt = mprintf("\nWARNING: Deletion of this file will "
785
+ "not be undoable via the 'undo'\n"
786
+ " command because %s.\n\n"
787
+ "Remove unmanaged file \"%s\" (a=all/y/N)? ",
788
+ undo_save_message(undoRc), zName+nRoot);
789
+ prompt_user(prompt, &ans);
790
+ cReply = blob_str(&ans)[0];
791
+ if( cReply=='a' || cReply=='A' ){
792
+ allFileFlag = 1;
793
+ }else if( cReply!='y' && cReply!='Y' ){
794
+ blob_reset(&ans);
795
+ continue;
796
+ }
797
+ blob_reset(&ans);
798
+ }
773799
}
774800
if( dryRunFlag || file_delete(zName)==0 ){
775801
if( verboseFlag || dryRunFlag ){
776802
fossil_print("Removed unmanaged file: %s\n", zName+nRoot);
777803
}
@@ -778,10 +804,11 @@
778804
}else if( verboseFlag ){
779805
fossil_print("Could not remove file: %s\n", zName+nRoot);
780806
}
781807
}
782808
db_finalize(&q);
809
+ if( !dryRunFlag && !disableUndo ) undo_finish();
783810
}
784811
if( emptyDirsFlag ){
785812
Glob *pEmptyDirs = glob_create(db_get("empty-dirs", 0));
786813
Stmt q;
787814
Blob root;
788815
--- src/checkin.c
+++ src/checkin.c
@@ -644,11 +644,12 @@
644 ** Files and subdirectories whose names begin with "." are automatically
645 ** ignored unless the --dotfiles option is used.
646 **
647 ** The --verily option ignores the keep-glob and ignore-glob settings
648 ** and turns on --force, --dotfiles, and --emptydirs. Use the --verily
649 ** option when you really want to clean up everything.
 
650 **
651 ** Options:
652 ** --allckouts Check for empty directories within any checkouts
653 ** that may be nested within the current one. This
654 ** option should be used with great care because the
@@ -657,24 +658,32 @@
657 ** not be checked.
658 ** --case-sensitive <BOOL> override case-sensitive setting
659 ** --dirsonly Only remove empty directories. No files will
660 ** be removed. Using this option will automatically
661 ** enable the --emptydirs option as well.
 
 
 
662 ** --dotfiles Include files beginning with a dot (".").
663 ** --emptydirs Remove any empty directories that are not
664 ** explicitly exempted via the empty-dirs setting
665 ** or another applicable setting or command line
666 ** argument. Matching files, if any, are removed
667 ** prior to checking for any empty directories;
668 ** therefore, directories that contain only files
669 ** that were removed will be removed as well.
670 ** -f|--force Remove files without prompting.
671 ** -x|--verily Remove everything that is not a managed file or
672 ** the repository itself. Implies -f --emptydirs
673 ** --dotfiles. Disregard keep-glob and ignore-glob.
674 ** --clean <CSG> Never prompt for files matching this
675 ** comma separated list of glob patterns.
 
 
 
 
 
676 ** --ignore <CSG> Ignore files matching patterns from the
677 ** comma separated list of glob patterns.
678 ** --keep <CSG> Keep files matching this comma separated
679 ** list of glob patterns.
680 ** -n|--dry-run Delete nothing, but display what would have been
@@ -685,23 +694,30 @@
685 ** See also: addremove, extras, status
686 */
687 void clean_cmd(void){
688 int allFileFlag, allDirFlag, dryRunFlag, verboseFlag;
689 int emptyDirsFlag, dirsOnlyFlag;
 
690 unsigned scanFlags = 0;
691 int verilyFlag = 0;
692 const char *zIgnoreFlag, *zKeepFlag, *zCleanFlag;
693 Glob *pIgnore, *pKeep, *pClean;
694 int nRoot;
695
 
 
 
 
 
696 dryRunFlag = find_option("dry-run","n",0)!=0;
697 if( !dryRunFlag ){
698 dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
699 }
700 if( !dryRunFlag ){
701 dryRunFlag = find_option("whatif",0,0)!=0;
702 }
 
703 allFileFlag = allDirFlag = find_option("force","f",0)!=0;
704 dirsOnlyFlag = find_option("dirsonly",0,0)!=0;
705 emptyDirsFlag = find_option("emptydirs","d",0)!=0 || dirsOnlyFlag;
706 if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL;
707 if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP;
@@ -734,10 +750,11 @@
734 nRoot = (int)strlen(g.zLocalRoot);
735 g.allowSymlinks = 1; /* Find symlinks too */
736 if( !dirsOnlyFlag ){
737 Stmt q;
738 Blob repo;
 
739 locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore, 0);
740 db_prepare(&q,
741 "SELECT %Q || x FROM sfile"
742 " WHERE x NOT IN (%s)"
743 " ORDER BY 1",
@@ -755,23 +772,32 @@
755 " or \"keep-glob\")\n", zName+nRoot);
756 }
757 continue;
758 }
759 if( !allFileFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){
760 Blob ans;
761 char cReply;
762 char *prompt = mprintf("Remove unmanaged file \"%s\" (a=all/y/N)? ",
763 zName+nRoot);
764 prompt_user(prompt, &ans);
765 cReply = blob_str(&ans)[0];
766 if( cReply=='a' || cReply=='A' ){
767 allFileFlag = 1;
768 }else if( cReply!='y' && cReply!='Y' ){
769 blob_reset(&ans);
770 continue;
771 }
772 blob_reset(&ans);
 
 
 
 
 
 
 
 
 
773 }
774 if( dryRunFlag || file_delete(zName)==0 ){
775 if( verboseFlag || dryRunFlag ){
776 fossil_print("Removed unmanaged file: %s\n", zName+nRoot);
777 }
@@ -778,10 +804,11 @@
778 }else if( verboseFlag ){
779 fossil_print("Could not remove file: %s\n", zName+nRoot);
780 }
781 }
782 db_finalize(&q);
 
783 }
784 if( emptyDirsFlag ){
785 Glob *pEmptyDirs = glob_create(db_get("empty-dirs", 0));
786 Stmt q;
787 Blob root;
788
--- src/checkin.c
+++ src/checkin.c
@@ -644,11 +644,12 @@
644 ** Files and subdirectories whose names begin with "." are automatically
645 ** ignored unless the --dotfiles option is used.
646 **
647 ** The --verily option ignores the keep-glob and ignore-glob settings
648 ** and turns on --force, --dotfiles, and --emptydirs. Use the --verily
649 ** option when you really want to clean up everything. Extreme care
650 ** should be exercised when using the --verily option.
651 **
652 ** Options:
653 ** --allckouts Check for empty directories within any checkouts
654 ** that may be nested within the current one. This
655 ** option should be used with great care because the
@@ -657,24 +658,32 @@
658 ** not be checked.
659 ** --case-sensitive <BOOL> override case-sensitive setting
660 ** --dirsonly Only remove empty directories. No files will
661 ** be removed. Using this option will automatically
662 ** enable the --emptydirs option as well.
663 ** --disable-undo WARNING: This option disables use of the undo
664 ** mechanism for this clean operation and should be
665 ** used with extreme caution.
666 ** --dotfiles Include files beginning with a dot (".").
667 ** --emptydirs Remove any empty directories that are not
668 ** explicitly exempted via the empty-dirs setting
669 ** or another applicable setting or command line
670 ** argument. Matching files, if any, are removed
671 ** prior to checking for any empty directories;
672 ** therefore, directories that contain only files
673 ** that were removed will be removed as well.
674 ** -f|--force Remove files without prompting.
675 ** -x|--verily WARNING: Remove everything that is not a managed
676 ** file or the repository itself. This option
677 ** implies the --force, --emptydirs, and --dotfiles
678 ** options. Furthermore, it completely disregards
679 ** the keep-glob and ignore-glob settings. However,
680 ** it does honor the --ignore and --keep options.
681 ** --clean <CSG> WARNING: Never prompt to delete any files matching
682 ** this comma separated list of glob patterns. Also,
683 ** deletions of any files matching this pattern list
684 ** cannot be undone.
685 ** --ignore <CSG> Ignore files matching patterns from the
686 ** comma separated list of glob patterns.
687 ** --keep <CSG> Keep files matching this comma separated
688 ** list of glob patterns.
689 ** -n|--dry-run Delete nothing, but display what would have been
@@ -685,23 +694,30 @@
694 ** See also: addremove, extras, status
695 */
696 void clean_cmd(void){
697 int allFileFlag, allDirFlag, dryRunFlag, verboseFlag;
698 int emptyDirsFlag, dirsOnlyFlag;
699 int disableUndo;
700 unsigned scanFlags = 0;
701 int verilyFlag = 0;
702 const char *zIgnoreFlag, *zKeepFlag, *zCleanFlag;
703 Glob *pIgnore, *pKeep, *pClean;
704 int nRoot;
705
706 #ifndef UNDO_SIZE_LIMIT /* TODO: Setting? */
707 #define UNDO_SIZE_LIMIT (10*1024*1024) /* 10MiB */
708 #endif
709
710 undo_capture_command_line();
711 dryRunFlag = find_option("dry-run","n",0)!=0;
712 if( !dryRunFlag ){
713 dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
714 }
715 if( !dryRunFlag ){
716 dryRunFlag = find_option("whatif",0,0)!=0;
717 }
718 disableUndo = find_option("disable-undo",0,0)!=0;
719 allFileFlag = allDirFlag = find_option("force","f",0)!=0;
720 dirsOnlyFlag = find_option("dirsonly",0,0)!=0;
721 emptyDirsFlag = find_option("emptydirs","d",0)!=0 || dirsOnlyFlag;
722 if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL;
723 if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP;
@@ -734,10 +750,11 @@
750 nRoot = (int)strlen(g.zLocalRoot);
751 g.allowSymlinks = 1; /* Find symlinks too */
752 if( !dirsOnlyFlag ){
753 Stmt q;
754 Blob repo;
755 if( !dryRunFlag && !disableUndo ) undo_begin();
756 locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore, 0);
757 db_prepare(&q,
758 "SELECT %Q || x FROM sfile"
759 " WHERE x NOT IN (%s)"
760 " ORDER BY 1",
@@ -755,23 +772,32 @@
772 " or \"keep-glob\")\n", zName+nRoot);
773 }
774 continue;
775 }
776 if( !allFileFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){
777 int undoRc = UNDO_NONE;
778 if( !disableUndo ){
779 undoRc = undo_maybe_save(zName+nRoot, UNDO_SIZE_LIMIT);
780 }
781 if( undoRc!=UNDO_SAVED_OK ){
782 Blob ans;
783 char cReply;
784 char *prompt = mprintf("\nWARNING: Deletion of this file will "
785 "not be undoable via the 'undo'\n"
786 " command because %s.\n\n"
787 "Remove unmanaged file \"%s\" (a=all/y/N)? ",
788 undo_save_message(undoRc), zName+nRoot);
789 prompt_user(prompt, &ans);
790 cReply = blob_str(&ans)[0];
791 if( cReply=='a' || cReply=='A' ){
792 allFileFlag = 1;
793 }else if( cReply!='y' && cReply!='Y' ){
794 blob_reset(&ans);
795 continue;
796 }
797 blob_reset(&ans);
798 }
799 }
800 if( dryRunFlag || file_delete(zName)==0 ){
801 if( verboseFlag || dryRunFlag ){
802 fossil_print("Removed unmanaged file: %s\n", zName+nRoot);
803 }
@@ -778,10 +804,11 @@
804 }else if( verboseFlag ){
805 fossil_print("Could not remove file: %s\n", zName+nRoot);
806 }
807 }
808 db_finalize(&q);
809 if( !dryRunFlag && !disableUndo ) undo_finish();
810 }
811 if( emptyDirsFlag ){
812 Glob *pEmptyDirs = glob_create(db_get("empty-dirs", 0));
813 Stmt q;
814 Blob root;
815
+25 -4
--- src/undo.c
+++ src/undo.c
@@ -20,15 +20,16 @@
2020
#include "config.h"
2121
#include "undo.h"
2222
2323
#if INTERFACE
2424
/*
25
-** Return values from the undo_maybe_save() routine.
25
+** Possible return values from the undo_maybe_save() routine.
2626
*/
27
-#define UNDO_SAVED_OK (0) /* The specified file was saved succesfully. */
28
-#define UNDO_INACTIVE (1) /* File not saved, subsystem is not active. */
29
-#define UNDO_TOOBIG (2) /* File not saved, it exceeded a size limit. */
27
+#define UNDO_NONE (0) /* Placeholder only used to initialize vars. */
28
+#define UNDO_SAVED_OK (1) /* The specified file was saved succesfully. */
29
+#define UNDO_INACTIVE (2) /* File not saved, subsystem is not active. */
30
+#define UNDO_TOOBIG (3) /* File not saved, it exceeded a size limit. */
3031
#endif
3132
3233
/*
3334
** Undo the change to the file zPathname. zPathname is the pathname
3435
** of the file relative to the root of the repository. If redoFlag is
@@ -342,10 +343,30 @@
342343
result = UNDO_TOOBIG;
343344
}
344345
free(zFullname);
345346
return result;
346347
}
348
+
349
+/*
350
+** Returns an error message for the undo_maybe_save() return code.
351
+** Currently, this function assumes that the caller is using the
352
+** returned error message in a context prefixed with "because".
353
+*/
354
+const char *undo_save_message(int rc){
355
+ static char zRc[32];
356
+
357
+ switch( rc ){
358
+ case UNDO_NONE: return "undo is disabled for this operation";
359
+ case UNDO_SAVED_OK: return "the save operation was successful";
360
+ case UNDO_INACTIVE: return "the undo subsystem is inactive";
361
+ case UNDO_TOOBIG: return "the file is too big";
362
+ default: {
363
+ sqlite3_snprintf(sizeof(zRc), zRc, "of error code %d", rc);
364
+ }
365
+ }
366
+ return zRc;
367
+}
347368
348369
/*
349370
** Make the current state of stashid undoable.
350371
*/
351372
void undo_save_stash(int stashid){
352373
--- src/undo.c
+++ src/undo.c
@@ -20,15 +20,16 @@
20 #include "config.h"
21 #include "undo.h"
22
23 #if INTERFACE
24 /*
25 ** Return values from the undo_maybe_save() routine.
26 */
27 #define UNDO_SAVED_OK (0) /* The specified file was saved succesfully. */
28 #define UNDO_INACTIVE (1) /* File not saved, subsystem is not active. */
29 #define UNDO_TOOBIG (2) /* File not saved, it exceeded a size limit. */
 
30 #endif
31
32 /*
33 ** Undo the change to the file zPathname. zPathname is the pathname
34 ** of the file relative to the root of the repository. If redoFlag is
@@ -342,10 +343,30 @@
342 result = UNDO_TOOBIG;
343 }
344 free(zFullname);
345 return result;
346 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
348 /*
349 ** Make the current state of stashid undoable.
350 */
351 void undo_save_stash(int stashid){
352
--- src/undo.c
+++ src/undo.c
@@ -20,15 +20,16 @@
20 #include "config.h"
21 #include "undo.h"
22
23 #if INTERFACE
24 /*
25 ** Possible return values from the undo_maybe_save() routine.
26 */
27 #define UNDO_NONE (0) /* Placeholder only used to initialize vars. */
28 #define UNDO_SAVED_OK (1) /* The specified file was saved succesfully. */
29 #define UNDO_INACTIVE (2) /* File not saved, subsystem is not active. */
30 #define UNDO_TOOBIG (3) /* File not saved, it exceeded a size limit. */
31 #endif
32
33 /*
34 ** Undo the change to the file zPathname. zPathname is the pathname
35 ** of the file relative to the root of the repository. If redoFlag is
@@ -342,10 +343,30 @@
343 result = UNDO_TOOBIG;
344 }
345 free(zFullname);
346 return result;
347 }
348
349 /*
350 ** Returns an error message for the undo_maybe_save() return code.
351 ** Currently, this function assumes that the caller is using the
352 ** returned error message in a context prefixed with "because".
353 */
354 const char *undo_save_message(int rc){
355 static char zRc[32];
356
357 switch( rc ){
358 case UNDO_NONE: return "undo is disabled for this operation";
359 case UNDO_SAVED_OK: return "the save operation was successful";
360 case UNDO_INACTIVE: return "the undo subsystem is inactive";
361 case UNDO_TOOBIG: return "the file is too big";
362 default: {
363 sqlite3_snprintf(sizeof(zRc), zRc, "of error code %d", rc);
364 }
365 }
366 return zRc;
367 }
368
369 /*
370 ** Make the current state of stashid undoable.
371 */
372 void undo_save_stash(int stashid){
373

Keyboard Shortcuts

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