# # # rename "tests/automate_attributes" # to "tests/automate_get_attributes" # # add_dir "tests/automate_set_drop_attribute" # # add_file "tests/automate_set_drop_attribute/__driver__.lua" # content [2cc985f6c0fe70267b1379c2f9ff87bea57f7353] # # patch "automate.cc" # from [17904f5962e731be01985d059526a3710d02e2b2] # to [2846a2b317c5cf312a104d8dd9a1a75ff98174f5] # # patch "cmd_automate.cc" # from [8b23b1c3fda2841bf1e5671cebdef0d3f67babc4] # to [550e5555b1c17a739f4d007fe700b906c5b56381] # # patch "cmd_ws_commit.cc" # from [c57fa42d0785729d02092ffce08aca4fcc8c6ed6] # to [26f58cb8a976a0db3ae0609f04bf3e962ab78e56] # # patch "monotone.texi" # from [6c4cceb28a7abb8b32d636ff10cdb5d40055fc0c] # to [0209ff76041fd586bcefef9534057ab7134976c8] # # patch "tests/automate_get_attributes/__driver__.lua" # from [9bdd4a14717871acb2b9c4910ffd59fa5de2d0a2] # to [de1a2e327e469ddc00a9950c1fd9bfeca8fcd945] # # patch "tests/two_parent_workspace_commands_that_fail/__driver__.lua" # from [ddb96c7133077b7166f21a65ad3d7e6bd97da49a] # to [87cfff5ae2edb3eb8deca9a605d607667fa297d4] # ============================================================ --- tests/automate_set_drop_attribute/__driver__.lua 2cc985f6c0fe70267b1379c2f9ff87bea57f7353 +++ tests/automate_set_drop_attribute/__driver__.lua 2cc985f6c0fe70267b1379c2f9ff87bea57f7353 @@ -0,0 +1,52 @@ + +mtn_setup() + +-- path / arg checks +check(mtn("automate", "set_attribute"), 1, false, true) +check(qgrep("wrong argument count", "stderr")) +check(mtn("automate", "set_attribute", "too", "many", "many", "args"), 1, false, true) +check(qgrep("wrong argument count", "stderr")) + +check(mtn("automate", "drop_attribute"), 1, false, true) +check(qgrep("wrong argument count", "stderr")) +check(mtn("automate", "drop_attribute", "too", "many", "args"), 1, false, true) +check(qgrep("wrong argument count", "stderr")) + +check(mtn("automate", "set_attribute", "unknown_path", "foo", "bar"), 1, false, true) +check(qgrep("Unknown path", "stderr")) +check(mtn("automate", "drop_attribute", "unknown_path"), 1, false, true) +check(qgrep("Unknown path", "stderr")) + +-- check if we can add an attribute +addfile("testfile", "foo") +check(mtn("automate", "set_attribute", "testfile", "foo", "bar"), 0, false, false) + +-- check if it has been really added +check(mtn("automate", "get_attributes", "testfile"), 0, true, false) +parsed = parse_basic_io(readfile("stdout")) + +check(table.getn(parsed) == 3) +for _,l in pairs(parsed) do + if l.name == "attr" then + key = l.values[1] + val = l.values[2] + check(key == "foo" and val == "bar") + end + if l.name == "state" then + state = l.values[1] + check(state == "added") + end +end + +-- check if we can drop it +check(mtn("automate", "drop_attribute", "testfile", "foo"), 0, true, true) + +-- check if it has been really dropped +check(mtn("automate", "get_attributes", "testfile"), 0, true, false) +parsed = parse_basic_io(readfile("stdout")) +check(table.getn(parsed) == 1) + +-- check if it escalates properly if there is no such attr to drop +check(mtn("automate", "drop_attribute", "testfile", "foo"), 1, false, true) +check(qgrep("does not have attribute", "stderr")) + ============================================================ --- automate.cc 17904f5962e731be01985d059526a3710d02e2b2 +++ automate.cc 2846a2b317c5cf312a104d8dd9a1a75ff98174f5 @@ -194,128 +194,6 @@ AUTOMATE(erase_ancestors, N_("[REV1 [REV output << (*i).inner()() << '\n'; } -// Name: attributes -// Arguments: -// 1: file name -// Added in: 1.0 -// Purpose: Prints all attributes for a file -// Output format: basic_io formatted output, each attribute has its own stanza: -// -// 'format_version' -// used in case this format ever needs to change. -// format: ('format_version', the string "1" currently) -// occurs: exactly once -// 'attr' -// represents an attribute entry -// format: ('attr', name, value), ('state', [unchanged|changed|added|dropped]) -// occurs: zero or more times -// -// Error conditions: If the file name has no attributes, prints only the -// format version, if the file is unknown, escalates -AUTOMATE(attributes, N_("FILE"), options::opts::none) -{ - N(args.size() > 0, - F("wrong argument count")); - - // this command requires a workspace to be run on - app.require_workspace(); - - // retrieve the path - split_path path; - file_path_external(idx(args,0)).split(path); - - roster_t base, current; - parent_map parents; - temp_node_id_source nis; - - // get the base and the current roster of this workspace - app.work.get_current_roster_shape(current, nis); - app.work.get_parent_rosters(parents); - N(parents.size() == 1, - F("this command can only be used in a single-parent workspace")); - base = parent_roster(parents.begin()); - - // escalate if the given path is unknown to the current roster - N(current.has_node(path), - F("file %s is unknown to the current workspace") % path); - - // create the printer - basic_io::printer pr; - - // print the format version - basic_io::stanza st; - st.push_str_pair(basic_io::syms::format_version, "1"); - pr.print_stanza(st); - - // the current node holds all current attributes (unchanged and new ones) - node_t n = current.get_node(path); - for (full_attr_map_t::const_iterator i = n->attrs.begin(); - i != n->attrs.end(); ++i) - { - std::string value(i->second.second()); - std::string state("unchanged"); - - // if if the first value of the value pair is false this marks a - // dropped attribute - if (!i->second.first) - { - // if the attribute is dropped, we should have a base roster - // with that node. we need to check that for the attribute as well - // because if it is dropped there as well it was already deleted - // in any previous revision - I(base.has_node(path)); - - node_t prev_node = base.get_node(path); - - // find the attribute in there - full_attr_map_t::const_iterator j = prev_node->attrs.find(i->first); - I(j != prev_node->attrs.end()); - - // was this dropped before? then ignore it - if (!j->second.first) { continue; } - - state = "dropped"; - // output the previous (dropped) value later - value = j->second.second(); - } - // this marks either a new or an existing attribute - else - { - if (base.has_node(path)) - { - node_t prev_node = base.get_node(path); - full_attr_map_t::const_iterator j = - prev_node->attrs.find(i->first); - // attribute not found? this is new - if (j == prev_node->attrs.end()) - { - state = "added"; - } - // check if this attribute has been changed - // (dropped and set again) - else if (i->second.second() != j->second.second()) - { - state = "changed"; - } - - } - // its added since the whole node has been just added - else - { - state = "added"; - } - } - - basic_io::stanza st; - st.push_str_triple(basic_io::syms::attr, i->first(), value); - st.push_str_pair(symbol("state"), state); - pr.print_stanza(st); - } - - // print the output - output.write(pr.buf.data(), pr.buf.size()); -} - // Name: toposort // Arguments: // 0 or more: revision ids ============================================================ --- cmd_automate.cc 8b23b1c3fda2841bf1e5671cebdef0d3f67babc4 +++ cmd_automate.cc 550e5555b1c17a739f4d007fe700b906c5b56381 @@ -61,7 +61,7 @@ automate_command(utf8 cmd, vector find_automation(cmd, root_cmd_name).run(args, root_cmd_name, app, output); } -static string const interface_version = "4.3"; +static string const interface_version = "5.0"; // Name: interface_version // Arguments: none ============================================================ --- cmd_ws_commit.cc c57fa42d0785729d02092ffce08aca4fcc8c6ed6 +++ cmd_ws_commit.cc 26f58cb8a976a0db3ae0609f04bf3e962ab78e56 @@ -20,6 +20,7 @@ #include "charset.hh" #include "ui.hh" #include "app_state.hh" +#include "basic_io.hh" using std::cout; using std::make_pair; @@ -691,8 +692,224 @@ CMD(attr, N_("workspace"), N_("set PATH throw usage(name); } +// Name: get_attributes +// Arguments: +// 1: file / directory name +// Added in: 1.0 +// Renamed from attributes to get_attributes in: 5.0 +// Purpose: Prints all attributes for the specified path +// Output format: basic_io formatted output, each attribute has its own stanza: +// +// 'format_version' +// used in case this format ever needs to change. +// format: ('format_version', the string "1" currently) +// occurs: exactly once +// 'attr' +// represents an attribute entry +// format: ('attr', name, value), ('state', [unchanged|changed|added|dropped]) +// occurs: zero or more times +// +// Error conditions: If the path has no attributes, prints only the +// format version, if the file is unknown, escalates +AUTOMATE(get_attributes, N_("PATH"), options::opts::none) +{ + N(args.size() > 0, + F("wrong argument count")); + // this command requires a workspace to be run on + app.require_workspace(); + // retrieve the path + split_path path; + file_path_external(idx(args,0)).split(path); + + roster_t base, current; + parent_map parents; + temp_node_id_source nis; + + // get the base and the current roster of this workspace + app.work.get_current_roster_shape(current, nis); + app.work.get_parent_rosters(parents); + N(parents.size() == 1, + F("this command can only be used in a single-parent workspace")); + base = parent_roster(parents.begin()); + + N(current.has_node(path), F("Unknown path '%s'") % path); + + // create the printer + basic_io::printer pr; + + // print the format version + basic_io::stanza st; + st.push_str_pair(basic_io::syms::format_version, "1"); + pr.print_stanza(st); + + // the current node holds all current attributes (unchanged and new ones) + node_t n = current.get_node(path); + for (full_attr_map_t::const_iterator i = n->attrs.begin(); + i != n->attrs.end(); ++i) + { + std::string value(i->second.second()); + std::string state("unchanged"); + + // if if the first value of the value pair is false this marks a + // dropped attribute + if (!i->second.first) + { + // if the attribute is dropped, we should have a base roster + // with that node. we need to check that for the attribute as well + // because if it is dropped there as well it was already deleted + // in any previous revision + I(base.has_node(path)); + + node_t prev_node = base.get_node(path); + + // find the attribute in there + full_attr_map_t::const_iterator j = prev_node->attrs.find(i->first); + I(j != prev_node->attrs.end()); + + // was this dropped before? then ignore it + if (!j->second.first) { continue; } + + state = "dropped"; + // output the previous (dropped) value later + value = j->second.second(); + } + // this marks either a new or an existing attribute + else + { + if (base.has_node(path)) + { + node_t prev_node = base.get_node(path); + full_attr_map_t::const_iterator j = + prev_node->attrs.find(i->first); + // attribute not found? this is new + if (j == prev_node->attrs.end()) + { + state = "added"; + } + // check if this attribute has been changed + // (dropped and set again) + else if (i->second.second() != j->second.second()) + { + state = "changed"; + } + + } + // its added since the whole node has been just added + else + { + state = "added"; + } + } + + basic_io::stanza st; + st.push_str_triple(basic_io::syms::attr, i->first(), value); + st.push_str_pair(symbol("state"), state); + pr.print_stanza(st); + } + + // print the output + output.write(pr.buf.data(), pr.buf.size()); +} + +// Name: set_attribute +// Arguments: +// 1: file / directory name +// 2: attribute key +// 3: attribute value +// Added in: 5.0 +// Purpose: Edits the workspace revision and sets an attribute on a certain path +// +// Error conditions: If PATH is unknown in the new roster, prints an error and +// exits with status 1. +AUTOMATE(set_attribute, N_("PATH KEY VALUE"), options::opts::none) +{ + N(args.size() == 3, + F("wrong argument count")); + + roster_t new_roster; + temp_node_id_source nis; + + app.require_workspace(); + app.work.get_current_roster_shape(new_roster, nis); + + file_path path = file_path_external(idx(args,0)); + 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); + + attr_key a_key = attr_key(idx(args,1)()); + attr_value a_value = attr_value(idx(args,2)()); + + node->attrs[a_key] = make_pair(true, a_value); + + parent_map parents; + app.work.get_parent_rosters(parents); + + revision_t new_work; + make_revision_for_workspace(parents, new_roster, new_work); + app.work.put_work_rev(new_work); + app.work.update_any_attrs(); +} + +// Name: drop_attribute +// Arguments: +// 1: file / directory name +// 2: attribute key (optional) +// Added in: 5.0 +// Purpose: Edits the workspace revision and drops an attribute or all +// attributes of the specified path +// +// Error conditions: If PATH is unknown in the new roster or the specified +// attribute key is unknown, prints an error and exits with +// status 1. +AUTOMATE(drop_attribute, N_("PATH [KEY]"), options::opts::none) +{ + N(args.size() ==1 || args.size() == 2, + F("wrong argument count")); + + roster_t new_roster; + temp_node_id_source nis; + + app.require_workspace(); + app.work.get_current_roster_shape(new_roster, nis); + + file_path path = file_path_external(idx(args,0)); + 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); + + // Clear all attrs (or a specific attr). + if (args.size() == 1) + { + for (full_attr_map_t::iterator i = node->attrs.begin(); + i != node->attrs.end(); ++i) + i->second = make_pair(false, ""); + } + else + { + attr_key a_key = attr_key(idx(args,1)()); + N(node->attrs.find(a_key) != node->attrs.end(), + F("Path '%s' does not have attribute '%s'") + % path % a_key); + node->attrs[a_key] = make_pair(false, ""); + } + + parent_map parents; + app.work.get_parent_rosters(parents); + + revision_t new_work; + make_revision_for_workspace(parents, new_roster, new_work); + app.work.put_work_rev(new_work); + app.work.update_any_attrs(); +} + + CMD(commit, N_("workspace"), N_("[PATH]..."), N_("commit workspace to database"), options::opts::branch | options::opts::message | options::opts::msgfile ============================================================ --- monotone.texi 6c4cceb28a7abb8b32d636ff10cdb5d40055fc0c +++ monotone.texi 0209ff76041fd586bcefef9534057ab7134976c8 @@ -6978,17 +6978,21 @@ @section Automation @end table address@hidden mtn automate attributes @var{file} address@hidden mtn automate get_attributes @var{path} @table @strong @item Arguments: -The argument @var{file} determines which file's attributes should be printed. +The argument @var{path} determines which path's attributes should be printed. @item Added in: 3.0 address@hidden Renamed from @command{attributes} to @command{get_attributes} in: + +5.0 + @item Purpose: Prints all attributes of the given file and the attribute states. @@ -7056,11 +7060,70 @@ @section Automation @item Error conditions: -If the file specified is unknown to the current workspace prints an error +If the path specified is unknown in the new workspace revision, prints an error message to stderr and exits with status 1. @end table address@hidden mtn automate set_attribute @var{path} @var{key} @var{value} + address@hidden @strong address@hidden Arguments: + +A path, an attribute key and an attribute value. + address@hidden Added in: + +5.0 + address@hidden Purpose: + +Edits the current workspace revision and inserts the given attribute key and +value for the specified path. Note that this change is not committed and +therefor behaves exactly like @command{mtn attr set @var{key} @var{value}}. + address@hidden Output format: + +This command does not print out anything if successful. + address@hidden Error conditions: + +If the path specified is unknown in the new workspace revision, prints an error +message to stderr and exits with status 1. + address@hidden table + address@hidden mtn automate drop_attribute @var{path} address@hidden + address@hidden @strong address@hidden Arguments: + +A path and an attribute key (optional). + address@hidden Added in: + +5.0 + address@hidden Purpose: + +Removes an attribute from the current workspace revision for the specified path. +If no attribute key is given, all attributes of this path are removed. Note that +this change is not committed and therefor behaves exactly like address@hidden attr drop @var{path} address@hidden + address@hidden Output format: + +This command does not print out anything if successful. + address@hidden Error conditions: + +If the path specified is unknown in the new workspace revision or the attribute +key is not found for this path, prints an error message to stderr and exits with +status 1. + address@hidden table + + @item mtn automate content_diff address@hidden address@hidden address@hidden ...] @table @strong ============================================================ --- tests/automate_get_attributes/__driver__.lua 9bdd4a14717871acb2b9c4910ffd59fa5de2d0a2 +++ tests/automate_get_attributes/__driver__.lua de1a2e327e469ddc00a9950c1fd9bfeca8fcd945 @@ -6,7 +6,7 @@ commit("mainbranch") commit("mainbranch") -- at first check for the version on the file w/o attributes -check(mtn("automate", "attributes", "testfile"), 0, true, true) +check(mtn("automate", "get_attributes", "testfile"), 0, true, true) check(fsize("stderr") == 0) parsed = parse_basic_io(readfile("stdout")) for _,l in pairs(parsed) do @@ -28,7 +28,7 @@ check(mtn("attr", "drop", "testfile", "k check(mtn("attr", "drop", "testfile", "key2"), 0, false, false) -- the actual check of the interface -check(mtn("automate", "attributes", "testfile"), 0, true, true) +check(mtn("automate", "get_attributes", "testfile"), 0, true, true) check(fsize("stderr") == 0) parsed = parse_basic_io(readfile("stdout")) -- make sure the output generated 9 stanzas @@ -71,7 +71,7 @@ commit("mainbranch") commit("mainbranch") -- check that dropped attributes do not popup in further revisions -check(mtn("automate", "attributes", "testfile"), 0, true, true) +check(mtn("automate", "get_attributes", "testfile"), 0, true, true) check(fsize("stderr") == 0) parsed = parse_basic_io(readfile("stdout")) ============================================================ --- tests/two_parent_workspace_commands_that_fail/__driver__.lua ddb96c7133077b7166f21a65ad3d7e6bd97da49a +++ tests/two_parent_workspace_commands_that_fail/__driver__.lua 87cfff5ae2edb3eb8deca9a605d607667fa297d4 @@ -52,7 +52,7 @@ check(mtn("automate", "inventory"), 1, n -- formats need updating to deal check(mtn("automate", "get_base_revision_id"), 1, nil, diag) check(mtn("automate", "inventory"), 1, nil, diag) -check(mtn("automate", "attributes", "testfile"), 1, nil, diag) +check(mtn("automate", "get_attributes", "testfile"), 1, nil, diag) -- commit cannot be restricted check(mtn("commit", "testfile", "--message", "blah-blah"),