# # # patch "monotone.cc" # from [fb3a6056cecbb09cb65bdb9b410700d06b51e62a] # to [cb755727610705cca2d31ce81ff02318c564d006] # # patch "paths.cc" # from [3ad115284f29028255846ea214381d342a77632f] # to [cde7cef55a0561ee5b62e11c3ef6ddcfc7c27941] # # patch "paths.hh" # from [60f51a22ea7531952d789c064b83490c3419eaef] # to [e3dcd5e03c1de9c53b22efa737daf70d9ed18d3b] # # patch "roster.cc" # from [0146e6f4ad2059d0e53032e4c1bc90e3497c76a5] # to [e88d4bee4877895f715544a61e5f6db48ee1ff18] # # patch "unit_tests.hh" # from [ba399f5ac277fddb0766e040a854889bbd4e7e5f] # to [7d72d4e087ff7c58368b1debccec192505a64efd] # # patch "work.cc" # from [4c903c77d9fa1b12223c6586c92b3d87462dd730] # to [0a620927cb7acea3ae46fb0cb5948e36cdbe9c6f] # ============================================================ --- monotone.cc fb3a6056cecbb09cb65bdb9b410700d06b51e62a +++ monotone.cc cb755727610705cca2d31ce81ff02318c564d006 @@ -14,17 +14,10 @@ #include #include #include - #include -#include -#include -#include - #include "botan/botan.h" - #include "i18n.h" - #include "app_state.hh" #include "commands.hh" #include "sanity.hh" @@ -39,7 +32,6 @@ #include "simplestring_xform.hh" #include "platform.hh" -namespace fs = boost::filesystem; using std::cout; using std::cerr; @@ -50,7 +42,6 @@ using std::ios_base; using std::string; using std::vector; using std::ios_base; -using boost::shared_ptr; // main option processing and exception handling code @@ -204,12 +195,12 @@ cpp_main(int argc, char ** argv) // find base name of executable, convert to utf8, and save it in the // global ui object { - string prog_name = fs::path(argv[0]).leaf(); + utf8 argv0_u; + system_to_utf8(external(argv[0]), argv0_u); + string prog_name = system_path(argv0_u).basename()(); if (prog_name.rfind(".exe") == prog_name.size() - 4) prog_name = prog_name.substr(0, prog_name.size() - 4); - utf8 prog_name_u; - system_to_utf8(external(prog_name), prog_name_u); - ui.prog_name = prog_name_u(); + ui.prog_name = prog_name; I(!ui.prog_name.empty()); } ============================================================ --- paths.cc 3ad115284f29028255846ea214381d342a77632f +++ paths.cc cde7cef55a0561ee5b62e11c3ef6ddcfc7c27941 @@ -315,22 +315,25 @@ file_path::file_path(file_path::source_t file_path::file_path(file_path::source_type type, string const & path) { - string normalized; - MM(path); - I(utf8_validate(utf8(path))); - switch (type) + if (type == prevalidated) + data = utf8(path); + else { - case internal: - data = utf8(path); - break; - case external: - normalize_external_path(path, normalized); - data = utf8(normalized); - N(!in_bookkeeping_dir(data()), F("path '%s' is in bookkeeping dir") % data); - break; + 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())); } - MM(data); - I(is_valid_internal(data())); } bookkeeping_path::bookkeeping_path(string const & path) @@ -432,39 +435,37 @@ file_path::split(split_path & sp) const } } -template <> -void dump(split_path const & sp, string & out) +// this peels off the last component of any path and returns it. +// the last component of a path with no slashes in it is the complete path. +// the last component of a path referring to the root directory is an +// empty string. +path_component +any_path::basename() const { - ostringstream oss; - oss << sp << '\n'; - out = oss.str(); + string const & s = data(); + string::size_type sep = s.rfind('/'); + if (sep == string::npos) + return path_component(s); + if (sep == s.size()) + return the_null_component; + return path_component(s.substr(sep + 1)); } -template <> -void dump(file_path const & p, string & out) +// this returns all but the last component of a file_path. it is only +// defined on file_paths because (a) that avoids problems at the root, +// and (b) that's the only version that we use. +// if there is only one component present, the dirname is the root +// (i.e. the empty string). +file_path +file_path::dirname() const { - ostringstream oss; - oss << p << '\n'; - out = oss.str(); + string const & s = data(); + string::size_type sep = s.rfind('/'); + if (sep == string::npos) + return file_path(); + return file_path(file_path::prevalidated, s.substr(0, sep)); } -template <> -void dump(system_path const & p, string & out) -{ - ostringstream oss; - oss << p << '\n'; - out = oss.str(); -} - -template <> -void dump(bookkeeping_path const & p, string & out) -{ - ostringstream oss; - oss << p << '\n'; - out = oss.str(); -} - - /////////////////////////////////////////////////////////////////////////// // localizing file names (externalizing them) // this code must be superfast when there is no conversion needed @@ -505,6 +506,38 @@ operator <<(ostream & o, split_path cons return o << tmp; } +template <> +void dump(split_path const & sp, string & out) +{ + ostringstream oss; + oss << sp << '\n'; + out = oss.str(); +} + +template <> +void dump(file_path const & p, string & out) +{ + ostringstream oss; + oss << p << '\n'; + out = oss.str(); +} + +template <> +void dump(system_path const & p, string & out) +{ + ostringstream oss; + oss << p << '\n'; + out = oss.str(); +} + +template <> +void dump(bookkeeping_path const & p, string & out) +{ + ostringstream oss; + oss << p << '\n'; + out = oss.str(); +} + /////////////////////////////////////////////////////////////////////////// // path manipulation // this code's speed does not matter much @@ -1154,6 +1187,122 @@ UNIT_TEST(paths, split_join) } } +UNIT_TEST(paths, basename) +{ + struct t + { + char const * in; + char const * out; + }; + // file_paths cannot be absolute, but may be the empty string. + struct t const fp_cases[] = { + { "", "" }, + { "foo", "foo" }, + { "foo/bar", "bar" }, + { "foo/bar/baz", "baz" }, + { 0, 0 } + }; + // bookkeeping_paths cannot be absolute and must start with the + // bookkeeping_root_component. + struct t const bp_cases[] = { + { "_MTN", "_MTN" }, + { "_MTN/foo", "foo" }, + { "_MTN/foo/bar", "bar" }, + { 0, 0 } + }; + + // system_paths must be absolute. this relies on the setting of + // initial_abs_path below. note that most of the cases whose full paths + // vary between Unix and Windows will still have the same basenames. + struct t const sp_cases[] = { + { "/", "" }, + { "//", "" }, + { "foo", "foo" }, + { "/foo", "foo" }, + { "//foo", "foo" }, + { "~/foo", "foo" }, + { "c:/foo", "foo" }, + { "foo/bar", "bar" }, + { "/foo/bar", "bar" }, + { "//foo/bar", "bar" }, + { "~/foo/bar", "bar" }, + { "c:/foo/bar", "bar" }, +#ifdef WIN32 + { "c:/", "" }, + { "c:foo", "foo" }, +#else + { "c:/", "c:" }, + { "c:foo", "c:foo" }, +#endif + { 0, 0 } + }; + + UNIT_TEST_CHECKPOINT("file_path basenames"); + for (struct t const *p = fp_cases; p->in; p++) + { + file_path fp = file_path_internal(p->in); + path_component pc(fp.basename()); + UNIT_TEST_CHECK_MSG(pc == path_component(p->out), + FL("basename('%s') = '%s' (expect '%s')") + % p->in % pc % p->out); + } + + UNIT_TEST_CHECKPOINT("bookkeeping_path basenames"); + for (struct t const *p = bp_cases; p->in; p++) + { + bookkeeping_path fp(p->in); + path_component pc(fp.basename()); + UNIT_TEST_CHECK_MSG(pc == path_component(p->out), + FL("basename('%s') = '%s' (expect '%s')") + % p->in % pc % p->out); + } + + + UNIT_TEST_CHECKPOINT("system_path basenames"); + + initial_abs_path.unset(); + initial_abs_path.set(system_path("/a/b"), true); + + for (struct t const *p = sp_cases; p->in; p++) + { + system_path fp(p->in); + path_component pc(fp.basename()); + UNIT_TEST_CHECK_MSG(pc == path_component(p->out), + FL("basename('%s') = '%s' (expect '%s')") + % p->in % pc % p->out); + } + + + initial_abs_path.unset(); +} + + +UNIT_TEST(paths, dirname) +{ + struct t + { + char const * in; + char const * out; + }; + // file_paths cannot be absolute, but may be the empty string. + struct t const fp_cases[] = { + { "", "" }, + { "foo", "" }, + { "foo/bar", "foo" }, + { "foo/bar/baz", "foo/bar" }, + { 0, 0 } + }; + + for (struct t const *p = fp_cases; p->in; p++) + { + file_path fp = file_path_internal(p->in); + file_path dn = fp.dirname(); + UNIT_TEST_CHECK_MSG(dn == file_path_internal(p->out), + FL("dirname('%s') = '%s' (expect '%s')") + % p->in % dn % p->out); + } +} + static void check_bk_normalizes_to(char * before, char * after) { bookkeeping_path bp(bookkeeping_root / before); ============================================================ --- paths.hh 60f51a22ea7531952d789c064b83490c3419eaef +++ paths.hh e3dcd5e03c1de9c53b22efa737daf70d9ed18d3b @@ -132,6 +132,8 @@ public: { return data(); } bool empty() const { return data().empty(); } + // returns the trailing component of the path + path_component basename() const; protected: utf8 data; any_path() {} @@ -157,6 +159,7 @@ public: file_path operator /(std::string const & to_append) const; void split(split_path & sp) const; + file_path dirname() const; bool operator ==(const file_path & other) const { return data == other.data; } @@ -192,7 +195,7 @@ private: void clear() { data = utf8(); } private: - typedef enum { internal, external } source_type; + typedef enum { internal, external, prevalidated } source_type; // input is always in utf8, because everything in our world is always in // utf8 (except interface code itself). // external paths: @@ -200,9 +203,13 @@ private: // -- normalized // -- assumed to be relative to the user's cwd, and munged // to become relative to root of the workspace instead - // both types of paths: + // 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); ============================================================ --- roster.cc 0146e6f4ad2059d0e53032e4c1bc90e3497c76a5 +++ roster.cc e88d4bee4877895f715544a61e5f6db48ee1ff18 @@ -701,27 +701,24 @@ roster_t::detach_node(file_path const & node_id roster_t::detach_node(file_path const & p) { - split_path sp; - p.split(sp); - split_path dirname; - path_component basename; - dirname_basename(sp, dirname, basename); + file_path dirname = p.dirname(); + path_component basename = p.basename(); I(has_root()); - if (dirname.empty()) + if (null_name(basename)) { // detaching the root dir - I(null_name(basename)); + I(dirname.empty()); node_id root_id = root_dir->self; - safe_insert(old_locations, - make_pair(root_id, make_pair(root_dir->parent, root_dir->name))); + safe_insert(old_locations, make_pair(root_id, make_pair(root_dir->parent, + root_dir->name))); // clear ("reset") the root_dir shared_pointer root_dir.reset(); I(!has_root()); return root_id; } - dir_t parent = downcast_to_dir_t(get_node(file_path(dirname))); + dir_t parent = downcast_to_dir_t(get_node(dirname)); node_id nid = parent->detach_child(basename)->self; safe_insert(old_locations, make_pair(nid, make_pair(parent->self, basename))); @@ -2305,16 +2302,12 @@ editable_roster_for_check::attach_node(n void editable_roster_for_check::attach_node(node_id nid, file_path const & dst) { - split_path sd; - dst.split(sd); - split_path dirname; - path_component basename; - dirname_basename(sd, dirname, basename); + file_path parent = dst.dirname(); - if (!dirname.empty() && !r.has_node(file_path(dirname))) + if (!r.has_node(parent) && !dst.empty()) { W(F("restriction excludes addition of '%s' but includes addition of '%s'") - % dirname % dst); + % parent % dst); problems++; } else ============================================================ --- unit_tests.hh ba399f5ac277fddb0766e040a854889bbd4e7e5f +++ unit_tests.hh 7d72d4e087ff7c58368b1debccec192505a64efd @@ -14,6 +14,11 @@ #define UNIT_TEST_CHECK(expression) \ unit_test::do_check(expression, __FILE__, __LINE__, #expression) +// Like UNIT_TEST_CHECK, but you get to specify what is logged. +// MSG should be an FL("...") % ... construct. +#define UNIT_TEST_CHECK_MSG(expression, msg) \ + unit_test::do_check(expression, __FILE__, __LINE__, (msg).str().c_str()) + // Like UNIT_TEST_CHECK, but abort the test immediately on failure #define UNIT_TEST_REQUIRE(expression) \ unit_test::do_require(expression, __FILE__, __LINE__, #expression) ============================================================ --- work.cc 4c903c77d9fa1b12223c6586c92b3d87462dd730 +++ work.cc 0a620927cb7acea3ae46fb0cb5948e36cdbe9c6f @@ -966,19 +966,14 @@ simulated_working_tree::attach_node(node } else { - split_path dsts; - dst.split(dsts); + file_path parent = dst.dirname(); - split_path dirname; - path_component basename; - dirname_basename(dsts, dirname, basename); - - if (blocked_paths.find(file_path(dirname)) == blocked_paths.end()) + if (blocked_paths.find(parent) == blocked_paths.end()) workspace.attach_node(nid, dst); else { W(F("attach node %d blocked by blocked parent '%s'") - % nid % file_path(dirname)); + % nid % parent); blocked_paths.insert(dst); } } @@ -1026,13 +1021,8 @@ add_parent_dirs(file_path const & dst, r editable_roster_base er(ros, nis); addition_builder build(db, lua, ros, er); - split_path sp, dirname; - path_component basename; - dst.split(sp); - dirname_basename(sp, dirname, basename); - // FIXME: this is a somewhat odd way to use the builder - build.visit_dir(file_path(dirname)); + build.visit_dir(dst.dirname()); } inline static bool @@ -1478,12 +1468,7 @@ workspace::perform_rename(set N(new_roster.has_node(*i), F("source file %s is not versioned") % *i); - path_component base; - split_path s, dir; - i->split(s); - dirname_basename(s, dir, base); - - file_path d = dst / base(); + file_path d = dst / i->basename()(); N(!new_roster.has_node(d), F("destination %s already exists in the workspace manifest") % d); @@ -1563,17 +1548,15 @@ workspace::perform_pivot_root(file_path { file_path current_path_to_put_old = (new_root / put_old.as_internal()); - split_path current_path_to_put_old_sp, current_path_to_put_old_parent_sp; - path_component basename; - current_path_to_put_old.split(current_path_to_put_old_sp); - dirname_basename(current_path_to_put_old_sp, - current_path_to_put_old_parent_sp, basename); - N(new_roster.has_node(file_path(current_path_to_put_old_parent_sp)), + file_path current_path_to_put_old_parent + = current_path_to_put_old.dirname(); + + N(new_roster.has_node(current_path_to_put_old_parent), F("directory '%s' is not versioned or does not exist") - % file_path(current_path_to_put_old_parent_sp)); - N(is_dir_t(new_roster.get_node(file_path(current_path_to_put_old_parent_sp))), + % current_path_to_put_old_parent); + N(is_dir_t(new_roster.get_node(current_path_to_put_old_parent)), F("'%s' is not a directory") - % file_path(current_path_to_put_old_parent_sp)); + % current_path_to_put_old_parent); N(!new_roster.has_node(current_path_to_put_old), F("'%s' is in the way") % current_path_to_put_old); }