# # # patch "cmd_merging.cc" # from [e7d15cb2e930e5b9f1c7ea814f07eebd6968f6d9] # to [d7e161a93969f9c9073093b4943063c0dcf640a8] # # patch "paths.cc" # from [7b8b7517f11ee0d21ceab98dc7c674ab0d1a8e39] # to [b0902623bc22d2c70bf7f9a17f6352dba3b165eb] # # patch "paths.hh" # from [62d574a1d6034240303fab1d453c0a786e52d996] # to [80790ad2366edac43b0ac04114fddfe526b97c8a] # # patch "revision.cc" # from [7211ea4e920a4f357b445b99c8f7713ae559d422] # to [c2aac33e95ef0d127b27fff00e93331650e442e1] # # patch "roster.cc" # from [ff1087ea2aba58f6063ac2ae70adef654b9f7f82] # to [95bc8ab68e236204ace5928a48d42fe31bd0f5c1] # # patch "roster.hh" # from [99d0e9db42ece21e45383b0dc8cef0ddb525f77c] # to [2a6c8163373bb6edd8ad291fc333c007e364b194] # # patch "roster_merge.cc" # from [77ae7b51617a9f59dfb97941a7b04fa8d250e25c] # to [f38966adfd7fdc871b2fbf662ff1bf82275d76a7] # # patch "vocab.cc" # from [05c0e03920108f99c1f89b9438a516736cc1b857] # to [3f244be28ec971fbcb5edb6e021d6596d999a4f6] # # patch "vocab_terms.hh" # from [bb5a992fb126662ed70bb7f41560f77615164e69] # to [234b8fe803456b22719405ad5ffefd62f6e9ba01] # # patch "work.cc" # from [1868eaed750a170cf4b0e69e7906c3582dee771a] # to [a8999fd1d8042bc71c9104385cdb5f2aa5313eba] # ============================================================ --- cmd_merging.cc e7d15cb2e930e5b9f1c7ea814f07eebd6968f6d9 +++ cmd_merging.cc d7e161a93969f9c9073093b4943063c0dcf640a8 @@ -573,7 +573,7 @@ CMD(merge_into_dir, "merge_into_dir", "" { dir_t moved_root = left_roster.root(); moved_root->parent = the_null_node; - moved_root->name = the_null_component; + moved_root->name = path_component(); } // Write new files into the db. ============================================================ --- paths.cc 7b8b7517f11ee0d21ceab98dc7c674ab0d1a8e39 +++ paths.cc b0902623bc22d2c70bf7f9a17f6352dba3b165eb @@ -132,13 +132,11 @@ bad_component(string const & component) static inline bool bad_component(string const & component) { - static const string dot("."); - static const string dotdot(".."); if (component.empty()) return true; - if (component == dot) + if (component == ".") return true; - if (component == dotdot) + if (component == "..") return true; return false; } @@ -159,13 +157,29 @@ has_bad_chars(string const & path) return false; } -// fully_normalized_path_split performs very similar function to -// file_path.split(). if want_split is set, split_path will be filled with -// the '/' separated components of the path. +// as above, but disallows / as well. static inline bool -fully_normalized_path_split(string const & path, bool want_split, - split_path & sp) +has_bad_component_chars(string const & pc) { + for (string::const_iterator c = pc.begin(); LIKELY(c != pc.end()); c++) + { + // char is often a signed type; convert to unsigned to ensure that + // bytes 0x80-0xff are considered > 0x1f. + u8 x = (u8)*c; + // 0x2f is '/' and 0x5c is '\\'; we use hex constants to make the + // dependency on ASCII encoding explicit. + if (UNLIKELY(x <= 0x1f || x == 0x2f || x == 0x5c || x == 0x7f)) + return true; + } + return false; + +} + +// fully_normalized_path verifies a complete pathname for validity and +// having been properly normalized (as if by normalize_path, below). +static inline bool +fully_normalized_path(string const & path) +{ // empty path is fine if (path.empty()) return true; @@ -177,43 +191,29 @@ fully_normalized_path_split(string const if (has_bad_chars(path)) return false; // now check each component - string::size_type start, stop; - start = 0; + string::size_type start = 0, stop; while (1) { stop = path.find('/', start); if (stop == string::npos) - { - string const & s(path.substr(start)); - if (bad_component(s)) - return false; - if (want_split) - sp.push_back(path_component(s)); - break; - } + break; string const & s(path.substr(start, stop - start)); if (bad_component(s)) return false; - if (want_split) - sp.push_back(path_component(s)); start = stop + 1; } - return true; -} -static inline bool -fully_normalized_path(string const & path) -{ - split_path sp; - return fully_normalized_path_split(path, false, sp); + string const & s(path.substr(start)); + return !bad_component(s); } // This function considers _MTN, _MTn, _MtN, _mtn etc. to all be bookkeeping // paths, because on case insensitive filesystems, files put in any of them // may end up in _MTN instead. This allows arbitrary code execution. A -// better solution would be to fix this in the working directory writing code -// -- this prevents all-unix projects from naming things "mt", which is a bit -// rude -- but as a temporary security kluge it works. +// better solution would be to fix this in the working directory writing +// code -- this prevents all-unix projects from naming things "_mtn", which +// is less rude than when the bookkeeping root was "MT", but still rude -- +// but as a temporary security kluge it works. static inline bool in_bookkeeping_dir(string const & path) { @@ -313,27 +313,69 @@ normalize_external_path(string const & p } } +/////////////////////////////////////////////////////////////////////////// +// single path component handling. +/////////////////////////////////////////////////////////////////////////// + +// these constructors confirm that what they are passed is a legitimate +// component. note that the empty string is a legitimate component, +// but is not acceptable to bad_component (above) and therefore we have +// to open-code most of those checks. +path_component::path_component(utf8 const & d) + : data(d) +{ + MM(data); + I(!has_bad_component_chars(data()) && data() != "." && data() != ".."); +} + +path_component::path_component(string const & d) + : data(d) +{ + MM(data); + I(utf8_validate(data) + && !has_bad_component_chars(data()) + && data() != "." && data() != ".."); +} + +path_component::path_component(char const * d) + : data(d) +{ + MM(data); + I(utf8_validate(data) + && !has_bad_component_chars(data()) + && data() != "." && data() != ".."); +} + +std::ostream & operator<<(std::ostream & s, path_component const & pc) +{ + return s << pc(); +} + +template <> void dump(path_component const & pc, std::string & to) +{ + to = pc(); +} + +/////////////////////////////////////////////////////////////////////////// +// complete paths to files within a working directory +/////////////////////////////////////////////////////////////////////////// + file_path::file_path(file_path::source_type type, string const & path) { - if (type == prevalidated) - data = utf8(path); - else + MM(path); + I(utf8_validate(utf8(path))); + if (type == external) { - MM(path); - I(utf8_validate(utf8(path))); - if (type == external) - { - string normalized; - normalize_external_path(path, normalized); - N(!in_bookkeeping_dir(normalized), - F("path '%s' is in bookkeeping dir") % data); - data = utf8(normalized); - } - else - data = utf8(path); - MM(data); - I(is_valid_internal(data())); + string normalized; + normalize_external_path(path, normalized); + N(!in_bookkeeping_dir(normalized), + F("path '%s' is in bookkeeping dir") % data); + data = utf8(normalized); } + else + data = utf8(path); + MM(data); + I(is_valid_internal(data())); } bookkeeping_path::bookkeeping_path(string const & path) @@ -377,7 +419,7 @@ file_path::file_path(split_path const & { split_path::const_iterator i = sp.begin(); I(i != sp.end()); - I(null_name(*i)); + I(i->empty()); string tmp; bool start = true; size_t size = 0; @@ -389,7 +431,7 @@ file_path::file_path(split_path const & i = sp.begin(); for (++i; i != sp.end(); ++i) { - I(!null_name(*i)); + I(!i->empty()); if (!start) tmp += "/"; tmp += (*i)(); @@ -416,7 +458,7 @@ file_path::split(split_path & sp) const file_path::split(split_path & sp) const { sp.clear(); - sp.push_back(the_null_component); + sp.push_back(path_component()); if (empty()) return; string::size_type start, stop; @@ -427,10 +469,10 @@ file_path::split(split_path & sp) const stop = s.find('/', start); if (stop == string::npos) { - sp.push_back(path_component(s.substr(start))); + sp.push_back(path_component(s, start)); break; } - sp.push_back(path_component(s.substr(start, stop - start))); + sp.push_back(path_component(s, start, stop - start)); start = stop + 1; } } @@ -445,10 +487,10 @@ any_path::basename() const string const & s = data(); string::size_type sep = s.rfind('/'); if (sep == string::npos) - return path_component(s); + return path_component(s, 0); // force use of short circuit if (sep == s.size()) - return the_null_component; - return path_component(s.substr(sep + 1)); + return path_component(); + return path_component(s, sep + 1); } // this returns all but the last component of a file_path. it is only @@ -463,7 +505,7 @@ file_path::dirname() const string::size_type sep = s.rfind('/'); if (sep == string::npos) return file_path(); - return file_path(file_path::prevalidated, s.substr(0, sep)); + return file_path(s, 0, sep); } /////////////////////////////////////////////////////////////////////////// @@ -573,16 +615,35 @@ is_absolute_somewhere(string const & pat return false; } +// relies on its arguments already being validated, except that you may not +// append the empty path component, and if you are appending to the empty +// path, you may not create an absolute path or a path into the bookkeeping +// directory. file_path -file_path::operator /(string const & to_append) const +file_path::operator /(path_component const & to_append) const { - I(!is_absolute_somewhere(to_append)); + I(!to_append.empty()); if (empty()) - return file_path_internal(to_append); + { + string const & s = to_append(); + I(!is_absolute_somewhere(s) && !in_bookkeeping_dir(s)); + return file_path(s, 0, string::npos); + } else - return file_path_internal(data() + "/" + to_append); + return file_path(data() + "/" + to_append(), 0, string::npos); } +// similarly, but even less checking is needed. +file_path +file_path::operator /(file_path const & to_append) const +{ + I(!to_append.empty()); + if (empty()) + return to_append; + return file_path(data() + "/" + to_append.as_internal(), 0, string::npos); +} + +// these take strings and do validation themselves. bookkeeping_path bookkeeping_path::operator /(string const & to_append) const { @@ -795,10 +856,44 @@ using std::logic_error; using std::logic_error; -UNIT_TEST(paths, null_name) +UNIT_TEST(paths, path_component) { - UNIT_TEST_CHECK(null_name(the_null_component)); + char const * baddies[] = {".", + "..", + "/foo", + "\\foo", + "foo/bar", + "foo\\bar", + 0 }; + + // these would not be okay in a full file_path, but are okay here. + char const * goodies[] = {"c:foo", + "_mtn", + "_mtN", + "_mTn", + "_Mtn", + "_MTn", + "_MtN", + "_MTN", + 0 }; + + + for (char const ** c = baddies; *c; ++c) + { + // the comparison prevents the compiler from eliminating the + // expression. + UNIT_TEST_CHECK_THROW(path_component(*c)() == *c, logic_error); + } + for (char const **c = goodies; *c; ++c) + { + path_component p(*c); + UNIT_TEST_CHECK_THROW(file_path() / p, logic_error); + } + + UNIT_TEST_CHECK_THROW(file_path_internal("foo") / path_component(), + logic_error); } + UNIT_TEST(paths, file_path_internal) { @@ -881,10 +976,10 @@ UNIT_TEST(paths, file_path_internal) UNIT_TEST_CHECK(!split_test.empty()); file_path fp2(split_test); UNIT_TEST_CHECK(fp == fp2); - UNIT_TEST_CHECK(null_name(split_test[0])); + UNIT_TEST_CHECK(split_test[0].empty()); for (split_path::const_iterator i = split_test.begin() + 1; i != split_test.end(); ++i) - UNIT_TEST_CHECK(!null_name(*i)); + UNIT_TEST_CHECK(!i->empty()); } } @@ -906,10 +1001,10 @@ static void check_fp_normalizes_to(char UNIT_TEST_CHECK(!split_test.empty()); file_path fp2(split_test); UNIT_TEST_CHECK(fp == fp2); - UNIT_TEST_CHECK(null_name(split_test[0])); + UNIT_TEST_CHECK(split_test[0].empty()); for (split_path::const_iterator i = split_test.begin() + 1; i != split_test.end(); ++i) - UNIT_TEST_CHECK(!null_name(*i)); + UNIT_TEST_CHECK(!i->empty()); } UNIT_TEST(paths, file_path_external_null_prefix) @@ -1089,10 +1184,10 @@ UNIT_TEST(paths, split_join) UNIT_TEST_CHECK(split1[1] != split1[2]); UNIT_TEST_CHECK(split1[1] != split1[3]); UNIT_TEST_CHECK(split1[2] != split1[3]); - UNIT_TEST_CHECK(null_name(split1[0]) - && !null_name(split1[1]) - && !null_name(split1[2]) - && !null_name(split1[3])); + UNIT_TEST_CHECK(split1[0].empty() + && !split1[1].empty() + && !split1[2].empty() + && !split1[3].empty()); UNIT_TEST_CHECK(split1[1] == split2[3]); UNIT_TEST_CHECK(split1[2] == split2[1]); UNIT_TEST_CHECK(split1[3] == split2[2]); @@ -1100,14 +1195,14 @@ UNIT_TEST(paths, split_join) file_path fp3 = file_path_internal(""); split_path split3; fp3.split(split3); - UNIT_TEST_CHECK(split3.size() == 1 && null_name(split3[0])); + UNIT_TEST_CHECK(split3.size() == 1 && split3[0].empty()); // empty split_path is invalid split_path split4; // this comparison tricks the compiler into not completely eliminating this // code as dead... UNIT_TEST_CHECK_THROW(file_path(split4) == file_path(), logic_error); - split4.push_back(the_null_component); + split4.push_back(path_component()); UNIT_TEST_CHECK(file_path(split4) == file_path()); // split_path without null first item is invalid @@ -1119,9 +1214,9 @@ UNIT_TEST(paths, split_join) // split_path with non-first item item null is invalid split4.clear(); - split4.push_back(the_null_component); + split4.push_back(path_component()); split4.push_back(split1[0]); - split4.push_back(the_null_component); + split4.push_back(path_component()); // this comparison tricks the compiler into not completely eliminating this // code as dead... UNIT_TEST_CHECK_THROW(file_path(split4) == file_path(), logic_error); @@ -1133,7 +1228,7 @@ UNIT_TEST(paths, split_join) file_path_internal("foo/_MTN").split(split_mt1); UNIT_TEST_CHECK(split_mt1.size() == 3); I(split_mt1[2] == bookkeeping_root_component); - split_mt2.push_back(the_null_component); + split_mt2.push_back(path_component()); split_mt2.push_back(split_mt1[2]); // split_mt2 now contains the component "_MTN" UNIT_TEST_CHECK_THROW(file_path(split_mt2) == file_path(), logic_error); @@ -1149,7 +1244,7 @@ UNIT_TEST(paths, split_join) split_path split_mt1, split_mt2; file_path_internal("foo/_mTn").split(split_mt1); UNIT_TEST_CHECK(split_mt1.size() == 3); - split_mt2.push_back(the_null_component); + split_mt2.push_back(path_component()); split_mt2.push_back(split_mt1[2]); // split_mt2 now contains the component "_mTn" UNIT_TEST_CHECK_THROW(file_path(split_mt2) == file_path(), logic_error); ============================================================ --- paths.hh 62d574a1d6034240303fab1d453c0a786e52d996 +++ paths.hh 80790ad2366edac43b0ac04114fddfe526b97c8a @@ -95,12 +95,6 @@ // F("my path is %s") % my_path // i.e., nothing fancy necessary, for purposes of F() just treat it like // it were a string -// -// -// There is also one "not really a path" type, 'split_path'. This is a vector -// of path_component's, and semantically equivalent to a file_path -- -// file_path's can be split into split_path's, and split_path's can be joined -// into file_path's. #include #include @@ -108,16 +102,58 @@ #include "vocab.hh" -typedef std::vector split_path; +// A path_component is one component of a path. It is always utf8, may not +// contain either kind of slash, and may not be a magic directory entry ("." +// or "..") It _may_ be the empty string, but you only get that if you ask +// for the basename of the root directory. It resembles, but is not, a +// vocab type. -const path_component the_null_component; +class any_path; +class file_path; -inline bool -null_name(path_component pc) +class path_component { - return pc == the_null_component; -} +public: + path_component() : data() {} + explicit path_component(utf8 const &); + explicit path_component(std::string const &); + explicit path_component(char const *); + std::string const & operator()() const { return data(); } + bool empty() const { return data().empty(); } + bool operator<(path_component const & other) const + { return data() < other(); } + bool operator==(path_component const & other) const + { return data() == other(); } + bool operator!=(path_component const & other) const + { return data() != other(); } + + friend std::ostream & operator<<(std::ostream &, path_component const &); + +private: + utf8 data; + + // constructor for use by trusted any_path and file_path operations. + // bypasses validation. + path_component(std::string const & path, + std::string::size_type start, + std::string::size_type stop = std::string::npos) + : data(path.substr(start, stop)) + {} + + friend class any_path; + friend class file_path; +}; +std::ostream & operator<<(std::ostream &, path_component const &); +template <> void dump(path_component const &, std::string &); + +// There is also one "not really a path" type, 'split_path'. This is a vector +// of path_component's, and semantically equivalent to a file_path -- +// file_path's can be split into split_path's, and split_path's can be joined +// into file_path's. + +typedef std::vector split_path; + // It's possible this will become a proper virtual interface in the future, // but since the implementation is exactly the same in all cases, there isn't // much point ATM... @@ -155,8 +191,8 @@ public: // join a file_path out of pieces explicit file_path(split_path const & sp); - // this currently doesn't do any normalization or anything. - file_path operator /(std::string const & to_append) const; + file_path operator /(path_component const & to_append) const; + file_path operator /(file_path const & to_append) const; void split(split_path & sp) const; file_path dirname() const; @@ -168,18 +204,22 @@ public: // see the "ordering" unit test in paths.cc. bool operator <(const file_path & other) const { - unsigned char const * p = (unsigned char const *)data().c_str(); - unsigned char const * q = (unsigned char const *)other.data().c_str(); - while (*p == *q && *p != '\0') + std::string::const_iterator p = data().begin(); + std::string::const_iterator plim = data().end(); + std::string::const_iterator q = other.data().begin(); + std::string::const_iterator qlim = other.data().end(); + + while (*p == *q && p != plim && q != qlim) p++, q++; - if (*p == *q) // equal -> not less + + if (p == plim && q == qlim) // equal -> not less return false; - // must do NUL before everything first, or 'foo' will sort after - // 'foo/bar' which is not what we want. - if (*p == '\0') + // must do end of string before everything else, or 'foo' will sort + // after 'foo/bar' which is not what we want. + if (p == plim) return true; - if (*q == '\0') + if (q == qlim) return false; // the only special case needed is that / sorts before everything - @@ -189,13 +229,14 @@ public: if (*q == '/') return false; - return *p < *q; + // ensure unsigned comparison + return static_cast(*p) < static_cast(*q); } void clear() { data = utf8(); } private: - typedef enum { internal, external, prevalidated } source_type; + typedef enum { internal, external } source_type; // input is always in utf8, because everything in our world is always in // utf8 (except interface code itself). // external paths: @@ -206,13 +247,18 @@ private: // internal and external paths: // -- are confirmed to be normalized and relative // -- not to be in _MTN/ - // prevalidated paths: - // -- receive no checking - // -- are only for use by other file_path methods which can - // guarantee that the path is already valid file_path(source_type type, std::string const & path); friend file_path file_path_internal(std::string const & path); friend file_path file_path_external(utf8 const & path); + + // private substring constructor, does no validation. used by dirname() + // and operator/ with a path_component. + file_path(std::string const & path, + std::string::size_type start, + std::string::size_type stop = std::string::npos) + { + data = utf8(path.substr(start, stop)); + } }; // these are the public file_path constructors ============================================================ --- revision.cc 7211ea4e920a4f357b445b99c8f7713ae559d422 +++ revision.cc c2aac33e95ef0d127b27fff00e93331650e442e1 @@ -1184,7 +1184,7 @@ find_old_path_for(map(1) << (sizeof(node_id) * 8 - 1); inline bool temp_node(node_id n) @@ -150,7 +157,7 @@ node::node(node_id i) node::node(node_id i) : self(i), parent(the_null_node), - name(the_null_component) + name() { } @@ -158,7 +165,7 @@ node::node() node::node() : self(the_null_node), parent(the_null_node), - name(the_null_component) + name() { } @@ -192,7 +199,7 @@ dir_node::attach_child(path_component co dir_node::attach_child(path_component const & pc, node_t child) { I(null_node(child->parent)); - I(null_name(child->name)); + I(child->name.empty()); safe_insert(children, make_pair(pc, child)); child->parent = this->self; child->name = pc; @@ -204,7 +211,7 @@ dir_node::detach_child(path_component co { node_t n = get_child(pc); n->parent = the_null_node; - n->name = the_null_component; + n->name = path_component(); safe_erase(children, pc); return n; } @@ -685,7 +692,7 @@ roster_t::detach_node(file_path const & path_component basename = p.basename(); I(has_root()); - if (null_name(basename)) + if (basename.empty()) { // detaching the root dir I(dirname.empty()); @@ -713,7 +720,7 @@ roster_t::detach_node(node_id nid) if (null_node(n->parent)) { // detaching the root dir - I(null_name(n->name)); + I(n->name.empty()); safe_insert(old_locations, make_pair(nid, make_pair(n->parent, n->name))); root_dir.reset(); @@ -735,7 +742,7 @@ roster_t::drop_detached_node(node_id nid // ensure the node is already detached node_t n = get_node(nid); I(null_node(n->parent)); - I(null_name(n->name)); + I(n->name.empty()); // if it's a dir, make sure it's empty if (is_dir_t(n)) I(downcast_to_dir_t(n)->children.empty()); @@ -793,7 +800,7 @@ roster_t::attach_node(node_id nid, file_ MM(p); if (p.empty()) // attaching the root node - attach_node(nid, the_null_node, the_null_component); + attach_node(nid, the_null_node, path_component()); else attach_node(nid, get_node(p.dirname())->self, p.basename()); } @@ -806,18 +813,18 @@ roster_t::attach_node(node_id nid, node_ I(!null_node(n->self)); // ensure the node is already detached (as best one can) I(null_node(n->parent)); - I(null_name(n->name)); + I(n->name.empty()); // this iterator might point to old_locations.end(), because old_locations // only includes entries for renames, not new nodes map >::iterator i = old_locations.find(nid); - if (null_node(parent) || null_name(name)) + if (null_node(parent) || name.empty()) { - I(null_node(parent) && null_name(name)); + I(null_node(parent) && name.empty()); I(null_node(n->parent)); - I(null_name(n->name)); + I(n->name.empty()); I(!has_root()); root_dir = downcast_to_dir_t(n); I(i == old_locations.end() || i->second != make_pair(root_dir->parent, @@ -966,14 +973,14 @@ roster_t::check_sane(bool temp_nodes_ok) I(n->self == nid); if (is_dir_t(n)) { - if (null_name(n->name) || null_node(n->parent)) - I(null_name(n->name) && null_node(n->parent)); + if (n->name.empty() || null_node(n->parent)) + I(n->name.empty() && null_node(n->parent)); else - I(!null_name(n->name) && !null_node(n->parent)); + I(!n->name.empty() && !null_node(n->parent)); } else { - I(!null_name(n->name) && !null_node(n->parent)); + I(!n->name.empty() && !null_node(n->parent)); I(!null_id(downcast_to_file_t(n)->content)); } for (full_attr_map_t::const_iterator i = n->attrs.begin(); i != n->attrs.end(); ++i) @@ -2262,7 +2269,7 @@ editable_roster_for_check::drop_detached { W(F("restriction includes deletion of '%s' " "but excludes deletion of '%s'") - % dir % (dir / (*p)())); + % dir % (dir / *p)); problems++; } } @@ -2575,7 +2582,7 @@ roster_t::parse_from(basic_io::parser & // Instantiate some lookaside caches to ensure this roster reuses // string storage across ATOMIC elements. id::symtab id_syms; - path_component::symtab path_syms; + utf8::symtab path_syms; attr_key::symtab attr_key_syms; attr_value::symtab attr_value_syms; ============================================================ --- roster.hh 99d0e9db42ece21e45383b0dc8cef0ddb525f77c +++ roster.hh 2a6c8163373bb6edd8ad291fc333c007e364b194 @@ -116,7 +116,7 @@ is_root_dir_t(node_t n) inline bool is_root_dir_t(node_t n) { - if (is_dir_t(n) && null_name(n->name)) + if (is_dir_t(n) && n->name.empty()) { I(null_node(n->parent)); return true; ============================================================ --- roster_merge.cc 77ae7b51617a9f59dfb97941a7b04fa8d250e25c +++ roster_merge.cc f38966adfd7fdc871b2fbf662ff1bf82275d76a7 @@ -305,7 +305,7 @@ namespace // make a dir loop. it can, however, have a name collision. if (null_node(parent)) { - I(null_name(name)); + I(name.empty()); if (result.roster.has_root()) { // see comments below about name collisions. @@ -866,7 +866,7 @@ struct name_shared_stuff : public virtua I(c.left == make_pair(parent_for(left_val), pc_for(left_val))); I(c.right == make_pair(parent_for(right_val), pc_for(right_val))); I(null_node(result.roster.get_node(thing_nid)->parent)); - I(null_name(result.roster.get_node(thing_nid)->name)); + I(result.roster.get_node(thing_nid)->name.empty()); // resolve the conflict, thus making sure that resolution works and // that this was the only conflict signaled // attach implicitly checks that we were already detached @@ -1742,12 +1742,12 @@ struct node_name_plus_missing_root : pub void check_helper(node_name_conflict const & left_c, node_name_conflict const & right_c) { I(left_c.nid == left_root_nid); - I(left_c.left == make_pair(the_null_node, the_null_component)); + I(left_c.left == make_pair(the_null_node, path_component())); I(left_c.right == make_pair(right_root_nid, path_component("left_root"))); I(right_c.nid == right_root_nid); I(right_c.left == make_pair(left_root_nid, path_component("right_root"))); - I(right_c.right == make_pair(the_null_node, the_null_component)); + I(right_c.right == make_pair(the_null_node, path_component())); } virtual void check() { @@ -1796,7 +1796,7 @@ struct rename_target_plus_missing_root : rename_target_conflict const & c = idx(result.rename_target_conflicts, 0); I((c.nid1 == left_root_nid && c.nid2 == right_root_nid) || (c.nid1 == right_root_nid && c.nid2 == left_root_nid)); - I(c.parent_name == make_pair(the_null_node, the_null_component)); + I(c.parent_name == make_pair(the_null_node, path_component())); I(result.missing_root_dir); ============================================================ --- vocab.cc 05c0e03920108f99c1f89b9438a516736cc1b857 +++ vocab.cc 3f244be28ec971fbcb5edb6e021d6596d999a4f6 @@ -49,13 +49,6 @@ verify_full(T & val) verify_full(T & val) { val.ok = true; } -inline void -verify_full(path_component & val) -{ - // FIXME: probably ought to do something here? - val.ok = true; -} - // NOTE: _not_ verify_full; you use verify_full for ATOMICs, verify() for // everything else. inline void ============================================================ --- vocab_terms.hh bb5a992fb126662ed70bb7f41560f77615164e69 +++ vocab_terms.hh 234b8fe803456b22719405ad5ffefd62f6e9ba01 @@ -16,8 +16,6 @@ ATOMIC(symbol); // valid b ATOMIC(ace); // unknown string in ACE form ATOMIC(symbol); // valid basic io symbol (alphanumeric or _ chars) -ATOMIC(path_component); // piece of a path (see paths.hh) - ATOMIC_NOVERIFY(id); // hash of data ATOMIC_NOVERIFY(data); // meaningless blob ATOMIC_NOVERIFY(delta); // xdelta between 2 datas ============================================================ --- work.cc 1868eaed750a170cf4b0e69e7906c3582dee771a +++ work.cc a8999fd1d8042bc71c9104385cdb5f2aa5313eba @@ -767,10 +767,12 @@ editable_working_tree::detach_node(file_ vector files, dirs; read_directory(src_pth, files, dirs); for (vector::const_iterator i = files.begin(); i != files.end(); ++i) - move_file(src_pth / (*i)(), dst_pth / (*i)()); + move_file(src_pth / path_component(*i), + dst_pth / (*i)()); for (vector::const_iterator i = dirs.begin(); i != dirs.end(); ++i) if (!bookkeeping_path::internal_string_is_bookkeeping_path(*i)) - move_dir(src_pth / (*i)(), dst_pth / (*i)()); + move_dir(src_pth / path_component(*i), + dst_pth / (*i)()); root_dir_attached = false; } else @@ -839,12 +841,14 @@ editable_working_tree::attach_node(node_ for (vector::const_iterator i = files.begin(); i != files.end(); ++i) { I(!bookkeeping_path::internal_string_is_bookkeeping_path(*i)); - move_file(src_pth / (*i)(), dst_pth / (*i)()); + move_file(src_pth / (*i)(), + dst_pth / path_component(*i)); } for (vector::const_iterator i = dirs.begin(); i != dirs.end(); ++i) { I(!bookkeeping_path::internal_string_is_bookkeeping_path(*i)); - move_dir(src_pth / (*i)(), dst_pth / (*i)()); + move_dir(src_pth / (*i)(), + dst_pth / path_component(*i)); } delete_dir_shallow(src_pth); root_dir_attached = true; @@ -1365,7 +1369,7 @@ workspace::perform_deletions(setchildren.begin(); j != d->children.end(); ++j) - todo.push_front(name / j->first()); + todo.push_front(name / j->first); continue; } } @@ -1464,7 +1468,7 @@ workspace::perform_rename(set N(new_roster.has_node(*i), F("source file %s is not versioned") % *i); - file_path d = dst / i->basename()(); + file_path d = dst / i->basename(); N(!new_roster.has_node(d), F("destination %s already exists in the workspace manifest") % d); @@ -1537,13 +1541,13 @@ workspace::perform_pivot_root(file_path N(is_dir_t(new_roster.get_node(new_root)), F("proposed new root directory '%s' is not a directory") % new_root); { - N(!new_roster.has_node(new_root / bookkeeping_root.as_internal()), + N(!new_roster.has_node(new_root / bookkeeping_root_component), F("proposed new root directory '%s' contains illegal path %s") % new_root % bookkeeping_root); } { - file_path current_path_to_put_old = (new_root / put_old.as_internal()); + file_path current_path_to_put_old = (new_root / put_old); file_path current_path_to_put_old_parent = current_path_to_put_old.dirname();