Index: src/checkout.c ================================================================== --- src/checkout.c +++ src/checkout.c @@ -39,15 +39,53 @@ } /* ** Undo the current check-out. Unlink all files from the disk. ** Clear the VFILE table. +** +** Also delete any directory that becomes empty as a result of deleting +** files due to this operation, as long as that directory is not the +** current working directory and is not on the empty-dirs list. */ void uncheckout(int vid){ - if( vid>0 ){ - vfile_unlink(vid); - } + char *zPwd; + if( vid<=0 ) return; + sqlite3_create_function(g.db, "dirname",1,SQLITE_UTF8,0, + file_dirname_sql_function, 0, 0); + sqlite3_create_function(g.db, "unlink",1,SQLITE_UTF8,0, + file_delete_sql_function, 0, 0); + sqlite3_create_function(g.db, "rmdir", 1, SQLITE_UTF8, 0, + file_rmdir_sql_function, 0, 0); + db_multi_exec( + "CREATE TEMP TABLE dir_to_delete(name TEXT %s PRIMARY KEY)WITHOUT ROWID", + filename_collation() + ); + db_multi_exec( + "INSERT OR IGNORE INTO dir_to_delete(name)" + " SELECT dirname(pathname) FROM vfile" + " WHERE vid=%d AND mrid>0", + vid + ); + do{ + db_multi_exec( + "INSERT OR IGNORE INTO dir_to_delete(name)" + " SELECT dirname(name) FROM dir_to_delete;" + ); + }while( db_changes() ); + db_multi_exec( + "SELECT unlink(%Q||pathname) FROM vfile" + " WHERE vid=%d AND mrid>0;", + g.zLocalRoot, vid + ); + ensure_empty_dirs_created(1); + zPwd = file_getcwd(0,0); + db_multi_exec( + "SELECT rmdir(%Q||name) FROM dir_to_delete" + " WHERE (%Q||name)<>%Q ORDER BY name DESC", + g.zLocalRoot, g.zLocalRoot, zPwd + ); + fossil_free(zPwd); db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid); } /* @@ -303,11 +341,11 @@ if( !keepFlag ){ vfile_to_disk(vid, 0, !g.fQuiet, promptFlag); } checkout_set_all_exe(vid); manifest_to_disk(vid); - ensure_empty_dirs_created(); + ensure_empty_dirs_created(0); db_set_checkout(vid); undo_reset(); db_multi_exec("DELETE FROM vmerge"); if( !keepFlag && db_get_boolean("repo-cksum",1) ){ vfile_aggregate_checksum_manifest(vid, &cksum1, &cksum1b); Index: src/file.c ================================================================== --- src/file.c +++ src/file.c @@ -437,10 +437,29 @@ return mprintf("%.*s", (int)(zTail-z-1), z); }else{ return 0; } } + +/* SQL Function: file_dirname(NAME) +** +** Return the directory for NAME +*/ +void file_dirname_sql_function( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName = (const char*)sqlite3_value_text(argv[0]); + char *zDir; + if( zName==0 ) return; + zDir = file_dirname(zName); + if( zDir ){ + sqlite3_result_text(context,zDir,-1,fossil_free); + } +} + /* ** Rename a file or directory. ** Returns zero upon success. */ @@ -594,10 +613,30 @@ rc = unlink(zFilename); #endif fossil_path_free(z); return rc; } + +/* SQL Function: file_delete(NAME) +** +** Remove file NAME. Return zero on success and non-zero if anything goes +** wrong. +*/ +void file_delete_sql_function( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName = (const char*)sqlite3_value_text(argv[0]); + int rc; + if( zName==0 ){ + rc = 1; + }else{ + rc = file_delete(zName); + } + sqlite3_result_int(context, rc); +} /* ** Create a directory called zName, if it does not already exist. ** If forceFlag is 1, delete any prior non-directory object ** with the same name. @@ -684,10 +723,30 @@ fossil_path_free(zMbcs); return rc; } return 0; } + +/* SQL Function: rmdir(NAME) +** +** Try to remove the directory NAME. Return zero on success and non-zero +** for failure. +*/ +void file_rmdir_sql_function( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName = (const char*)sqlite3_value_text(argv[0]); + int rc; + if( zName==0 ){ + rc = 1; + }else{ + rc = file_rmdir(zName); + } + sqlite3_result_int(context, rc); +} /* ** Return true if the filename given is a valid filename for ** a file in a repository. Valid filenames follow all of the ** following rules: @@ -890,12 +949,20 @@ ** Get the current working directory. ** ** On windows, the name is converted from unicode to UTF8 and all '\\' ** characters are converted to '/'. No conversions are needed on ** unix. +** +** Store the value of the CWD in zBuf which is nBuf bytes in size. +** or if zBuf==0, allocate space to hold the result using fossil_malloc(). */ -void file_getcwd(char *zBuf, int nBuf){ +char *file_getcwd(char *zBuf, int nBuf){ + char zTemp[2000]; + if( zBuf==0 ){ + zBuf = zTemp; + nBuf = sizeof(zTemp); + } #ifdef _WIN32 win32_getcwd(zBuf, nBuf); #else if( getcwd(zBuf, nBuf-1)==0 ){ if( errno==ERANGE ){ @@ -904,10 +971,11 @@ fossil_panic("cannot find current working directory; %s", strerror(errno)); } } #endif + return zBuf==zTemp ? fossil_strdup(zBuf) : zBuf; } /* ** Return true if zPath is an absolute pathname. Return false ** if it is relative. Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -160,11 +160,11 @@ } } /* Create any empty directories now, as well as after the update, ** so changes in settings are reflected now */ - if( !dryRunFlag ) ensure_empty_dirs_created(); + if( !dryRunFlag ) ensure_empty_dirs_created(0); if( internalUpdate ){ tid = internalUpdate; }else if( g.argc>=3 ){ if( fossil_strcmp(g.argv[2], "current")==0 ){ @@ -227,10 +227,14 @@ if( tid==0 ){ return; } db_begin_transaction(); + db_multi_exec( + "CREATE TEMP TABLE dir_to_delete(name TEXT %s PRIMARY KEY)WITHOUT ROWID", + filename_collation() + ); vfile_check_signature(vid, CKSIG_ENOTFILE); if( !dryRunFlag && !internalUpdate ) undo_begin(); if( load_vfile_from_rid(tid) && !forceMissingFlag ){ fossil_fatal("missing content, unable to update"); }; @@ -455,11 +459,23 @@ zName); nConflict++; }else{ fossil_print("REMOVE %s\n", zName); if( !dryRunFlag && !internalUpdate ) undo_save(zName); - if( !dryRunFlag ) file_delete(zFullPath); + if( !dryRunFlag ){ + char *zDir; + file_delete(zFullPath); + zDir = file_dirname(zName); + while( zDir!=0 ){ + char *zNext; + db_multi_exec("INSERT OR IGNORE INTO dir_to_delete(name)" + "VALUES(%Q)", zDir); + zNext = db_changes() ? file_dirname(zDir) : 0; + fossil_free(zDir); + zDir = zNext; + } + } } }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){ /* Merge the changes in the current tree into the target version */ Blob r, t, v; int rc; @@ -566,11 +582,21 @@ ** Clean up the mid and pid VFILE entries. Then commit the changes. */ if( dryRunFlag ){ db_end_transaction(1); /* With --dry-run, rollback changes */ }else{ - ensure_empty_dirs_created(); + char *zPwd; + ensure_empty_dirs_created(1); + sqlite3_create_function(g.db, "rmdir", 1, SQLITE_UTF8, 0, + file_rmdir_sql_function, 0, 0); + zPwd = file_getcwd(0,0); + db_multi_exec( + "SELECT rmdir(%Q||name) FROM dir_to_delete" + " WHERE (%Q||name)<>%Q ORDER BY name DESC", + g.zLocalRoot, g.zLocalRoot, zPwd + ); + fossil_free(zPwd); if( g.argc<=3 ){ /* All files updated. Shift the current checkout to the target. */ db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid); checkout_set_all_exe(tid); manifest_to_disk(tid); @@ -587,11 +613,11 @@ } /* ** Create empty directories specified by the empty-dirs setting. */ -void ensure_empty_dirs_created(void){ +void ensure_empty_dirs_created(int clearDirTable){ char *zEmptyDirs = db_get("empty-dirs", 0); if( zEmptyDirs!=0 ){ int i; Blob dirName; Blob dirsList; @@ -613,11 +639,16 @@ "required by empty-dirs setting", zDir); } break; } case 1: { /* exists, and is a directory */ - /* do nothing - required directory exists already */ + /* make sure this directory is not on the delete list */ + if( clearDirTable ){ + db_multi_exec( + "DELETE FROM dir_to_delete WHERE name=%Q", zDir + ); + } break; } case 2: { /* exists, but isn't a directory */ fossil_warning("file %s found, but a directory is required " "by empty-dirs setting", zDir); Index: src/vfile.c ================================================================== --- src/vfile.c +++ src/vfile.c @@ -359,28 +359,10 @@ file_mtime(zName, RepoFILE), id); } db_finalize(&q); } - -/* -** Delete from the disk every file in VFILE vid. -*/ -void vfile_unlink(int vid){ - Stmt q; - db_prepare(&q, "SELECT %Q || pathname FROM vfile" - " WHERE vid=%d AND mrid>0", g.zLocalRoot, vid); - while( db_step(&q)==SQLITE_ROW ){ - const char *zName; - - zName = db_column_text(&q, 0); - file_delete(zName); - } - db_finalize(&q); - db_multi_exec("UPDATE vfile SET mtime=NULL WHERE vid=%d AND mrid>0", vid); -} - /* ** Check to see if the directory named in zPath is the top of a checkout. ** In other words, check to see if directory pPath contains a file named ** "_FOSSIL_" or ".fslckout". Return true or false. */