# # # add_dir "tests/resolve_conflict_all_resolutions" # # add_file "tests/resolve_conflict_all_resolutions/__driver__.lua" # content [709ab05fab5b76ebcbe76b2a9fdf12307f690522] # # add_file "tests/resolve_conflict_all_resolutions/conflicts-1" # content [699148173160c1d22938da045b8bbe737f7b518f] # # add_file "tests/resolve_duplicate_name_conflict/conflicts-1" # content [699148173160c1d22938da045b8bbe737f7b518f] # # patch "cmd_merging.cc" # from [17336c78322596cd4cbce0492c646acf9fcf9ded] # to [1641d6a228ca07cc5b747ed7586b72cfab7ead48] # # patch "roster_merge.cc" # from [0b686863088ab9897a3412e0feb7041d0accf527] # to [07e0797d91191e7eb2004e4720ac710a02f2df02] # # patch "roster_merge.hh" # from [4ec07d04f5be97555137df722f44f990aa560a91] # to [cc362cd4a0b539c2fbdfd892a9a171586fc84d5c] # # patch "tests/resolve_duplicate_name_conflict/__driver__.lua" # from [3d62e380338471ba08f7a7fb7b186bbfa259ba76] # to [37b9d15331a7f470419dbc20e416c0142c45237c] # ============================================================ --- tests/resolve_conflict_all_resolutions/__driver__.lua 709ab05fab5b76ebcbe76b2a9fdf12307f690522 +++ tests/resolve_conflict_all_resolutions/__driver__.lua 709ab05fab5b76ebcbe76b2a9fdf12307f690522 @@ -0,0 +1,67 @@ +-- Test setting conflict resolutions in a conflict file + +mtn_setup() + +-- Generate a conflicts file, with one conflict for each type of +-- resolution list of resolutions is in roster_merge.cc, syms +-- declaration; any sym that starts with "resolved". +-- +-- resolution file +-- resolved_drop_left checkout_left.sh abe +-- resolved_drop_right checkout_right.sh beth +-- resolved_rename_right thermostat.c -> thermostat-honeywell.c +-- resolved_rename_left thermostat.c -> thermostat-westinghouse.c +-- resolved_user randomfile +-- resolved_user_left checkout_left.sh beth +-- resolved_user_right checkout_right.sh abe +-- +-- We can't set 'resolved_internal'. + +addfile("randomfile", "blah blah blah") +commit() +base = base_revision() + +writefile("randomfile", "randomfile abe 1") +addfile("checkout_left.sh", "checkout_left.sh abe 1") +addfile("checkout_right.sh", "checkout_right.sh abe 1") +addfile("thermostat.c", "thermostat westinghouse") +commit("testbranch", "abe_1") +abe_1 = base_revision() + +revert_to(base) + +writefile("randomfile", "randomfile beth 1") +addfile("checkout_left.sh", "checkout_left.sh beth 1") +addfile("checkout_right.sh", "checkout_right.sh beth 1") +addfile("thermostat.c", "thermostat honeywell") +commit("testbranch", "beth_1") +beth_1 = base_revision() + +check (mtn("automate", "show_conflicts"), 0, true, nil) +canonicalize("stdout") +check(samefilestd("conflicts-1", "stdout")) + +-- Save the conflicts file so we can edit it +mkdir("resolutions") +rename("stdout", "resolutions/conflicts") + +-- 'resolve_conflict' specifies a resolution for the first unresolved +-- conflict in the file. +resolution = "resolved_drop_left\n resolved_user_right \"resolutions/checkout_left.sh\"" +check(mtn("resolve_conflict", "resolutions/conflicts", resolution), 0, nil, nil) + +resolution = "resolved_rename_left \"thermostat-westinghouse.c\"\n resolved_rename_right \"thermostat-honeywell.c\"" +check(mtn("resolve_conflict", "resolutions/conflicts", resolution), 0, nil, nil) + +check(samefilestd("conflicts-2", "resolutions/conflicts")) + +-- This succeeds +check(mtn("merge", "--resolve-conflicts-file", "resolutions/conflicts"), 0, true, nil) +canonicalize("stdout") +check(samefilestd("merge-1", "stdout")) + +check(mtn("update"), 0, nil, false) + +check("checkout_left.sh beth 2" == readfile("checkout_left.sh")) + +-- end of file ============================================================ --- tests/resolve_conflict_all_resolutions/conflicts-1 699148173160c1d22938da045b8bbe737f7b518f +++ tests/resolve_conflict_all_resolutions/conflicts-1 699148173160c1d22938da045b8bbe737f7b518f @@ -0,0 +1,19 @@ + left [1337cb1059c4bc3e376b14381b43e9383c654da1] + right [d5f1dd136c86b5bbd5e71b0c3365667e328af492] +ancestor [b3ac8a77cee78263b66800c635441ecb1f259a42] + + conflict duplicate_name + left_type "added file" + left_name "checkout.sh" + left_file_id [61b8d4fb0e5d78be111f691b955d523c782fa92e] + right_type "added file" + right_name "checkout.sh" +right_file_id [dd6805ae36432d6edcbdff6ea578ea981ffa2144] + + conflict duplicate_name + left_type "added file" + left_name "thermostat.c" + left_file_id [4cdcec6fa2f9d5c075d5b80d03c708c8e4801196] + right_type "added file" + right_name "thermostat.c" +right_file_id [7e9f2712c5d3570815f1546772d9119474d32afc] ============================================================ --- tests/resolve_duplicate_name_conflict/conflicts-1 699148173160c1d22938da045b8bbe737f7b518f +++ tests/resolve_duplicate_name_conflict/conflicts-1 699148173160c1d22938da045b8bbe737f7b518f @@ -0,0 +1,19 @@ + left [1337cb1059c4bc3e376b14381b43e9383c654da1] + right [d5f1dd136c86b5bbd5e71b0c3365667e328af492] +ancestor [b3ac8a77cee78263b66800c635441ecb1f259a42] + + conflict duplicate_name + left_type "added file" + left_name "checkout.sh" + left_file_id [61b8d4fb0e5d78be111f691b955d523c782fa92e] + right_type "added file" + right_name "checkout.sh" +right_file_id [dd6805ae36432d6edcbdff6ea578ea981ffa2144] + + conflict duplicate_name + left_type "added file" + left_name "thermostat.c" + left_file_id [4cdcec6fa2f9d5c075d5b80d03c708c8e4801196] + right_type "added file" + right_name "thermostat.c" +right_file_id [7e9f2712c5d3570815f1546772d9119474d32afc] ============================================================ --- cmd_merging.cc 17336c78322596cd4cbce0492c646acf9fcf9ded +++ cmd_merging.cc 1641d6a228ca07cc5b747ed7586b72cfab7ead48 @@ -870,8 +870,8 @@ show_conflicts_core (database & db, *r_roster, r_marking, r_uncommon_ancestors, result); - // note that left and right are in the order specified on the command line - // they are not in lexical order as they are with other merge commands so + // Note that left and right are in the order specified on the command line. + // They are not in lexical order as they are with other merge commands so // they may appear swapped here. The user may have done that deliberately, // especially via automate, so we don't sort them here. @@ -1002,7 +1002,29 @@ CMD_AUTOMATE(show_conflicts, N_("[LEFT_R show_conflicts_core(db, app.lua, l_id, r_id, true, output); } -CMD_AUTOMATE(file_merge, N_("[LEFT_REVID LEFT_FILENAME RIGHT_REVID RIGHT_FILENAME]"), +CMD(resolve_conflict, "resolve_conflict", "", CMD_REF(tree), + N_("CONFLICTS-FILE CONFLICT"), + N_("Set the resolution for the first conflict in the conflicts file."), + "", + options::opts::none) +{ + database db(app); + roster_merge_result roster; + revision_id ancestor_rid, left_rid, right_rid; + boost::shared_ptr left_roster = shared_ptr(new roster_t()); + boost::shared_ptr right_roster = shared_ptr(new roster_t()); + marking_map left_marking, right_marking; + + roster.clear(); // default constructor doesn't do this. + + roster.read_conflict_file(db, idx(args,0)(), ancestor_rid, left_rid, right_rid, + *left_roster, left_marking, *right_roster, right_marking); + roster.set_first_conflict(idx(args,1)()); + roster.write_conflict_file(db, app.lua, idx(args,0)(), ancestor_rid, left_rid, right_rid, + left_roster, left_marking, right_roster, right_marking); +} + +CMD_AUTOMATE(file_merge, N_("LEFT_REVID LEFT_FILENAME RIGHT_REVID RIGHT_FILENAME"), N_("Prints the results of the internal line merger, given two child revisions and file names"), "", options::opts::none) ============================================================ --- roster_merge.cc 0b686863088ab9897a3412e0feb7041d0accf527 +++ roster_merge.cc 07e0797d91191e7eb2004e4720ac710a02f2df02 @@ -217,6 +217,7 @@ namespace namespace syms { + symbol const ancestor("ancestor"); symbol const ancestor_file_id("ancestor_file_id"); symbol const ancestor_name("ancestor_name"); symbol const attr_name("attr_name"); @@ -226,6 +227,7 @@ namespace symbol const directory_loop_created("directory_loop_created"); symbol const duplicate_name("duplicate_name"); symbol const invalid_name("invalid_name"); + symbol const left("left"); symbol const left_attr_state("left_attr_state"); symbol const left_attr_value("left_attr_value"); symbol const left_file_id("left_file_id"); @@ -240,6 +242,7 @@ namespace symbol const resolved_rename_left("resolved_rename_left"); symbol const resolved_rename_right("resolved_rename_right"); symbol const resolved_user("resolved_user"); + symbol const right("right"); symbol const right_attr_state("right_attr_state"); symbol const right_attr_value("right_attr_value"); symbol const right_file_id("right_file_id"); @@ -1465,138 +1468,399 @@ namespace resolve_conflicts } } +static char const * const conflicts_mismatch_msg = "conflicts file does not match current conflicts"; +static char const * const conflict_resolution_not_supported_msg = "%s is not a supported conflict resolution for %s"; +static char const * const conflict_extra = "extra chars at end of conflict"; + static void -parse_duplicate_name_conflicts(basic_io::parser & pars, - std::vector & conflicts, - roster_t const & left_roster, - roster_t const & right_roster) +read_duplicate_name_conflict(basic_io::parser & pars, + duplicate_name_conflict & conflict, + roster_t const & left_roster, + roster_t const & right_roster) { + string tmp; + string left_name, right_name; + + // we don't care what type of file/dir these are; the right type will get + // written back out. + pars.esym(syms::left_type); pars.str(); + pars.esym (syms::left_name); pars.str(left_name); + pars.esym(syms::left_file_id); pars.hex(); + + pars.esym(syms::right_type); pars.str(); + pars.esym (syms::right_name); pars.str(right_name); + pars.esym(syms::right_file_id); pars.hex(); + + conflict.left_nid = left_roster.get_node (file_path_internal (left_name))->self; + conflict.right_nid = right_roster.get_node (file_path_internal (right_name))->self; + + // check for a resolution + while ((!pars.symp (syms::conflict)) && pars.tok.in.lookahead != EOF) + { + if (pars.symp (syms::resolved_rename_left)) + { + conflict.left_resolution.first = resolve_conflicts::rename; + pars.sym(); + // File path is specified by the user, so it's an external string. + conflict.left_resolution.second = file_path_external (utf8(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_external (utf8(pars.token)); + pars.str(); + } + else + N(false, F(conflict_resolution_not_supported_msg) % pars.token % "duplicate_name"); + } + +} // read_duplicate_name_conflict + +static void +read_duplicate_name_conflicts(basic_io::parser & pars, + std::vector & conflicts, + roster_t const & left_roster, + roster_t const & right_roster) +{ + while (pars.tok.in.lookahead != EOF && pars.symp(syms::duplicate_name)) + { + duplicate_name_conflict conflict; + + pars.sym(); + + read_duplicate_name_conflict(pars, conflict, left_roster, right_roster); + + conflicts.push_back(conflict); + + if (pars.tok.in.lookahead != EOF) + pars.esym (syms::conflict); + } +} // read_duplicate_name_conflicts + +static void +validate_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; + duplicate_name_conflict & merge_conflict = *i; + duplicate_name_conflict file_conflict; pars.esym(syms::duplicate_name); - node_id left_nid, right_nid; - string left_name, right_name; + read_duplicate_name_conflict(pars, file_conflict, left_roster, right_roster); - pars.esym(syms::left_type); pars.str(); - pars.esym (syms::left_name); pars.str(left_name); - pars.esym(syms::left_file_id); pars.hex(); + // Note that we do not confirm the file ids. + N(merge_conflict.left_nid == file_conflict.left_nid && + merge_conflict.right_nid == file_conflict.right_nid, + F(conflicts_mismatch_msg)); - pars.esym(syms::right_type); pars.str(); - pars.esym (syms::right_name); pars.str(right_name); - pars.esym(syms::right_file_id); pars.hex(); + merge_conflict.left_resolution = file_conflict.left_resolution; + merge_conflict.right_resolution = file_conflict.right_resolution; + + if (pars.tok.in.lookahead != EOF) + pars.esym (syms::conflict); + else + { + std::vector::iterator tmp = i; + N(++tmp == conflicts.end(), F(conflicts_mismatch_msg)); + } + } +} // validate_duplicate_name_conflicts - left_nid = left_roster.get_node (file_path_internal (left_name))->self; - right_nid = right_roster.get_node (file_path_internal (right_name))->self; +static void +read_file_content_conflict(basic_io::parser & pars, + file_content_conflict & conflict, + roster_t const & left_roster, + roster_t const & right_roster) +{ + string tmp; + string left_name, right_name, result_name; - // Note that we cannot confirm the file ids. - N(left_nid == conflict.left_nid && right_nid == conflict.right_nid, - F("conflicts file does not match current conflicts: (duplicate_name, left %s, right %s") - % left_name % right_name); + pars.esym(syms::node_type); pars.str(tmp); I(tmp == "file"); - // check for a resolution - while ((!pars.symp (syms::conflict)) && pars.tok.in.lookahead != EOF) + pars.esym (syms::ancestor_name); pars.str(); + pars.esym (syms::ancestor_file_id); pars.hex(); + + pars.esym (syms::left_name); pars.str(left_name); + pars.esym(syms::left_file_id); pars.hex(); + + pars.esym (syms::right_name); pars.str(right_name); + pars.esym(syms::right_file_id); pars.hex(); + + conflict.nid = left_roster.get_node (file_path_internal (left_name))->self; + I(conflict.nid = right_roster.get_node (file_path_internal (right_name))->self); + + // check for a resolution + if ((!pars.symp (syms::conflict)) && pars.tok.in.lookahead != EOF) + { + if (pars.symp (syms::resolved_internal)) { - if (pars.symp (syms::resolved_rename_left)) - { - conflict.left_resolution.first = resolve_conflicts::rename; - pars.sym(); - // File path is specified by the user, so it's an external string. - conflict.left_resolution.second = file_path_external (utf8(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_external (utf8(pars.token)); - pars.str(); - } - else - N(false, F("%s is not a supported conflict resolution for %s") % pars.token % "duplicate_name"); + conflict.resolution.first = resolve_conflicts::content_internal; + pars.sym(); } + else if (pars.symp (syms::resolved_user)) + { + conflict.resolution.first = resolve_conflicts::content_user; + pars.sym(); + conflict.resolution.second = pars.token; + pars.str(); + } + else + N(false, F(conflict_resolution_not_supported_msg) % pars.token % "file_content"); + } +} // read_file_content_conflict + +static void +read_file_content_conflicts(basic_io::parser & pars, + std::vector & conflicts, + roster_t const & left_roster, + roster_t const & right_roster) +{ + while (pars.tok.in.lookahead != EOF && pars.symp(syms::content)) + { + file_content_conflict conflict; + + pars.sym(); + + read_file_content_conflict(pars, conflict, left_roster, right_roster); + + conflicts.push_back(conflict); + 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 +} // read_file_content_conflicts static void -parse_file_content_conflicts(basic_io::parser & pars, - std::vector & conflicts, - roster_t const & left_roster, - roster_t const & right_roster) +validate_file_content_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) { - string tmp; - node_id left_nid, right_nid; - string left_name, right_name, result_name; + file_content_conflict & merge_conflict = *i; + file_content_conflict file_conflict; - file_content_conflict & conflict = *i; + pars.esym(syms::content); - pars.esym(syms::content); + read_file_content_conflict(pars, file_conflict, left_roster, right_roster); - pars.esym(syms::node_type); - pars.str(tmp); - I(tmp == "file"); + N(merge_conflict.nid == file_conflict.nid, + F("conflicts_mismatch_msg")); - pars.esym (syms::ancestor_name); pars.str(); - pars.esym (syms::ancestor_file_id); pars.hex(); + merge_conflict.resolution = file_conflict.resolution; - pars.esym (syms::left_name); pars.str(left_name); - pars.esym(syms::left_file_id); pars.hex(); + 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")); + } + } +} // validate_file_content_conflicts - pars.esym (syms::right_name); pars.str(right_name); - pars.esym(syms::right_file_id); pars.hex(); +static void +read_conflict_file_core(basic_io::parser pars, + roster_t const & left_roster, + roster_t const & right_roster, + roster_merge_result & result, + bool validate) +{ + pars.esym (syms::conflict); - left_nid = left_roster.get_node (file_path_internal (left_name))->self; - right_nid = right_roster.get_node (file_path_internal (right_name))->self; + // If we are validating, 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. - N(left_nid == conflict.nid & right_nid == conflict.nid, - F("conflicts file does not match current conflicts: (file_content, left %s, right %s") - % left_name % right_name); + // resolve_merge_conflicts should not call us if there are any conflicts + // for which we don't currently support resolutions; assert that. - // check for a resolution - if ((!pars.symp (syms::conflict)) && pars.tok.in.lookahead != EOF) + 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); + + // These are the ones we know how to resolve. + + if (validate) + { + validate_duplicate_name_conflicts(pars, result.duplicate_name_conflicts, left_roster, right_roster); + validate_file_content_conflicts(pars, result.file_content_conflicts, left_roster, right_roster); + } + else + { + read_duplicate_name_conflicts(pars, result.duplicate_name_conflicts, left_roster, right_roster); + read_file_content_conflicts(pars, result.file_content_conflicts, left_roster, right_roster); + } + + N(pars.tok.in.lookahead == EOF, F("extra data in file")); +} // read_conflict_file_core + +void +roster_merge_result::read_conflict_file(database & db, + std::string const file_name, + revision_id & ancestor_rid, + revision_id & left_rid, + revision_id & right_rid, + roster_t & left_roster, + marking_map & left_marking, + roster_t & right_roster, + marking_map & right_marking) +{ + data dat; + + read_data (system_path(utf8(file_name)), dat); + + basic_io::input_source src(dat(), file_name); + basic_io::tokenizer tok(src); + basic_io::parser pars(tok); + std::string temp; + + // Read left, right, ancestor. + pars.esym(syms::left); + pars.hex(temp); + left_rid = revision_id(decode_hexenc(temp)); + pars.esym(syms::right); + pars.hex(temp); + right_rid = revision_id(decode_hexenc(temp)); + pars.esym(syms::ancestor); + pars.hex(temp); + ancestor_rid = revision_id(decode_hexenc(temp)); + + db.get_roster(left_rid, left_roster, left_marking); + db.get_roster(right_rid, right_roster, right_marking); + + read_conflict_file_core(pars, left_roster, right_roster, *this, false); + +} // roster_merge_result::read_conflict_file + +void +roster_merge_result::set_first_conflict(std::string conflict) +{ + basic_io::input_source src(conflict, "resolve_conflicts string"); + basic_io::tokenizer tok(src); + basic_io::parser pars(tok); + + // To find the first conflict, go thru the conflicts we know how to + // resolve in the same order we output them. + for (std::vector::iterator i = duplicate_name_conflicts.begin(); + i != duplicate_name_conflicts.end(); + ++i) + { + duplicate_name_conflict & conflict = *i; + + if (conflict.left_resolution.first == resolve_conflicts::none) { - if (pars.symp (syms::resolved_internal)) + for (int i = 1; i <= 2; i++) { - conflict.resolution.first = resolve_conflicts::content_internal; - pars.sym(); - } - else if (pars.symp (syms::resolved_user)) + if (pars.symp (syms::resolved_rename_left)) + { + conflict.left_resolution.first = resolve_conflicts::rename; + pars.sym(); + conflict.left_resolution.second = file_path_external (utf8(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_external (utf8(pars.token)); + pars.str(); + } + else + N(false, F(conflict_resolution_not_supported_msg) % pars.token % "duplicate_name"); + } + + N(pars.tok.in.lookahead == EOF, F(conflict_extra)); + return; + } + } + + for (std::vector::iterator i = file_content_conflicts.begin(); + i != file_content_conflicts.end(); + ++i) + { + file_content_conflict & conflict = *i; + + if (conflict.resolution.first == resolve_conflicts::none) + { + if (pars.symp (syms::resolved_user)) { - conflict.resolution.first = resolve_conflicts::content_user; + conflict.resolution.first = resolve_conflicts::content_user; pars.sym(); conflict.resolution.second = pars.token; pars.str(); } else - N(false, F("%s is not a supported conflict resolution for %s") % pars.token % "file_content"); + { + // We don't allow the user to specify 'resolved_internal'; that + // is only done by automate show_conflicts. + N(false, F(conflict_resolution_not_supported_msg) % pars.token % "file_content"); + } + + N(pars.tok.in.lookahead == EOF, F(conflict_extra)); + return; } + } - 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_file_content_conflicts + N(false, F("no resolvable yet unresolved conflicts")); + +} // roster_merge_result::set_first_conflict + +void +roster_merge_result::write_conflict_file(database & db, + lua_hooks & lua, + std::string file_name, + revision_id const & ancestor_rid, + revision_id const & left_rid, + revision_id const & right_rid, + boost::shared_ptr left_roster, + marking_map const & left_marking, + boost::shared_ptr right_roster, + marking_map const & right_marking) +{ + std::ostringstream output; + + content_merge_database_adaptor adaptor(db, left_rid, right_rid, + left_marking, right_marking); + + adaptor.cache_roster (left_rid, left_roster); + adaptor.cache_roster (right_rid, right_roster); + { + // match format in cmd_merging.cc show_conflicts_core + basic_io::stanza st; + basic_io::printer pr; + st.push_binary_pair(syms::left, left_rid.inner()); + st.push_binary_pair(syms::right, right_rid.inner()); + st.push_binary_pair(syms::ancestor, adaptor.lca.inner()); + pr.print_stanza(st); + output.write(pr.buf.data(), pr.buf.size()); + } + + report_duplicate_name_conflicts(*left_roster, *right_roster, adaptor, false, output); + report_file_content_conflicts(lua, *left_roster, *right_roster, adaptor, false, output); + + data dat(output.str()); + write_data(system_path(file_name), dat, system_path("_MTN")); + +} // roster_merge_result::write_conflict_file + static void parse_resolve_conflicts_str(basic_io::parser & pars, roster_merge_result & result) { @@ -1651,7 +1915,7 @@ parse_resolve_conflicts_str(basic_io::pa N(false, F("%s is not a supported conflict resolution") % pars.token); } // while -} +} // parse_resolve_conflicts_str void parse_resolve_conflicts_opts (options const & opts, @@ -1679,10 +1943,7 @@ parse_resolve_conflicts_opts (options co 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); + read_data (system_path(opts.resolve_conflicts_file()), dat); basic_io::input_source src(dat(), opts.resolve_conflicts_file()); basic_io::tokenizer tok(src); @@ -1695,33 +1956,8 @@ parse_resolve_conflicts_opts (options co 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. - - // resolve_merge_conflicts should not call us if there are any - // conflicts for which we don't currently support resolutions; assert - // that - - 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); - - // These are the ones we know how to resolve. - - parse_duplicate_name_conflicts(pars, result.duplicate_name_conflicts, left_roster, right_roster); - parse_file_content_conflicts(pars, result.file_content_conflicts, left_roster, right_roster); - - if (src.lookahead != EOF) - pars.err("extra conflicts in file"); + + read_conflict_file_core (pars, left_roster, right_roster, result, true); } else resolutions_given = false; @@ -1871,17 +2107,7 @@ roster_merge_result::resolve_file_conten adaptor.get_version(conflict.left, left_data); adaptor.get_version(conflict.right, right_data); - // FIXME: there aught to be a simpler way to read an arbitrary file! - if (bookkeeping_path::external_string_is_bookkeeping_path(utf8(conflict.resolution.second))) - { - bookkeeping_path p(conflict.resolution.second); - read_data(p, result_raw_data); - } - else - { - file_path p(file_path_external(utf8(conflict.resolution.second))); - read_data(p, result_raw_data); - } + read_data(system_path(conflict.resolution.second), result_raw_data); result_data = file_data(result_raw_data); calculate_ident(result_data, result_id); ============================================================ --- roster_merge.hh 4ec07d04f5be97555137df722f44f990aa560a91 +++ roster_merge.hh cc362cd4a0b539c2fbdfd892a9a171586fc84d5c @@ -233,6 +233,33 @@ struct roster_merge_result roster_t const & right_roster, content_merge_adaptor & adaptor); void clear(); + + // Conflict file editing + + // If validate, compare file contents to existing conflicts, and add + // resolutions. Otherwise just read into conflicts. + void read_conflict_file(database & db, + std::string file_name, + revision_id & ancestor_rid, + revision_id & left_rid, + revision_id & right_rid, + roster_t & left_roster, + marking_map & left_marking, + roster_t & right_roster, + marking_map & r_marking); + + void set_first_conflict(std::string conflict); + + void write_conflict_file(database & db, + lua_hooks & lua, + std::string file_name, + revision_id const & ancestor_rid, + revision_id const & left_rid, + revision_id const & right_rid, + boost::shared_ptr left_roster, + marking_map const & left_marking, + boost::shared_ptr right_roster, + marking_map const & right_marking); }; template <> void dump(roster_merge_result const & result, std::string & out); ============================================================ --- tests/resolve_duplicate_name_conflict/__driver__.lua 3d62e380338471ba08f7a7fb7b186bbfa259ba76 +++ tests/resolve_duplicate_name_conflict/__driver__.lua 37b9d15331a7f470419dbc20e416c0142c45237c @@ -8,7 +8,6 @@ mtn_setup() -- thermostat-westinghouse.c and thermostat-honeywell.c mtn_setup() -include ("common/test_utils_inventory.lua") -- Get a non-empty base revision addfile("randomfile", "blah blah blah")