# # # patch "basic_io.cc" # from [0e2ba2ebec547db8ee201b40856cbce01c8b38d1] # to [5aacaa8dcf17e4113dbaf2e91b476a9b795ccbf5] # # patch "basic_io.hh" # from [7978cce5b4f235c365101530ac8f8924a0516565] # to [df2ce0a862161cd5c0d7061b75fe3804d1cfec87] # # patch "cmd_merging.cc" # from [50ece2eb73ddae422599dcce5041c642d29ec27d] # to [20504d3e8e5127145c04b82e32d3aa08ff7bec38] # # patch "luaext_parse_basic_io.cc" # from [e0dd6eaf52ad9523b67eae29bf67b26afd09f628] # to [9c379d8bf3e99a55daa942ed7a66d583382a39ae] # # patch "merge.cc" # from [288d47860edae452dbd9b07e4b908501c228bf1e] # to [9ec8924bac7082af2a9fe16889f874a5f5a0c67c] # # patch "merge.hh" # from [3af2f8f6f84638fc680afb960727160bce3f55fd] # to [8cd4bcbd4dfc7b481569c7725003e42bee951969] # # patch "options_list.hh" # from [efa82172817ceccbb319bb059589f6c0b4cb32bb] # to [7c7240ddf23a0cbd44b172be81a952e3de5755d1] # # patch "roster_merge.cc" # from [2b4ee06493381d9b97986dc8d1b6bccdfc234059] # to [b53b18bbe337ad0e4577a577f5cf5c716d621b85] # # patch "roster_merge.hh" # from [592b393e6407e933d7e4b49db40afeb09c9217fa] # to [95c9ce256cafc7df3ced08337e104b85fccfbe37] # # patch "tests/common/test_utils_inventory.lua" # from [780502a124f1af69badad0d2e53b0b957cfa3caa] # to [565ac5789000a661ce7ac4ec1f3d5b6a46522b92] # # patch "tests/resolve_duplicate_name_conflict/__driver__.lua" # from [e5bd5ea41933be4c5e85b28338323ee48da57857] # to [bd0f02e6897beeb64dba8119288df66a3ef1128f] # ============================================================ --- basic_io.cc 0e2ba2ebec547db8ee201b40856cbce01c8b38d1 +++ basic_io.cc 5aacaa8dcf17e4113dbaf2e91b476a9b795ccbf5 @@ -97,6 +97,15 @@ void basic_io::stanza::push_str_pair(sym indent = k().size(); } +void basic_io::stanza::push_str_pair(symbol const & k, symbol const & v) +{ + // Note that this value is a symbol, not a string; the Lua basic_io parser + // will return this pair as two lines with no values. + entries.push_back(make_pair(k, v())); + if (k().size() > indent) + indent = k().size(); +} + void basic_io::stanza::push_file_pair(symbol const & k, file_path const & v) { push_str_pair(k, v.as_internal()); ============================================================ --- basic_io.hh 7978cce5b4f235c365101530ac8f8924a0516565 +++ basic_io.hh df2ce0a862161cd5c0d7061b75fe3804d1cfec87 @@ -29,29 +29,29 @@ namespace basic_io namespace { - namespace syms + namespace syms { // general format symbol symbol const format_version("format_version"); - + // roster symbols symbol const dir("dir"); symbol const file("file"); symbol const content("content"); symbol const attr("attr"); - + // 'local' roster and marking symbols // FIXME: should these be listed as "general" symbols here as well? symbol const ident("ident"); symbol const birth("birth"); symbol const dormant_attr("dormant_attr"); - + symbol const path_mark("path_mark"); symbol const content_mark("content_mark"); symbol const attr_mark("attr_mark"); } } - + typedef enum { TOK_SYMBOL, @@ -77,7 +77,7 @@ namespace basic_io inline void peek() { if (LIKELY(curr != in.end())) - // we do want to distinguish between EOF and '\xff', + // we do want to distinguish between EOF and '\xff', // so we translate '\xff' to 255u lookahead = widen(*curr); else @@ -162,7 +162,7 @@ namespace basic_io in.err("non-hex character in hex string"); advance(); } - + store(val); if (UNLIKELY(static_cast(in.lookahead) != ']')) @@ -230,13 +230,13 @@ namespace basic_io } advance(); } - + store(val); if (UNLIKELY(static_cast(in.lookahead) != '"')) in.err("string did not end with '\"'"); in.advance(); - + return basic_io::TOK_STRING; } else @@ -258,6 +258,7 @@ namespace basic_io void push_binary_triple(symbol const & k, std::string const & n, id const & v); void push_str_pair(symbol const & k, std::string const & v); + void push_str_pair(symbol const & k, symbol const & v); void push_str_triple(symbol const & k, std::string const & n, std::string const & v); void push_file_pair(symbol const & k, file_path const & v); @@ -266,7 +267,7 @@ namespace basic_io }; - // Note: printer uses a static buffer; thus only one buffer + // Note: printer uses a static buffer; thus only one buffer // may be referenced (globally). An invariant will be triggered // if more than one basic_io::printer is instantiated. struct ============================================================ --- cmd_merging.cc 50ece2eb73ddae422599dcce5041c642d29ec27d +++ cmd_merging.cc 20504d3e8e5127145c04b82e32d3aa08ff7bec38 @@ -144,6 +144,8 @@ CMD(update, "update", "", CMD_REF(worksp "If a revision is given, update the workspace to that revision. " "If not, update the workspace to the head of the branch."), options::opts::branch | options::opts::revision) + // this command does not accept resolve_conflict_opts, because we don't support + // resolving workspace conflicts. { if (args.size() > 0) throw usage(execid); @@ -282,7 +284,7 @@ CMD(update, "update", "", CMD_REF(worksp left_markings, right_markings, paths); wca.cache_roster(working_rid, working_roster); resolve_merge_conflicts(app.lua, *working_roster, chosen_roster, - result, wca, app.opts); + result, wca, false); // Make sure it worked... I(result.is_clean()); @@ -418,7 +420,7 @@ CMD(merge, "merge", "", CMD_REF(tree), " N_("Merges unmerged heads of a branch"), "", options::opts::branch | options::opts::date | options::opts::author | - options::opts::resolve_conflict_opts) + options::opts::resolve_conflicts_opts) { database db(app); key_store keys(app); @@ -497,7 +499,8 @@ CMD(propagate, "propagate", "", CMD_REF( N_("SOURCE-BRANCH DEST-BRANCH"), N_("Merges from one branch to another asymmetrically"), "", - options::opts::date | options::opts::author | options::opts::message | options::opts::msgfile) + options::opts::date | options::opts::author | options::opts::message | options::opts::msgfile | + options::opts::resolve_conflicts_opts) { if (args.size() != 2) throw usage(execid); @@ -536,7 +539,8 @@ CMD(merge_into_dir, "merge_into_dir", "" N_("SOURCE-BRANCH DEST-BRANCH DIR"), N_("Merges one branch into a subdirectory in another branch"), "", - options::opts::date | options::opts::author | options::opts::message | options::opts::msgfile) + options::opts::date | options::opts::author | options::opts::message | options::opts::msgfile | + options::opts::resolve_conflicts_opts) { database db(app); key_store keys(app); @@ -638,8 +642,12 @@ CMD(merge_into_dir, "merge_into_dir", "" content_merge_database_adaptor dba(db, left_rid, right_rid, left_marking_map, right_marking_map); - resolve_merge_conflicts(app.lua, left_roster, right_roster, result, dba, app.opts); + bool resolutions_given; + parse_resolve_conflicts_opts (app.opts, left_roster, right_roster, result, resolutions_given); + + resolve_merge_conflicts(app.lua, left_roster, right_roster, result, dba, resolutions_given); + { dir_t moved_root = left_roster.root(); moved_root->parent = the_null_node; @@ -682,6 +690,8 @@ CMD(merge_into_workspace, "merge_into_wo "the workspace's base revision will be recorded as parents on commit. " "The workspace's selected branch is not changed."), options::opts::none) + // this command does not accept resolve_conflict_opts, because we don't + // support resolving workspace conflicts. { revision_id left_id, right_id; cached_roster left, right; @@ -752,7 +762,7 @@ CMD(merge_into_workspace, "merge_into_wo content_merge_workspace_adaptor wca(db, lca_id, lca.first, *left.second, *right.second, paths); wca.cache_roster(working_rid, working_roster); - resolve_merge_conflicts(app.lua, *left.first, *right.first, merge_result, wca, app.opts); + resolve_merge_conflicts(app.lua, *left.first, *right.first, merge_result, wca, false); // Make sure it worked... I(merge_result.is_clean()); @@ -789,7 +799,8 @@ CMD(explicit_merge, "explicit_merge", "" N_("Merges two explicitly given revisions"), N_("The results of the merge are placed on the branch specified by " "DEST-BRANCH."), - options::opts::date | options::opts::author) + options::opts::date | options::opts::author | + options::opts::resolve_conflicts_opts) { database db(app); key_store keys(app); @@ -1006,6 +1017,8 @@ CMD(pluck, "pluck", "", CMD_REF(workspac "If two revisions are given, applies the changes made to get from the " "first revision to the second."), options::opts::revision | options::opts::depth | options::opts::exclude) + // this command does not accept resolve_conflict_opts, because we don't support + // resolving workspace conflicts. { database db(app); workspace work(app); @@ -1136,7 +1149,7 @@ CMD(pluck, "pluck", "", CMD_REF(workspac wca.cache_roster(to_rid, to_roster); resolve_merge_conflicts(app.lua, *working_roster, *to_roster, - result, wca, app.opts); + result, wca, false); I(result.is_clean()); // temporary node ids may appear ============================================================ --- luaext_parse_basic_io.cc e0dd6eaf52ad9523b67eae29bf67b26afd09f628 +++ luaext_parse_basic_io.cc 9c379d8bf3e99a55daa942ed7a66d583382a39ae @@ -11,6 +11,9 @@ LUAEXT(parse_basic_io, ) LUAEXT(parse_basic_io, ) { + // This has no notion of a 'stanza'. It assumes a 'line' is a symbol + // followed by zero or more string or hex values. It returns a table of + // lines. vector > > res; const string str(luaL_checkstring(L, -1), lua_strlen(L, -1)); basic_io::input_source in(str, "monotone_parse_basic_io_for_lua"); ============================================================ --- merge.cc 288d47860edae452dbd9b07e4b908501c228bf1e +++ merge.cc 9ec8924bac7082af2a9fe16889f874a5f5a0c67c @@ -132,17 +132,17 @@ resolve_merge_conflicts(lua_hooks & lua, roster_t const & right_roster, roster_merge_result & result, content_merge_adaptor & adaptor, - options const & opts) + bool resolutions_given) { if (!result.is_clean()) result.log_conflicts(); if (result.has_non_content_conflicts()) { - if (opts.resolve_conflicts_given || opts.resolve_conflicts_file_given) + if (resolutions_given) { - // We report the conflicts we don't know how to resolve yet. - // FIXME_ROSTERS: perhaps add lua hooks to resolve them? + // We just report the conflicts we don't know how to resolve yet. + result.report_missing_root_conflicts(left_roster, right_roster, adaptor, false, std::cout); result.report_invalid_name_conflicts(left_roster, right_roster, adaptor, false, std::cout); result.report_directory_loop_conflicts(left_roster, right_roster, adaptor, false, std::cout); @@ -150,10 +150,12 @@ resolve_merge_conflicts(lua_hooks & lua, result.report_orphaned_node_conflicts(left_roster, right_roster, adaptor, false, std::cout); result.report_multiple_name_conflicts(left_roster, right_roster, adaptor, false, std::cout); - result.resolve_duplicate_name_conflicts(lua, left_roster, right_roster, adaptor, opts); - result.report_attribute_conflicts(left_roster, right_roster, adaptor, false, std::cout); result.report_file_content_conflicts(left_roster, right_roster, adaptor, false, std::cout); + + // If there aren't any we can't resolve, resolve the ones we can. + result.resolve_duplicate_name_conflicts(lua, left_roster, right_roster, adaptor); + } else { @@ -219,11 +221,14 @@ interactive_merge_and_store(lua_hooks & right_roster, right_marking_map, right_uncommon_ancestors, result); + bool resolutions_given; content_merge_database_adaptor dba(db, left_rid, right_rid, left_marking_map, right_marking_map); - resolve_merge_conflicts(lua, left_roster, right_roster, - result, dba, opts); + parse_resolve_conflicts_opts (opts, left_roster, right_roster, result, resolutions_given); + + resolve_merge_conflicts(lua, left_roster, right_roster, result, dba, resolutions_given); + // write new files into the db store_roster_merge_result(db, left_roster, right_roster, result, ============================================================ --- merge.hh 3af2f8f6f84638fc680afb960727160bce3f55fd +++ merge.hh 8cd4bcbd4dfc7b481569c7725003e42bee951969 @@ -32,7 +32,7 @@ resolve_merge_conflicts(lua_hooks & lua, roster_t const & right_roster, roster_merge_result & result, content_merge_adaptor & adaptor, - options const & opts); + bool const resolutions_given); // traditional resolve-all-conflicts-as-you-go style merging with 3-way merge // for file texts ============================================================ --- options_list.hh efa82172817ceccbb319bb059589f6c0b4cb32bb +++ options_list.hh 7c7240ddf23a0cbd44b172be81a952e3de5755d1 @@ -1,3 +1,4 @@ +// Copyright 2008 Stephen Leake // Copyright 2006 Timothy Brownawell // This is made available under the GNU GPL v2 or later. @@ -635,22 +636,26 @@ OPTION(automate_inventory_opts, no_corre } #endif -OPTSET(resolve_conflict_opts) -OPTVAR(resolve_conflict_opts, bool, resolve_conflicts_file, false) -OPTVAR(resolve_conflict_opts, std::string, resolve_conflicts, string ("")) +OPTSET(resolve_conflicts_opts) +OPTVAR(resolve_conflicts_opts, utf8, resolve_conflicts_file, ) +OPTVAR(resolve_conflicts_opts, std::string, resolve_conflicts, ) -OPTION(resolve_conflict_opts, resolve_conflicts_file, false, "resolve_conflicts_file", +OPTION(resolve_conflicts_opts, resolve_conflicts_file, true, "resolve-conflicts-file", gettext_noop("use _MTN/conflicts to resolve conflicts")) #ifdef option_bodies { - resolve_conflicts_file = true; + N(!resolve_conflicts_given, + F("only one of --resolve-conflicts or --resolve-conflicts-file may be given")); + resolve_conflicts_file = utf8(arg); } #endif -OPTION(resolve_conflict_opts, resolve_conflicts, true, "resolve_conflicts", +OPTION(resolve_conflicts_opts, resolve_conflicts, true, "resolve-conflicts", gettext_noop("use argument to resolve conflicts")) #ifdef option_bodies { + N(!resolve_conflicts_file_given, + F("only one of --resolve-conflicts or --resolve-conflicts-file may be given")); resolve_conflicts = arg; } #endif ============================================================ --- roster_merge.cc 2b4ee06493381d9b97986dc8d1b6bccdfc234059 +++ roster_merge.cc b53b18bbe337ad0e4577a577f5cf5c716d621b85 @@ -180,22 +180,31 @@ namespace else I(false); } -} -namespace -{ namespace syms { symbol const ancestor_file_id("ancestor_file_id"); symbol const ancestor_name("ancestor_name"); symbol const attr_name("attr_name"); + symbol const attribute("attribute"); symbol const conflict("conflict"); + symbol const content("content"); + symbol const directory_loop_created("directory_loop_created"); + symbol const duplicate_name("duplicate_name"); + symbol const invalid_name("invalid_name"); symbol const left_attr_state("left_attr_state"); symbol const left_attr_value("left_attr_value"); symbol const left_file_id("left_file_id"); symbol const left_name("left_name"); symbol const left_type("left_type"); + symbol const missing_root("missing_root"); + symbol const multiple_names("multiple_names"); symbol const node_type("node_type"); + symbol const orphaned_directory("orphaned_directory"); + symbol const orphaned_file("orphaned_file"); + symbol const resolved_rename_left("resolved_rename_left"); + symbol const resolved_rename_right("resolved_rename_right"); + symbol const resolved_suture ("resolved_suture"); symbol const right_attr_state("right_attr_state"); symbol const right_attr_value("right_attr_value"); symbol const right_file_id("right_file_id"); @@ -226,13 +235,13 @@ put_added_conflict_left (basic_io::stanz file_id fid; db_adaptor.db.get_file_content (db_adaptor.left_rid, nid, fid); st.push_str_pair(syms::left_type, "added file"); - st.push_str_pair(syms::left_name, name.as_external()); + st.push_file_pair(syms::left_name, name); st.push_binary_pair(syms::left_file_id, fid.inner()); } else { st.push_str_pair(syms::left_type, "added directory"); - st.push_str_pair(syms::left_name, name.as_external()); + st.push_file_pair(syms::left_name, name); } } @@ -255,13 +264,13 @@ put_added_conflict_right (basic_io::stan db_adaptor.db.get_file_content (db_adaptor.right_rid, nid, fid); st.push_str_pair(syms::right_type, "added file"); - st.push_str_pair(syms::right_name, name.as_external()); + st.push_file_pair(syms::right_name, name); st.push_binary_pair(syms::right_file_id, fid.inner()); } else { st.push_str_pair(syms::right_type, "added directory"); - st.push_str_pair(syms::right_name, name.as_external()); + st.push_file_pair(syms::right_name, name); } } @@ -290,14 +299,14 @@ put_rename_conflict_left (basic_io::stan st.push_binary_pair(syms::ancestor_file_id, ancestor_fid.inner()); file_id left_fid; db_adaptor.db.get_file_content (db_adaptor.left_rid, nid, left_fid); - st.push_str_pair(syms::left_name, left_name.as_external()); + st.push_file_pair(syms::left_name, left_name); st.push_binary_pair(syms::left_file_id, left_fid.inner()); } else { st.push_str_pair(syms::left_type, "renamed directory"); st.push_str_pair(syms::ancestor_name, ancestor_name.as_external()); - st.push_str_pair(syms::left_name, left_name.as_external()); + st.push_file_pair(syms::left_name, left_name); } } @@ -327,14 +336,14 @@ put_rename_conflict_right (basic_io::sta st.push_binary_pair(syms::ancestor_file_id, ancestor_fid.inner()); file_id right_fid; db_adaptor.db.get_file_content (db_adaptor.right_rid, nid, right_fid); - st.push_str_pair(syms::right_name, right_name.as_external()); + st.push_file_pair(syms::right_name, right_name); st.push_binary_pair(syms::right_file_id, right_fid.inner()); } else { st.push_str_pair(syms::right_type, "renamed directory"); st.push_str_pair(syms::ancestor_name, ancestor_name.as_external()); - st.push_str_pair(syms::right_name, right_name.as_external()); + st.push_file_pair(syms::right_name, right_name); } } @@ -394,12 +403,12 @@ put_attr_conflict (basic_io::stanza & st // FIXME: don't have this. st.push_str_pair(syms::ancestor_attr_value, ???); file_id left_fid; db_adaptor.db.get_file_content (db_adaptor.left_rid, conflict.nid, left_fid); - st.push_str_pair(syms::left_name, left_name.as_external()); + st.push_file_pair(syms::left_name, left_name); st.push_binary_pair(syms::left_file_id, left_fid.inner()); put_attr_state_left (st, conflict); file_id right_fid; db_adaptor.db.get_file_content (db_adaptor.right_rid, conflict.nid, right_fid); - st.push_str_pair(syms::right_name, right_name.as_external()); + st.push_file_pair(syms::right_name, right_name); st.push_binary_pair(syms::right_file_id, right_fid.inner()); put_attr_state_right (st, conflict); } @@ -409,9 +418,9 @@ put_attr_conflict (basic_io::stanza & st st.push_str_pair(syms::attr_name, conflict.key()); st.push_str_pair(syms::ancestor_name, ancestor_name.as_external()); // FIXME: don't have this. st.push_str_pair(syms::ancestor_attr_value, ???); - st.push_str_pair(syms::left_name, left_name.as_external()); + st.push_file_pair(syms::left_name, left_name); put_attr_state_left (st, conflict); - st.push_str_pair(syms::right_name, right_name.as_external()); + st.push_file_pair(syms::right_name, right_name); put_attr_state_right (st, conflict); } } @@ -452,19 +461,19 @@ put_content_conflict (basic_io::stanza & st.push_binary_pair(syms::ancestor_file_id, ancestor_fid.inner()); file_id left_fid; db_adaptor.db.get_file_content (db_adaptor.left_rid, conflict.nid, left_fid); - st.push_str_pair(syms::left_name, left_name.as_external()); + st.push_file_pair(syms::left_name, left_name); st.push_binary_pair(syms::left_file_id, left_fid.inner()); file_id right_fid; db_adaptor.db.get_file_content (db_adaptor.right_rid, conflict.nid, right_fid); - st.push_str_pair(syms::right_name, right_name.as_external()); + st.push_file_pair(syms::right_name, right_name); st.push_binary_pair(syms::right_file_id, right_fid.inner()); } else { st.push_str_pair(syms::node_type, "directory"); st.push_str_pair(syms::ancestor_name, ancestor_name.as_external()); - st.push_str_pair(syms::left_name, left_name.as_external()); - st.push_str_pair(syms::right_name, right_name.as_external()); + st.push_file_pair(syms::left_name, left_name); + st.push_file_pair(syms::right_name, right_name); } } @@ -518,7 +527,7 @@ roster_merge_result::report_missing_root basic_io::stanza st; if (basic_io) - st.push_str_pair(syms::conflict, "missing root"); + st.push_str_pair(syms::conflict, syms::missing_root); else P(F("conflict: missing root directory")); @@ -636,7 +645,7 @@ roster_merge_result::report_invalid_name parent_lca_rid, parent_lca_roster); if (basic_io) - st.push_str_pair(syms::conflict, "invalid name"); + st.push_str_pair(syms::conflict, syms::invalid_name); else P(F("conflict: invalid name _MTN in root directory")); @@ -743,7 +752,7 @@ roster_merge_result::report_directory_lo lca_roster->get_name(conflict.parent_name.first, lca_parent_name); if (basic_io) - st.push_str_pair(syms::conflict, "directory loop created"); + st.push_str_pair(syms::conflict, syms::directory_loop_created); else P(F("conflict: directory loop created")); @@ -813,14 +822,14 @@ roster_merge_result::report_orphaned_nod if (type == file_type) if (basic_io) - st.push_str_pair(syms::conflict, "orphaned file"); + st.push_str_pair(syms::conflict, syms::orphaned_file); else P(F("conflict: orphaned file '%s' from revision %s") % lca_name % lca_rid); else { if (basic_io) - st.push_str_pair(syms::conflict, "orphaned directory"); + st.push_str_pair(syms::conflict, syms::orphaned_directory); else P(F("conflict: orphaned directory '%s' from revision %s") % lca_name % lca_rid); @@ -953,7 +962,7 @@ roster_merge_result::report_multiple_nam if (basic_io) { - st.push_str_pair(syms::conflict, "multiple names"); + st.push_str_pair(syms::conflict, syms::multiple_names); put_rename_conflict_left (st, adaptor, conflict.nid); put_rename_conflict_right (st, adaptor, conflict.nid); } @@ -1017,7 +1026,7 @@ roster_merge_result::report_duplicate_na basic_io::stanza st; if (basic_io) - st.push_str_pair(syms::conflict, "duplicate name"); + st.push_str_pair(syms::conflict, syms::duplicate_name); else { if (left_name == right_name) @@ -1171,7 +1180,7 @@ roster_merge_result::report_attribute_co { basic_io::stanza st; - st.push_str_pair(syms::conflict, "attribute"); + st.push_str_pair(syms::conflict, syms::attribute); put_attr_conflict (st, adaptor, conflict); put_stanza (st, output); } @@ -1287,7 +1296,7 @@ roster_merge_result::report_file_content { basic_io::stanza st; - st.push_str_pair(syms::conflict, "content"); + st.push_str_pair(syms::conflict, syms::content); put_content_conflict (st, adaptor, conflict); put_stanza (st, output); } @@ -1333,90 +1342,266 @@ roster_merge_result::report_file_content // Resolving non-content conflicts -namespace +namespace resolve_conflicts { - // FIXME: use symbols instead? - enum resolution_t {resolved_none, resolved_content_ws, resolved_rename}; - - string image (resolution_t resolution) + char* image (resolution_t resolution) { switch (resolution) { - case resolved_none: - return "resolved_none"; + case none: + return "none"; - case resolved_content_ws: - return "resolved_content_ws"; + case suture: + return "suture"; - case resolved_rename: - return "resolved_rename"; + case rename: + return "rename"; } return ""; // suppress bogus compiler warning } +} - void - parse_resolution (std::string input, - resolution_t & resolution_left, - file_path & resolution_left_name, - resolution_t & resolution_right, - file_path & resolution_right_name) - { - // FIXME: working on core, not UI right now. This matches one use case :) +static void +parse_duplicate_name_conflicts(basic_io::parser & pars, + std::vector & conflicts, + roster_t const & left_roster, + roster_t const & right_roster) +{ + for (std::vector::iterator i = conflicts.begin(); + i != conflicts.end(); + ++i) + { + duplicate_name_conflict & conflict = *i; - I(input == "resolved_rename_left \"thermostat-westinghouse.c\""); + pars.esym(syms::duplicate_name); - resolution_left = resolved_rename; - resolution_left_name = file_path_internal ("thermostat_westinghouse.c"); - resolution_right = resolved_none; - resolution_right_name = file_path_internal (""); - } + node_id left_nid, right_nid; + string left_name, right_name; - static void - set_new_name_in_roster (lua_hooks & lua, - roster_t & new_roster, - node_id nid, - file_path const source_path, - file_path const target_path) - { - // Simplified from workspace::perform_rename in work.cc + pars.esym(syms::left_type); pars.str(); + pars.esym (syms::left_name); + left_name = pars.token; + pars.str(); + pars.esym(syms::left_file_id); pars.hex(); - I(!target_path.empty()); + pars.esym(syms::right_type); pars.str(); + pars.esym (syms::right_name); + right_name = pars.token; + pars.str(); + pars.esym(syms::right_file_id); pars.hex(); - N(!new_roster.has_node(target_path), F("%s already exists") % target_path.as_external()); - N(new_roster.has_node(target_path.dirname()), - F("directory %s does not exist or is unknown") % target_path.dirname()); + left_nid = left_roster.get_node (file_path_internal (left_name))->self; + right_nid = right_roster.get_node (file_path_internal (right_name))->self; - P(F("renaming %s to %s") % source_path % target_path); + N(left_nid == conflict.left_nid & right_nid == conflict.right_nid, + F("conflict mismatch: (duplicate_name, left %s, right %s") + % left_name % right_name); - // FIXME: is this really all we have to do? - new_roster.attach_node (nid, target_path); + // check for a resolution + while ((!pars.symp (syms::conflict)) && pars.tok.in.lookahead != EOF) + { + if (pars.symp (syms::resolved_suture)) + { + conflict.left_resolution.first = resolve_conflicts::suture; + conflict.right_resolution.first = resolve_conflicts::suture; + pars.sym(); + conflict.left_resolution.second = file_path_internal (pars.token); + pars.str(); + } + else if (pars.symp (syms::resolved_rename_left)) + { + conflict.left_resolution.first = resolve_conflicts::rename; + pars.sym(); + conflict.left_resolution.second = file_path_internal (pars.token); + pars.str(); + } + else if (pars.symp (syms::resolved_rename_right)) + { + conflict.right_resolution.first = resolve_conflicts::rename; + pars.sym(); + conflict.right_resolution.second = file_path_internal (pars.token); + pars.str(); + } + else + N(false, F("%s is not a supported conflict resolution for duplicate_name") % pars.token); + } - node_t node = new_roster.get_node (nid); - for (full_attr_map_t::const_iterator attr = node->attrs.begin(); - attr != node->attrs.end(); - ++attr) - lua.hook_apply_attribute (attr->first(), target_path, attr->second.second()); + if (pars.tok.in.lookahead != EOF) + pars.esym (syms::conflict); + else + { + std::vector::iterator tmp = i; + N(++tmp == conflicts.end(), F("conflicts file does not match current conflicts")); + } + } +} // parse_duplicate_name_conflicts - } // set_new_name_in_roster +static void +parse_resolve_conflicts_str(basic_io::parser & pars, roster_merge_result & result) +{ + while (pars.tok.in.lookahead != EOF) + { + if (pars.symp (syms::resolved_suture)) + { + pars.sym(); -} // end anonymous namespace + for (std::vector::iterator i = result.duplicate_name_conflicts.begin(); + i != result.duplicate_name_conflicts.end(); + ++i) + { + duplicate_name_conflict & conflict = *i; + conflict.left_resolution.first = resolve_conflicts::suture; + conflict.right_resolution.first = resolve_conflicts::suture; + } + } + else if (pars.symp (syms::resolved_rename_left)) + { + pars.sym(); + + N(result.duplicate_name_conflicts.size() == 1, + F("can't specify a rename conflict resolution for more than one conflict")); + + duplicate_name_conflict & conflict = *result.duplicate_name_conflicts.begin(); + + conflict.left_resolution.first = resolve_conflicts::rename; + pars.str(); + conflict.left_resolution.second = file_path_internal (pars.token); + } + else if (pars.symp (syms::resolved_rename_right)) + { + pars.sym(); + + N(result.duplicate_name_conflicts.size() == 1, + F("can't specify a rename conflict resolution for more than one conflict")); + + duplicate_name_conflict & conflict = *result.duplicate_name_conflicts.begin(); + + conflict.right_resolution.first = resolve_conflicts::rename; + pars.str(); + conflict.right_resolution.second = file_path_internal (pars.token); + } + else + N(false, F("%s is not a supported conflict resolution") % pars.token); + + } // while + +} // parse_resolv_conflicts_str + void +parse_resolve_conflicts_opts (options const & opts, + roster_t const & left_roster, + roster_t const & right_roster, + roster_merge_result & result, + bool & resolutions_given) +{ + if (opts.resolve_conflicts_given) + { + resolutions_given = true; + + basic_io::input_source src(opts.resolve_conflicts, "resolve_conflicts string"); + basic_io::tokenizer tok(src); + basic_io::parser pars(tok); + + parse_resolve_conflicts_str(pars, result); + + if (src.lookahead != EOF) + pars.err("invalid conflict resolution syntax"); + } + else if (opts.resolve_conflicts_file_given) + { + resolutions_given = true; + + data dat; + + if (opts.resolve_conflicts_file().substr(0, 4) == "_MTN") + read_data (bookkeeping_path(opts.resolve_conflicts_file()), dat); + else + read_data (file_path_external(opts.resolve_conflicts_file), dat); + + basic_io::input_source src(dat(), opts.resolve_conflicts_file()); + basic_io::tokenizer tok(src); + basic_io::parser pars(tok); + + // Skip left, right, ancestor. FIXME: should check these! But don't + // see how to access them right now. + for (int i = 1; i <= 3; i++) + { + pars.sym(); + pars.hex(); + } + + // Get into the first conflict + pars.esym (syms::conflict); + + // There must be one stanza in the file for each conflict; otherwise + // something has changed since the file was regenerated. So we go thru + // the conflicts in the same order they are generated; see merge.cc + // resolve_merge_conflicts. + + // We should not get here if there are any conflicts we don't support, + // so assert that first. + I(!result.missing_root_dir); + I(result.invalid_name_conflicts.size() == 0); + I(result.directory_loop_conflicts.size() == 0); + I(result.orphaned_node_conflicts.size() == 0); + I(result.multiple_name_conflicts.size() == 0); + I(result.attribute_conflicts.size() == 0); + I(result.file_content_conflicts.size() == 0); + + // These are the ones we know how to resolve. + + parse_duplicate_name_conflicts(pars, result.duplicate_name_conflicts, left_roster, right_roster); + + if (src.lookahead != EOF) + pars.err("extra conflicts in file"); + } + else + resolutions_given = false; + +} // parse_resolve_conflicts_opts + +static void +set_new_name_in_roster (lua_hooks & lua, + roster_t & new_roster, + node_id nid, + file_path const source_path, + file_path const target_path) +{ + // Simplified from workspace::perform_rename in work.cc + + I(!target_path.empty()); + + N(!new_roster.has_node(target_path), F("%s already exists") % target_path.as_external()); + N(new_roster.has_node(target_path.dirname()), + F("directory %s does not exist or is unknown") % target_path.dirname()); + + P(F("renaming %s to %s") % source_path % target_path); + + new_roster.attach_node (nid, target_path); + + node_t node = new_roster.get_node (nid); + for (full_attr_map_t::const_iterator attr = node->attrs.begin(); + attr != node->attrs.end(); + ++attr) + lua.hook_apply_attribute (attr->first(), target_path, attr->second.second()); + +} // set_new_name_in_roster + +void roster_merge_result::resolve_duplicate_name_conflicts(lua_hooks & lua, roster_t const & left_roster, roster_t const & right_roster, - content_merge_adaptor & adaptor, - options const & opts) + content_merge_adaptor & adaptor) { MM(left_roster); MM(right_roster); MM(this->roster); // New roster - I(opts.resolve_conflicts_given || opts.resolve_conflicts_file_given); - - std::vector::iterator i = duplicate_name_conflicts.begin(); - while (i != duplicate_name_conflicts.end()) + for (std::vector::const_iterator i = duplicate_name_conflicts.begin(); + i != duplicate_name_conflicts.end(); + ++i) { // FIXME: share this code with above? duplicate_name_conflict const & conflict = *i; @@ -1425,9 +1610,7 @@ roster_merge_result::resolve_duplicate_n node_id left_nid = conflict.left_nid; node_id right_nid= conflict.right_nid; - // conflict nodes are present but without filenames in new roster - I(!roster.is_attached(left_nid)); - I(!roster.is_attached(right_nid)); + // conflict nodes are present but detached (without filenames) in new roster file_path left_name, right_name; @@ -1436,67 +1619,52 @@ roster_merge_result::resolve_duplicate_n // end FIXME: - string conflict_type ("duplicate name"); - // The resolution is either to suture the two files together, or to rename one or both. - // If 'suture', only resolution_left is set. - resolution_t resolution_left = resolved_none; - file_path resolution_left_name; - resolution_t resolution_right = resolved_none; - file_path resolution_right_name; - if (opts.resolve_conflicts_given) - parse_resolution - (opts.resolve_conflicts, resolution_left, resolution_left_name, resolution_right, resolution_right_name); -// else - // FIXME: add this back: -// parse_find_resolution -// (opts.resolve_conflicts, &resolution_left, &resolution_right, -// conflict_type, left_name, left_type, right_name, right_type); + switch (conflict.left_resolution.first) + { + case resolve_conflicts::suture: + I(conflict.right_resolution.first == resolve_conflicts::suture); - switch (resolution_left) - { - case resolved_content_ws: // FIXME: do the suturing, mark conflict as resolved in result roster I(false); break; - case resolved_rename: - set_new_name_in_roster (lua, this->roster, left_nid, left_name, resolution_left_name); + case resolve_conflicts::rename: + set_new_name_in_roster (lua, this->roster, left_nid, left_name, conflict.left_resolution.second); break; - case resolved_none: + case resolve_conflicts::none: // Just keep current name this->roster.attach_node (left_nid, left_name); break; default: - N(false, F("%s: invalid resolution for this conflict") % image (resolution_left)); + N(false, F("%s: invalid resolution for this conflict") % image (conflict.left_resolution.first)); } - if (resolution_left != resolved_content_ws) + switch (conflict.right_resolution.first) { - switch (resolution_right) - { - case resolved_rename: - set_new_name_in_roster (lua, this->roster, right_nid, right_name, resolution_right_name); - break; + case resolve_conflicts::suture: + I(conflict.left_resolution.first == resolve_conflicts::suture); + // suture already done in left above + break; - case resolved_none: - // Just keep current name - this->roster.attach_node (right_nid, right_name); - break; + case resolve_conflicts::rename: + set_new_name_in_roster (lua, this->roster, right_nid, right_name, conflict.right_resolution.second); + break; - default: - N(false, F("%s: invalid resolution for this conflict") % image (resolution_right)); - } + case resolve_conflicts::none: + // Just keep current name + this->roster.attach_node (right_nid, right_name); + break; + + default: + N(false, F("%s: invalid resolution for this conflict") % image (conflict.right_resolution.first)); } + } // end for - duplicate_name_conflicts.erase(i); - - // no 'i++' needed; erase does that by side effect. FIXME: is this proper std library usage? - - } // end while + duplicate_name_conflicts.clear(); } void ============================================================ --- roster_merge.hh 592b393e6407e933d7e4b49db40afeb09c9217fa +++ roster_merge.hh 95c9ce256cafc7df3ced08337e104b85fccfbe37 @@ -16,6 +16,14 @@ #include "diff_patch.hh" #include "roster.hh" // needs full definition of roster_t available +// our general strategy is to return a (possibly insane) roster, and a list of +// conflicts encountered in that roster. Each conflict encountered in merging +// the roster creates an entry in this list. +// +// If the user specifies a --resolve-conflicts option, and it contains a +// resolution for a given conflict, the conflict resolutions are added to +// each conflict object when the option is parsed. + // interactions between conflict types: // node rename conflicts never participate in structural conflicts // (e.g., merge , could be @@ -25,6 +33,11 @@ // manifest.) // +namespace resolve_conflicts +{ + enum resolution_t {none, suture, rename}; +} + // renaming the root dir allows these: // -- _MTN in root // -- missing root directory @@ -52,10 +65,6 @@ struct orphaned_node_conflict std::pair parent_name; }; -// our general strategy is to return a (possibly insane) roster, and a list of -// conflicts encountered in that roster. Each conflict encountered in merging -// the roster creates an entry in this list. - // nodes with multiple name conflicts are left detached in the resulting // roster, with null parent and name fields. // note that it is possible that the parent node on the left, the right, or @@ -87,6 +96,11 @@ struct duplicate_name_conflict { node_id left_nid, right_nid; std::pair parent_name; + std::pair left_resolution, right_resolution; + + duplicate_name_conflict () + {left_resolution.first = resolve_conflicts::none; + right_resolution.first = resolve_conflicts::none;}; }; // nodes with attribute conflicts are left attached in the resulting tree (unless @@ -190,8 +204,7 @@ struct roster_merge_result void resolve_duplicate_name_conflicts(lua_hooks & lua, roster_t const & left_roster, roster_t const & right_roster, - content_merge_adaptor & adaptor, - options const & opts); + content_merge_adaptor & adaptor); void report_attribute_conflicts(roster_t const & left, roster_t const & right, @@ -218,6 +231,12 @@ roster_merge(roster_t const & left_paren std::set const & right_uncommon_ancestors, roster_merge_result & result); +void +parse_resolve_conflicts_opts (options const & opts, + roster_t const & left_roster, + roster_t const & right_roster, + roster_merge_result & result, + bool & resolutions_given); // Local Variables: // mode: C++ ============================================================ --- tests/common/test_utils_inventory.lua 780502a124f1af69badad0d2e53b0b957cfa3caa +++ tests/common/test_utils_inventory.lua 565ac5789000a661ce7ac4ec1f3d5b6a46522b92 @@ -23,6 +23,8 @@ function check_basic_io_line (label, com checkexp(label .. i, computed.values[i], value[i], xfail) end + elseif value == nil then + checkexp(label .. ".length", #computed.values, 0, xfail) else checkexp(label .. ".length", #computed.values, 1, xfail) checkexp(label .. "." .. name, computed.values[1], value, xfail) ============================================================ --- tests/resolve_duplicate_name_conflict/__driver__.lua e5bd5ea41933be4c5e85b28338323ee48da57857 +++ tests/resolve_duplicate_name_conflict/__driver__.lua bd0f02e6897beeb64dba8119288df66a3ef1128f @@ -15,11 +15,9 @@ base = base_revision() commit() base = base_revision() --- FIXME: only doing "thermostat" case for now, since we only support rename resolution - -- Abe adds conflict files addfile("thermostat.c", "thermostat westinghouse") ---addfile("checkout.sh", "checkout.sh abe 1") +addfile("checkout.sh", "checkout.sh abe 1") commit("testbranch", "abe_1") abe_1 = base_revision() @@ -27,14 +25,14 @@ addfile("thermostat.c", "thermostat hone -- Beth adds files, and attempts to merge addfile("thermostat.c", "thermostat honeywell") ---addfile("checkout.sh", "checkout.sh beth 1") +addfile("checkout.sh", "checkout.sh beth 1") commit("testbranch", "beth_1") beth_1 = base_revision() -- This fails due to duplicate name conflicts check(mtn("merge"), 1, false, false) --- Beth uses 'automate show_conflicts' and 'merge --resolve_conflicts' +-- Beth uses 'automate show_conflicts' and 'merge --resolve-conflicts-file' -- to fix the conflicts -- -- For thermostat.c, she renames the files in her workspace (to allow @@ -53,22 +51,23 @@ parsed = parse_basic_io(readfile("stdout -- Verify that we got the expected revisions, conflicts and file ids parsed = parse_basic_io(readfile("stdout")) +-- The Lua parser returns the 'conflict ' line as two lines +-- with no values, so the line count here seems odd. check_basic_io_line (1, parsed[1], "left", abe_1) check_basic_io_line (2, parsed[2], "right", beth_1) check_basic_io_line (3, parsed[3], "ancestor", base) --- check_basic_io_line (6, parsed[6], "left_name", "checkout.sh") --- check_basic_io_line (7, parsed[7], "left_file_id", "61b8d4fb0e5d78be111f691b955d523c782fa92e") --- abe_checkout = parsed[7].values[1] +check_basic_io_line (4, parsed[4], "conflict") +check_basic_io_line (5, parsed[5], "duplicate_name") +check_basic_io_line (6, parsed[6], "left_type", "added file") +check_basic_io_line (7, parsed[7], "left_name", "checkout.sh") +check_basic_io_line (8, parsed[8], "left_file_id", "61b8d4fb0e5d78be111f691b955d523c782fa92e") +abe_checkout = parsed[8].values[1] --- check_basic_io_line (13, parsed[13], "left_name", "thermostat.c") --- check_basic_io_line (14, parsed[14], "left_file_id", "4cdcec6fa2f9d5c075d5b80d03c708c8e4801196") --- abe_thermostat = parsed[14].values[1] +check_basic_io_line (15, parsed[15], "left_name", "thermostat.c") +check_basic_io_line (16, parsed[16], "left_file_id", "4cdcec6fa2f9d5c075d5b80d03c708c8e4801196") +abe_thermostat = parsed[16].values[1] -check_basic_io_line (6, parsed[6], "left_name", "thermostat.c") -check_basic_io_line (7, parsed[7], "left_file_id", "4cdcec6fa2f9d5c075d5b80d03c708c8e4801196") -abe_thermostat = parsed[7].values[1] - -- Do the filesystem rename for Beth's thermostat.c, and retrieve Abe's version rename ("thermostat.c", "thermostat-honeywell.c") @@ -77,22 +76,21 @@ check ("thermostat westinghouse" == read check ("thermostat westinghouse" == readfile ("thermostat-westinghouse.c")) -- Do the manual merge for checkout.sh; retrieve Abe's version --- check (mtn ("automate", "get_file", abe_checkout), 0, true, false) --- rename ("stdout", "checkout.sh-abe") --- check ("checkout.sh abe 1\n" == readfile ("checkout.sh-abe") +check (mtn ("automate", "get_file", abe_checkout), 0, true, false) +rename ("stdout", "checkout.sh-abe") +check ("checkout.sh abe 1" == readfile ("checkout.sh-abe")) --- get ("checkout.sh-merged", "checkout.sh") +get ("checkout.sh-merged", "checkout.sh") -- This has the resolution lines --- get ("conflicts-resolved", "_MTN/conflicts") +get ("conflicts-resolved", "_MTN/conflicts") -- This succeeds --- FIXME: just working on rename for now -check(mtn("merge", '--resolve_conflicts=resolved_rename_left "thermostat-westinghouse.c"'), 0, true, true) +check(mtn("merge", "--resolve-conflicts-file=_MTN/conflicts"), 0, true, true) ---check(mtn("update"), 0, false, false) +check(mtn("update"), 0, false, false) -- Verify the merged checkout.sh got committed ---check("checkout.sh merged" == readfile("checkout.sh")) +check("checkout.sh merged" == readfile("checkout.sh")) -- end of file