# # # add_file "cmd_conflicts.cc" # content [c4100c05922de53abdd3312733827efd0c45e6ec] # # add_file "tests/resolve_duplicate_name_conflict/conflicts-resolved" # content [65a583cc0d3a3f50cc7e24bac786ef88027671c6] # # patch "Makefile.am" # from [4eba20c4f0654b122564fe6845edbc7f5cd55f18] # to [658abc8efdc2afc5eb7ceded3a8d8279dd2b0056] # # patch "cmd.hh" # from [f0e4d46973929a8c7b70b0e12593de40e7aec203] # to [dd969e8f9644c72b50fb94c56cd5655d095f160d] # # patch "cmd_merging.cc" # from [1641d6a228ca07cc5b747ed7586b72cfab7ead48] # to [d4113f1b227d26e48ca23fce88ee1b9fd929bedf] # # patch "options_list.hh" # from [154daf8d9f4ab4673441dc8e30bf7531fb7fe3eb] # to [872b69d45a0ac08b4f8cc04d9fc7c294a132d579] # # patch "roster_merge.cc" # from [07e0797d91191e7eb2004e4720ac710a02f2df02] # to [5c4a8791df3ca572589f2c7e91a565ed8f99dd32] # # patch "roster_merge.hh" # from [cc362cd4a0b539c2fbdfd892a9a171586fc84d5c] # to [3bbdacaa0de545b88f71e178ca7931616ea0eb83] # # patch "tests/resolve_conflict_all_resolutions/__driver__.lua" # from [709ab05fab5b76ebcbe76b2a9fdf12307f690522] # to [2c013201e50e953df1ca9b55ad726b4c633c9610] # # patch "tests/resolve_duplicate_name_conflict/__driver__.lua" # from [37b9d15331a7f470419dbc20e416c0142c45237c] # to [320c07fc8141d2eac97f2f6a71e6b9d4754799c9] # ============================================================ --- cmd_conflicts.cc c4100c05922de53abdd3312733827efd0c45e6ec +++ cmd_conflicts.cc c4100c05922de53abdd3312733827efd0c45e6ec @@ -0,0 +1,290 @@ +// Copyright (C) 2008 Stephen Leake +// +// This program is made available under the GNU GPL version 2.0 or +// greater. See the accompanying file COPYING for details. +// +// This program is distributed WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. + +#include "base.hh" + +#include "app_state.hh" +#include "cmd.hh" +#include "database.hh" +#include "roster_merge.hh" + +CMD_GROUP(conflicts, "conflicts", "", CMD_REF(tree), + N_("Commands for conflict resolutions."), + ""); + +struct conflicts_t +{ + roster_merge_result result; + revision_id ancestor_rid, left_rid, right_rid; + boost::shared_ptr left_roster; + boost::shared_ptr right_roster; + marking_map left_marking, right_marking; + + conflicts_t(database & db, system_path file): + left_roster(boost::shared_ptr(new roster_t())), + right_roster(boost::shared_ptr(new roster_t())) + { + result.clear(); // default constructor doesn't do this. + + result.read_conflict_file(db, file, ancestor_rid, left_rid, right_rid, + *left_roster, left_marking, + *right_roster, right_marking); + }; + + void write (database & db, lua_hooks & lua, system_path file) + { + result.write_conflict_file + (db, lua, file, ancestor_rid, left_rid, right_rid, + left_roster, left_marking, right_roster, right_marking); + }; +}; + +static void +show_first_conflict(conflicts_t conflicts) +{ + // 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 = conflicts.result.duplicate_name_conflicts.begin(); + i != conflicts.result.duplicate_name_conflicts.end(); + ++i) + { + duplicate_name_conflict & conflict = *i; + + if (conflict.left_resolution.first == resolve_conflicts::none || + conflict.right_resolution.first == resolve_conflicts::none) + { + file_path left_name; + conflicts.left_roster->get_name(conflict.left_nid, left_name); + P(F("duplicate_name %s") % left_name); + P(F("possible resolutions:")); + + if (conflict.left_resolution.first == resolve_conflicts::none) + { + P(F("resolve_first_left drop")); + P(F("resolve_first_left rename \"name\"")); + P(F("resolve_first_left user \"name\"")); + } + + if (conflict.right_resolution.first == resolve_conflicts::none) + { + P(F("resolve_first_right drop")); + P(F("resolve_first_right rename \"name\"")); + P(F("resolve_first_right user \"name\"")); + } + return; + } + } + + for (std::vector::iterator i = conflicts.result.file_content_conflicts.begin(); + i != conflicts.result.file_content_conflicts.end(); + ++i) + { + file_content_conflict & conflict = *i; + + if (conflict.resolution.first == resolve_conflicts::none) + { + file_path name; + conflicts.left_roster->get_name(conflict.nid, name); + P(F("content %s") % name); + P(F("possible resolutions:")); + P(F("resolve user \"file_name\"")); + return; + } + } + + N(false, F("no resolvable yet unresolved conflicts")); + +} // show_first_conflict + +enum side_t {left, right, neither}; +static char const * const conflict_resolution_not_supported_msg = "%s is not a supported conflict resolution for %s"; + +static void +set_duplicate_name_conflict(resolve_conflicts::file_resolution_t & resolution, + args_vector const & args) +{ + if ("drop" == idx(args, 0)()) + { + N(args.size() == 1, F("too many arguments")); + resolution.first = resolve_conflicts::drop; + } + else if ("rename" == idx(args, 0)()) + { + N(args.size() == 2, F("wrong number of arguments")); + resolution.first = resolve_conflicts::rename; + resolution.second = resolve_conflicts::new_file_path(idx(args,1)()); + } + else if ("user" == idx(args, 0)()) + { + N(args.size() == 2, F("wrong number of arguments")); + resolution.first = resolve_conflicts::content_user; + resolution.second = resolve_conflicts::new_optimal_path(idx(args,1)()); + } + else + N(false, F(conflict_resolution_not_supported_msg) % idx(args,0) % "duplicate_name"); + +} //set_duplicate_name_conflict + +static void +set_first_conflict(side_t side, conflicts_t & conflicts, args_vector const & args) +{ + if (side != neither) + { + for (std::vector::iterator i = conflicts.result.duplicate_name_conflicts.begin(); + i != conflicts.result.duplicate_name_conflicts.end(); + ++i) + { + duplicate_name_conflict & conflict = *i; + + switch (side) + { + case left: + if (conflict.left_resolution.first == resolve_conflicts::none) + { + set_duplicate_name_conflict(conflict.left_resolution, args); + return; + } + break; + + case right: + if (conflict.right_resolution.first == resolve_conflicts::none) + { + set_duplicate_name_conflict(conflict.right_resolution, args); + return; + } + break; + + case neither: + I(false); + } + } + } + + if (side == neither) + { + for (std::vector::iterator i = conflicts.result.file_content_conflicts.begin(); + i != conflicts.result.file_content_conflicts.end(); + ++i) + { + file_content_conflict & conflict = *i; + + if (conflict.resolution.first == resolve_conflicts::none) + { + if ("user" == idx(args,0)()) + { + N(args.size() == 2, F("wrong number of arguments")); + + conflict.resolution.first = resolve_conflicts::content_user; + conflict.resolution.second = resolve_conflicts::new_optimal_path(idx(args,1)()); + } + else + { + // 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) % idx(args,0) % "file_content"); + } + return; + } + } + } + + switch (side) + { + case left: + N(false, F("no resolvable yet unresolved left side conflicts")); + break; + + case right: + N(false, F("no resolvable yet unresolved right side conflicts")); + break; + + case neither: + N(false, F("no resolvable yet unresolved single-file conflicts")); + break; + } + +} // set_first_conflict + + +/// commands + +// CMD(store) is in cmd_merging, since it needs access to +// show_conflicts_core, and doesn't need conflicts_t. + +CMD(show_first, "show_first", "", CMD_REF(conflicts), + "", + N_("Show the first conflict in the conflicts file, and possible resolutions."), + "", + options::opts::conflicts_opts) +{ + database db(app); + conflicts_t conflicts (db, app.opts.conflicts_file); + + show_first_conflict(conflicts); +} + +CMD(resolve_first, "resolve_first", "", CMD_REF(conflicts), + N_("RESOLUTION"), + N_("Set the resolution for the first single-file conflict."), + "", + options::opts::conflicts_opts) +{ + database db(app); + conflicts_t conflicts (db, app.opts.conflicts_file); + + set_first_conflict(neither, conflicts, args); + + conflicts.write (db, app.lua, app.opts.conflicts_file); +} + +CMD(resolve_first_left, "resolve_first_left", "", CMD_REF(conflicts), + N_("RESOLUTION"), + N_("Set the left resolution for the first two-file conflict."), + "", + options::opts::conflicts_opts) +{ + database db(app); + conflicts_t conflicts (db, app.opts.conflicts_file); + + set_first_conflict(left, conflicts, args); + + conflicts.write (db, app.lua, app.opts.conflicts_file); +} + +CMD(resolve_first_right, "resolve_first_right", "", CMD_REF(conflicts), + N_("RESOLUTION"), + N_("Set the right resolution for the first two-file conflict."), + "", + options::opts::conflicts_opts) +{ + database db(app); + conflicts_t conflicts (db, app.opts.conflicts_file); + + set_first_conflict(right, conflicts, args); + + conflicts.write (db, app.lua, app.opts.conflicts_file); +} + +CMD(clean, "clean", "", CMD_REF(conflicts), + N_(""), + N_("Delete any bookkeeping files related to conflict resolution."), + "", + options::opts::none) +{ + bookkeeping_path conflicts_file("conflicts"); + bookkeeping_path resolutions_dir("resolutions"); + + if (path_exists(conflicts_file)) + delete_file(conflicts_file); + + if (path_exists(resolutions_dir)) + delete_dir_recursive(resolutions_dir); +} + +// end of file ============================================================ --- tests/resolve_duplicate_name_conflict/conflicts-resolved 65a583cc0d3a3f50cc7e24bac786ef88027671c6 +++ tests/resolve_duplicate_name_conflict/conflicts-resolved 65a583cc0d3a3f50cc7e24bac786ef88027671c6 @@ -0,0 +1,23 @@ + 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] + resolved_drop_left +resolved_user_right "_MTN/resolutions/checkout.sh" + + 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] + resolved_rename_left "thermostat-westinghouse.c" +resolved_rename_right "thermostat-honeywell.c" ============================================================ --- Makefile.am 4eba20c4f0654b122564fe6845edbc7f5cd55f18 +++ Makefile.am 658abc8efdc2afc5eb7ceded3a8d8279dd2b0056 @@ -4,7 +4,7 @@ CMD_SOURCES = \ CMD_SOURCES = \ cmd.hh cmd_netsync.cc cmd_list.cc cmd_packet.cc cmd_key_cert.cc \ cmd_merging.cc cmd_db.cc cmd_diff_log.cc cmd_ws_commit.cc \ - cmd_othervcs.cc cmd_automate.cc cmd_files.cc + cmd_othervcs.cc cmd_automate.cc cmd_files.cc cmd_conflicts.cc SANITY_CORE_SOURCES = \ sanity.cc sanity.hh quick_alloc.hh vector.hh base.hh \ ============================================================ --- cmd.hh f0e4d46973929a8c7b70b0e12593de40e7aec203 +++ cmd.hh dd969e8f9644c72b50fb94c56cd5655d095f160d @@ -279,6 +279,7 @@ CMD_FWD_DECL(automation); CMD_FWD_DECL(__root__); CMD_FWD_DECL(automation); +CMD_FWD_DECL(conflicts); CMD_FWD_DECL(database); CMD_FWD_DECL(debug); CMD_FWD_DECL(informative); ============================================================ --- cmd_merging.cc 1641d6a228ca07cc5b747ed7586b72cfab7ead48 +++ cmd_merging.cc d4113f1b227d26e48ca23fce88ee1b9fd929bedf @@ -947,6 +947,40 @@ CMD(show_conflicts, "show_conflicts", "" show_conflicts_core(db, app.lua, l_id, r_id, false, std::cout); } +static void get_conflicts_rids(args_vector const & args, + database & db, + project_t & project, + app_state & app, + revision_id & left_rid, + revision_id & right_rid) +{ + if (args.size() == 0) + { + // get ids from heads + N(app.opts.branchname() != "", + F("please specify a branch, with --branch=BRANCH")); + + set heads; + project.get_branch_heads(app.opts.branchname, heads, + app.opts.ignore_suspend_certs); + + N(heads.size() >= 2, + F("branch '%s' has only 1 head; must be at least 2 for conflicts") % app.opts.branchname); + + revpair p = find_heads_to_merge (db, heads); + left_rid = p.first; + right_rid = p.second; + } + else if (args.size() == 2) + { + // get ids from args + complete(app.opts, app.lua, project, idx(args,0)(), left_rid); + complete(app.opts, app.lua, project, idx(args,1)(), right_rid); + } + else + N(false, F("wrong argument count")); +} + // Name: show_conflicts // Arguments: // Two revision ids (optional, determined from the workspace if not given; there must be exactly two heads) @@ -967,61 +1001,34 @@ CMD_AUTOMATE(show_conflicts, N_("[LEFT_R N_("Shows the conflicts between two revisions."), N_("If no arguments are given, LEFT_REVID and RIGHT_REVID default to the " "first two heads that would be chosen by the 'merge' command."), - options::opts::branch) + options::opts::branch | options::opts::ignore_suspend_certs) { database db(app); project_t project(db); revision_id l_id, r_id; - if (args.size() == 0) - { - // get ids from heads - N(app.opts.branchname() != "", - F("please specify a branch, with --branch=BRANCH")); - - set heads; - project.get_branch_heads(app.opts.branchname, heads, - app.opts.ignore_suspend_certs); - - N(heads.size() >= 2, - F("branch '%s' has %d heads; must be at least 2 for show_conflicts") % app.opts.branchname % heads.size()); - - revpair p = find_heads_to_merge (db, heads); - l_id = p.first; - r_id = p.second; - } - else if (args.size() == 2) - { - // get ids from args - complete(app.opts, app.lua, project, idx(args,0)(), l_id); - complete(app.opts, app.lua, project, idx(args,1)(), r_id); - } - else - N(false, F("wrong argument count")); - + get_conflicts_rids(args, db, project, app, l_id, r_id); show_conflicts_core(db, app.lua, l_id, r_id, true, output); } -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) +CMD(store, "store", "", CMD_REF(conflicts), + "[LEFT_REVID RIGHT_REVID]", + N_("Store the conflicts from merging two revisions."), + N_("If no arguments are given, LEFT_REVID and RIGHT_REVID default to the " + "first two heads that would be chosen by the 'merge' command."), + options::opts::branch | options::opts::conflicts_opts) { - 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; + database db(app); + project_t project(db); + revision_id left_id, right_id; - 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); + get_conflicts_rids(args, db, project, app, left_id, right_id); + + std::ostringstream output; + show_conflicts_core(db, app.lua, left_id, right_id, true, output); + + data dat(output.str()); + write_data(system_path(app.opts.conflicts_file), dat, system_path("_MTN")); } CMD_AUTOMATE(file_merge, N_("LEFT_REVID LEFT_FILENAME RIGHT_REVID RIGHT_FILENAME"), ============================================================ --- options_list.hh 154daf8d9f4ab4673441dc8e30bf7531fb7fe3eb +++ options_list.hh 872b69d45a0ac08b4f8cc04d9fc7c294a132d579 @@ -658,8 +658,20 @@ OPTION(resolve_conflicts_opts, resolve_c F("only one of --resolve-conflicts or --resolve-conflicts-file may be given")); resolve_conflicts = arg; } + +OPTSET(conflicts_opts) +OPTVAR(conflicts_opts, system_path, conflicts_file, system_path("_MTN/conflicts")) + +OPTION(conflicts_opts, conflicts_file, true, "conflicts-file", + gettext_noop("file in which to store conflicts")) +#ifdef option_bodies +{ + conflicts_file = system_path(utf8(arg)); +} #endif +#endif + // Local Variables: // mode: C++ // fill-column: 76 ============================================================ --- roster_merge.cc 07e0797d91191e7eb2004e4720ac710a02f2df02 +++ roster_merge.cc 5c4a8791df3ca572589f2c7e91a565ed8f99dd32 @@ -29,22 +29,48 @@ using std::string; using std::set; using std::string; -static char * const -image(resolve_conflicts::resolution_t resolution) +namespace resolve_conflicts { - switch (resolution) - { - case resolve_conflicts::none: - return "none"; - case resolve_conflicts::content_user: - return "content_user"; - case resolve_conflicts::content_internal: - return "content_internal"; - case resolve_conflicts::rename: - return "rename"; - default: - I(false); - } + boost::shared_ptr + new_file_path(std::string path) + { + return boost::shared_ptr(new file_path(file_path_external(utf8(path)))); + }; + + boost::shared_ptr + new_optimal_path(std::string path) + { + if (bookkeeping_path::external_string_is_bookkeeping_path(utf8(path))) + return boost::shared_ptr(new bookkeeping_path(path)); + else + try + { + return new_file_path(path); + } + catch (informative_failure &) + { + return boost::shared_ptr(new system_path(path)); + } + }; + + static char * const + image(resolve_conflicts::resolution_t resolution) + { + switch (resolution) + { + case resolve_conflicts::none: + return "none"; + case resolve_conflicts::content_user: + return "content_user"; + case resolve_conflicts::content_internal: + return "content_internal"; + case resolve_conflicts::rename: + return "rename"; + case resolve_conflicts::drop: + return "drop"; + } + I(false); // keep compiler happy + } } template <> void @@ -238,10 +264,14 @@ namespace symbol const node_type("node_type"); symbol const orphaned_directory("orphaned_directory"); symbol const orphaned_file("orphaned_file"); + symbol const resolved_drop_right("resolved_drop_right"); + symbol const resolved_drop_left("resolved_drop_left"); symbol const resolved_internal("resolved_internal"); symbol const resolved_rename_left("resolved_rename_left"); symbol const resolved_rename_right("resolved_rename_right"); symbol const resolved_user("resolved_user"); + symbol const resolved_user_left("resolved_user_left"); + symbol const resolved_user_right("resolved_user_right"); symbol const right("right"); symbol const right_attr_state("right_attr_state"); symbol const right_attr_value("right_attr_value"); @@ -461,7 +491,63 @@ put_attr_conflict (basic_io::stanza & st } } +enum side_t {left_side, right_side}; + static void +put_duplicate_name_resolution(basic_io::stanza & st, + side_t side, + resolve_conflicts::file_resolution_t const & resolution) +{ + switch (resolution.first) + { + case resolve_conflicts::none: + break; + + case resolve_conflicts::content_user: + switch (side) + { + case left_side: + st.push_str_pair(syms::resolved_user_left, resolution.second->as_external()); + break; + + case right_side: + st.push_str_pair(syms::resolved_user_right, resolution.second->as_external()); + break; + } + break; + + case resolve_conflicts::rename: + switch (side) + { + case left_side: + st.push_str_pair(syms::resolved_rename_left, resolution.second->as_external()); + break; + + case right_side: + st.push_str_pair(syms::resolved_rename_right, resolution.second->as_external()); + break; + } + break; + + case resolve_conflicts::drop: + switch (side) + { + case left_side: + st.push_symbol(syms::resolved_drop_left); + break; + + case right_side: + st.push_symbol(syms::resolved_drop_right); + break; + } + break; + + default: + I(false); + } +} + +static void put_content_conflict (basic_io::stanza & st, roster_t const & left_roster, roster_t const & right_roster, @@ -510,7 +596,7 @@ put_content_conflict (basic_io::stanza & break; case resolve_conflicts::content_user: - st.push_str_pair(syms::resolved_user, conflict.resolution.second); + st.push_str_pair(syms::resolved_user, conflict.resolution.second->as_external()); break; default: @@ -1216,7 +1302,11 @@ roster_merge_result::report_duplicate_na I(false); if (basic_io) - put_stanza(st, output); + { + put_duplicate_name_resolution (st, left_side, conflict.left_resolution); + put_duplicate_name_resolution (st, right_side, conflict.right_resolution); + put_stanza(st, output); + } } } @@ -1388,7 +1478,7 @@ roster_merge_result::report_file_content basic_io::stanza st; if (auto_merge_succeeds(lua, conflict, adaptor, left_roster, right_roster)) - conflict.resolution = make_pair(resolve_conflicts::content_internal, std::string()); + conflict.resolution.first = resolve_conflicts::content_internal; st.push_str_pair(syms::conflict, syms::content); put_content_conflict (st, left_roster, right_roster, adaptor, conflict); @@ -1497,21 +1587,44 @@ read_duplicate_name_conflict(basic_io::p // check for a resolution while ((!pars.symp (syms::conflict)) && pars.tok.in.lookahead != EOF) { - if (pars.symp (syms::resolved_rename_left)) + if (pars.symp (syms::resolved_drop_left)) { + conflict.left_resolution.first = resolve_conflicts::drop; + pars.sym(); + } + else if (pars.symp (syms::resolved_drop_right)) + { + conflict.right_resolution.first = resolve_conflicts::drop; + pars.sym(); + } + else 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)); + conflict.left_resolution.second = resolve_conflicts::new_file_path(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)); + conflict.right_resolution.second = resolve_conflicts::new_file_path(pars.token); pars.str(); } + else if (pars.symp (syms::resolved_user_left)) + { + conflict.left_resolution.first = resolve_conflicts::content_user; + pars.sym(); + conflict.left_resolution.second = resolve_conflicts::new_optimal_path(pars.token); + pars.str(); + } + else if (pars.symp (syms::resolved_user_right)) + { + conflict.right_resolution.first = resolve_conflicts::content_user; + pars.sym(); + conflict.right_resolution.second = resolve_conflicts::new_optimal_path(pars.token); + pars.str(); + } else N(false, F(conflict_resolution_not_supported_msg) % pars.token % "duplicate_name"); } @@ -1609,7 +1722,7 @@ read_file_content_conflict(basic_io::par { conflict.resolution.first = resolve_conflicts::content_user; pars.sym(); - conflict.resolution.second = pars.token; + conflict.resolution.second = resolve_conflicts::new_optimal_path(pars.token); pars.str(); } else @@ -1713,7 +1826,7 @@ roster_merge_result::read_conflict_file( void roster_merge_result::read_conflict_file(database & db, - std::string const file_name, + system_path const file_name, revision_id & ancestor_rid, revision_id & left_rid, revision_id & right_rid, @@ -1724,9 +1837,9 @@ roster_merge_result::read_conflict_file( { data dat; - read_data (system_path(utf8(file_name)), dat); + read_data (file_name, dat); - basic_io::input_source src(dat(), file_name); + basic_io::input_source src(dat(), file_name.as_external()); basic_io::tokenizer tok(src); basic_io::parser pars(tok); std::string temp; @@ -1750,83 +1863,9 @@ void } // 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) - { - for (int i = 1; i <= 2; i++) - { - 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; - pars.sym(); - conflict.resolution.second = pars.token; - pars.str(); - } - else - { - // 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; - } - } - - 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, + system_path const file_name, revision_id const & ancestor_rid, revision_id const & left_rid, revision_id const & right_rid, @@ -1853,11 +1892,11 @@ roster_merge_result::write_conflict_file 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); + report_duplicate_name_conflicts(*left_roster, *right_roster, adaptor, true, output); + report_file_content_conflicts(lua, *left_roster, *right_roster, adaptor, true, output); data dat(output.str()); - write_data(system_path(file_name), dat, system_path("_MTN")); + write_data(file_name, dat, system_path("_MTN")); } // roster_merge_result::write_conflict_file @@ -1884,7 +1923,7 @@ parse_resolve_conflicts_str(basic_io::pa conflict.left_resolution.first = resolve_conflicts::rename; pars.sym(); - conflict.left_resolution.second = file_path_external (utf8(pars.token)); + conflict.left_resolution.second = resolve_conflicts::new_file_path(pars.token); pars.str(); } else if (pars.symp (syms::resolved_rename_right)) @@ -1896,7 +1935,7 @@ parse_resolve_conflicts_str(basic_io::pa conflict.right_resolution.first = resolve_conflicts::rename; pars.sym(); - conflict.right_resolution.second = file_path_external (utf8(pars.token)); + conflict.right_resolution.second = resolve_conflicts::new_file_path(pars.token); pars.str(); } else if (pars.symp (syms::resolved_user)) @@ -1908,7 +1947,7 @@ parse_resolve_conflicts_str(basic_io::pa conflict.resolution.first = resolve_conflicts::content_user; pars.sym(); - conflict.resolution.second = pars.token; + conflict.resolution.second = resolve_conflicts::new_optimal_path(pars.token); pars.str(); } else @@ -2019,9 +2058,22 @@ roster_merge_result::resolve_duplicate_n switch (conflict.left_resolution.first) { + case resolve_conflicts::content_user: + P(F("replacing content of %s with %s") % left_name % conflict.left_resolution.second->as_external()); + I(false); // FIXME_RESOLVE_CONFLICTS: not implemented + // something like adaptor.record_file(); + break; + + case resolve_conflicts::drop: + P(F("dropping %s") % left_name); + I(false); // FIXME_RESOLVE_CONFLICTS: not implemented + // something like drop_detached_node(); + break; + case resolve_conflicts::rename: P(F("renaming %s to %s") % left_name % conflict.left_resolution.second); - attach_node (lua, this->roster, left_nid, conflict.left_resolution.second); + attach_node + (lua, this->roster, left_nid, file_path_internal (conflict.left_resolution.second->as_internal())); break; case resolve_conflicts::none: @@ -2036,7 +2088,8 @@ roster_merge_result::resolve_duplicate_n { case resolve_conflicts::rename: P(F("renaming %s to %s") % right_name % conflict.right_resolution.second); - attach_node (lua, this->roster, right_nid, conflict.right_resolution.second); + attach_node + (lua, this->roster, right_nid, file_path_internal (conflict.right_resolution.second->as_internal())); break; case resolve_conflicts::none: @@ -2099,7 +2152,8 @@ roster_merge_result::resolve_file_conten case resolve_conflicts::content_user: { - P(F("replacing content of %s, %s with %s") % left_name % right_name % conflict.resolution.second); + P(F("replacing content of %s, %s with %s") % + left_name % right_name % conflict.resolution.second->as_external()); file_id result_id; file_data left_data, right_data, result_data; @@ -2107,7 +2161,7 @@ roster_merge_result::resolve_file_conten adaptor.get_version(conflict.left, left_data); adaptor.get_version(conflict.right, right_data); - read_data(system_path(conflict.resolution.second), result_raw_data); + read_data(*conflict.resolution.second, result_raw_data); result_data = file_data(result_raw_data); calculate_ident(result_data, result_id); @@ -2277,8 +2331,6 @@ namespace return false; } - enum side_t { left_side, right_side }; - void assign_name(roster_merge_result & result, node_id nid, node_id parent, path_component name, side_t side) ============================================================ --- roster_merge.hh cc362cd4a0b539c2fbdfd892a9a171586fc84d5c +++ roster_merge.hh 3bbdacaa0de545b88f71e178ca7931616ea0eb83 @@ -29,7 +29,15 @@ namespace resolve_conflicts namespace resolve_conflicts { - enum resolution_t {none, content_user, content_internal, rename}; + enum resolution_t {none, content_user, content_internal, rename, drop}; + + typedef std::pair > file_resolution_t; + + boost::shared_ptr new_file_path(std::string path); + + // Return a file_path, bookkeeping_path, or system_path, as appropriate. + // This keeps the file names in the conflict file relative if possible. + boost::shared_ptr new_optimal_path(std::string path); } // renaming the root dir allows these: @@ -94,7 +102,9 @@ struct duplicate_name_conflict { node_id left_nid, right_nid; std::pair parent_name; - std::pair left_resolution, right_resolution; + // file part of resolution must be a file_path if resolution is 'rename'; + // it may be a bookkeeping or system path if resolution is 'user'. + resolve_conflicts::file_resolution_t left_resolution, right_resolution; duplicate_name_conflict () {left_resolution.first = resolve_conflicts::none; @@ -121,18 +131,14 @@ struct file_content_conflict { node_id nid; file_id left, right; - // The second item is a file path. We don't use type 'file_path' because - // that can't be in _MTN. We don't specify 'bookkeeping_path' because that - // _must_ be in _MTN. We want users to have a choice of workflow. This is - // local data only, so it is in system encoding. - std::pair resolution; + resolve_conflicts::file_resolution_t resolution; file_content_conflict () : - nid(the_null_node), - resolution(std::make_pair(resolve_conflicts::none, std::string())) {}; + nid(the_null_node) + {resolution.first = resolve_conflicts::none;}; file_content_conflict(node_id nid) : - nid(nid), resolution(std::make_pair(resolve_conflicts::none, std::string())) {}; + nid(nid) {resolution.first = resolve_conflicts::none;}; }; template <> void dump(invalid_name_conflict const & conflict, std::string & out); @@ -239,7 +245,7 @@ struct roster_merge_result // 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, + system_path const file_name, revision_id & ancestor_rid, revision_id & left_rid, revision_id & right_rid, @@ -248,11 +254,9 @@ struct roster_merge_result 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, + system_path const file_name, revision_id const & ancestor_rid, revision_id const & left_rid, revision_id const & right_rid, ============================================================ --- tests/resolve_conflict_all_resolutions/__driver__.lua 709ab05fab5b76ebcbe76b2a9fdf12307f690522 +++ tests/resolve_conflict_all_resolutions/__driver__.lua 2c013201e50e953df1ca9b55ad726b4c633c9610 @@ -1,4 +1,5 @@ --- Test setting conflict resolutions in a conflict file +-- Test showing and setting all possible conflict resolutions in a +-- conflict file. mtn_setup() @@ -37,31 +38,45 @@ beth_1 = base_revision() commit("testbranch", "beth_1") beth_1 = base_revision() -check (mtn("automate", "show_conflicts"), 0, true, nil) -canonicalize("stdout") -check(samefilestd("conflicts-1", "stdout")) +-- Don't use _MTN/conflicts, to test that capability +mkdir("resolutions") +check (mtn("conflicts", "--conflict_file=resolutions/conflicts", "store", abe_1, beth_1), 0, true, nil) +check(samefilestd("conflicts-1", "resolutions/conflicts")) --- 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. +check(mtn("conflicts", "--conflict_file=resolutions/conflicts", "show_first", resolution), 0, true, nil) +canonicalize("stdout") +check(samefilestd("show_first-checkout_left"), "stdout") + resolution = "resolved_drop_left\n resolved_user_right \"resolutions/checkout_left.sh\"" -check(mtn("resolve_conflict", "resolutions/conflicts", resolution), 0, nil, nil) +check(mtn("conflicts", "--conflict_file=resolutions/conflicts", "resolve_first", resolution), 0, nil, nil) +check(mtn("conflicts", "--conflict_file=resolutions/conflicts", "show_first", resolution), 0, true, nil) +canonicalize("stdout") +check(samefilestd("show_first-checkout_right"), "stdout") + +resolution = "resolved_drop_rightt\n resolved_user_left \"resolutions/checkout_right.sh\"" +check(mtn("conflicts", "--conflict_file=resolutions/conflicts", "resolve_first", resolution), 0, nil, nil) + +check(mtn("conflicts", "--conflict_file=resolutions/conflicts", "show_first", resolution), 0, true, nil) +canonicalize("stdout") +check(samefilestd("show_first-thermostat"), "stdout") + 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(mtn("conflicts", "--conflict_file=resolutions/conflicts", "resolve_first", resolution), 0, nil, nil) -check(samefilestd("conflicts-2", "resolutions/conflicts")) +check(samefilestd("conflicts-resolved", "resolutions/conflicts")) -- This succeeds check(mtn("merge", "--resolve-conflicts-file", "resolutions/conflicts"), 0, true, nil) canonicalize("stdout") check(samefilestd("merge-1", "stdout")) +-- Verify user specified resolution files check(mtn("update"), 0, nil, false) check("checkout_left.sh beth 2" == readfile("checkout_left.sh")) +check("checkout_right.sh beth 2" == readfile("checkout_right.sh")) -- end of file ============================================================ --- tests/resolve_duplicate_name_conflict/__driver__.lua 37b9d15331a7f470419dbc20e416c0142c45237c +++ tests/resolve_duplicate_name_conflict/__driver__.lua 320c07fc8141d2eac97f2f6a71e6b9d4754799c9 @@ -44,35 +44,39 @@ check(mtn("merge"), 1, nil, false) -- For thermostat.c, she specifies a conflict resolution that renames -- both versions. -check (mtn("automate", "show_conflicts"), 0, true, nil) -canonicalize("stdout") -check(samefilestd("conflicts-1", "stdout")) +check(mtn("conflicts", "store"), 0, true, nil) +check(samefilestd("conflicts-1", "_MTN/conflicts")) --- Save the conflicts file so we can edit it later -mkdir("resolutions") -rename("stdout", "resolutions/conflicts") +-- Find out what the first unresolved conflict is +check(mtn("conflicts", "show_first"), 0, nil, true) -- Retrieve Abe's version of checkout.sh, and pretend we did a manual --- merge, using our favorite merge tool. We put the files outside the --- workspace, so 'update' doesn't complain. +-- merge, using our favorite merge tool. We put the files in the +-- bookkeeping area, so mtn doesn't see them. +mkdir("_MTN/resolutions") check(mtn("automate", "get_file", "61b8d4fb0e5d78be111f691b955d523c782fa92e"), 0, true, nil) -rename("stdout", "resolutions/checkout.sh-abe") -check(readfile("resolutions/checkout.sh-abe") == "checkout.sh abe 1") +rename("stdout", "_MTN/resolutions/checkout.sh-abe") +check(readfile("_MTN/resolutions/checkout.sh-abe") == "checkout.sh abe 1") -writefile("resolutions/checkout.sh", "checkout.sh beth 2") +writefile("_MTN/resolutions/checkout.sh", "checkout.sh beth 2") --- 'resolve_conflict' specifies a resolution for the first unresolved --- conflict in the file. -resolution = "resolved_drop_left\n resolved_user_right \"resolutions/checkout.sh\"" -check(mtn("resolve_conflict", "resolutions/conflicts", resolution), 0, nil, nil) +-- specify a part of the resolution for the first unresolved conflict in the file. +check(mtn("conflicts", "resolve_first_left", "drop"), 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) +-- and now the other part +check(mtn("conflicts", "show_first"), 0, nil, true) +check(mtn("conflicts", "resolve_first_right", "user", "_MTN/resolutions/checkout.sh"), 0, nil, nil) -check(samefilestd("conflicts-2", "resolutions/conflicts")) +-- Find out what the next unresolved conflict is +check(mtn("conflicts", "show_first"), 0, nil, true) +check(mtn("conflicts", "resolve_first_left", "rename", "thermostat-westinghouse.c"), 0, nil, nil) +check(mtn("conflicts", "resolve_first_right", "rename", "thermostat-honeywell.c"), 0, nil, nil) + +check(samefilestd("conflicts-resolved", "_MTN/conflicts")) + -- This succeeds -check(mtn("merge", "--resolve-conflicts-file", "resolutions/conflicts"), 0, true, nil) +check(mtn("merge", "--resolve-conflicts-file", "_MTN/conflicts"), 0, true, nil) canonicalize("stdout") check(samefilestd("merge-1", "stdout"))