# # old_revision [8c6ce7cb2ccd21290b435e042c2be4554ec6a048] # # add_file "examples/mtn-dosh" # content [4b27481a2dc207c901c0acbbf761d5d26d2fdc69] # # add_file "examples/unix_attributes.lua" # content [b4b0797b13a7923e7e1f71285551d17adc142336] # # patch "cmd_ws_commit.cc" # from [f24529cd245deb0002fdaa15f0f8b6d1e9d0e338] # to [562517ec4d18604fd4a5e211aeb78895d131c196] # # patch "lua_hooks.cc" # from [0650e44236b780eca2e9bd964b303d08291a5a68] # to [e3b7f945cbb1eafe5a1ae172926a600622d54db5] # # patch "lua_hooks.hh" # from [f7acc9339fb54e2093797b8c7192b6846d7b6fe5] # to [a7552e1c059eb98b54299255eb9d810108ad2afc] # # patch "roster.cc" # from [3773a5236971e2912de250e1c76757b1c5e0cdbc] # to [1ae5df20aa4213f621febefdab136d47db09d603] # # patch "roster.hh" # from [49ce9f36b5f507091d99d4889e04fd231275029c] # to [dd199b8cacb11703aa559df819dbcac1fa3c7f0e] # # patch "std_hooks.lua" # from [b49b50671ccf60eca56f16f7333eaf6c4b79465c] # to [0526c17008c70e3432791e9125ba781aaa839057] # # patch "work.cc" # from [02f33f9a20217a5ceaf213e892e66a41a2780441] # to [99ee8ae1f14fd0212d72cf853b4e2efcb02c938e] # # patch "work.hh" # from [7634d8afc541e4a1ddc3e770b033e058338601de] # to [0229f6f68b577fcb30bcb0f1084d1bd500c9052f] # # set "examples/mtn-dosh" # attr "group" # value "1001" # # set "examples/mtn-dosh" # attr "perms" # value "664" # # set "examples/mtn-dosh" # attr "user" # value "1001" # # set "examples/unix_attributes.lua" # attr "group" # value "1001" # # set "examples/unix_attributes.lua" # attr "perms" # value "664" # # set "examples/unix_attributes.lua" # attr "user" # value "1001" # ============================================================ --- examples/mtn-dosh 4b27481a2dc207c901c0acbbf761d5d26d2fdc69 +++ examples/mtn-dosh 4b27481a2dc207c901c0acbbf761d5d26d2fdc69 @@ -0,0 +1,4 @@ +#!/bin/sh +TMPFILE=$1 +shift +$1 $2 $3 $4 $5 $6 $7 $8 $9 > $TMPFILE ============================================================ --- examples/unix_attributes.lua b4b0797b13a7923e7e1f71285551d17adc142336 +++ examples/unix_attributes.lua b4b0797b13a7923e7e1f71285551d17adc142336 @@ -0,0 +1,161 @@ +function getdbgval() + return 0 +end + +if (attr_update_functions == nil) then + attr_update_functions = {} +end + +function argstr(...) + local argstr = "" + for i,v in ipairs(arg) do + if (argstr ~= "") then + argstr = argstr .. ' ' + end + argstr = argstr .. tostring(v) + end + if (getdbgval() > 3) then print("Argument string is: " .. argstr) end + return argstr +end + +function execute_redirout(tmpfilename, command, ...) + return execute('mtn-dosh', tmpfilename, command, unpack(arg)) +end + +function execute_out(command, ...) + local out = nil + local exec_retval + local outhnd + local errstr + local out + local tmpfile, tmpfilename = temp_file() + tmpfile:close() + exec_retval = execute_redirout(tmpfilename, command, unpack(arg)) + if (exec_retval == 0) then + outhnd, errstr = io.open(tmpfilename) + if (outhnd) then + out, errstr = outhnd:read("*line") + if (out == nil) then + if (getdbgval() > 1) then + if (errstr) then + print("Error reading " .. tmpfilename .. ": " .. errstr) + else + print("Error reading " .. tmpfilename .. ": " .. 'nil') + end + end + end + outhnd:close() + os.remove(tmpfilename) + else + if (getdbgval() > 1) then + if (errstr) then + print("Error opening " .. tmpfilename .. ": " .. errstr) + else + print("Error opening " .. tmpfilename .. ": " .. 'nil') + end + end + os.remove(tmpfilename) + end + else + print('Error executing ' .. 'mtn-dosh ' .. command .. argstr(unpack(arg)) .. ' > ' .. tmpfilename .. ': ' .. tostring(exec_retval)) + os.remove(tmpfilename) + end + + if (getdbgval() > 1) then + if (out ~= nil) then + print('execute_redirout; got ' .. out) + else + print('no output') + end + end + return out +end + +function is_symlink(filename) + local filetype = execute_out('stat', '-c', '%F', filename) + local link_target + local retlink = nil + if (filetype == "symbolic link") then + link_target = execute_out('readlink', filename) + if (link_target) then + if (link_target ~= "" ) then + retlink = link_target + end + end + end + if ((getdbgval() > 2) and retlink) then + print("linktarget = " .. retlink) + end + return retlink +end + +attr_init_functions["perms"] = function(filename) + return execute_out('stat', '-c', '%a', filename) +end + +attr_init_functions["user"] = function(filename) + return execute_out('stat', '-c', '%u', filename) +end + +attr_init_functions["group"] = function(filename) + return execute_out('stat', '-c', '%g', filename) +end + +attr_init_functions["symlink"] = function(filename) + return is_symlink(filename) +end + +attr_update_functions["perms"] = function(filename, value) + return execute_out('stat', '-c', '%a', filename) +end + +attr_update_functions["user"] = function(filename, value) + return execute_out('stat', '-c', '%u', filename) +end + +attr_update_functions["group"] = function(filename, value) + return execute_out('stat', '-c', '%g', filename) +end + +attr_update_functions["symlink"] = function(filename, value) + return is_symlink(filename) +end + +attr_functions["perms"] = function(filename, value) + if (value ~= nil) then + execute("/bin/chmod", value, filename) + end +end + +attr_functions["user"] = function(filename, value) + if (value ~= nil) then + execute("/bin/chown", value, filename) + end +end + +attr_functions["group"] = function(filename, value) + if (value ~= nil) then + execute("/bin/chgrp", value, filename) + end +end + +attr_functions["symlink"] = function(filename, value) + if (value ~= nil) then + execute("/bin/mv", filename, filename .. ".old") + if (execute("/bin/ln", "-s", value, filename) == 0) then + os.remove(filename .. ".old") + else + execute("/bin/cp", '-a', filename .. ".old", filename) + end + else + execute("/bin/mv", filename, filename .. ".old") + local realname = execute("/bin/readlink", filename) + if (realname ~= nil) then + if (execute("/bin/cp", '-a', realname, filename) == 0) then + os.remove(filename .. ".old") + else + execute("/bin/cp", '-a', filename .. ".old", filename) + end + end + end +end ============================================================ --- cmd_ws_commit.cc f24529cd245deb0002fdaa15f0f8b6d1e9d0e338 +++ cmd_ws_commit.cc 562517ec4d18604fd4a5e211aeb78895d131c196 @@ -162,6 +162,8 @@ CMD(revert, N_("workspace"), N_("[PATH]. // Race. put_work_cset(excluded); + // FIXME_ATTRS: All attributes are reverted, even if we specify + // path to revert update_any_attrs(app); maybe_update_inodeprints(app); } @@ -519,38 +521,61 @@ ALIAS(co, checkout) ALIAS(co, checkout) -CMD(attr, N_("workspace"), N_("set PATH ATTR VALUE\nget PATH [ATTR]\ndrop PATH [ATTR]"), - N_("set, get or drop file attributes"), +CMD(attr, N_("workspace"), N_("set PATH ATTR VALUE\nget PATH [ATTR]\ndrop PATH [ATTR]\nmass_set [PATH...]"), + N_("set, get or drop file attributes\nor set recorded attributes to match actual attributes on all\nfiles in PATH(s)."), OPT_NONE) { - if (args.size() < 2 || args.size() > 4) + // FIXME_ATTRS: General question; should attributes only be modified on + // filesystem when --execute is specified? + + if (args.size() < 1) throw usage(name); - - 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); - - - file_path path = file_path_external(idx(args,1)); - split_path sp; - path.split(sp); - - N(new_roster.has_node(sp), F("Unknown path '%s'") % path); - node_t node = new_roster.get_node(sp); - + string subcmd = idx(args, 0)(); - if (subcmd == "set" || subcmd == "drop") + + if (subcmd == "mass_set") + { + 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_update(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); + + file_path path = file_path_external(idx(args,1)); + split_path sp; + path.split(sp); + + N(new_roster.has_node(sp), F("Unknown path '%s'") % path); + node_t node = new_roster.get_node(sp); + if (subcmd == "set") { if (args.size() != 4) - throw usage(name); - + throw usage(name); + attr_key a_key = idx(args, 2)(); attr_value a_value = idx(args, 3)(); - + node->attrs[a_key] = make_pair(true, a_value); } else @@ -581,6 +606,19 @@ CMD(attr, N_("workspace"), N_("set PATH } else if (subcmd == "get") { + 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); + + file_path path = file_path_external(idx(args,1)); + split_path sp; + path.split(sp); + + N(new_roster.has_node(sp), F("Unknown path '%s'") % path); + node_t node = new_roster.get_node(sp); + if (args.size() == 2) { bool has_any_live_attrs = false; @@ -609,14 +647,12 @@ CMD(attr, N_("workspace"), N_("set PATH % a_key % path) << "\n"; } else - throw usage(name); + throw usage(name); } else throw usage(name); } - - CMD(commit, N_("workspace"), N_("[PATH]..."), N_("commit workspace to database"), OPT_BRANCH_NAME % OPT_MESSAGE % OPT_MSGFILE % OPT_DATE % ============================================================ --- lua_hooks.cc 0650e44236b780eca2e9bd964b303d08291a5a68 +++ lua_hooks.cc e3b7f945cbb1eafe5a1ae172926a600622d54db5 @@ -684,6 +684,59 @@ bool } bool +lua_hooks::hook_get_avail_attr_update_functions(std::vector & avail_funcs) +{ + Lua ll(st); + string attr; + + ll + .push_str("attr_update_functions") + .get_tab(); + + ll.begin(); + while (ll.next()) + { + ll.pop().extract_str(attr); + L(FL("avail attr_update '%s'") % attr); + avail_funcs.push_back(attr); + } + return ll.ok(); +} + +bool +lua_hooks::hook_update_attribute(std::string const & inattr, + file_path const & filename, + std::string const & invalue, + std::string & outvalue) +{ + Lua ll(st); + + ll + .push_str("attr_update_functions") + .get_tab() + .push_str(inattr) + .get_fn(-2) + .push_str(filename.as_external()) + .push_str(invalue); + + L(FL("calling an attr_update_function for attribute '%s' with value '%s'") % inattr % invalue); + ll.call(2,1); + + string luakey, luavalue; + + if (lua_isnil(st, -1)) + { + outvalue = ""; + } + else + { + ll.extract_str(outvalue); + } + + return ll.pop().ok(); +} + +bool lua_hooks::hook_init_attributes(file_path const & filename, map & attrs) { ============================================================ --- lua_hooks.hh f7acc9339fb54e2093797b8c7192b6846d7b6fe5 +++ lua_hooks.hh a7552e1c059eb98b54299255eb9d810108ad2afc @@ -106,6 +106,14 @@ public: // attribute hooks bool hook_init_attributes(file_path const & filename, std::map & attrs); + + bool hook_get_avail_attr_update_functions(std::vector & avail_funcs); + + bool hook_update_attribute(std::string const & inattr, + file_path const & filename, + std::string const & invalue, + std::string & outvalue); + bool hook_apply_attribute(std::string const & attr, file_path const & filename, std::string const & value); ============================================================ --- roster.cc 3773a5236971e2912de250e1c76757b1c5e0cdbc +++ roster.cc 1ae5df20aa4213f621febefdab136d47db09d603 @@ -2295,12 +2295,13 @@ update_current_roster_from_filesystem(ro void update_current_roster_from_filesystem(roster_t & ros, node_restriction const & mask, - app_state & app) + app_state & app, + bool use_inodeprints_mode) { temp_node_id_source nis; inodeprint_map ipm; - if (in_inodeprints_mode()) + if (use_inodeprints_mode && in_inodeprints_mode()) { data dat; read_inodeprints(dat); ============================================================ --- roster.hh 49ce9f36b5f507091d99d4889e04fd231275029c +++ roster.hh dd199b8cacb11703aa559df819dbcac1fa3c7f0e @@ -363,8 +363,10 @@ update_current_roster_from_filesystem(ro void update_current_roster_from_filesystem(roster_t & ros, node_restriction const & mask, - app_state & app); + app_state & app, + bool use_inodeprints_mode = true); + void update_current_roster_from_filesystem(roster_t & ros, app_state & app); ============================================================ --- std_hooks.lua b49b50671ccf60eca56f16f7333eaf6c4b79465c +++ std_hooks.lua 0526c17008c70e3432791e9125ba781aaa839057 @@ -872,3 +872,8 @@ end function get_mtn_command(host) return "mtn" end + +if (attr_update_functions == nil) then + attr_update_functions = {} +end + ============================================================ --- work.cc 02f33f9a20217a5ceaf213e892e66a41a2780441 +++ work.cc 99ee8ae1f14fd0212d72cf853b4e2efcb02c938e @@ -208,6 +208,92 @@ void } void +perform_attr_update(std::vector const & paths, app_state & app) +{ + + std::vector avail_funcs; + bool get_update_func_ok = app.lua.hook_get_avail_attr_update_functions(avail_funcs); + E(get_update_func_ok, F("Failed to find attribute update functions")); + if (!get_update_func_ok) + { + return; + } + + 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); + + std::vector exclude_file_paths; + // Determine currently tracked files + for (std::vector::const_iterator i = app.exclude_patterns.begin(); + i != app.exclude_patterns.end(); ++i) + { + exclude_file_paths.push_back(file_path_external(*i)); + } + node_restriction mask(paths, exclude_file_paths, new_roster, app); + editable_roster_base er(new_roster, nis); + + 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 node = i->second; + + // Only analyze restriction-included files. + if (!mask.includes(new_roster, nid)) + continue; + + split_path sp; + new_roster.get_name(nid, sp); + file_path name(sp); + + P(F("Updating attributes for %s") % name); + + string curval; + string newval; + bool luaok; + + if (path_exists(name)) + { + for (std::vector::const_iterator i = avail_funcs.begin(); + i != avail_funcs.end(); ++i) + { + curval = node->attrs[attr_key(*i)].second(); + L(FL("Attribute '%s' currently is '%s'") % *i % curval); + + luaok = app.lua.hook_update_attribute(*i, name, curval, newval); + E(luaok, F("Error doing lua hook_update_attribute for attribute %s, value %s") % *i % curval); + if (curval == newval) + { + L(FL("Skipping; filesystem matches recorded workspace.")); + continue; + } + else + { + if (newval == string("")) + { + L(FL("Clearing attribute")); + er.clear_attr(sp, attr_key(*i)); + } + else + { + L(FL("Setting attribute to '%s'") % newval); + er.set_attr(sp, attr_key(*i), attr_value(newval)); + } + } + } + } + } + cset new_work; + make_cset(old_roster, new_roster, new_work); + put_work_cset(new_work); + // update_any_attrs(app); + + } + +void perform_additions(path_set const & paths, app_state & app, bool recursive) { if (paths.empty()) @@ -247,7 +333,7 @@ perform_additions(path_set const & paths cset new_work; make_cset(base_roster, new_roster, new_work); put_work_cset(new_work); - update_any_attrs(app); + // update_any_attrs(app); } void @@ -315,7 +401,7 @@ perform_deletions(path_set const & paths cset new_work; make_cset(base_roster, new_roster, new_work); put_work_cset(new_work); - update_any_attrs(app); + // update_any_attrs(app); } static void @@ -439,7 +525,7 @@ perform_rename(set const & sr } } } - update_any_attrs(app); + // update_any_attrs(app); } void @@ -502,7 +588,7 @@ perform_pivot_root(file_path const & new editable_working_tree e(app, efcs); cs.apply_to(e); } - update_any_attrs(app); + // update_any_attrs(app); } @@ -1041,7 +1127,8 @@ editable_working_tree::clear_attr(split_ editable_working_tree::clear_attr(split_path const & pth, attr_key const & name) { - // FIXME_ROSTERS: call a lua hook + file_path pth_unsplit(pth); + app.lua.hook_apply_attribute(name(), pth_unsplit, ""); } void @@ -1049,7 +1136,8 @@ editable_working_tree::set_attr(split_pa attr_key const & name, attr_value const & val) { - // FIXME_ROSTERS: call a lua hook + file_path pth_unsplit(pth); + app.lua.hook_apply_attribute(name(), pth_unsplit, val()); } void ============================================================ --- work.hh 7634d8afc541e4a1ddc3e770b033e058338601de +++ work.hh 0229f6f68b577fcb30bcb0f1084d1bd500c9052f @@ -90,6 +90,10 @@ perform_pivot_root(file_path const & new perform_pivot_root(file_path const & new_root, file_path const & put_old, app_state & app); +void +perform_attr_update(std::vector const & paths, app_state & app); + + // the "work" file contains the current cset representing uncommitted // add/drop/rename operations (not deltas)