# # # patch "file_io.cc" # from [d09e05619e36cf4ed969a596b897b92a9a9050b1] # to [b770c3c79705465b970d3cef9b066825e34069cd] # # patch "file_io.hh" # from [653187d3891d5bdcfa979c55e48163ed451951b0] # to [b25393c180869ad70aa6344b0e0a08d3ab82d474] # # patch "key_store.cc" # from [b083bb11781ce170e067859a28944fb9375843a4] # to [b6fed5993775bff8dd4e377c04c440e1d17328d4] # # patch "paths.cc" # from [29c4401e73c9b708fb3b95c41677c8b46e0d43af] # to [defa5bd893d6db66107e50122713ee2769ce8d15] # # patch "paths.hh" # from [7405e173fac28aa9974fd515a06b7d66e1b3fa67] # to [57c50de2ab3cb44925e731b2ee4c60f4b6d7174b] # # patch "platform-wrapped.hh" # from [52b85ed314d2f3dd0aef3619809d0abf9a900659] # to [2ea673d7e51b36dc70b933191a90fe709aaa2115] # # patch "platform.hh" # from [579df5c8d9106dcec4fce731f445877ed5754cba] # to [087ce9de4576d093203693159cd99412bf21c89a] # # patch "tester-plaf.hh" # from [b892b8cab5cd8d8571b5eea0282777883ea9a48c] # to [8baa3ee08bfaf7b38fcbd004613daf3c0c7c3cf2] # # patch "tester.cc" # from [13d974099651f132d26ffbf669bfbf9574875c01] # to [a92e282d849052d4b5fbc423577457068fee877a] # # patch "tests/skip_invalid_paths/__driver__.lua" # from [482cbe86e9545c3615390887ff7e150d0c360084] # to [18ccf9b67e40c8297e2638e49bbf300770145efc] # # patch "unix/fs.cc" # from [b5909760f49692cdd13ea1aa76a0323e07b95b92] # to [216c1ac46fa07c136aa521c871b39806e4391669] # # patch "unix/tester-plaf.cc" # from [db23c14fe19860f95d51463995652cdbbc51cf4b] # to [ffc04609551521fb260ac56c4137e5396d0cb69e] # # patch "win32/fs.cc" # from [14806bf816131ecf8e940f5ba2f386f258f85ac8] # to [4e8d4a113a02dcbc60256241680df4e0686580c8] # # patch "win32/tester-plaf.cc" # from [b7a03cf159f5228fb467db08349691d57db6c310] # to [ebbaac9b027e0bf19f7569a3c11071988775fa33] # # patch "work.cc" # from [7badc3da940f8ef7f9ed821adca729c8b5cad408] # to [6121b48399665ea41043e2f67905412d22a3fc8c] # ============================================================ --- file_io.cc d09e05619e36cf4ed969a596b897b92a9a9050b1 +++ file_io.cc b770c3c79705465b970d3cef9b066825e34069cd @@ -111,7 +111,8 @@ file_exists(any_path const & p) return get_path_status(p) == path::file; } -namespace +bool +directory_empty(any_path const & path) { struct directory_not_empty_exception {}; struct directory_empty_helper : public dirent_consumer @@ -119,14 +120,10 @@ namespace virtual void consume(char const *) { throw directory_not_empty_exception(); } }; -} -bool -directory_empty(any_path const & path) -{ directory_empty_helper h; try { - do_read_directory(system_path(path).as_external(), h, h, h); + do_read_directory(path, h, h, h); } catch (directory_not_empty_exception) { return false; } @@ -226,68 +223,6 @@ delete_file_or_dir_shallow(any_path cons do_remove(p.as_external()); } -namespace -{ - struct fill_pc_vec : public dirent_consumer - { - fill_pc_vec(vector & v) : v(v) { v.clear(); } - - // FIXME BUG: this treats 's' as being already utf8, - // but it is actually in the external character set. - virtual void consume(char const * s) - { - try - { - v.push_back(path_component(s)); - } - catch (...) - { - W(F("skipping invalid path '%s'") % s); - } - } - - private: - vector & v; - }; - - struct file_deleter : public dirent_consumer - { - file_deleter(any_path const & p) : parent(p) {} - virtual void consume(char const * f) - { - try - { - do_remove((parent / path_component(f)).as_external()); - } - catch (...) - { - W(F("skipping invalid path '%s'") % f); - } - } - private: - any_path const & parent; - }; -} - -static void -do_remove_recursive(any_path const & p) -{ - // for the reasons described in walk_tree_recursive, we read the entire - // directory before recursing into any subdirs. however, it is safe to - // delete files as we encounter them, and we do so. - vector subdirs; - fill_pc_vec subdir_fill(subdirs); - file_deleter delete_files(p); - - do_read_directory(p.as_external(), delete_files, subdir_fill, delete_files); - for (vector::const_iterator i = subdirs.begin(); - i != subdirs.end(); i++) - do_remove_recursive(p / *i); - - do_remove(p.as_external()); -} - - void delete_dir_recursive(any_path const & p) { @@ -295,7 +230,7 @@ delete_dir_recursive(any_path const & p) F("directory to delete, '%s', does not exist") % p, F("directory to delete, '%s', is a file") % p); - do_remove_recursive(p); + do_remove_recursive(p.as_external()); } void @@ -359,17 +294,6 @@ read_data(any_path const & p, data & dat data_from); } -void read_directory(any_path const & path, - vector & files, - vector & dirs) -{ - vector special_files; - fill_pc_vec ff(files), df(dirs), sf(special_files); - do_read_directory(path.as_external(), ff, df, sf); - E(special_files.empty(), origin::system, - F("cannot handle special files in dir '%s'") % path); -} - // This function can only be called once per run. void read_data_stdin(data & dat) @@ -456,40 +380,6 @@ tree_walker::visit_dir(file_path const & return true; } -// subroutine of walk_tree_recursive: if the path composition of PATH and PC -// is a valid file_path, write it to ENTRY and return true. otherwise, -// generate an appropriate diagnostic and return false. in this context, an -// invalid path is *not* an invariant failure, because it came from a -// directory scan. ??? arguably belongs as a file_path method. -static bool -safe_compose(file_path const & path, path_component const & pc, bool isdir, - file_path & entry) -{ - try - { - entry = path / pc; - return true; - } - catch (logic_error) - { - // do what the above operator/ did, by hand, and then figure out what - // sort of diagnostic to issue. - utf8 badpth; - if (path.empty()) - badpth = typecast_vocab(pc); - else - badpth = utf8(path.as_internal() + "/" + pc(), pc.made_from); - - if (!isdir) - W(F("skipping file '%s' with unsupported name") % badpth); - else if (bookkeeping_path::internal_string_is_bookkeeping_path(badpth)) - L(FL("ignoring bookkeeping directory '%s'") % badpth); - else - W(F("skipping directory '%s' with unsupported name") % badpth); - return false; - } -} - static void walk_tree_recursive(file_path const & path, tree_walker & walker) @@ -504,25 +394,20 @@ walk_tree_recursive(file_path const & pa // peak memory. By splitting the loop in half, we avoid this problem. // // [1] http://lkml.org/lkml/2006/2/24/215 - vector files, dirs; - read_directory(path, files, dirs); + vector files, dirs; + fill_path_vec fill_files(path, files, false); + fill_path_vec fill_dirs(path, dirs, true); - for (vector::const_iterator i = files.begin(); + do_read_directory(path, fill_files, fill_dirs); + + for (vector::const_iterator i = files.begin(); i != files.end(); ++i) - { - file_path entry; - if (safe_compose(path, *i, false, entry)) - walker.visit_file(entry); - } + walker.visit_file(*i); - for (vector::const_iterator i = dirs.begin(); + for (vector::const_iterator i = dirs.begin(); i != dirs.end(); ++i) - { - file_path entry; - if (safe_compose(path, *i, true, entry)) - if (walker.visit_dir(entry)) - walk_tree_recursive(entry, walker); - } + if (walker.visit_dir(*i)) + walk_tree_recursive(*i, walker); } // from some (safe) sub-entry of cwd ============================================================ --- file_io.hh 653187d3891d5bdcfa979c55e48163ed451951b0 +++ file_io.hh b25393c180869ad70aa6344b0e0a08d3ab82d474 @@ -72,10 +72,6 @@ void read_data(any_path const & path, da void read_data(any_path const & path, data & data); -void read_directory(any_path const & path, - std::vector & files, - std::vector & dirs); - void read_data_stdin(data & dat); // This function knows that "-" means "stdin". ============================================================ --- key_store.cc b083bb11781ce170e067859a28944fb9375843a4 +++ key_store.cc b6fed5993775bff8dd4e377c04c440e1d17328d4 @@ -216,25 +216,26 @@ key_store_state::maybe_read_key_dir() return; have_read = true; - vector key_files, dirs; - if (directory_exists(key_dir)) + if (!directory_exists(key_dir)) { - L(FL("reading key dir '%s'") % key_dir); - read_directory(key_dir, key_files, dirs); - } - else - { L(FL("key dir '%s' does not exist") % key_dir); return; } + L(FL("reading key dir '%s'") % key_dir); + + vector key_files; + fill_path_vec fill_key_files(key_dir, key_files, false); + dirent_ignore ignore; + do_read_directory(key_dir, fill_key_files, ignore, ignore); + keyreader kr(*this); - for (vector::const_iterator i = key_files.begin(); + for (vector::const_iterator i = key_files.begin(); i != key_files.end(); ++i) { L(FL("reading keys from file '%s'") % (*i)); data dat; - read_data(key_dir / *i, dat); + read_data(*i, dat); istringstream is(dat()); read_packets(is, kr); } ============================================================ --- paths.cc 29c4401e73c9b708fb3b95c41677c8b46e0d43af +++ paths.cc defa5bd893d6db66107e50122713ee2769ce8d15 @@ -871,6 +871,30 @@ new_optimal_path(std::string path, bool return boost::shared_ptr(new file_path(file_path_internal(normalized))); }; +// Either conversion of S to a path_component, or composition of P / S, has +// failed; figure out what went wrong and issue an appropriate diagnostic. + +void +report_failed_path_composition(any_path const & p, char const * s, + bool isdir) +{ + utf8 badpth; + if (p.empty()) + badpth = utf8(s); + else + badpth = utf8(p.as_internal() + "/" + s, p.made_from); + if (bookkeeping_path::internal_string_is_bookkeeping_path(badpth)) + L(FL("ignoring bookkeeping directory '%s'") % badpth); + else + { + // We rely on caller to tell us whether this is a directory. + if (isdir) + W(F("skipping directory '%s' with unsupported name") % badpth); + else + W(F("skipping file '%s' with unsupported name") % badpth); + } +} + /////////////////////////////////////////////////////////////////////////// // workspace (and path root) handling /////////////////////////////////////////////////////////////////////////// @@ -1066,12 +1090,6 @@ find_new_path_for(map #include "origin_type.hh" #include +#include class any_path; class file_path; @@ -175,6 +176,7 @@ public: any_path operator /(path_component const &) const; any_path dirname() const; + any_path() {} any_path(any_path const & other) : origin_aware(other.made_from), data(other.data) {} any_path & operator=(any_path const & other) @@ -182,7 +184,6 @@ protected: protected: std::string data; - any_path() {} any_path(origin::type whence) : origin_aware(whence) {} private: @@ -399,6 +400,29 @@ template <> void dump(system_path const template <> void dump(bookkeeping_path const & sp, std::string & out); template <> void dump(system_path const & sp, std::string & out); +// Attempt to form the composed path P / S, as operator/ would do, but if +// this is unsuccessful, warn the user and return false, rather than +// aborting. For use when scanning directories. Do try to tell +// safe_compose whether S refers to a directory; it only improves +// diagnostics, but giving good diagnostics is very important. +extern void report_failed_path_composition(any_path const & p, + char const * s, bool isdir); + +template +bool safe_compose(T const & p, char const * s, T & result, bool isdir=false) +{ + try + { + result = p / path_component(s); + return true; + } + catch (std::logic_error) + { + report_failed_path_composition(p, s, isdir); + return false; + } +} + // Base class for predicate functors on paths. T must be one of the path // classes. template ============================================================ --- platform-wrapped.hh 52b85ed314d2f3dd0aef3619809d0abf9a900659 +++ platform-wrapped.hh 2ea673d7e51b36dc70b933191a90fe709aaa2115 @@ -13,6 +13,7 @@ #include "paths.hh" #include "platform.hh" #include "vocab.hh" +#include "vector.hh" inline void change_current_working_dir(any_path const & to) { @@ -30,6 +31,64 @@ inline void rename_clobberingly(any_path rename_clobberingly(from.as_external(), to.as_external()); } +// Some generally-useful dirent consumers + +struct dirent_ignore : public dirent_consumer +{ + virtual void consume(char const *) {} +}; + +template +struct fill_path_vec : public dirent_consumer +{ + fill_path_vec(T const & parent, std::vector & v, bool isdir) + : parent(parent), v(v), isdir(isdir) + { v.clear(); } + + virtual void consume(char const * s) + { + T result; + if (safe_compose(parent, s, result, isdir)) + v.push_back(result); + } +private: + T const & parent; + std::vector & v; + bool isdir; +}; + +struct special_file_error : public dirent_consumer +{ + special_file_error(any_path const & p) : parent(p) {} + virtual void consume(char const * f) + { + any_path result; + if (safe_compose(parent, f, result, false)) + E(false, origin::system, + F("'%s' is neither a file nor a directory") % result); + } +private: + any_path const & parent; +}; + +inline void +do_read_directory(any_path const & path, + dirent_consumer & files, + dirent_consumer & dirs, + dirent_consumer & specials) +{ + do_read_directory(path.as_external(), files, dirs, specials); +} + +inline void +do_read_directory(any_path const & path, + dirent_consumer & files, + dirent_consumer & dirs) +{ + special_file_error sfe(path); + do_read_directory(path.as_external(), files, dirs, sfe); +} + #endif // Local Variables: ============================================================ --- platform.hh 579df5c8d9106dcec4fce731f445877ed5754cba +++ platform.hh 087ce9de4576d093203693159cd99412bf21c89a @@ -133,8 +133,10 @@ void do_read_directory(std::string const dirent_consumer & dirs, dirent_consumer & other_files); +void make_accessible(std::string const & name); void rename_clobberingly(std::string const & from, std::string const & to); void do_remove(std::string const & path); +void do_remove_recursive(std::string const & path); void do_mkdir(std::string const & path); void write_data_worker(std::string const & p, ============================================================ --- tester-plaf.hh b892b8cab5cd8d8571b5eea0282777883ea9a48c +++ tester-plaf.hh 8baa3ee08bfaf7b38fcbd004613daf3c0c7c3cf2 @@ -15,7 +15,6 @@ #include -void make_accessible(std::string const & name); std::time_t get_last_write_time(char const * name); void do_copy_file(std::string const & from, std::string const & to); void set_env(char const * var, char const * val); ============================================================ --- tester.cc 13d974099651f132d26ffbf669bfbf9574875c01 +++ tester.cc a92e282d849052d4b5fbc423577457068fee877a @@ -117,20 +117,6 @@ namespace vector & v; }; - struct file_deleter : public dirent_consumer - { - file_deleter(string const & p) : parent(p) {} - virtual void consume(char const * f) - { - string e(parent + "/" + f); - make_accessible(e); - do_remove(e); - } - - private: - string const & parent; - }; - struct file_accessible_maker : public dirent_consumer { file_accessible_maker(string const & p) : parent(p) {} @@ -155,35 +141,6 @@ namespace }; } -void do_remove_recursive(string const & p) -{ - switch (get_path_status(p)) - { - case path::directory: - { - make_accessible(p); - vector subdirs; - struct fill_vec get_subdirs(subdirs); - struct file_deleter del_files(p); - - do_read_directory(p, del_files, get_subdirs, del_files); - for(vector::const_iterator i = subdirs.begin(); - i != subdirs.end(); i++) - do_remove_recursive(p + "/" + *i); - do_remove(p); - } - return; - - case path::file: - make_accessible(p); - do_remove(p); - return; - - case path::nonexistent: - return; - } -} - void do_make_tree_accessible(string const & p) { switch (get_path_status(p)) ============================================================ --- tests/skip_invalid_paths/__driver__.lua 482cbe86e9545c3615390887ff7e150d0c360084 +++ tests/skip_invalid_paths/__driver__.lua 18ccf9b67e40c8297e2638e49bbf300770145efc @@ -7,11 +7,13 @@ writefile("foo/\\", "invalid path") mkdir("foo") writefile("foo/\\", "invalid path") -check(mtn("add", "foo"), 0, false, true) -check(qgrep("skipping invalid path '\\\\'", "stderr")) +check(mtn("add", "--recursive", "foo"), 0, false, true) +check(qgrep("skipping file 'foo/\\\\' with unsupported name", "stderr")) check(qgrep("adding foo to workspace manifest", "stderr")) +check(mtn("add", "foo/\\"), 1, false, true) +check(qgrep("misuse: path 'foo/\\\\' is invalid", "stderr")) + check(mtn("automate", "inventory"), 0, true, true) -check(qgrep("skipping invalid path '\\\\'", "stderr")) +check(qgrep("skipping file 'foo/\\\\' with unsupported name", "stderr")) check(qgrep('path "foo"', "stdout")) - ============================================================ --- unix/fs.cc b5909760f49692cdd13ea1aa76a0323e07b95b92 +++ unix/fs.cc 216c1ac46fa07c136aa521c871b39806e4391669 @@ -26,8 +26,10 @@ #include "sanity.hh" #include "platform.hh" +#include "vector.hh" using std::string; +using std::vector; /* On Linux, AT_SYMLNK_NOFOLLOW is spellt AT_SYMLINK_NOFOLLOW. Hoooray for compatibility! */ @@ -259,8 +261,30 @@ do_read_directory(string const & path, return; } +void +make_accessible(string const & name) +{ + struct stat st; + if (stat(name.c_str(), &st) != 0) + { + const int err = errno; + E(false, origin::system, + F("stat(%s) failed: %s") % name % os_strerror(err)); + } + mode_t new_mode = st.st_mode; + new_mode |= S_IRUSR | S_IWUSR; + if (S_ISDIR(st.st_mode)) + new_mode |= S_IXUSR; + if (chmod(name.c_str(), new_mode) != 0) + { + const int err = errno; + E(false, origin::system, + F("chmod(%s) failed: %s") % name % os_strerror(err)); + } +} + void rename_clobberingly(string const & from, string const & to) { @@ -285,6 +309,74 @@ do_remove(string const & path) } } +// For the reasons described in file_io.cc::walk_tree_recursive, we read the +// entire directory before recursing into any subdirs. However, it is safe +// to delete files as we encounter them, and we do so. +// It is not an error to call this function on a path that doesn't exist, +// or is a file rather than a directory. +void +do_remove_recursive(string const & path) +{ + struct delete_nondir : public dirent_consumer + { + delete_nondir(string const & parent) : parent(parent) {} + virtual void consume(char const * name) + { + string pn = parent; + pn += "/"; + pn += name; + // On Unix it is not necessary to force a file writable + // in order to remove it, only its parent directory. + do_remove(pn); + } + private: + string const & parent; + }; + struct record_subdirs : public dirent_consumer + { + record_subdirs(string const & parent, vector & v) + : parent(parent), v(v) + { v.clear(); } + virtual void consume(char const * name) + { + string pn = parent; + pn += "/"; + pn += name; + v.push_back(pn); + } + private: + string const & parent; + vector & v; + }; + + // Try plain remove() first; it will tell us if we have anything else + // to do. + if (!remove(path.c_str())) + return; // successfully deleted a plain file + + const int err = errno; + if (err == ENOENT) + return; // nothing to delete + + E(err == ENOTEMPTY, origin::system, + F("could not remove '%s': %s") % path % os_strerror(err)); + + // If we get here, it's a non-empty directory to be recursed through. + // Make sure it is writable. + make_accessible(path); + + vector subdirs; + delete_nondir del(path); + record_subdirs rec(path, subdirs); + + do_read_directory(path, del, rec, del); + for (vector::const_iterator i = subdirs.begin(); + i != subdirs.end(); i++) + do_remove_recursive(*i); + + do_remove(path); +} + // Create the directory DIR. It will be world-accessible modulo umask. // Caller is expected to check for the directory already existing. void ============================================================ --- unix/tester-plaf.cc db23c14fe19860f95d51463995652cdbbc51cf4b +++ unix/tester-plaf.cc ffc04609551521fb260ac56c4137e5396d0cb69e @@ -31,27 +31,6 @@ using std::make_pair; using std::map; using std::make_pair; -void make_accessible(string const & name) -{ - struct stat st; - if (stat(name.c_str(), &st) != 0) - { - const int err = errno; - E(false, origin::system, F("stat(%s) failed: %s") % name % os_strerror(err)); - } - - mode_t new_mode = st.st_mode; - if (S_ISDIR(st.st_mode)) - new_mode |= S_IEXEC; - new_mode |= S_IREAD | S_IWRITE; - - if (chmod(name.c_str(), new_mode) != 0) - { - const int err = errno; - E(false, origin::system, F("chmod(%s) failed: %s") % name % os_strerror(err)); - } -} - time_t get_last_write_time(char const * name) { struct stat st; ============================================================ --- win32/fs.cc 14806bf816131ecf8e940f5ba2f386f258f85ac8 +++ win32/fs.cc 4e8d4a113a02dcbc60256241680df4e0686580c8 @@ -249,6 +249,41 @@ void } void +do_remove_recursive(std::string const & path) +{ + // SHFileOperation makes the weird requirement that its pFrom (and pTo) + // arguments be terminated with *two* ASCII NULs. + size_t pfLen = path.size(); + LPCTSTR pFrom = malloc(pfLen + 2); + memcpy(pFrom, path.data(), pfLen); + pFrom[pfLen] = '\0'; + pFrom[pfLen+1] = '\0'; + + SHFILEOPSTRUCTA op; + op.hwnd = INVALID_HANDLE_VALUE; + op.wFunc = FO_DELETE; + op.pFrom = pFrom; + op.pTo = NULL; + op.fFlags = (FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR + | FOF_NOERRORUI); // aka FOF_NO_UI, but that's vista only + op.fAnyOperationsAborted = FALSE; + op.hNameMappings = INVALID_HANDLE_VALUE; + op.lpszProgressTitle = NULL; + + int rc = SHFileOperationA(&op); + // http://msdn.microsoft.com/en-us/library/bb762164(VS.85).aspx + // warns that the return codes from SHFileOperation are *not* normal + // Win32 error codes; so we don't try to do os_strerror on them. + E(rc == 0, origin::system, + F("could not remove '%s' and contents: SHFileOperation error code 0x%x") + % path % rc); + E(!op.fAnyOperationsAborted, origin::system, + F("could not remove '%s' and contents: SHFileOperation partially aborted") + % path); + +} + +void do_mkdir(std::string const & path) { E(CreateDirectoryA(path.c_str(), 0) != 0, origin::system, @@ -304,6 +339,19 @@ rename_clobberingly_impl(const char * fr return MoveFile(from, to); } +void make_accessible(std::string const & name) +{ + DWORD attrs = GetFileAttributes(name.c_str()); + E(attrs != INVALID_FILE_ATTRIBUTES, origin::system, + F("GetFileAttributes(%s) failed: %s") + % name % os_strerror(GetLastError())); + + E(SetFileAttributes(name.c_str(), attrs & ~FILE_ATTRIBUTE_READONLY), + origin::system, + F("SetFileAttributes(%s) failed: %s") + % name % os_strerror(GetLastError())); +} + void rename_clobberingly(std::string const & from, std::string const & to) { ============================================================ --- win32/tester-plaf.cc b7a03cf159f5228fb467db08349691d57db6c310 +++ win32/tester-plaf.cc ebbaac9b027e0bf19f7569a3c11071988775fa33 @@ -19,16 +19,6 @@ #include -void make_accessible(std::string const & name) -{ - DWORD attrs = GetFileAttributes(name.c_str()); - E(attrs != INVALID_FILE_ATTRIBUTES, origin::system, - F("GetFileAttributes(%s) failed: %s") % name % os_strerror(GetLastError())); - - E(SetFileAttributes(name.c_str(), attrs & ~FILE_ATTRIBUTE_READONLY), origin::system, - F("SetFileAttributes(%s) failed: %s") % name % os_strerror(GetLastError())); -} - time_t get_last_write_time(char const * name) { HANDLE h = CreateFile(name, GENERIC_READ, FILE_SHARE_READ, NULL, ============================================================ --- work.cc 7badc3da940f8ef7f9ed821adca729c8b5cad408 +++ work.cc 6121b48399665ea41043e2f67905412d22a3fc8c @@ -872,45 +872,57 @@ addition_builder::visit_dir(file_path co bool addition_builder::visit_dir(file_path const & path) { - if (!recursive) { - bool warn = false; + struct directory_has_unignored_files_exception {}; + struct directory_has_unignored_files : public dirent_consumer + { + directory_has_unignored_files(workspace & work, file_path const & p) + : work(work), p(p) {} + virtual void consume(char const * s) + { + try + { + file_path entry = p / path_component(s); + if (!work.ignore_file(entry)) + throw directory_has_unignored_files_exception(); + } + catch (std::logic_error) + { + // ignore this file for purposes of the warning; this file + // wouldn't have been added by a recursive add anyway. + } + } + private: + workspace & work; + file_path const & p; + }; - // If the db can ever be stored in a dir - // then revisit this logic - I(!db.is_dbfile(path)); + if (!recursive) + { + bool warn = false; - if (!respect_ignore) - warn = !directory_empty(path); - else if (respect_ignore && !work.ignore_file(path)) - { - vector children; - read_directory(path, children, children); + // If the db can ever be stored in a dir + // then revisit this logic + I(!db.is_dbfile(path)); - for (vector::const_iterator i = children.begin(); - i != children.end(); ++i) - { - try - { - file_path entry = path / *i; - if (!work.ignore_file(entry) && !db.is_dbfile(entry)) - { - warn = true; - break; - } - } - catch (std::logic_error) - { - // ignore this file for purposes of the warning - // this file wouldn't have been added by a - // recursive add anyway. - } - } - } + if (!respect_ignore) + warn = !directory_empty(path); + else if (!work.ignore_file(path)) + { + directory_has_unignored_files dhuf(work, path); + try + { + do_read_directory(path, dhuf, dhuf, dhuf); + } + catch (directory_has_unignored_files_exception) + { + warn = true; + } + } - if (warn) - W(F("Non-recursive add: Files in the directory '%s' " - "will not be added automatically.") % path); - } + if (warn) + W(F("Non-recursive add: Files in the directory '%s' " + "will not be added automatically.") % path); + } this->visit_file(path); return true; @@ -1057,18 +1069,19 @@ editable_working_tree::detach_node(file_ { // root dir detach, so we move contents, rather than the dir itself mkdir_p(dst_pth); - vector files, dirs; - read_directory(src_pth, files, dirs); - for (vector::const_iterator i = files.begin(); + + vector files, dirs; + fill_path_vec fill_files(src_pth, files, false); + fill_path_vec fill_dirs(src_pth, dirs, true); + do_read_directory(src_pth, fill_files, fill_dirs); + + for (vector::const_iterator i = files.begin(); i != files.end(); ++i) - move_file(src_pth / *i, dst_pth / *i); - for (vector::const_iterator i = dirs.begin(); + move_file(*i, dst_pth / (*i).basename()); + for (vector::const_iterator i = dirs.begin(); i != dirs.end(); ++i) - { - utf8 item = typecast_vocab(*i); - if (!bookkeeping_path::internal_string_is_bookkeeping_path(item)) - move_dir(src_pth / *i, dst_pth / *i); - } + move_dir(*i, dst_pth / (*i).basename()); + root_dir_attached = false; } else @@ -1132,22 +1145,18 @@ editable_working_tree::attach_node(node_ if (dst_pth == file_path()) { // root dir attach, so we move contents, rather than the dir itself - vector files, dirs; - read_directory(src_pth, files, dirs); - for (vector::const_iterator i = files.begin(); + vector files, dirs; + fill_path_vec fill_files(src_pth, files, false); + fill_path_vec fill_dirs(src_pth, dirs, true); + do_read_directory(src_pth, fill_files, fill_dirs); + + for (vector::const_iterator i = files.begin(); i != files.end(); ++i) - { - utf8 item = typecast_vocab(*i); - I(!bookkeeping_path::internal_string_is_bookkeeping_path(item)); - move_file(src_pth / *i, dst_pth / *i); - } - for (vector::const_iterator i = dirs.begin(); + move_file(*i, dst_pth / (*i).basename()); + for (vector::const_iterator i = dirs.begin(); i != dirs.end(); ++i) - { - utf8 item = typecast_vocab(*i); - I(!bookkeeping_path::internal_string_is_bookkeeping_path(item)); - move_dir(src_pth / *i, dst_pth / *i); - } + move_dir(*i, dst_pth / (*i).basename()); + delete_dir_shallow(src_pth); root_dir_attached = true; }