DELETED .fossil-settings/clean-glob Index: .fossil-settings/clean-glob ================================================================== --- .fossil-settings/clean-glob +++ /dev/null @@ -1,17 +0,0 @@ -*.a -*.lib -*.manifest -*.o -*.obj -*.pdb -*.res -Makefile -bld/* -wbld/* -win/*.c -win/*.h -win/*.exe -win/headers -win/linkopts -autoconfig.h -config.log Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -1,5 +1,22 @@ compat/openssl* compat/tcl* fossil fossil.exe win/fossil.exe +*.a +*.lib +*.manifest +*.o +*.obj +*.pdb +*.res +Makefile +bld/* +wbld/* +win/*.c +win/*.h +win/*.exe +win/headers +win/linkopts +autoconfig.h +config.log Index: src/checkin.c ================================================================== --- src/checkin.c +++ src/checkin.c @@ -646,10 +646,13 @@ ** ** The --verily option ignores the keep-glob and ignore-glob settings ** and turns on --force, --dotfiles, and --emptydirs. Use the --verily ** option when you really want to clean up everything. ** +** If a source tree is cleaned accidentally, it can be restored using +** the "fossil undo" command, except for files matching ignore-glob. +** ** Options: ** --allckouts Check for empty directories within any checkouts ** that may be nested within the current one. This ** option should be used with great care because the ** empty-dirs setting (and other applicable settings) @@ -665,14 +668,16 @@ ** or another applicable setting or command line ** argument. Matching files, if any, are removed ** prior to checking for any empty directories; ** therefore, directories that contain only files ** that were removed will be removed as well. -** -f|--force Remove files without prompting. +** -f|--force Remove directories without prompting. ** -x|--verily Remove everything that is not a managed file or ** the repository itself. Implies -f --emptydirs -** --dotfiles. Disregard keep-glob and ignore-glob. +** --dotfiles. Disregard keep-glob and ignore-glob, +** except that files matching ignore-glob are never +** undo-able. ** --clean Never prompt for files matching this ** comma separated list of glob patterns. ** --ignore Ignore files matching patterns from the ** comma separated list of glob patterns. ** --keep Keep files matching this comma separated @@ -683,11 +688,11 @@ ** -v|--verbose Show all files as they are removed. ** ** See also: addremove, extras, status */ void clean_cmd(void){ - int allFileFlag, allDirFlag, dryRunFlag, verboseFlag; + int allDirFlag, dryRunFlag, verboseFlag; int emptyDirsFlag, dirsOnlyFlag; unsigned scanFlags = 0; int verilyFlag = 0; const char *zIgnoreFlag, *zKeepFlag, *zCleanFlag; Glob *pIgnore, *pKeep, *pClean; @@ -698,11 +703,14 @@ dryRunFlag = find_option("test",0,0)!=0; /* deprecated */ } if( !dryRunFlag ){ dryRunFlag = find_option("whatif",0,0)!=0; } - allFileFlag = allDirFlag = find_option("force","f",0)!=0; + allDirFlag = find_option("force","f",0)!=0; + if( !dryRunFlag ){ + undo_capture_command_line(); + } dirsOnlyFlag = find_option("dirsonly",0,0)!=0; emptyDirsFlag = find_option("emptydirs","d",0)!=0 || dirsOnlyFlag; if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL; if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP; if( find_option("allckouts",0,0)!=0 ) scanFlags |= SCAN_NESTED; @@ -710,16 +718,16 @@ verboseFlag = find_option("verbose","v",0)!=0; zKeepFlag = find_option("keep",0,1); zCleanFlag = find_option("clean",0,1); db_must_be_within_tree(); if( find_option("verily","x",0)!=0 ){ - verilyFlag = allFileFlag = allDirFlag = 1; + verilyFlag = allDirFlag = 1; emptyDirsFlag = 1; scanFlags |= SCAN_ALL; zCleanFlag = 0; } - if( zIgnoreFlag==0 && !verilyFlag ){ + if( zIgnoreFlag==0 ){ zIgnoreFlag = db_get("ignore-glob", 0); } if( zKeepFlag==0 && !verilyFlag ){ zKeepFlag = db_get("keep-glob", 0); } @@ -731,14 +739,19 @@ pIgnore = glob_create(zIgnoreFlag); pKeep = glob_create(zKeepFlag); pClean = glob_create(zCleanFlag); nRoot = (int)strlen(g.zLocalRoot); g.allowSymlinks = 1; /* Find symlinks too */ + if( !dryRunFlag ) undo_begin(); if( !dirsOnlyFlag ){ Stmt q; Blob repo; - locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore, 0); + char cReply; + Blob ans; + char *prompt; + + locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, verilyFlag ? 0 : pIgnore, 0); db_prepare(&q, "SELECT %Q || x FROM sfile" " WHERE x NOT IN (%s)" " ORDER BY 1", g.zLocalRoot, fossil_all_reserved_names(0) @@ -754,22 +767,19 @@ fossil_print("KEPT file \"%s\" not removed (due to --keep" " or \"keep-glob\")\n", zName+nRoot); } continue; } - if( !allFileFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){ - Blob ans; - char cReply; - char *prompt = mprintf("Remove unmanaged file \"%s\" (a=all/y/N)? ", - zName+nRoot); + if( !dryRunFlag && !glob_match(pClean, zName+nRoot) + && !(verilyFlag && glob_match(pIgnore, zName+nRoot)) + && undo_save(zName+nRoot, 10*1024*1024) ){ + prompt = mprintf("file \"%s\" too big. Deletion will not be " + "undo-able. Continue (y/N)? ", zName+nRoot); prompt_user(prompt, &ans); cReply = blob_str(&ans)[0]; - if( cReply=='a' || cReply=='A' ){ - allFileFlag = 1; - }else if( cReply!='y' && cReply!='Y' ){ - blob_reset(&ans); - continue; + if( cReply!='y' && cReply!='Y' ){ + fossil_fatal("Clean aborted"); } blob_reset(&ans); } if( dryRunFlag || file_delete(zName)==0 ){ if( verboseFlag || dryRunFlag ){ @@ -777,19 +787,20 @@ } }else if( verboseFlag ){ fossil_print("Could not remove file: %s\n", zName+nRoot); } } + undo_finish(); db_finalize(&q); } if( emptyDirsFlag ){ Glob *pEmptyDirs = glob_create(db_get("empty-dirs", 0)); Stmt q; Blob root; blob_init(&root, g.zLocalRoot, nRoot - 1); - vfile_dir_scan(&root, blob_size(&root), scanFlags, pIgnore, - pEmptyDirs); + vfile_dir_scan(&root, blob_size(&root), scanFlags, + verilyFlag ? 0 : pIgnore, pEmptyDirs); blob_reset(&root); db_prepare(&q, "SELECT %Q || x FROM dscan_temp" " WHERE x NOT IN (%s) AND y = 0" " ORDER BY 1 DESC", Index: src/merge.c ================================================================== --- src/merge.c +++ src/merge.c @@ -526,11 +526,11 @@ }else{ fossil_print("ADDED %s\n", zName); } fossil_free(zFullName); if( !dryRunFlag ){ - undo_save(zName); + undo_save(zName, -1); vfile_to_disk(0, idm, 0, 0); } } db_finalize(&q); @@ -549,11 +549,11 @@ const char *zName = db_column_text(&q, 2); int islinkm = db_column_int(&q, 3); /* Copy content from idm over into idv. Overwrite idv. */ fossil_print("UPDATE %s\n", zName); if( !dryRunFlag ){ - undo_save(zName); + undo_save(zName, -1); db_multi_exec( "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d " " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, idv ); vfile_to_disk(0, idv, 0, 0); @@ -592,11 +592,11 @@ } if( islinkv || islinkm /* || file_wd_islink(zFullPath) */ ){ fossil_print("***** Cannot merge symlink %s\n", zName); nConflict++; }else{ - undo_save(zName); + undo_save(zName, -1); zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); content_get(ridp, &p); content_get(ridm, &m); if( isBinary ){ rc = -1; @@ -643,11 +643,11 @@ fossil_print("DELETE %s\n", zName); if( chnged ){ fossil_warning("WARNING: local edits lost for %s\n", zName); nConflict++; } - undo_save(zName); + undo_save(zName, -1); db_multi_exec( "UPDATE vfile SET deleted=1 WHERE id=%d", idv ); if( !dryRunFlag ){ char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName); @@ -669,12 +669,12 @@ while( db_step(&q)==SQLITE_ROW ){ int idv = db_column_int(&q, 0); const char *zOldName = db_column_text(&q, 1); const char *zNewName = db_column_text(&q, 2); fossil_print("RENAME %s -> %s\n", zOldName, zNewName); - undo_save(zOldName); - undo_save(zNewName); + undo_save(zOldName, -1); + undo_save(zNewName, -1); db_multi_exec( "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)" " WHERE id=%d AND vid=%d", zNewName, idv, vid ); if( !dryRunFlag ){ Index: src/stash.c ================================================================== --- src/stash.c +++ src/stash.c @@ -219,11 +219,11 @@ const char *zOrig = db_column_text(&q, 4); const char *zNew = db_column_text(&q, 5); char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig); char *zNPath = mprintf("%s%s", g.zLocalRoot, zNew); Blob delta; - undo_save(zNew); + undo_save(zNew, -1); blob_zero(&delta); if( rid==0 ){ db_multi_exec("INSERT OR IGNORE INTO sfile(x) VALUES(%Q)", zNew); db_ephemeral_blob(&q, 6, &delta); blob_write_to_file(&delta, zNPath); @@ -276,11 +276,11 @@ blob_reset(&b); blob_reset(&disk); } blob_reset(&delta); if( fossil_strcmp(zOrig,zNew)!=0 ){ - undo_save(zOrig); + undo_save(zOrig, -1); file_delete(zOPath); } } stash_add_files_in_sfile(vid); db_finalize(&q); Index: src/undo.c ================================================================== --- src/undo.c +++ src/undo.c @@ -258,44 +258,53 @@ static int undoNeedRollback = 0; /* ** Save the current content of the file zPathname so that it ** will be undoable. The name is relative to the root of the -** tree. +** tree. Any file bigger than "limit" will not be stored in +** the undo table, and cause this function to return 1. */ -void undo_save(const char *zPathname){ +int undo_save(const char *zPathname, i64 limit){ char *zFullname; Blob content; int existsFlag; int isLink; Stmt q; + int result = 0; + i64 size; - if( !undoActive ) return; + if( !undoActive ) return 0; zFullname = mprintf("%s%s", g.zLocalRoot, zPathname); - existsFlag = file_wd_size(zFullname)>=0; + size = file_wd_size(zFullname); + existsFlag = size>=0; isLink = file_wd_islink(zFullname); - db_prepare(&q, - "INSERT OR IGNORE INTO" - " undo(pathname,redoflag,existsflag,isExe,isLink,content)" - " VALUES(%Q,0,%d,%d,%d,:c)", - zPathname, existsFlag, file_wd_isexe(zFullname), isLink - ); - if( existsFlag ){ - if( isLink ){ - blob_read_link(&content, zFullname); - }else{ - blob_read_from_file(&content, zFullname); - } - db_bind_blob(&q, ":c", &content); + if( limit>=0 && size>limit ){ + result = 1; + }else{ + db_prepare(&q, + "INSERT OR IGNORE INTO" + " undo(pathname,redoflag,existsflag,isExe,isLink,content)" + " VALUES(%Q,0,%d,%d,%d,:c)", + zPathname, size>=0, file_wd_isexe(zFullname), isLink + ); + if( existsFlag ){ + if( isLink ){ + blob_read_link(&content, zFullname); + }else{ + blob_read_from_file(&content, zFullname); + } + db_bind_blob(&q, ":c", &content); + } + db_step(&q); + db_finalize(&q); + if( existsFlag ){ + blob_reset(&content); + } } free(zFullname); - db_step(&q); - db_finalize(&q); - if( existsFlag ){ - blob_reset(&content); - } undoNeedRollback = 1; + return result; } /* ** Make the current state of stashid undoable. */ @@ -357,14 +366,14 @@ ** or: %fossil redo ?OPTIONS? ?FILENAME...? ** ** Undo the changes to the working checkout caused by the most recent ** of the following operations: ** -** (1) fossil update (5) fossil stash apply +** (1) fossil clean (5) fossil stash apply ** (2) fossil merge (6) fossil stash drop ** (3) fossil revert (7) fossil stash goto -** (4) fossil stash pop +** (4) fossil update (8) fossil stash pop ** ** If FILENAME is specified then restore the content of the named ** file(s) but otherwise leave the update or merge or revert in effect. ** The redo command undoes the effect of the most recent undo. ** Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -424,15 +424,15 @@ fossil_print("ADD %s - overwrites an unmanaged file\n", zName); nOverwrite++; }else{ fossil_print("ADD %s\n", zName); } - undo_save(zName); + undo_save(zName, -1); if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); }else if( idt>0 && idv>0 && ridt!=ridv && (chnged==0 || deleted) ){ /* The file is unedited. Change it to the target version */ - undo_save(zName); + undo_save(zName, -1); if( deleted ){ fossil_print("UPDATE %s - change to unmanaged file\n", zName); }else{ fossil_print("UPDATE %s\n", zName); } @@ -439,11 +439,11 @@ if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); }else if( idt>0 && idv>0 && !deleted && file_wd_size(zFullPath)<0 ){ /* The file missing from the local check-out. Restore it to the ** version that appears in the target. */ fossil_print("UPDATE %s\n", zName); - undo_save(zName); + undo_save(zName, -1); if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); }else if( idt==0 && idv>0 ){ if( ridv==0 ){ /* Added in current checkout. Continue to hold the file as ** as an addition */ @@ -454,11 +454,11 @@ fossil_print("CONFLICT %s - edited locally but deleted by update\n", zName); nConflict++; }else{ fossil_print("REMOVE %s\n", zName); - undo_save(zName); + undo_save(zName, -1); if( !dryRunFlag ) file_delete(zFullPath); } }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){ /* Merge the changes in the current tree into the target version */ Blob r, t, v; @@ -471,11 +471,11 @@ if( islinkv || islinkt /* || file_wd_islink(zFullPath) */ ){ fossil_print("***** Cannot merge symlink %s\n", zNewName); nConflict++; }else{ unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0; - undo_save(zName); + undo_save(zName, -1); content_get(ridt, &t); content_get(ridv, &v); rc = merge_3way(&v, zFullPath, &t, &r, mergeFlags); if( rc>=0 ){ if( !dryRunFlag ){ @@ -786,11 +786,11 @@ if( errCode==2 ){ if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q", zFile, zFile)==0 ){ fossil_print("UNMANAGE: %s\n", zFile); }else{ - undo_save(zFile); + undo_save(zFile, -1); file_delete(zFull); fossil_print("DELETE: %s\n", zFile); } db_multi_exec( "UPDATE OR REPLACE vfile" @@ -799,11 +799,11 @@ "DELETE FROM vfile WHERE pathname=%Q", zFile, zFile ); }else{ sqlite3_int64 mtime; - undo_save(zFile); + undo_save(zFile, -1); if( file_wd_size(zFull)>=0 && (isLink || file_wd_islink(0)) ){ file_delete(zFull); } if( isLink ){ symlink_create(blob_str(&record), zFull);