# # # patch "cmd_diff_log.cc" # from [9bf839e131811070ee4518463761205937bf6e6f] # to [5d72346a546e1ce00b3815efd8ccce9e022de550] # # patch "cmd_list.cc" # from [4481e430ec7ad9a903827eb164f06c010ca82825] # to [b2b220ba0f9f48e29f8e3dcf89884fd4907542df] # # patch "cmd_merging.cc" # from [7d6bcab7660bdece14f740cbfed0d1eef3bff6c1] # to [b1951ad07c8566fda9e640a4c9a51a03f62a2d82] # # patch "cmd_ws_commit.cc" # from [86b109b736ae4ea62349b8a37133e855c264ec53] # to [ee5f3cf77e4afcd0d6f22e75e8e619e75b06eeee] # # patch "restrictions.cc" # from [b59982c1c4ab148fc44aafaa72f0c3c7548f98b9] # to [f1d46d5a441513d1c21c90baa644404cd88e58df] # # patch "restrictions.hh" # from [5b16498cc58ffea38a6e98f4b32efda724dc7ef3] # to [aef753efe26a40a6819de36756c9f99527b80faa] # # patch "work.cc" # from [3d52c005916784461d059e64cf2779d49befdb1e] # to [90d466377088546516e4af86d021b5748b5ff84a] # # patch "work.hh" # from [c23d39b71193d9ddc5f715cf60b000e0dae0982a] # to [1948f6514935330b875598aa407efa26da571a1c] # ============================================================ --- cmd_diff_log.cc 9bf839e131811070ee4518463761205937bf6e6f +++ cmd_diff_log.cc 5d72346a546e1ce00b3815efd8ccce9e022de550 @@ -365,7 +365,7 @@ CMD(diff, N_("informative"), N_("[PATH]. node_restriction mask(args_to_paths(args), args_to_paths(app.exclude_patterns), app.depth, - old_roster, new_roster, app); + old_roster, new_roster, app.lua); app.work.update_current_roster_from_filesystem(new_roster, mask); make_restricted_csets(old_roster, new_roster, @@ -394,7 +394,7 @@ CMD(diff, N_("informative"), N_("[PATH]. node_restriction mask(args_to_paths(args), args_to_paths(app.exclude_patterns), app.depth, - old_roster, new_roster, app); + old_roster, new_roster, app.lua); app.work.update_current_roster_from_filesystem(new_roster, mask); make_restricted_csets(old_roster, new_roster, @@ -423,7 +423,7 @@ CMD(diff, N_("informative"), N_("[PATH]. node_restriction mask(args_to_paths(args), args_to_paths(app.exclude_patterns), app.depth, - old_roster, new_roster, app); + old_roster, new_roster, app.lua); // FIXME: this is *possibly* a UI bug, insofar as we // look at the restriction name(s) you provided on the command @@ -586,7 +586,7 @@ CMD(log, N_("informative"), N_("[FILE] . mask = node_restriction(args_to_paths(args), args_to_paths(app.exclude_patterns), app.depth, - old_roster, new_roster, app); + old_roster, new_roster, app.lua); } cert_name author_name(author_cert_name); ============================================================ --- cmd_list.cc 4481e430ec7ad9a903827eb164f06c010ca82825 +++ cmd_list.cc b2b220ba0f9f48e29f8e3dcf89884fd4907542df @@ -349,7 +349,7 @@ ls_known(app_state & app, vector c node_restriction mask(args_to_paths(args), args_to_paths(app.exclude_patterns), app.depth, - new_roster, app); + new_roster, app.lua); node_map const & nodes = new_roster.all_nodes(); for (node_map::const_iterator i = nodes.begin(); @@ -374,7 +374,7 @@ ls_unknown_or_ignored(app_state & app, b app.require_workspace(); vector roots = args_to_paths(args); - path_restriction mask(roots, args_to_paths(app.exclude_patterns), app.depth, app); + path_restriction mask(roots, args_to_paths(app.exclude_patterns), app.depth, app.lua); path_set unknown, ignored; // if no starting paths have been specified use the workspace root @@ -402,7 +402,7 @@ ls_missing(app_state & app, vector node_restriction mask(args_to_paths(args), args_to_paths(app.exclude_patterns), app.depth, - current_roster_shape, app); + current_roster_shape, app.lua); path_set missing; app.work.find_missing(current_roster_shape, mask, missing); @@ -430,7 +430,7 @@ ls_changed(app_state & app, vector node_restriction mask(args_to_paths(args), args_to_paths(app.exclude_patterns), app.depth, - old_roster, new_roster, app); + old_roster, new_roster, app.lua); app.work.update_current_roster_from_filesystem(new_roster, mask); make_restricted_csets(old_roster, new_roster, ============================================================ --- cmd_merging.cc 7d6bcab7660bdece14f740cbfed0d1eef3bff6c1 +++ cmd_merging.cc b1951ad07c8566fda9e640a4c9a51a03f62a2d82 @@ -262,7 +262,7 @@ CMD(update, N_("workspace"), "", P(F("switched branch; next commit will use branch %s") % app.branch_name()); P(F("updated to base revision %s") % chosen_rid); - maybe_update_inodeprints(app); + app.work.maybe_update_inodeprints(); } // Subroutine of CMD(merge) and CMD(explicit_merge). Merge LEFT with RIGHT, @@ -749,7 +749,7 @@ CMD(pluck, N_("workspace"), N_("[-r FROM node_restriction mask(args_to_paths(args), args_to_paths(app.exclude_patterns), app.depth, - *from_roster, to_true_roster, app); + *from_roster, to_true_roster, app.lua); make_restricted_csets(*from_roster, to_true_roster, from_to_to, from_to_to_excluded, mask); @@ -787,8 +787,16 @@ CMD(pluck, N_("workspace"), N_("[-r FROM P(F("applied changes to workspace")); - put_work_cset(remaining); + // and record any remaining changes in _MTN/revision + revision_id base_id; + revision_t remaining; + MM(remaining); + app.work.get_revision_id(base_id); + make_revision_for_workspace(base_id, base_roster, merged_roster, remaining); + // small race condition here... + app.work.put_work_rev(remaining); + // add a note to the user log file about what we did { utf8 log; ============================================================ --- cmd_ws_commit.cc 86b109b736ae4ea62349b8a37133e855c264ec53 +++ cmd_ws_commit.cc ee5f3cf77e4afcd0d6f22e75e8e619e75b06eeee @@ -80,7 +80,7 @@ CMD(revert, N_("workspace"), N_("[PATH]. node_restriction mask(args_to_paths(args), args_to_paths(app.exclude_patterns), app.depth, - old_roster, new_roster, app); + old_roster, new_roster, app.lua); if (app.missing) { @@ -106,7 +106,7 @@ CMD(revert, N_("workspace"), N_("[PATH]. // replace the original mask with a more restricted one mask = node_restriction(missing_files, std::vector(), app.depth, - old_roster, new_roster, app); + old_roster, new_roster, app.lua); } make_restricted_csets(old_roster, new_roster, @@ -205,11 +205,7 @@ CMD(revert, N_("workspace"), N_("[PATH]. } } if (attrs_differ) - { - // FIXME_ATTRS: If fp is a directory the call to update_any_attrs - // will also affect files and/or subdirectories. - fpvect.push_back(fp); - } + app.work.apply_attrs(fp, node->attrs); } // Included_work is thrown away which effectively reverts any adds, @@ -223,10 +219,8 @@ CMD(revert, N_("workspace"), N_("[PATH]. make_revision_for_workspace(base, excluded, remaining); // Race. - put_work_cset(excluded); - if (fpvect.size() > 0) - update_any_attrs(fpvect, app); - maybe_update_inodeprints(app); + app.work.put_work_rev(remaining); + app.work.maybe_update_inodeprints(); } CMD(disapprove, N_("review"), N_("REVISION"), @@ -294,7 +288,7 @@ CMD(add, N_("workspace"), N_("[PATH]..." if (app.unknown) { vector roots = args_to_paths(args); - path_restriction mask(roots, args_to_paths(app.exclude_patterns), app.depth, app); + path_restriction mask(roots, args_to_paths(app.exclude_patterns), app.depth, app.lua); path_set ignored; // if no starting paths have been specified use the workspace root @@ -333,7 +327,7 @@ CMD(drop, N_("workspace"), N_("[PATH]... node_restriction mask(args_to_paths(args), args_to_paths(app.exclude_patterns), app.depth, - current_roster_shape, app); + current_roster_shape, app.lua); app.work.find_missing(current_roster_shape, mask, paths); } else @@ -411,7 +405,7 @@ CMD(status, N_("informative"), N_("[PATH node_restriction mask(args_to_paths(args), args_to_paths(app.exclude_patterns), app.depth, - old_roster, new_roster, app); + old_roster, new_roster, app.lua); app.work.update_current_roster_from_filesystem(new_roster, mask); make_restricted_csets(old_roster, new_roster, @@ -592,11 +586,11 @@ CMD(checkout, N_("tree"), N_("[DIRECTORY app.db.get_file_version(file->content, dat); write_localized_data(path, dat.inner(), app.lua); } + + app.work.apply_attrs(path, node->attrs); } - remove_work_cset(); - std::vector all_files; - update_any_attrs(all_files, app); - maybe_update_inodeprints(app); + + app.work.maybe_update_inodeprints(); guard.commit(); } @@ -611,32 +605,13 @@ CMD(attr, N_("workspace"), N_("set PATH string subcmd = idx(args, 0)(); - if (subcmd == "scan") + if (subcmd == "scan" || subcmd == "set" || subcmd == "drop") { - std::vector paths; - - if (args.size() < 2) - { - paths = std::vector(); - } - else - { - std::vector::const_iterator pbegin = args.begin(); - ++pbegin; - for ( ; pbegin != args.end(); ++pbegin) - { - paths.push_back(file_path_external(*pbegin)); - } - } - perform_attr_scan(paths, app); - } - else if (subcmd == "set" || subcmd == "drop") - { roster_t old_roster, new_roster; temp_node_id_source nis; app.require_workspace(); - get_base_and_current_roster_shape(old_roster, new_roster, nis, app); + app.work.get_base_and_current_roster_shape(old_roster, new_roster, nis); file_path path = file_path_external(idx(args,1)); split_path sp; @@ -645,8 +620,17 @@ CMD(attr, N_("workspace"), N_("set PATH N(new_roster.has_node(sp), F("Unknown path '%s'") % path); node_t node = new_roster.get_node(sp); - if (subcmd == "set") + if (subcmd == "scan") { + vector pathargs(args.begin() + 1, args.end()); + node_restriction mask(args_to_paths(pathargs), + args_to_paths(app.exclude_patterns), + app.depth, + old_roster, new_roster, app.lua); + app.work.perform_attr_scan(new_roster, mask); + } + else if (subcmd == "set") + { if (args.size() != 4) throw usage(name); @@ -658,7 +642,7 @@ CMD(attr, N_("workspace"), N_("set PATH if (app.execute) app.lua.hook_apply_attribute(a_key(), path, a_value(), false); } - else + else if (subcmd == "drop") { // Clear all attrs (or a specific attr). if (args.size() == 2) @@ -692,7 +676,6 @@ CMD(attr, N_("workspace"), N_("set PATH revision_t new_work; make_revision_for_workspace(base, old_roster, new_roster, new_work); app.work.put_work_rev(new_work); - app.work.update_any_attrs(); } else if (subcmd == "get") { @@ -700,7 +683,7 @@ CMD(attr, N_("workspace"), N_("set PATH temp_node_id_source nis; app.require_workspace(); - get_base_and_current_roster_shape(old_roster, new_roster, nis, app); + app.work.get_base_and_current_roster_shape(old_roster, new_roster, nis); file_path path = file_path_external(idx(args,1)); split_path sp; @@ -765,7 +748,7 @@ CMD(commit, N_("workspace"), N_("[PATH]. node_restriction mask(args_to_paths(args), args_to_paths(app.exclude_patterns), app.depth, - old_roster, new_roster, app); + old_roster, new_roster, app.lua); app.work.update_current_roster_from_filesystem(new_roster, mask); make_restricted_csets(old_roster, new_roster, @@ -963,8 +946,7 @@ CMD(commit, N_("workspace"), N_("[PATH]. % ui.prog_name); } - update_any_attrs(args_to_paths(args), app); - maybe_update_inodeprints(app); + app.work.maybe_update_inodeprints(); { // Tell lua what happened. Yes, we might lose some information ============================================================ --- restrictions.cc b59982c1c4ab148fc44aafaa72f0c3c7548f98b9 +++ restrictions.cc f1d46d5a441513d1c21c90baa644404cd88e58df @@ -84,7 +84,7 @@ validate_roster_paths(path_set const & i validate_roster_paths(path_set const & included_paths, path_set const & excluded_paths, path_set const & known_paths, - app_state & app) + lua_hooks & lua) { int bad = 0; @@ -97,7 +97,7 @@ validate_roster_paths(path_set const & i if (known_paths.find(*i) == known_paths.end()) { file_path fp(*i); - if (!app.lua.hook_ignore_file(fp)) + if (!lua.hook_ignore_file(fp)) { bad++; W(F("restriction includes unknown path '%s'") % *i); @@ -121,7 +121,7 @@ validate_workspace_paths(path_set const void validate_workspace_paths(path_set const & included_paths, path_set const & excluded_paths, - app_state & app) + lua_hooks & lua) { int bad = 0; @@ -135,7 +135,7 @@ validate_workspace_paths(path_set const // considered invalid if they are found in none of the restriction's // rosters file_path fp(*i); - if (!path_exists(fp) && !app.lua.hook_ignore_file(fp)) + if (!path_exists(fp) && !lua.hook_ignore_file(fp)) { bad++; W(F("restriction includes unknown path '%s'") % *i); @@ -172,7 +172,7 @@ node_restriction::node_restriction(std:: std::vector const & excludes, long depth, roster_t const & roster, - app_state & a) : + lua_hooks & lua) : restriction(includes, excludes, depth) { map_nodes(node_map, roster, included_paths, known_paths, @@ -180,7 +180,7 @@ node_restriction::node_restriction(std:: map_nodes(node_map, roster, excluded_paths, known_paths, restricted_path::excluded); - validate_roster_paths(included_paths, excluded_paths, known_paths, a); + validate_roster_paths(included_paths, excluded_paths, known_paths, lua); } node_restriction::node_restriction(std::vector const & includes, @@ -188,7 +188,7 @@ node_restriction::node_restriction(std:: long depth, roster_t const & roster1, roster_t const & roster2, - app_state & a) : + lua_hooks & lua) : restriction(includes, excludes, depth) { map_nodes(node_map, roster1, included_paths, known_paths, @@ -201,19 +201,19 @@ node_restriction::node_restriction(std:: map_nodes(node_map, roster2, excluded_paths, known_paths, restricted_path::excluded); - validate_roster_paths(included_paths, excluded_paths, known_paths, a); + validate_roster_paths(included_paths, excluded_paths, known_paths, lua); } path_restriction::path_restriction(std::vector const & includes, std::vector const & excludes, long depth, - app_state & a) : + lua_hooks & lua) : restriction(includes, excludes, depth) { map_paths(path_map, included_paths, restricted_path::included); map_paths(path_map, excluded_paths, restricted_path::excluded); - validate_workspace_paths(included_paths, excluded_paths, a); + validate_workspace_paths(included_paths, excluded_paths, lua); } bool @@ -619,7 +619,7 @@ UNIT_TEST(restrictions, simple_include) // check restricted nodes - node_restriction nmask(includes, excludes, -1, roster, app); + node_restriction nmask(includes, excludes, -1, roster, app.lua); BOOST_CHECK(!nmask.empty()); @@ -649,7 +649,7 @@ UNIT_TEST(restrictions, simple_include) // check restricted paths - path_restriction pmask(includes, excludes, -1, app); + path_restriction pmask(includes, excludes, -1, app.lua); BOOST_CHECK(!pmask.empty()); @@ -691,7 +691,7 @@ UNIT_TEST(restrictions, simple_exclude) // check restricted nodes - node_restriction nmask(includes, excludes, -1, roster, app); + node_restriction nmask(includes, excludes, -1, roster, app.lua); BOOST_CHECK(!nmask.empty()); @@ -721,7 +721,7 @@ UNIT_TEST(restrictions, simple_exclude) // check restricted paths - path_restriction pmask(includes, excludes, -1, app); + path_restriction pmask(includes, excludes, -1, app.lua); BOOST_CHECK(!pmask.empty()); @@ -765,7 +765,7 @@ UNIT_TEST(restrictions, include_exclude) // check restricted nodes - node_restriction nmask(includes, excludes, -1, roster, app); + node_restriction nmask(includes, excludes, -1, roster, app.lua); BOOST_CHECK(!nmask.empty()); @@ -795,7 +795,7 @@ UNIT_TEST(restrictions, include_exclude) // check restricted paths - path_restriction pmask(includes, excludes, -1, app); + path_restriction pmask(includes, excludes, -1, app.lua); BOOST_CHECK(!pmask.empty()); @@ -842,7 +842,7 @@ UNIT_TEST(restrictions, exclude_include) // check restricted nodes - node_restriction nmask(includes, excludes, -1, roster, app); + node_restriction nmask(includes, excludes, -1, roster, app.lua); BOOST_CHECK(!nmask.empty()); @@ -872,7 +872,7 @@ UNIT_TEST(restrictions, exclude_include) // check restricted paths - path_restriction pmask(includes, excludes, -1, app); + path_restriction pmask(includes, excludes, -1, app.lua); BOOST_CHECK(!pmask.empty()); @@ -911,7 +911,7 @@ UNIT_TEST(restrictions, invalid_roster_p excludes.push_back(file_path_internal("bar")); app_state app; - BOOST_CHECK_THROW(node_restriction(includes, excludes, -1, roster, app), + BOOST_CHECK_THROW(node_restriction(includes, excludes, -1, roster, app.lua), informative_failure); } @@ -925,7 +925,7 @@ UNIT_TEST(restrictions, invalid_workspac excludes.push_back(file_path_internal("bar")); app_state app; - BOOST_CHECK_THROW(path_restriction(includes, excludes, -1, app), + BOOST_CHECK_THROW(path_restriction(includes, excludes, -1, app.lua), informative_failure); } @@ -946,7 +946,7 @@ UNIT_TEST(restrictions, include_depth_0) // check restricted nodes - node_restriction nmask(includes, excludes, depth, roster, app); + node_restriction nmask(includes, excludes, depth, roster, app.lua); BOOST_CHECK(!nmask.empty()); @@ -976,7 +976,7 @@ UNIT_TEST(restrictions, include_depth_0) // check restricted paths - path_restriction pmask(includes, excludes, depth, app); + path_restriction pmask(includes, excludes, depth, app.lua); BOOST_CHECK(!pmask.empty()); @@ -1020,7 +1020,7 @@ UNIT_TEST(restrictions, include_depth_0_ // check restricted nodes - node_restriction nmask(includes, excludes, depth, roster, app); + node_restriction nmask(includes, excludes, depth, roster, app.lua); BOOST_CHECK( nmask.empty()); @@ -1050,7 +1050,7 @@ UNIT_TEST(restrictions, include_depth_0_ // check restricted paths - path_restriction pmask(includes, excludes, depth, app); + path_restriction pmask(includes, excludes, depth, app.lua); BOOST_CHECK( pmask.empty()); @@ -1096,7 +1096,7 @@ UNIT_TEST(restrictions, include_depth_1) // check restricted nodes - node_restriction nmask(includes, excludes, depth, roster, app); + node_restriction nmask(includes, excludes, depth, roster, app.lua); BOOST_CHECK(!nmask.empty()); @@ -1126,7 +1126,7 @@ UNIT_TEST(restrictions, include_depth_1) // check restricted paths - path_restriction pmask(includes, excludes, depth, app); + path_restriction pmask(includes, excludes, depth, app.lua); BOOST_CHECK(!pmask.empty()); ============================================================ --- restrictions.hh 5b16498cc58ffea38a6e98f4b32efda724dc7ef3 +++ restrictions.hh aef753efe26a40a6819de36756c9f99527b80faa @@ -81,14 +81,14 @@ class node_restriction : public restrict std::vector const & excludes, long depth, roster_t const & roster, - app_state & a); + lua_hooks & lua); node_restriction(std::vector const & includes, std::vector const & excludes, long depth, roster_t const & roster1, roster_t const & roster2, - app_state & a); + lua_hooks & lua); bool includes(roster_t const & roster, node_id nid) const; @@ -115,7 +115,7 @@ class path_restriction : public restrict path_restriction(std::vector const & includes, std::vector const & excludes, long depth, - app_state & a); + lua_hooks & lua); bool includes(split_path const & sp) const; ============================================================ --- work.cc 3d52c005916784461d059e64cf2779d49befdb1e +++ work.cc 90d466377088546516e4af86d021b5748b5ff84a @@ -798,7 +798,7 @@ editable_working_tree::clear_attr(split_ attr_key const & name) { file_path pth_unsplit(pth); - app.lua.hook_apply_attribute(name(), pth_unsplit, string(""), true); + lua.hook_apply_attribute(name(), pth_unsplit, string(""), true); } void @@ -807,7 +807,7 @@ editable_working_tree::set_attr(split_pa attr_value const & val) { file_path pth_unsplit(pth); - app.lua.hook_apply_attribute(name(), pth_unsplit, val(), false); + lua.hook_apply_attribute(name(), pth_unsplit, val(), false); } void @@ -1350,49 +1350,91 @@ workspace::perform_content_update(cset c update.apply_to(ewt); } -void -update_any_attrs(std::vector const & include_paths, app_state & app) -{ +void +workspace::perform_attr_scan(roster_t & new_roster, node_restriction const & mask) +{ + std::vector init_functions; + bool get_update_func_ok = lua.hook_list_init_functions(init_functions); + E(get_update_func_ok, + F("Failed to find attribute init functions")); + temp_node_id_source nis; - roster_t new_roster; - get_current_roster_shape(new_roster, nis, app); - node_restriction mask(include_paths, app.get_exclude_paths(), new_roster, app); - node_map const & nodes = new_roster.all_nodes(); + editable_roster_base er(new_roster, nis); - P(F("updating attributes on filesystem")); - - for (node_map::const_iterator i = nodes.begin(); - i != nodes.end(); ++i) + P(F("scanning filesystem for attributes")); + + node_map const & nodes = new_roster.all_nodes(); + for (node_map::const_iterator i = nodes.begin(); i != nodes.end(); ++i) { node_id nid = i->first; - node_t n = i->second; - split_path sp; + node_t node = i->second; - // Only update restriction-included files. - // If this is not used all files in the roster have the corresponding - // files in the workspace's attributes on the filesystem set to the - // attributes recorded in the roster. This is rather like reverting - // files in the workspace to what was last recorded in the roster, for - // every workspace command. - + // Only analyze restriction-included files. if (!mask.includes(new_roster, nid)) continue; - - new_roster.get_name(i->first, sp); + + split_path sp; + new_roster.get_name(nid, sp); file_path name(sp); - L(FL(" updating %s") % name); + L(FL("scanning attributes of %s") % name); + + std::pair curval; + std::pair getval; + bool luaok; + + if (path_exists(name)) + { + for (std::vector::const_iterator i = init_functions.begin(); + i != init_functions.end(); ++i) + { + curval = node->attrs[attr_key(*i)]; + if (curval.first) + { + L(FL("attribute '%s' is currently '%s'") % *i % curval.second); + } + else + { + L(FL("attribute '%s' is currently unset") % *i); + } + + luaok = lua.hook_scan_attribute(*i, name, getval); + E(luaok, F("error doing lua hook_scan_attribute for attribute %s") % *i); + if (getval.first) + { + if (curval.first && (curval.second() == getval.second)) + { + L(FL("skipping; filesystem matches recorded workspace.")); + continue; + } + else + { + L(FL("setting attribute to '%s'") % getval.second); + er.set_attr(sp, attr_key(*i), attr_value(getval.second)); + } } + else + { + L(FL("clearing attribute")); + er.clear_attr(sp, attr_key(*i)); + } + } + } + } +} - for (full_attr_map_t::const_iterator j = n->attrs.begin(); - j != n->attrs.end(); ++j) +void +workspace::apply_attrs(file_path const & pth, full_attr_map_t const & attrs) +{ + L(FL("applying attrs to %s") % pth); + for (full_attr_map_t::const_iterator j = attrs.begin(); + j != attrs.end(); ++j) + { + if (j->second.first) { - if (j->second.first) - { - app.lua.hook_apply_attribute (j->first(), - file_path(sp), - j->second.second(), - false); - } + lua.hook_apply_attribute (j->first(), + pth, + j->second.second(), + false); } } } ============================================================ --- work.hh c23d39b71193d9ddc5f715cf60b000e0dae0982a +++ work.hh 1948f6514935330b875598aa407efa26da571a1c @@ -93,8 +93,10 @@ struct workspace void perform_content_update(cset const & cs, content_merge_adaptor const & ca); - void update_any_attrs(std::vector const & include_paths, app_state & app); + void perform_attr_scan(roster_t & new_roster, node_restriction const & mask); + void apply_attrs(file_path const & pth, full_attr_map_t const & attrs); + // transitional: the write half of this is exposed, the read half isn't. // write out a new (partial) revision describing the current workspace; // the important pieces of this are the base revision id and the "shape"