# # # patch "ChangeLog" # from [9810ac424b8cbf50d4cff1ada017f42a66fbca63] # to [0e56d5cc8c66c8de9e9a294a4c2f13eed17723e7] # # patch "commands.cc" # from [50b3887eb01f57759a86a6ea6af692b2a83bcb9d] # to [1ad5a06a41749d05fdf9b0ba1543095a4d743678] # # patch "restrictions.cc" # from [b5729cdde64bdc8bba05b6285627e216b7ea9dc4] # to [9000266d8470672a6d623df3c5d0c4ad866678de] # # patch "restrictions.hh" # from [0eb6233836902c52790331da782bdebc731c22e3] # to [997d9f85ddfb423b9a535acdef8a4c44bc384395] # ============================================================ --- ChangeLog 9810ac424b8cbf50d4cff1ada017f42a66fbca63 +++ ChangeLog 0e56d5cc8c66c8de9e9a294a4c2f13eed17723e7 @@ -1,3 +1,10 @@ +2006-02-20 Derek Scherger + + * commands.cc (status, ls_known, find_unknown_and_ignored, + find_missing, commit, diff, revert, log): add excludes to restrictions + * restrictions.{cc,hh}: preliminary support for excludes and unit + tests to see if it works + 2006-02-13 Derek Scherger * app_state.{cc,hh}: ============================================================ --- commands.cc 50b3887eb01f57759a86a6ea6af692b2a83bcb9d +++ commands.cc 1ad5a06a41749d05fdf9b0ba1543095a4d743678 @@ -1295,7 +1295,7 @@ app.require_working_copy(); get_base_and_current_roster_shape(old_roster, new_roster, app); - restriction mask(args, old_roster, new_roster); + restriction mask(args, app.exclude_patterns, old_roster, new_roster); update_current_roster_from_filesystem(new_roster, mask, app); make_restricted_csets(old_roster, new_roster, included, excluded, mask); @@ -1655,7 +1655,7 @@ app.require_working_copy(); get_base_and_current_roster_shape(old_roster, new_roster, app); - restriction mask(args, new_roster); + restriction mask(args, app.exclude_patterns, new_roster); node_map const & nodes = new_roster.all_nodes(); for (node_map::const_iterator i = nodes.begin(); i != nodes.end(); ++i) @@ -1683,7 +1683,7 @@ get_base_and_current_roster_shape(old_roster, new_roster, app); - restriction mask(args, new_roster); + restriction mask(args, app.exclude_patterns, new_roster); new_roster.extract_path_set(known); @@ -1716,7 +1716,7 @@ get_base_and_current_roster_shape(old_roster, new_roster, app); - restriction mask(args, new_roster); + restriction mask(args, app.exclude_patterns, new_roster); node_map const & nodes = new_roster.all_nodes(); for (node_map::const_iterator i = nodes.begin(); i != nodes.end(); ++i) @@ -2306,7 +2306,7 @@ app.require_working_copy(); get_base_and_current_roster_shape(old_roster, new_roster, app); - restriction mask(args, old_roster, new_roster); + restriction mask(args, app.exclude_patterns, old_roster, new_roster); update_current_roster_from_filesystem(new_roster, mask, app); make_restricted_csets(old_roster, new_roster, included, excluded, mask); @@ -2698,7 +2698,7 @@ get_base_and_current_roster_shape(old_roster, new_roster, app); get_revision_id(old_rid); - restriction mask(args, old_roster, new_roster); + restriction mask(args, app.exclude_patterns, old_roster, new_roster); update_current_roster_from_filesystem(new_roster, mask, app); make_restricted_csets(old_roster, new_roster, included, excluded, mask); @@ -2722,7 +2722,7 @@ // FIXME: handle no ancestor case // N(r_new.edges.size() == 1, F("current revision has no ancestor")); - restriction mask(args, old_roster, new_roster); + restriction mask(args, app.exclude_patterns, old_roster, new_roster); update_current_roster_from_filesystem(new_roster, mask, app); make_restricted_csets(old_roster, new_roster, included, excluded, mask); @@ -2746,7 +2746,7 @@ app.db.get_roster(r_old_id, old_roster); app.db.get_roster(r_new_id, new_roster); - restriction mask(args, old_roster, new_roster); + restriction mask(args, app.exclude_patterns, old_roster, new_roster); // FIXME: this is *possibly* a UI bug, insofar as we // look at the restriction name(s) you provided on the command @@ -3277,7 +3277,9 @@ app.require_working_copy(); - vector restricted_args; + vector includes; + vector excludes; + if (app.missing) { // --missing is a further filter on the files included by a restriction @@ -3295,16 +3297,17 @@ { file_path fp(*i); L(F("missing files are '%s'") % fp); - restricted_args.push_back(fp.as_external()); + includes.push_back(fp.as_external()); } } else { - restricted_args = args; + includes = args; + excludes = app.exclude_patterns; } get_base_and_current_roster_shape(old_roster, new_roster, app); - restriction mask(restricted_args, old_roster, new_roster); + restriction mask(includes, excludes, old_roster, new_roster); make_restricted_csets(old_roster, new_roster, included, excluded, mask); @@ -3527,7 +3530,7 @@ // FIXME_RESTRICTIONS: should this add paths from the rosters of all selected revs? - mask = restriction(args, old_roster, new_roster); + mask = restriction(args, app.exclude_patterns, old_roster, new_roster); } cert_name author_name(author_cert_name); ============================================================ --- restrictions.cc b5729cdde64bdc8bba05b6285627e216b7ea9dc4 +++ restrictions.cc 9000266d8470672a6d623df3c5d0c4ad866678de @@ -12,63 +12,147 @@ #include "safe_map.hh" #include "transforms.hh" +using std::make_pair; + // TODO: add support for --depth (replace recursive boolean with depth value) -// TODO: add support for --exclude // TODO: add check for relevant rosters to be used by log +// // i.e. as log goes back through older and older rosters it may hit one that // pre-dates any of the nodes in the restriction. the nodes that the restriction // includes or excludes may not have been born in a sufficiently old roster. at // this point log should stop because no earlier roster will include these nodes. -restriction::restriction(vector const & args, - roster_t const & roster) +static void +make_path_set(vector const & args, path_set & paths) { - set_paths(args); - add_nodes(roster); - check_paths(); + for (vector::const_iterator i = args.begin(); i != args.end(); ++i) + { + split_path sp; + file_path_external(*i).split(sp); + paths.insert(sp); + } } -restriction::restriction(vector const & args, - roster_t const & roster1, - roster_t const & roster2) +static void +merge_states(path_state const & old_state, + path_state const & new_state, + path_state & merged_state, + split_path const & sp) { - set_paths(args); - add_nodes(roster1); - add_nodes(roster2); - check_paths(); + if (old_state == new_state) + { + merged_state = old_state; + } + else if (old_state == explicit_include && new_state == implicit_include || + old_state == implicit_include && new_state == explicit_include) + { + merged_state = explicit_include; + } + else if (old_state == explicit_exclude && new_state == implicit_include || + old_state == implicit_include && new_state == explicit_exclude) + { + // allowing a mix of explicit excludes and implicit includes on a path is + // rather questionable but is required by things like exclude x include + // x/y. (i.e. includes within excludes) these are also somewhat + // questionable themselves since they are equivalent to simply include + // x/y. + // + // this is also required more sensible things like include x exclude x/y + // include x/y/z. + merged_state = implicit_include; + } + else + { + L(F("path '%s' %d %d") % sp % old_state % new_state); + N(false, F("conflicting include/exclude on path '%s'") % sp); + } } -void -restriction::set_paths(vector const & args) +static void +update_path_map(map & path_map, split_path const & sp, path_state const state) { - L(F("setting paths with %d args") % args.size()); + map::iterator p = path_map.find(sp); + if (p != path_map.end()) + { + path_state merged; + merge_states(p->second.state, state, merged, sp); + p->second.state = merged; + } + else + { + path_map.insert(make_pair(sp, path_entry(state))); + } +} - for (vector::const_iterator i = args.begin(); i != args.end(); ++i) +static void +add_included_paths(map & path_map, path_set const & includes) +{ + for (path_set::const_iterator i = includes.begin(); i != includes.end(); ++i) { - split_path sp; - file_path_external(*i).split(sp); - paths.insert(sp); + split_path sp(*i); + path_state state = explicit_include; + + while (!sp.empty()) + { + update_path_map(path_map, sp, state); + sp.pop_back(); + state = implicit_include; + } + } } -void -restriction::add_nodes(roster_t const & roster) +static void +add_excluded_paths(map & path_map, path_set const & excludes) { - for (path_set::const_iterator i = paths.begin(); i != paths.end(); ++i) + for (path_set::const_iterator i = excludes.begin(); i != excludes.end(); ++i) { + update_path_map(path_map, *i, explicit_exclude); + } +} + +static void +update_node_map(map & node_map, node_id const nid, split_path const & sp, path_state const state) +{ + map::iterator n = node_map.find(nid); + if (n != node_map.end()) + { + path_state merged; + merge_states(n->second, state, merged, sp); + n->second = merged; + } + else + { + node_map.insert(make_pair(nid, state)); + } +} + +static void +add_included_nodes(map & node_map, + map & path_map, + path_set const & includes, + roster_t const & roster) +{ + for (path_set::const_iterator i = includes.begin(); i != includes.end(); ++i) + { + // TODO: (future) handle some sort of peg revision path syntax here. // note that the idea of a --peg option doesn't work because each // path may be relative to a different revision. if (roster.has_node(*i)) { + + // FIXME: this may be better as a list of invalid paths + map::iterator p = path_map.find(*i); + I(p != path_map.end()); + + p->second.roster_count++; + // TODO: proper recursive wildcard paths like foo/... // for now explicit paths are recursive // and implicit parents are non-recursive - // TODO: possibly fail with nice error if path is already explicitly - // in the map? - // currently we need to insert the parents of included nodes so that // the included nodes are not orphaned in a restricted roster. this // happens in cases like add "a" + add "a/b" when only "a/b" is @@ -76,45 +160,153 @@ // isn't entirely sensible and should probably be revisited. it does // match the current (old restrictions) semantics though. - valid_paths.insert(*i); + node_id nid = roster.get_node(*i)->self; - bool recursive = true; - node_id nid = roster.get_node(*i)->self; + path_state state = explicit_include; while (!null_node(nid)) { split_path sp; roster.get_name(nid, sp); - L(F("adding nid %d path '%s' recursive %s") % nid % file_path(sp) % recursive); - restricted_node_map[nid] |= recursive; - restricted_path_map[sp] |= recursive; + update_node_map(node_map, nid, sp, state); - recursive = false; - nid = roster.get_node(nid)->parent; + state = implicit_include; } } - else + } +} + +static void +add_excluded_nodes(map & node_map, + map & path_map, + path_set const & excludes, + roster_t const & roster) +{ + for (path_set::const_iterator i = excludes.begin(); i != excludes.end(); ++i) + { + + // TODO: (future) handle some sort of peg revision path syntax here. + // note that the idea of a --peg option doesn't work because each + // path may be relative to a different revision. + + if (roster.has_node(*i)) { - L(F("missed path '%s'") % *i); + // FIXME: this may be better as a list of invalid paths + map::iterator p = path_map.find(*i); + I(p != path_map.end()); + + p->second.roster_count++; + + // TODO: proper recursive wildcard paths like foo/... + // for now explicit paths are recursive + // and implicit parents are non-recursive + + node_id nid = roster.get_node(*i)->self; + + split_path sp; + roster.get_name(nid, sp); + update_node_map(node_map, nid, sp, explicit_exclude); } } } +static void +check_paths(map const & path_map) +{ + int bad = 0; + + for (map::const_iterator i = path_map.begin(); + i != path_map.end(); ++i) + { + L(F("%d %d %s") % i->second.state % i->second.roster_count % i->first); + + if (i->second.state == explicit_include && i->second.roster_count == 0) + { + bad++; + W(F("unknown path included %s") % i->first); + } + else if (i->second.state == explicit_exclude && i->second.roster_count == 0) + { + bad++; + W(F("unknown path excluded %s") % i->first); + } + } + + N(bad == 0, F("%d unknown paths") % bad); +} + + +//////////////////////////////////////////////////////////////////////////////// +// public constructors +//////////////////////////////////////////////////////////////////////////////// + +// FIXME: add_paths and add_nodes should take the nodes to add and their state +// includes/explicit_include, excludes/explicit_exclude, parents/implicit_include +// then we need things to generate lists of parent paths and nodes + +restriction::restriction(vector const & include_args, + vector const & exclude_args, + roster_t const & roster) +{ + path_set includes, excludes; + make_path_set(include_args, includes); + make_path_set(exclude_args, excludes); + + add_included_paths(path_map, includes); + add_excluded_paths(path_map, excludes); + + add_included_nodes(node_map, path_map, includes, roster); + add_excluded_nodes(node_map, path_map, excludes, roster); + + default_result = includes.empty(); + + check_paths(path_map); +} + +restriction::restriction(vector const & include_args, + vector const & exclude_args, + roster_t const & roster1, + roster_t const & roster2) +{ + path_set includes, excludes; + make_path_set(include_args, includes); + make_path_set(exclude_args, excludes); + + add_included_paths(path_map, includes); + add_excluded_paths(path_map, excludes); + + add_included_nodes(node_map, path_map, includes, roster1); + add_excluded_nodes(node_map, path_map, excludes, roster1); + + add_included_nodes(node_map, path_map, includes, roster2); + add_excluded_nodes(node_map, path_map, excludes, roster2); + + default_result = includes.empty(); + + check_paths(path_map); +} + + +//////////////////////////////////////////////////////////////////////////////// +// public api +//////////////////////////////////////////////////////////////////////////////// + bool restriction::includes(roster_t const & roster, node_id nid) const { MM(roster); I(roster.has_node(nid)); + split_path sp; + roster.get_name(nid, sp); + // empty restriction includes everything - if (restricted_node_map.empty()) + if (node_map.empty()) { - split_path sp; - roster.get_name(nid, sp); - L(F("included nid %d path '%s'") % nid % file_path(sp)); + L(F("empty include of nid %d path '%s'") % nid % file_path(sp)); return true; } @@ -122,18 +314,27 @@ while (!null_node(current)) { - split_path sp; - roster.get_name(current, sp); + map::const_iterator r = node_map.find(current); - map::const_iterator r = restricted_node_map.find(current); - - if (r != restricted_node_map.end()) + if (r != node_map.end()) { - // found exact node or a explicit/recusrive parent - if (r->second || current == nid) + switch (r->second) { - L(F("included nid %d path '%s'") % current % file_path(sp)); + case explicit_include: + L(F("explicit include of nid %d path '%s'") % current % file_path(sp)); return true; + + case explicit_exclude: + L(F("explicit exclude of nid %d path '%s'") % current % file_path(sp)); + return false; + + case implicit_include: + // this is non-recursive and requires an exact match + if (current == nid) + { + L(F("implicit include of nid %d path '%s'") % current % file_path(sp)); + return true; + } } } @@ -141,19 +342,24 @@ current = node->parent; } - split_path sp; - roster.get_name(nid, sp); - L(F("excluded nid %d path '%s'\n") % nid % file_path(sp)); - - return false; + if (default_result) + { + L(F("default include of nid %d path '%s'\n") % nid % file_path(sp)); + return true; + } + else + { + L(F("default exclude of nid %d path '%s'\n") % nid % file_path(sp)); + return false; + } } bool restriction::includes(split_path const & sp) const { - if (restricted_path_map.empty()) + if (path_map.empty()) { - L(F("included path '%s'") % file_path(sp)); + L(F("empty include of path '%s'") % file_path(sp)); return true; } @@ -161,45 +367,48 @@ while (!current.empty()) { - L(F("checking path '%s'\n") % current); - map::const_iterator r = restricted_path_map.find(current); + map::const_iterator r = path_map.find(current); - if (r != restricted_path_map.end()) + if (r != path_map.end()) { - if (r->second || current == sp) + switch (r->second.state) { - L(F("included path '%s'") % file_path(sp)); + case explicit_include: + L(F("explicit include of path '%s'") % file_path(sp)); return true; + + case explicit_exclude: + L(F("explicit exclude of path '%s'") % file_path(sp)); + return false; + + case implicit_include: + // this is non-recursive and requires an exact match + if (current == sp) + { + L(F("implicit include of path '%s'") % file_path(sp)); + return true; + } } } current.pop_back(); } - L(F("excluded path '%s'") % file_path(sp)); - return false; - -} - -void -restriction::check_paths() -{ - int bad = 0; - for (path_set::const_iterator i = paths.begin(); i != paths.end(); ++i) + if (default_result) { - if (valid_paths.find(*i) == valid_paths.end()) - { - bad++; - W(F("unknown path %s") % *i); - } + L(F("default include of path '%s'\n") % file_path(sp)); + return true; } - - E(bad == 0, F("%d unknown paths") % bad); + else + { + L(F("default exclude of path '%s'\n") % file_path(sp)); + return false; + } } -//////////////////////////////////////////////////////////////////// -// testing -//////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// tests +//////////////////////////////////////////////////////////////////////////////// #ifdef BUILD_UNIT_TESTS #include "unit_tests.hh" @@ -208,150 +417,499 @@ using std::string; -file_id file1_id(string("1000000000000000000000000000000000000000")); -file_id file2_id(string("2000000000000000000000000000000000000000")); -file_id file3_id(string("3000000000000000000000000000000000000000")); +// f's and g's are files +// x's and y's are directories +// and this is rather painful -static void -test_basic_restrictions() +split_path sp_root; +split_path sp_f; +split_path sp_g; + +split_path sp_x; +split_path sp_xf; +split_path sp_xg; +split_path sp_xx; +split_path sp_xxf; +split_path sp_xxg; +split_path sp_xy; +split_path sp_xyf; +split_path sp_xyg; + +split_path sp_y; +split_path sp_yf; +split_path sp_yg; +split_path sp_yx; +split_path sp_yxf; +split_path sp_yxg; +split_path sp_yy; +split_path sp_yyf; +split_path sp_yyg; + +node_id nid_root; +node_id nid_f; +node_id nid_g; + +node_id nid_x; +node_id nid_xf; +node_id nid_xg; +node_id nid_xx; +node_id nid_xxf; +node_id nid_xxg; +node_id nid_xy; +node_id nid_xyf; +node_id nid_xyg; + +node_id nid_y; +node_id nid_yf; +node_id nid_yg; +node_id nid_yx; +node_id nid_yxf; +node_id nid_yxg; +node_id nid_yy; +node_id nid_yyf; +node_id nid_yyg; + +file_id fid_f(string("1000000000000000000000000000000000000000")); +file_id fid_g(string("2000000000000000000000000000000000000000")); + +file_id fid_xf(string("3000000000000000000000000000000000000000")); +file_id fid_xg(string("4000000000000000000000000000000000000000")); +file_id fid_xxf(string("5000000000000000000000000000000000000000")); +file_id fid_xxg(string("6000000000000000000000000000000000000000")); +file_id fid_xyf(string("7000000000000000000000000000000000000000")); +file_id fid_xyg(string("8000000000000000000000000000000000000000")); + +file_id fid_yf(string("9000000000000000000000000000000000000000")); +file_id fid_yg(string("a000000000000000000000000000000000000000")); +file_id fid_yxf(string("b000000000000000000000000000000000000000")); +file_id fid_yxg(string("c000000000000000000000000000000000000000")); +file_id fid_yyf(string("d000000000000000000000000000000000000000")); +file_id fid_yyg(string("e000000000000000000000000000000000000000")); + +static void setup(roster_t & roster) { temp_node_id_source nis; + + file_path_internal("").split(sp_root); + file_path_internal("f").split(sp_f); + file_path_internal("g").split(sp_g); + + file_path_internal("x").split(sp_x); + file_path_internal("x/f").split(sp_xf); + file_path_internal("x/g").split(sp_xg); + file_path_internal("x/x").split(sp_xx); + file_path_internal("x/x/f").split(sp_xxf); + file_path_internal("x/x/g").split(sp_xxg); + file_path_internal("x/y").split(sp_xy); + file_path_internal("x/y/f").split(sp_xyf); + file_path_internal("x/y/g").split(sp_xyg); + + file_path_internal("y").split(sp_y); + file_path_internal("y/f").split(sp_yf); + file_path_internal("y/g").split(sp_yg); + file_path_internal("y/x").split(sp_yx); + file_path_internal("y/x/f").split(sp_yxf); + file_path_internal("y/x/g").split(sp_yxg); + file_path_internal("y/y").split(sp_yy); + file_path_internal("y/y/f").split(sp_yyf); + file_path_internal("y/y/g").split(sp_yyg); + + nid_root = roster.create_dir_node(nis); + nid_f = roster.create_file_node(fid_f, nis); + nid_g = roster.create_file_node(fid_g, nis); + + nid_x = roster.create_dir_node(nis); + nid_xf = roster.create_file_node(fid_xf, nis); + nid_xg = roster.create_file_node(fid_xg, nis); + nid_xx = roster.create_dir_node(nis); + nid_xxf = roster.create_file_node(fid_xxf, nis); + nid_xxg = roster.create_file_node(fid_xxg, nis); + nid_xy = roster.create_dir_node(nis); + nid_xyf = roster.create_file_node(fid_xxf, nis); + nid_xyg = roster.create_file_node(fid_xxg, nis); + + nid_y = roster.create_dir_node(nis); + nid_yf = roster.create_file_node(fid_yf, nis); + nid_yg = roster.create_file_node(fid_yg, nis); + nid_yx = roster.create_dir_node(nis); + nid_yxf = roster.create_file_node(fid_yxf, nis); + nid_yxg = roster.create_file_node(fid_yxg, nis); + nid_yy = roster.create_dir_node(nis); + nid_yyf = roster.create_file_node(fid_yxf, nis); + nid_yyg = roster.create_file_node(fid_yxg, nis); + + roster.attach_node(nid_root, sp_root); + roster.attach_node(nid_f, sp_f); + roster.attach_node(nid_g, sp_g); + + roster.attach_node(nid_x, sp_x); + roster.attach_node(nid_xf, sp_xf); + roster.attach_node(nid_xg, sp_xg); + roster.attach_node(nid_xx, sp_xx); + roster.attach_node(nid_xxf, sp_xxf); + roster.attach_node(nid_xxg, sp_xxg); + roster.attach_node(nid_xy, sp_xy); + roster.attach_node(nid_xyf, sp_xyf); + roster.attach_node(nid_xyg, sp_xyg); + + roster.attach_node(nid_y, sp_y); + roster.attach_node(nid_yf, sp_yf); + roster.attach_node(nid_yg, sp_yg); + roster.attach_node(nid_yx, sp_yx); + roster.attach_node(nid_yxf, sp_yxf); + roster.attach_node(nid_yxg, sp_yxg); + roster.attach_node(nid_yy, sp_yy); + roster.attach_node(nid_yyf, sp_yyf); + roster.attach_node(nid_yyg, sp_yyg); +} + +static void +test_empty_restriction() +{ roster_t roster; + setup(roster); - node_id root_nid = roster.create_dir_node(nis); - node_id file1_nid = roster.create_file_node(file1_id, nis); - node_id file2_nid = roster.create_file_node(file2_id, nis); - node_id file3_nid = roster.create_file_node(file3_id, nis); + restriction mask; + + BOOST_CHECK(mask.empty()); - split_path root_path, file1_path, file2_path, file3_path; - file_path().split(root_path); - file_path_internal("file1").split(file1_path); - file_path_internal("file2").split(file2_path); - file_path_internal("file3").split(file3_path); + // check restricted nodes + BOOST_CHECK(mask.includes(roster, nid_root)); + BOOST_CHECK(mask.includes(roster, nid_f)); + BOOST_CHECK(mask.includes(roster, nid_g)); - roster.attach_node(root_nid, root_path); - roster.attach_node(file1_nid, file1_path); - roster.attach_node(file2_nid, file2_path); - roster.attach_node(file3_nid, file3_path); + BOOST_CHECK(mask.includes(roster, nid_x)); + BOOST_CHECK(mask.includes(roster, nid_xf)); + BOOST_CHECK(mask.includes(roster, nid_xg)); + BOOST_CHECK(mask.includes(roster, nid_xx)); + BOOST_CHECK(mask.includes(roster, nid_xxf)); + BOOST_CHECK(mask.includes(roster, nid_xxg)); + BOOST_CHECK(mask.includes(roster, nid_xy)); + BOOST_CHECK(mask.includes(roster, nid_xyf)); + BOOST_CHECK(mask.includes(roster, nid_xyg)); - { - // empty restriction - restriction mask; + BOOST_CHECK(mask.includes(roster, nid_y)); + BOOST_CHECK(mask.includes(roster, nid_yf)); + BOOST_CHECK(mask.includes(roster, nid_yg)); + BOOST_CHECK(mask.includes(roster, nid_yx)); + BOOST_CHECK(mask.includes(roster, nid_yxf)); + BOOST_CHECK(mask.includes(roster, nid_yxg)); + BOOST_CHECK(mask.includes(roster, nid_yy)); + BOOST_CHECK(mask.includes(roster, nid_yyf)); + BOOST_CHECK(mask.includes(roster, nid_yyg)); - BOOST_CHECK(mask.empty()); + // check restricted paths + BOOST_CHECK(mask.includes(sp_root)); + BOOST_CHECK(mask.includes(sp_f)); + BOOST_CHECK(mask.includes(sp_g)); - // check restricted nodes - BOOST_CHECK(mask.includes(roster, root_nid)); - BOOST_CHECK(mask.includes(roster, file1_nid)); - BOOST_CHECK(mask.includes(roster, file2_nid)); - BOOST_CHECK(mask.includes(roster, file3_nid)); + BOOST_CHECK(mask.includes(sp_x)); + BOOST_CHECK(mask.includes(sp_xf)); + BOOST_CHECK(mask.includes(sp_xg)); + BOOST_CHECK(mask.includes(sp_xx)); + BOOST_CHECK(mask.includes(sp_xxf)); + BOOST_CHECK(mask.includes(sp_xxg)); + BOOST_CHECK(mask.includes(sp_xy)); + BOOST_CHECK(mask.includes(sp_xyf)); + BOOST_CHECK(mask.includes(sp_xyg)); - // check restricted paths - BOOST_CHECK(mask.includes(root_path)); - BOOST_CHECK(mask.includes(file1_path)); - BOOST_CHECK(mask.includes(file2_path)); - BOOST_CHECK(mask.includes(file3_path)); - } + BOOST_CHECK(mask.includes(sp_y)); + BOOST_CHECK(mask.includes(sp_yf)); + BOOST_CHECK(mask.includes(sp_yg)); + BOOST_CHECK(mask.includes(sp_yx)); + BOOST_CHECK(mask.includes(sp_yxf)); + BOOST_CHECK(mask.includes(sp_yxg)); + BOOST_CHECK(mask.includes(sp_yy)); + BOOST_CHECK(mask.includes(sp_yyf)); + BOOST_CHECK(mask.includes(sp_yyg)); +} - { - // non-empty restriction - vector args; - args.push_back(utf8(string("file1"))); +static void +test_simple_include() +{ + roster_t roster; + setup(roster); - restriction mask(args, roster); + vector includes, excludes; + includes.push_back(utf8(string("x/x"))); + includes.push_back(utf8(string("y/y"))); - BOOST_CHECK(!mask.empty()); + restriction mask(includes, excludes, roster); - // check restricted nodes - BOOST_CHECK(mask.includes(roster, root_nid)); - BOOST_CHECK(mask.includes(roster, file1_nid)); - BOOST_CHECK(!mask.includes(roster, file2_nid)); - BOOST_CHECK(!mask.includes(roster, file3_nid)); + BOOST_CHECK(!mask.empty()); - // check restricted paths - BOOST_CHECK(mask.includes(root_path)); - BOOST_CHECK(mask.includes(file1_path)); - BOOST_CHECK(!mask.includes(file2_path)); - BOOST_CHECK(!mask.includes(file3_path)); - } + // check restricted nodes + BOOST_CHECK( mask.includes(roster, nid_root)); + BOOST_CHECK(!mask.includes(roster, nid_f)); + BOOST_CHECK(!mask.includes(roster, nid_g)); - { - // invalid paths - // non-empty restriction - vector args; - args.push_back(utf8(string("file4"))); + BOOST_CHECK( mask.includes(roster, nid_x)); + BOOST_CHECK(!mask.includes(roster, nid_xf)); + BOOST_CHECK(!mask.includes(roster, nid_xg)); + BOOST_CHECK( mask.includes(roster, nid_xx)); + BOOST_CHECK( mask.includes(roster, nid_xxf)); + BOOST_CHECK( mask.includes(roster, nid_xxg)); + BOOST_CHECK(!mask.includes(roster, nid_xy)); + BOOST_CHECK(!mask.includes(roster, nid_xyf)); + BOOST_CHECK(!mask.includes(roster, nid_xyg)); - BOOST_CHECK_THROW(restriction(args, roster), informative_failure); - } + BOOST_CHECK( mask.includes(roster, nid_y)); + BOOST_CHECK(!mask.includes(roster, nid_yf)); + BOOST_CHECK(!mask.includes(roster, nid_yg)); + BOOST_CHECK(!mask.includes(roster, nid_yx)); + BOOST_CHECK(!mask.includes(roster, nid_yxf)); + BOOST_CHECK(!mask.includes(roster, nid_yxg)); + BOOST_CHECK( mask.includes(roster, nid_yy)); + BOOST_CHECK( mask.includes(roster, nid_yyf)); + BOOST_CHECK( mask.includes(roster, nid_yyg)); + // check restricted paths + BOOST_CHECK( mask.includes(sp_root)); + BOOST_CHECK(!mask.includes(sp_f)); + BOOST_CHECK(!mask.includes(sp_g)); + + BOOST_CHECK( mask.includes(sp_x)); + BOOST_CHECK(!mask.includes(sp_xf)); + BOOST_CHECK(!mask.includes(sp_xg)); + BOOST_CHECK( mask.includes(sp_xx)); + BOOST_CHECK( mask.includes(sp_xxf)); + BOOST_CHECK( mask.includes(sp_xxg)); + BOOST_CHECK(!mask.includes(sp_xy)); + BOOST_CHECK(!mask.includes(sp_xyf)); + BOOST_CHECK(!mask.includes(sp_xyg)); + + BOOST_CHECK( mask.includes(sp_y)); + BOOST_CHECK(!mask.includes(sp_yf)); + BOOST_CHECK(!mask.includes(sp_yg)); + BOOST_CHECK(!mask.includes(sp_yx)); + BOOST_CHECK(!mask.includes(sp_yxf)); + BOOST_CHECK(!mask.includes(sp_yxg)); + BOOST_CHECK( mask.includes(sp_yy)); + BOOST_CHECK( mask.includes(sp_yyf)); + BOOST_CHECK( mask.includes(sp_yyg)); } -static void -test_recursive_nonrecursive() +static void +test_simple_exclude() { - temp_node_id_source nis; roster_t roster; + setup(roster); - node_id root_nid = roster.create_dir_node(nis); + vector includes, excludes; + excludes.push_back(utf8(string("x/x"))); + excludes.push_back(utf8(string("y/y"))); - node_id dir1_nid = roster.create_dir_node(nis); - node_id dir2_nid = roster.create_dir_node(nis); + restriction mask(includes, excludes, roster); - node_id file1_nid = roster.create_file_node(file1_id, nis); - node_id file2_nid = roster.create_file_node(file2_id, nis); - node_id file3_nid = roster.create_file_node(file3_id, nis); + BOOST_CHECK(!mask.empty()); - // root/file1 - // root/dir1 - // root/dir1/file2 - // root/dir1/dir2 - // root/dir1/dir2/file3 + // check restricted nodes + BOOST_CHECK( mask.includes(roster, nid_root)); + BOOST_CHECK( mask.includes(roster, nid_f)); + BOOST_CHECK( mask.includes(roster, nid_g)); - split_path root_path, file1_path, dir1_path, file2_path, dir2_path, file3_path; + BOOST_CHECK( mask.includes(roster, nid_x)); + BOOST_CHECK( mask.includes(roster, nid_xf)); + BOOST_CHECK( mask.includes(roster, nid_xg)); + BOOST_CHECK(!mask.includes(roster, nid_xx)); + BOOST_CHECK(!mask.includes(roster, nid_xxf)); + BOOST_CHECK(!mask.includes(roster, nid_xxg)); + BOOST_CHECK( mask.includes(roster, nid_xy)); + BOOST_CHECK( mask.includes(roster, nid_xyf)); + BOOST_CHECK( mask.includes(roster, nid_xyg)); - file_path().split(root_path); - file_path_internal("file1").split(file1_path); - file_path_internal("dir1").split(dir1_path); - file_path_internal("dir1/file2").split(file2_path); - file_path_internal("dir1/dir2").split(dir2_path); - file_path_internal("dir1/dir2/file3").split(file3_path); + BOOST_CHECK( mask.includes(roster, nid_y)); + BOOST_CHECK( mask.includes(roster, nid_yf)); + BOOST_CHECK( mask.includes(roster, nid_yg)); + BOOST_CHECK( mask.includes(roster, nid_yx)); + BOOST_CHECK( mask.includes(roster, nid_yxf)); + BOOST_CHECK( mask.includes(roster, nid_yxg)); + BOOST_CHECK(!mask.includes(roster, nid_yy)); + BOOST_CHECK(!mask.includes(roster, nid_yyf)); + BOOST_CHECK(!mask.includes(roster, nid_yyg)); - roster.attach_node(root_nid, root_path); - roster.attach_node(file1_nid, file1_path); - roster.attach_node(dir1_nid, dir1_path); - roster.attach_node(file2_nid, file2_path); - roster.attach_node(dir2_nid, dir2_path); - roster.attach_node(file3_nid, file3_path); + // check restricted paths + BOOST_CHECK( mask.includes(sp_root)); + BOOST_CHECK( mask.includes(sp_f)); + BOOST_CHECK( mask.includes(sp_g)); - vector args; - args.push_back(utf8(string("dir1/dir2"))); + BOOST_CHECK( mask.includes(sp_x)); + BOOST_CHECK( mask.includes(sp_xf)); + BOOST_CHECK( mask.includes(sp_xg)); + BOOST_CHECK(!mask.includes(sp_xx)); + BOOST_CHECK(!mask.includes(sp_xxf)); + BOOST_CHECK(!mask.includes(sp_xxg)); + BOOST_CHECK( mask.includes(sp_xy)); + BOOST_CHECK( mask.includes(sp_xyf)); + BOOST_CHECK( mask.includes(sp_xyg)); - restriction mask(args, roster); + BOOST_CHECK( mask.includes(sp_y)); + BOOST_CHECK( mask.includes(sp_yf)); + BOOST_CHECK( mask.includes(sp_yg)); + BOOST_CHECK( mask.includes(sp_yx)); + BOOST_CHECK( mask.includes(sp_yxf)); + BOOST_CHECK( mask.includes(sp_yxg)); + BOOST_CHECK(!mask.includes(sp_yy)); + BOOST_CHECK(!mask.includes(sp_yyf)); + BOOST_CHECK(!mask.includes(sp_yyg)); +} +static void +test_include_exclude() +{ + roster_t roster; + setup(roster); + + vector includes, excludes; + includes.push_back(utf8(string("x"))); + includes.push_back(utf8(string("y"))); + excludes.push_back(utf8(string("x/x"))); + excludes.push_back(utf8(string("y/y"))); + + restriction mask(includes, excludes, roster); + BOOST_CHECK(!mask.empty()); // check restricted nodes - BOOST_CHECK(mask.includes(roster, root_nid)); - BOOST_CHECK(!mask.includes(roster, file1_nid)); - BOOST_CHECK(mask.includes(roster, dir1_nid)); - BOOST_CHECK(!mask.includes(roster, file2_nid)); - BOOST_CHECK(mask.includes(roster, dir2_nid)); - BOOST_CHECK(mask.includes(roster, file3_nid)); + BOOST_CHECK( mask.includes(roster, nid_root)); + BOOST_CHECK(!mask.includes(roster, nid_f)); + BOOST_CHECK(!mask.includes(roster, nid_g)); + BOOST_CHECK( mask.includes(roster, nid_x)); + BOOST_CHECK( mask.includes(roster, nid_xf)); + BOOST_CHECK( mask.includes(roster, nid_xg)); + BOOST_CHECK(!mask.includes(roster, nid_xx)); + BOOST_CHECK(!mask.includes(roster, nid_xxf)); + BOOST_CHECK(!mask.includes(roster, nid_xxg)); + BOOST_CHECK( mask.includes(roster, nid_xy)); + BOOST_CHECK( mask.includes(roster, nid_xyf)); + BOOST_CHECK( mask.includes(roster, nid_xyg)); + + BOOST_CHECK( mask.includes(roster, nid_y)); + BOOST_CHECK( mask.includes(roster, nid_yf)); + BOOST_CHECK( mask.includes(roster, nid_yg)); + BOOST_CHECK( mask.includes(roster, nid_yx)); + BOOST_CHECK( mask.includes(roster, nid_yxf)); + BOOST_CHECK( mask.includes(roster, nid_yxg)); + BOOST_CHECK(!mask.includes(roster, nid_yy)); + BOOST_CHECK(!mask.includes(roster, nid_yyf)); + BOOST_CHECK(!mask.includes(roster, nid_yyg)); + // check restricted paths - BOOST_CHECK(mask.includes(root_path)); - BOOST_CHECK(!mask.includes(file1_path)); - BOOST_CHECK(mask.includes(dir1_path)); - BOOST_CHECK(!mask.includes(file2_path)); - BOOST_CHECK(mask.includes(dir2_path)); - BOOST_CHECK(mask.includes(file3_path)); + BOOST_CHECK( mask.includes(sp_root)); + BOOST_CHECK(!mask.includes(sp_f)); + BOOST_CHECK(!mask.includes(sp_g)); + + BOOST_CHECK( mask.includes(sp_x)); + BOOST_CHECK( mask.includes(sp_xf)); + BOOST_CHECK( mask.includes(sp_xg)); + BOOST_CHECK(!mask.includes(sp_xx)); + BOOST_CHECK(!mask.includes(sp_xxf)); + BOOST_CHECK(!mask.includes(sp_xxg)); + BOOST_CHECK( mask.includes(sp_xy)); + BOOST_CHECK( mask.includes(sp_xyf)); + BOOST_CHECK( mask.includes(sp_xyg)); + + BOOST_CHECK( mask.includes(sp_y)); + BOOST_CHECK( mask.includes(sp_yf)); + BOOST_CHECK( mask.includes(sp_yg)); + BOOST_CHECK( mask.includes(sp_yx)); + BOOST_CHECK( mask.includes(sp_yxf)); + BOOST_CHECK( mask.includes(sp_yxg)); + BOOST_CHECK(!mask.includes(sp_yy)); + BOOST_CHECK(!mask.includes(sp_yyf)); + BOOST_CHECK(!mask.includes(sp_yyg)); } +static void +test_exclude_include() +{ + roster_t roster; + setup(roster); + + vector includes, excludes; + excludes.push_back(utf8(string("x"))); + excludes.push_back(utf8(string("y"))); + includes.push_back(utf8(string("x/x"))); + includes.push_back(utf8(string("y/y"))); + + restriction mask(includes, excludes, roster); + + BOOST_CHECK(!mask.empty()); + + // check restricted nodes + BOOST_CHECK( mask.includes(roster, nid_root)); + BOOST_CHECK(!mask.includes(roster, nid_f)); + BOOST_CHECK(!mask.includes(roster, nid_g)); + + BOOST_CHECK( mask.includes(roster, nid_x)); + BOOST_CHECK(!mask.includes(roster, nid_xf)); + BOOST_CHECK(!mask.includes(roster, nid_xg)); + BOOST_CHECK( mask.includes(roster, nid_xx)); + BOOST_CHECK( mask.includes(roster, nid_xxf)); + BOOST_CHECK( mask.includes(roster, nid_xxg)); + BOOST_CHECK(!mask.includes(roster, nid_xy)); + BOOST_CHECK(!mask.includes(roster, nid_xyf)); + BOOST_CHECK(!mask.includes(roster, nid_xyg)); + + BOOST_CHECK( mask.includes(roster, nid_y)); + BOOST_CHECK(!mask.includes(roster, nid_yf)); + BOOST_CHECK(!mask.includes(roster, nid_yg)); + BOOST_CHECK(!mask.includes(roster, nid_yx)); + BOOST_CHECK(!mask.includes(roster, nid_yxf)); + BOOST_CHECK(!mask.includes(roster, nid_yxg)); + BOOST_CHECK( mask.includes(roster, nid_yy)); + BOOST_CHECK( mask.includes(roster, nid_yyf)); + BOOST_CHECK( mask.includes(roster, nid_yyg)); + + // check restricted paths + BOOST_CHECK( mask.includes(sp_root)); + BOOST_CHECK(!mask.includes(sp_f)); + BOOST_CHECK(!mask.includes(sp_g)); + + BOOST_CHECK( mask.includes(sp_x)); + BOOST_CHECK(!mask.includes(sp_xf)); + BOOST_CHECK(!mask.includes(sp_xg)); + BOOST_CHECK( mask.includes(sp_xx)); + BOOST_CHECK( mask.includes(sp_xxf)); + BOOST_CHECK( mask.includes(sp_xxg)); + BOOST_CHECK(!mask.includes(sp_xy)); + BOOST_CHECK(!mask.includes(sp_xyf)); + BOOST_CHECK(!mask.includes(sp_xyg)); + + BOOST_CHECK( mask.includes(sp_y)); + BOOST_CHECK(!mask.includes(sp_yf)); + BOOST_CHECK(!mask.includes(sp_yg)); + BOOST_CHECK(!mask.includes(sp_yx)); + BOOST_CHECK(!mask.includes(sp_yxf)); + BOOST_CHECK(!mask.includes(sp_yxg)); + BOOST_CHECK( mask.includes(sp_yy)); + BOOST_CHECK( mask.includes(sp_yyf)); + BOOST_CHECK( mask.includes(sp_yyg)); +} + +static void +test_invalid_paths() +{ + roster_t roster; + setup(roster); + + vector includes, excludes; + includes.push_back(utf8(string("foo"))); + excludes.push_back(utf8(string("bar"))); + + BOOST_CHECK_THROW(restriction(includes, excludes, roster), informative_failure); +} + void add_restrictions_tests(test_suite * suite) { I(suite); - suite->add(BOOST_TEST_CASE(&test_basic_restrictions)); - suite->add(BOOST_TEST_CASE(&test_recursive_nonrecursive)); + suite->add(BOOST_TEST_CASE(&test_empty_restriction)); + suite->add(BOOST_TEST_CASE(&test_simple_include)); + suite->add(BOOST_TEST_CASE(&test_simple_exclude)); + suite->add(BOOST_TEST_CASE(&test_include_exclude)); + suite->add(BOOST_TEST_CASE(&test_exclude_include)); + suite->add(BOOST_TEST_CASE(&test_invalid_paths)); } #endif // BUILD_UNIT_TESTS ============================================================ --- restrictions.hh 0eb6233836902c52790331da782bdebc731c22e3 +++ restrictions.hh 997d9f85ddfb423b9a535acdef8a4c44bc384395 @@ -50,39 +50,49 @@ // // revision A ... included ... revision X ... excluded ... revision B +// TODO: move these into the class below?!? +enum path_state { explicit_include, explicit_exclude, implicit_include }; + +struct path_entry { + path_state state; + int roster_count; + path_entry(path_state const s) : state(s), roster_count(0) {} +}; + + class restriction { public: restriction() {} - restriction(vector const & args, + restriction(vector const & includes, + vector const & excludes, roster_t const & roster); - - restriction(vector const & args, + + restriction(vector const & includes, + vector const & excludes, roster_t const & roster1, roster_t const & roster2); bool includes(roster_t const & roster, node_id nid) const; + bool includes(split_path const & sp) const; - bool empty() const { return restricted_node_map.empty(); } + bool empty() { return node_map.empty(); } + + // explicit in the sense that the path was explicitly given on the command line + // implicit in the sense that parent directories are included for explicit paths + private: - // true in these two maps indicates an explicitly included node or path and - // explicitly included directories are recursive. alternatively, false in - // these maps indicated an implicitly included parent directory which is - // specifically required for the restriction but does not include any of its - // children - - map restricted_node_map; - map restricted_path_map; - path_set paths; + bool default_result; - path_set valid_paths; + // we maintain maps by node_id and also by split_path which is not + // particularly nice, but is required for checking unknown and ignored + // files and used for tracking the paths that are known in the rosters + map node_map; + map path_map; - void set_paths(vector const & args); - void add_nodes(roster_t const & roster); - void check_paths(); }; #endif // header guard