# # # add_file "tests/resolve_conflicts_all_resolutions/merge3_hook.lua" # content [742df075bd83fcd250b6c7476b0a7bad4151f81e] # # add_file "tests/resolve_conflicts_all_resolutions/show_first-interactive" # content [7abca3d3c5a80fd96a1991a86d0e1b5395851feb] # # patch "cmd_conflicts.cc" # from [4cadb787981f0b6bf5bba88ca7d31f2866bcaf5d] # to [419fe5e356704374a400c7e679eac0a1d2658ce9] # # patch "monotone.texi" # from [ecedcde4c73e4cfb2db1d8ec74077ef5abe65a54] # to [d4d8166717d7a91bcca0f54ffb7225d35a2caaa4] # # patch "options_list.hh" # from [3bb5b9b1bd536dfe468700f54e22603ddc0abb92] # to [0027aea743f03cfcaaa48cdd7c979c9782c72d6f] # # patch "roster_merge.cc" # from [76d7a8b6130772c5b0da6ca8db48856d6cae0463] # to [d5c979ae93fd972fbca04fab78da8d99cf08e402] # # patch "roster_merge.hh" # from [122f23807d016675871888e41ef496f4fdc352a9] # to [70962158e3ca990c80c46074a88cf84edd70a14c] # # patch "tests/resolve_conflicts_all_resolutions/__driver__.lua" # from [51025b5e3e0cfd195b905975cae905939a95ce94] # to [050392dc818517aa201a0930f59a1969c96c4495] # # patch "tests/resolve_conflicts_all_resolutions/conflicts-1" # from [bf24db060f7d4f25c09eff14083d02b9e6a2533b] # to [09ae554632fc05d64f3f59b9e536380d937895eb] # # patch "tests/resolve_conflicts_all_resolutions/conflicts-resolved" # from [4018b395bff3124e1d091e5c600462c88cf8ea62] # to [bbfb68dde1ac7c4394118c6efa86ef722167183b] # # patch "tests/resolve_conflicts_all_resolutions/merge-1" # from [4d660533ce23cbfacdc84e5fed181452a20f8b36] # to [8e3a1ba80b20c6b945ab48e83d5ce0df06720c1a] # # patch "tests/resolve_conflicts_all_resolutions/show_first-user" # from [abab342008177d9c3dd49edfb582f0b9f483003e] # to [28e16e7e03af8954cbbd55b17827c92bc51f4bbf] # # patch "tests/resolve_conflicts_all_resolutions/show_remaining-checkout_left" # from [cd1882e61df967d6ee9f21f466be8d0a03c88fb5] # to [457e3d43e1c99cff6224665099b74068733ed97a] # # patch "tests/resolve_conflicts_all_resolutions/show_remaining-thermostat" # from [2bf5ad8ca1cd6a549e4bbe01c0e0440b99dabb61] # to [7ec8e586d80ebca3687341f78f60b0aacb329678] # ============================================================ --- tests/resolve_conflicts_all_resolutions/merge3_hook.lua 742df075bd83fcd250b6c7476b0a7bad4151f81e +++ tests/resolve_conflicts_all_resolutions/merge3_hook.lua 742df075bd83fcd250b6c7476b0a7bad4151f81e @@ -0,0 +1,8 @@ +-- show that we called the merge3 hook + +function merge3 (anc_path, left_path, right_path, merged_path, ancestor, left, right) + io.write("running merge3 hook\n") + return "interactive_file merged" +end + +-- end of file ============================================================ --- tests/resolve_conflicts_all_resolutions/show_first-interactive 7abca3d3c5a80fd96a1991a86d0e1b5395851feb +++ tests/resolve_conflicts_all_resolutions/show_first-interactive 7abca3d3c5a80fd96a1991a86d0e1b5395851feb @@ -0,0 +1,4 @@ +mtn: content interactive_file +mtn: possible resolutions: +mtn: resolve_first interactive "file_name" +mtn: resolve_first user "file_name" ============================================================ --- cmd_conflicts.cc 4cadb787981f0b6bf5bba88ca7d31f2866bcaf5d +++ cmd_conflicts.cc 419fe5e356704374a400c7e679eac0a1d2658ce9 @@ -23,6 +23,7 @@ struct conflicts_t { roster_merge_result result; revision_id ancestor_rid, left_rid, right_rid; + boost::shared_ptr ancestor_roster; boost::shared_ptr left_roster; boost::shared_ptr right_roster; marking_map left_marking, right_marking; @@ -108,7 +109,8 @@ show_conflicts(database & db, conflicts_ { case first: P(F("possible resolutions:")); - P(F("resolve user \"file_name\"")); + P(F("resolve_first interactive \"file_name\"")); + P(F("resolve_first user \"file_name\"")); return; case remaining: @@ -165,6 +167,47 @@ static char const * const conflict_resol enum side_t {left, right, neither}; static char const * const conflict_resolution_not_supported_msg = "%s is not a supported conflict resolution for %s"; +// Call Lua merge3 hook to merge left_fid, right_fid, store result in result_path +static bool +do_interactive_merge(database & db, + lua_hooks & lua, + conflicts_t & conflicts, + node_id const nid, + file_id const & ancestor_fid, + file_id const & left_fid, + file_id const & right_fid, + bookkeeping_path const & result_path) +{ + file_path ancestor_path, left_path, right_path; + + if (!conflicts.ancestor_roster) + { + conflicts.ancestor_roster = boost::shared_ptr(new roster_t()); + db.get_roster(conflicts.ancestor_rid, *conflicts.ancestor_roster); + } + + conflicts.ancestor_roster->get_name(nid, ancestor_path); + conflicts.left_roster->get_name(nid, left_path); + conflicts.right_roster->get_name(nid, right_path); + + file_data left_data, right_data, ancestor_data; + data merged_unpacked; + + db.get_file_version(left_fid, left_data); + db.get_file_version(ancestor_fid, ancestor_data); + db.get_file_version(right_fid, right_data); + + if (lua.hook_merge3(ancestor_path, left_path, right_path, file_path(), + ancestor_data.inner(), left_data.inner(), + right_data.inner(), merged_unpacked)) + { + write_data(result_path, merged_unpacked); + return true; + } + + return false; +} // do_interactive_merge + static void set_duplicate_name_conflict(resolve_conflicts::file_resolution_t & resolution, args_vector const & args) @@ -192,7 +235,11 @@ static void } //set_duplicate_name_conflict static void -set_first_conflict(side_t side, conflicts_t & conflicts, args_vector const & args) +set_first_conflict(database & db, + lua_hooks & lua, + conflicts_t & conflicts, + args_vector const & args, + side_t side) { if (side != neither) { @@ -236,10 +283,25 @@ set_first_conflict(side_t side, conflict if (conflict.resolution.first == resolve_conflicts::none) { - if ("user" == idx(args,0)()) + if ("interactive" == idx(args,0)()) { + N(bookkeeping_path::external_string_is_bookkeeping_path(utf8(idx(args,1)())), + F("result path must be under _MTN")); + bookkeeping_path const result_path(idx(args,1)()); + N(args.size() == 2, F("wrong number of arguments")); + if (do_interactive_merge(db, lua, conflicts, conflict.nid, + conflict.ancestor, conflict.left, conflict.right, result_path)) + { + conflict.resolution.first = resolve_conflicts::content_user; + conflict.resolution.second = boost::shared_ptr(new bookkeeping_path(result_path)); + } + } + else if ("user" == idx(args,0)()) + { + N(args.size() == 2, F("wrong number of arguments")); + conflict.resolution.first = resolve_conflicts::content_user; conflict.resolution.second = new_optimal_path(idx(args,1)(), false); } @@ -310,7 +372,7 @@ CMD(resolve_first, "resolve_first", "", database db(app); conflicts_t conflicts (db, app.opts.conflicts_file); - set_first_conflict(neither, conflicts, args); + set_first_conflict(db, app.lua, conflicts, args, neither); conflicts.write (db, app.lua, app.opts.conflicts_file); } @@ -324,7 +386,7 @@ CMD(resolve_first_left, "resolve_first_l database db(app); conflicts_t conflicts (db, app.opts.conflicts_file); - set_first_conflict(left, conflicts, args); + set_first_conflict(db, app.lua, conflicts, args, left); conflicts.write (db, app.lua, app.opts.conflicts_file); } @@ -338,7 +400,7 @@ CMD(resolve_first_right, "resolve_first_ database db(app); conflicts_t conflicts (db, app.opts.conflicts_file); - set_first_conflict(right, conflicts, args); + set_first_conflict(db, app.lua, conflicts, args, right); conflicts.write (db, app.lua, app.opts.conflicts_file); } ============================================================ --- monotone.texi ecedcde4c73e4cfb2db1d8ec74077ef5abe65a54 +++ monotone.texi d4d8166717d7a91bcca0f54ffb7225d35a2caaa4 @@ -4714,9 +4714,18 @@ @subsection Conflicts convenient way to clean up. @end ftable -For single file conflicts, the only possible resolution is: +For single file conflicts, there are two possible resolutions: @ftable @command address@hidden interactive @var{file} +The Lua @code{merge3} hook is called to allow the user to manually +merge the left and right files, leaving the result in the specified file. + address@hidden must be a bookkeeping path; under @file{_MTN}. + +This inserts a @var{resolved_user file} conflict resolution in the +conflicts file. + @item user @var{file} The file contents are replaced by the contents of the specified file. ============================================================ --- options_list.hh 3bb5b9b1bd536dfe468700f54e22603ddc0abb92 +++ options_list.hh 0027aea743f03cfcaaa48cdd7c979c9782c72d6f @@ -644,7 +644,9 @@ OPTION(resolve_conflicts_opts, resolve_c gettext_noop("use file to resolve conflicts")) #ifdef option_bodies { - N(bookkeeping_path::external_string_is_bookkeeping_path(utf8(arg)), + // we can't call bookkeeping_path::external_string_is_bookkeeping_path + // here, because we haven't found the workspace yet. + N(bookkeeping_path::internal_string_is_bookkeeping_path(utf8(arg)), F("conflicts file must be under _MTN")); resolve_conflicts_file = bookkeeping_path(arg); } @@ -667,7 +669,9 @@ OPTION(conflicts_opts, conflicts_file, t gettext_noop("file in which to store conflicts")) #ifdef option_bodies { - N(bookkeeping_path::external_string_is_bookkeeping_path(utf8(arg)), + // we can't call bookkeeping_path::external_string_is_bookkeeping_path + // here, because we haven't found the workspace yet. + N(bookkeeping_path::internal_string_is_bookkeeping_path(utf8(arg)), F("conflicts file must be under _MTN")); conflicts_file = bookkeeping_path(arg); } ============================================================ --- roster_merge.cc 76d7a8b6130772c5b0da6ca8db48856d6cae0463 +++ roster_merge.cc d5c979ae93fd972fbca04fab78da8d99cf08e402 @@ -2105,13 +2105,16 @@ read_file_content_conflict(basic_io::par pars.esym(syms::node_type); pars.str(tmp); I(tmp == "file"); pars.esym (syms::ancestor_name); pars.str(); - pars.esym (syms::ancestor_file_id); pars.hex(); - + pars.esym (syms::ancestor_file_id); pars.hex(tmp); + conflict.ancestor = file_id(decode_hexenc(tmp)); + pars.esym (syms::left_name); pars.str(left_name); - pars.esym(syms::left_file_id); pars.hex(); + pars.esym(syms::left_file_id); pars.hex(tmp); + conflict.left = file_id(decode_hexenc(tmp)); pars.esym (syms::right_name); pars.str(right_name); - pars.esym(syms::right_file_id); pars.hex(); + pars.esym(syms::right_file_id); pars.hex(tmp); + conflict.right = file_id(decode_hexenc(tmp)); 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); @@ -2269,6 +2272,8 @@ roster_merge_result::read_conflict_file( pars.hex(temp); ancestor_rid = revision_id(decode_hexenc(temp)); + // we don't fetch the ancestor roster here, because not every function + // needs it. db.get_roster(left_rid, left_roster, left_marking); db.get_roster(right_rid, right_roster, right_marking); ============================================================ --- roster_merge.hh 122f23807d016675871888e41ef496f4fdc352a9 +++ roster_merge.hh 70962158e3ca990c80c46074a88cf84edd70a14c @@ -133,7 +133,7 @@ struct file_content_conflict struct file_content_conflict { node_id nid; - file_id left, right; + file_id ancestor, left, right; // ancestor is set only when reading in a conflicts file resolve_conflicts::file_resolution_t resolution; file_content_conflict () : ============================================================ --- tests/resolve_conflicts_all_resolutions/__driver__.lua 51025b5e3e0cfd195b905975cae905939a95ce94 +++ tests/resolve_conflicts_all_resolutions/__driver__.lua 050392dc818517aa201a0930f59a1969c96c4495 @@ -2,6 +2,7 @@ mtn_setup() -- conflict file. Also test 'conflict show_remaining'. mtn_setup() +get("merge3_hook.lua") -- Generate a conflicts file, with one conflict for each type of -- resolution. The list of currently supported resolutions is in @@ -15,18 +16,25 @@ mtn_setup() -- resolved_rename_left thermostat.c -> thermostat-westinghouse.c -- resolved_rename_right thermostat.c -> thermostat-honeywell.c -- resolved_user user_file +-- resolved_user interactive_file -- resolved_user_left checkout_left.sh beth -- resolved_user_right checkout_right.sh abe -- -- We can't set 'resolved_internal' directly; it is set by 'conflicts store'. +-- +-- We have two files that have 'resolved_user' resolutions; one by 'mtn +-- conflicts resolve_first user ...', one by 'mtn conflicts +-- resolve_first interactive ...'. addfile("simple_file", "simple\none\ntwo\nthree\n") addfile("user_file", "blah blah blah") +addfile("interactive_file", "interactive base") commit() base = base_revision() addfile("simple_file", "simple\nzero\none\ntwo\nthree\n") writefile("user_file", "user_file abe 1") +writefile("interactive_file", "interactive_file 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") @@ -37,6 +45,7 @@ writefile("user_file", "user_file beth 1 addfile("simple_file", "simple\none\ntwo\nthree\nfour\n") writefile("user_file", "user_file beth 1") +writefile("interactive_file", "interactive_file 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") @@ -81,6 +90,13 @@ canonicalize("stderr") check(mtn("conflicts", "--conflicts-file=_MTN/conflicts-1", "show_first"), 0, nil, true) canonicalize("stderr") +check(samefilestd("show_first-interactive", "stderr")) + +mkdir("_MTN/resolutions") +check(mtn("--rcfile=merge3_hook.lua", "conflicts", "--conflicts-file=_MTN/conflicts-1", "resolve_first", "interactive", "_MTN/resolutions/interactive_file"), 0, true, nil) + +check(mtn("conflicts", "--conflicts-file=_MTN/conflicts-1", "show_first"), 0, nil, true) +canonicalize("stderr") check(samefilestd("show_first-user", "stderr")) writefile("resolutions/user_file", "user_file merged") @@ -99,5 +115,6 @@ check("user_file merged" == readfile("us check("checkout_left.sh beth 2" == readfile("checkout_left.sh")) check("checkout_right.sh beth 2" == readfile("checkout_right.sh")) check("user_file merged" == readfile("user_file")) +check("interactive_file merged" == readfile("interactive_file")) -- end of file ============================================================ --- tests/resolve_conflicts_all_resolutions/conflicts-1 bf24db060f7d4f25c09eff14083d02b9e6a2533b +++ tests/resolve_conflicts_all_resolutions/conflicts-1 09ae554632fc05d64f3f59b9e536380d937895eb @@ -1,6 +1,6 @@ - left [deb9772dea922e56623683f9fa58558670cda9a6] - right [670f6a2810983d1f6ce4c6c076f5f685ffd4b17c] -ancestor [59f6060f7ed4dd514c2e0a9bd291ed9a9af29e23] + left [6fcbac878d4e20dc024bf16e63bd88906ac21afd] + right [138053fd821daa41aa98e55641d087da1d70ca2b] +ancestor [ecceebea23b1c81b980671b4cac91ed3421396c3] conflict duplicate_name left_type "added file" @@ -26,6 +26,15 @@ right_file_id [7e9f2712c5d3570815f154677 right_name "thermostat.c" right_file_id [7e9f2712c5d3570815f1546772d9119474d32afc] + conflict content + node_type "file" + ancestor_name "interactive_file" +ancestor_file_id [c0377dbf53083e56aec683b6f54790321963ac62] + left_name "interactive_file" + left_file_id [942f274ea0d3ef68b821d67f8b01f79d17cfbf8f] + right_name "interactive_file" + right_file_id [f5545d552609d36494a195deee7f9df617cb3443] + conflict content node_type "file" ancestor_name "simple_file" ============================================================ --- tests/resolve_conflicts_all_resolutions/conflicts-resolved 4018b395bff3124e1d091e5c600462c88cf8ea62 +++ tests/resolve_conflicts_all_resolutions/conflicts-resolved bbfb68dde1ac7c4394118c6efa86ef722167183b @@ -1,6 +1,6 @@ - left [deb9772dea922e56623683f9fa58558670cda9a6] - right [670f6a2810983d1f6ce4c6c076f5f685ffd4b17c] -ancestor [59f6060f7ed4dd514c2e0a9bd291ed9a9af29e23] + left [6fcbac878d4e20dc024bf16e63bd88906ac21afd] + right [138053fd821daa41aa98e55641d087da1d70ca2b] +ancestor [ecceebea23b1c81b980671b4cac91ed3421396c3] conflict duplicate_name left_type "added file" @@ -32,6 +32,16 @@ resolved_rename_right "thermostat-honeyw resolved_rename_left "thermostat-westinghouse.c" resolved_rename_right "thermostat-honeywell.c" + conflict content + node_type "file" + ancestor_name "interactive_file" +ancestor_file_id [c0377dbf53083e56aec683b6f54790321963ac62] + left_name "interactive_file" + left_file_id [942f274ea0d3ef68b821d67f8b01f79d17cfbf8f] + right_name "interactive_file" + right_file_id [f5545d552609d36494a195deee7f9df617cb3443] + resolved_user "_MTN/resolutions/interactive_file" + conflict content node_type "file" ancestor_name "simple_file" ============================================================ --- tests/resolve_conflicts_all_resolutions/merge-1 4d660533ce23cbfacdc84e5fed181452a20f8b36 +++ tests/resolve_conflicts_all_resolutions/merge-1 8e3a1ba80b20c6b945ab48e83d5ce0df06720c1a @@ -1,15 +1,16 @@ mtn: calculating best pair of heads to m mtn: 2 heads on branch 'testbranch' mtn: merge 1 / 1: mtn: calculating best pair of heads to merge next -mtn: [left] 670f6a2810983d1f6ce4c6c076f5f685ffd4b17c -mtn: [right] deb9772dea922e56623683f9fa58558670cda9a6 +mtn: [left] 138053fd821daa41aa98e55641d087da1d70ca2b +mtn: [right] 6fcbac878d4e20dc024bf16e63bd88906ac21afd mtn: dropping checkout_left.sh mtn: replacing content of checkout_left.sh with resolutions/checkout_left.sh mtn: replacing content of checkout_right.sh with resolutions/checkout_right.sh mtn: dropping checkout_right.sh mtn: renaming thermostat.c to thermostat-westinghouse.c mtn: renaming thermostat.c to thermostat-honeywell.c +mtn: replacing content of interactive_file, interactive_file with _MTN/resolutions/interactive_file mtn: merged simple_file, simple_file mtn: replacing content of user_file, user_file with resolutions/user_file -mtn: [merged] 1280be5b732ba73dc73a63c5e6a77efc4f690d90 +mtn: [merged] d617fe493dc8a88bf4d5463b782208cb5e4c9768 mtn: note: your workspaces have not been updated ============================================================ --- tests/resolve_conflicts_all_resolutions/show_first-user abab342008177d9c3dd49edfb582f0b9f483003e +++ tests/resolve_conflicts_all_resolutions/show_first-user 28e16e7e03af8954cbbd55b17827c92bc51f4bbf @@ -1,3 +1,4 @@ mtn: possible resolutions: mtn: content user_file mtn: possible resolutions: -mtn: resolve user "file_name" +mtn: resolve_first interactive "file_name" +mtn: resolve_first user "file_name" ============================================================ --- tests/resolve_conflicts_all_resolutions/show_remaining-checkout_left cd1882e61df967d6ee9f21f466be8d0a03c88fb5 +++ tests/resolve_conflicts_all_resolutions/show_remaining-checkout_left 457e3d43e1c99cff6224665099b74068733ed97a @@ -1,4 +1,5 @@ mtn: duplicate_name thermostat.c mtn: duplicate_name checkout_left.sh mtn: duplicate_name checkout_right.sh mtn: duplicate_name thermostat.c +mtn: content interactive_file mtn: content user_file ============================================================ --- tests/resolve_conflicts_all_resolutions/show_remaining-thermostat 2bf5ad8ca1cd6a549e4bbe01c0e0440b99dabb61 +++ tests/resolve_conflicts_all_resolutions/show_remaining-thermostat 7ec8e586d80ebca3687341f78f60b0aacb329678 @@ -1,2 +1,3 @@ mtn: duplicate_name thermostat.c mtn: duplicate_name thermostat.c +mtn: content interactive_file mtn: content user_file