# # # add_dir "tests/resolve_duplicate_name_conflict_multiple_parents" # # add_file "tests/resolve_duplicate_name_conflict_multiple_parents/__driver__.lua" # content [92d0fee7b0d1942d9b5c5c8c262f242e9d77d67b] # # patch "cmd_merging.cc" # from [e0c8a13558e04e4efd35a3f290984fd24abd9b98] # to [6e63c4c6ea5238e974c78ca1c8e90dd5e9dbb1f5] # # patch "merge.cc" # from [35f671a616eda3a70deece487b9fe44900c17d92] # to [c84890589ae4c0f8444c195cd1ef65bb72a7ffe6] # # patch "merge.hh" # from [8cd4bcbd4dfc7b481569c7725003e42bee951969] # to [1962ac9da8c2c32edc2e5d7f6321416d0e42fe9a] # # patch "roster.cc" # from [97f54be64fef75c3d99c81582dbdc7d636d13b9c] # to [d5a2952201e0bd3cc02cce2bda95866c9d7cac09] # # patch "roster.hh" # from [a6b7d285ee2e18db35b4b7139d5be562a9136fa6] # to [b5abbe161a5776c6fdafcc61a6596d47c5c0a442] # # patch "roster_merge.cc" # from [797095308451d07094dc20f8791dc529822822a0] # to [b58ad49e129c29eb22aaa2eae29ce336aca4ac7a] # # patch "roster_merge.hh" # from [d10a16027738710cd50617a340b149a29d102d2f] # to [f2481eac7fed1d1fb3b1d2e22ee8cf03ea795ee3] # # patch "ss-existence-merge.text" # from [4f5e53ab1b719beae34333bfb7417248a8411b92] # to [a58b78ac10d1436495823576067c8e3387ba6eea] # ============================================================ --- tests/resolve_duplicate_name_conflict_multiple_parents/__driver__.lua 92d0fee7b0d1942d9b5c5c8c262f242e9d77d67b +++ tests/resolve_duplicate_name_conflict_multiple_parents/__driver__.lua 92d0fee7b0d1942d9b5c5c8c262f242e9d77d67b @@ -0,0 +1,110 @@ +-- Test/demonstrate handling of a duplicate name conflict, with +-- multiple parents of a suture. +-- +-- We have this revision history graph: +-- +-- A3 B4 C5 D6 A3 B4 C5 D6 +-- \ / \ / \ / \ / +-- E7 F8 G9 H10 +-- \ / \ / +-- \ / \ / +-- I11 J12 +-- \ / +-- K13 +-- +-- node numbers are the node ids used by monotone for checkout.sh in +-- each revision; root is 1, randomfile is 2. +-- +-- In A, B, C, D, four developers each add a file 'checkout.sh'. +-- +-- In E, F, G, H, each developer sutures two of them. +-- In I, J, two developers suture more. +-- In K, the final suture is done. + +-- Then we explore some other merge situations. + +function merged_revision() + local workrev = readfile("stderr") + local extract = string.gsub(workrev, "^.*mtn: %[merged%] (%x*).*$", "%1") + if extract == workrev then + err("failed to extract merged revision from stderr") + end + return extract +end + +mtn_setup() + +-- Get a non-empty base revision, for revert_to +addfile("randomfile", "blah blah blah") +commit("testbranch", "base") +base = base_revision() + +addfile("checkout.sh", "A3") +commit("testbranch", "A3") +A3 = base_revision() + +revert_to(base) + +addfile("checkout.sh", "B4") +commit("testbranch", "B4") +B4 = base_revision() + +revert_to(base) + +addfile("checkout.sh", "C5") +commit("testbranch", "C5") +C5 = base_revision() + +revert_to(base) + +addfile("checkout.sh", "D6") +commit("testbranch", "D6") +D6 = base_revision() + +-- First round of merges +writefile ("checkout.sh", "E7") +check(mtn("explicit_merge", "--resolve-conflicts", "resolved_suture \"checkout.sh\"", A3, B4, "testbranch"), 0, nil, true) +E7 = merged_revision() + +writefile ("checkout.sh", "F8") +check(mtn("explicit_merge", "--resolve-conflicts", "resolved_suture \"checkout.sh\"", C5, D6, "testbranch"), 0, nil, true) +F8 = merged_revision() + +writefile ("checkout.sh", "G9") +check(mtn("explicit_merge", "--resolve-conflicts", "resolved_suture \"checkout.sh\"", A3, B4, "testbranch"), 0, nil, true) +G9 = merged_revision() + +writefile ("checkout.sh", "H10") +check(mtn("explicit_merge", "--resolve-conflicts", "resolved_suture \"checkout.sh\"", C5, D6, "testbranch"), 0, nil, true) +H10 = merged_revision() + +-- Second round +writefile ("checkout.sh", "I11") +check(mtn("explicit_merge", "--resolve-conflicts", "resolved_suture \"checkout.sh\"", E7, F8, "testbranch"), 0, nil, true) +I11 = merged_revision() + +writefile ("checkout.sh", "J12") +check(mtn("explicit_merge", "--resolve-conflicts", "resolved_suture \"checkout.sh\"", G9, H10, "testbranch"), 0, nil, true) +J12 = merged_revision() + +-- Final merge +writefile ("checkout.sh", "K13") +check(mtn("explicit_merge", "--resolve-conflicts", "resolved_suture \"checkout.sh\"", I11, J12, "testbranch"), 0, nil, true) +K13 = merged_revision() + +-- Merge I11 with G9 encounters a content conflict, not a suture +-- conflict; they share common parents +check(mtn("automate", "show_conflicts", I11, G9), 0, true, nil) +check(qgrep("conflict content", "stdout")) +-- this should not be resolvable by the internal merger +xfail(not qgrep("resolved_internal", "stdout")) + +writefile ("checkout.sh", "L14") +check(mtn("explicit_merge", "--resolve-conflicts", "resolved_user \"checkout.sh\"", I11, G9, "testbranch"), 0, nil, true) +L14 = merged_revision() + +-- Similarly for L14 with H10 +writefile ("checkout.sh", "M15") +check(mtn("explicit_merge", "--resolve-conflicts", "resolved_user \"checkout.sh\"", L14, H10, "testbranch"), 0, nil, true) + +-- end of file ============================================================ --- cmd_merging.cc e0c8a13558e04e4efd35a3f290984fd24abd9b98 +++ cmd_merging.cc 6e63c4c6ea5238e974c78ca1c8e90dd5e9dbb1f5 @@ -312,7 +312,7 @@ CMD(update, "update", "", CMD_REF(worksp content_merge_workspace_adaptor wca(db, base_rid, base_roster, chosen_markings, working_markings, paths); wca.cache_roster(working_rid, working_roster); - resolve_merge_conflicts(app.lua, *working_roster, chosen_roster, + resolve_merge_conflicts(app.lua, nis, *working_roster, chosen_roster, result, wca, false); // Make sure it worked... @@ -681,7 +681,7 @@ CMD(merge_into_dir, "merge_into_dir", "" 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); + resolve_merge_conflicts(app.lua, nis, left_roster, right_roster, result, dba, resolutions_given); { dir_t moved_root = left_roster.root(); @@ -798,7 +798,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, false); + resolve_merge_conflicts(app.lua, nis, *left.first, *right.first, merge_result, wca, false); // Make sure it worked... I(merge_result.is_clean()); @@ -1182,7 +1182,7 @@ CMD(pluck, "pluck", "", CMD_REF(workspac // to_roster is not fetched from the db which does not have temporary nids wca.cache_roster(to_rid, to_roster); - resolve_merge_conflicts(app.lua, *working_roster, *to_roster, + resolve_merge_conflicts(app.lua, nis, *working_roster, *to_roster, result, wca, false); I(result.is_clean()); @@ -1291,27 +1291,26 @@ CMD(get_roster, "get_roster", "", CMD_RE else { parent_map::const_iterator i = parents.begin(); - revision_id left_id = parent_id(i); + revision_id left_rid = parent_id(i); roster_t const & left_roster = parent_roster(i); marking_map const & left_markings = parent_marking(i); i++; - revision_id right_id = parent_id(i); + revision_id right_rid = parent_id(i); roster_t const & right_roster = parent_roster(i); marking_map const & right_markings = parent_marking(i); i++; I(i == parents.end()); set left_uncommon_ancestors, right_uncommon_ancestors; - db.get_uncommon_ancestors(left_id, right_id, + db.get_uncommon_ancestors(left_rid, right_rid, left_uncommon_ancestors, right_uncommon_ancestors); - mark_merge_roster(left_roster, left_markings, - left_uncommon_ancestors, - right_roster, right_markings, - right_uncommon_ancestors, - rid, roster, mm); + mark_merge_roster + (left_rid, left_roster, left_markings, left_uncommon_ancestors, + right_rid, right_roster, right_markings, right_uncommon_ancestors, + rid, roster, mm); } } else if (args.size() == 1) ============================================================ --- merge.cc 35f671a616eda3a70deece487b9fe44900c17d92 +++ merge.cc c84890589ae4c0f8444c195cd1ef65bb72a7ffe6 @@ -115,6 +115,7 @@ resolve_merge_conflicts(lua_hooks & lua, void resolve_merge_conflicts(lua_hooks & lua, + temp_node_id_source & nis, roster_t const & left_roster, roster_t const & right_roster, roster_merge_result & result, @@ -139,7 +140,7 @@ resolve_merge_conflicts(lua_hooks & lua, N(result.attribute_conflicts.size() == 0, F(msg) % "attribute_conflicts"); // resolve the ones we can. - result.resolve_duplicate_name_conflicts(lua, left_roster, right_roster, adaptor); + result.resolve_duplicate_name_conflicts(lua, nis, left_roster, right_roster, adaptor); result.resolve_content_drop_conflicts(left_roster, right_roster); result.resolve_suture_drop_conflicts(left_roster, right_roster); result.resolve_file_content_conflicts(lua, left_roster, right_roster, adaptor); @@ -206,10 +207,11 @@ interactive_merge_and_store(lua_hooks & left_uncommon_ancestors, right_uncommon_ancestors); roster_merge_result result; + temp_node_id_source nis; roster_merge(left_roster, left_marking_map, left_uncommon_ancestors, right_roster, right_marking_map, right_uncommon_ancestors, - result); + nis, result); bool resolutions_given; content_merge_database_adaptor dba(db, left_rid, right_rid, @@ -217,7 +219,7 @@ interactive_merge_and_store(lua_hooks & parse_resolve_conflicts_opts (opts, left_roster, right_roster, result, resolutions_given); - resolve_merge_conflicts(lua, left_roster, right_roster, result, dba, resolutions_given); + resolve_merge_conflicts(lua, nis, left_roster, right_roster, result, dba, resolutions_given); // write new files into the db store_roster_merge_result(db, ============================================================ --- merge.hh 8cd4bcbd4dfc7b481569c7725003e42bee951969 +++ merge.hh 1962ac9da8c2c32edc2e5d7f6321416d0e42fe9a @@ -16,6 +16,7 @@ class roster_t; class database; class lua_hooks; class roster_t; +class temp_node_id_source; // Destructively alter a roster_merge_result to attempt to remove any // conflicts in it. Takes a content_merge_adaptor to pass on to the content @@ -28,6 +29,7 @@ resolve_merge_conflicts(lua_hooks & lua, void resolve_merge_conflicts(lua_hooks & lua, + temp_node_id_source & nis, roster_t const & left_roster, roster_t const & right_roster, roster_merge_result & result, ============================================================ --- roster.cc 97f54be64fef75c3d99c81582dbdc7d636d13b9c +++ roster.cc d5a2952201e0bd3cc02cce2bda95866c9d7cac09 @@ -51,13 +51,14 @@ namespace { namespace syms { - symbol const ident("ident"); symbol const birth("birth"); + symbol const birth_add("birth_add"); symbol const birth_cause("birth_cause"); - symbol const birth_add("birth_add"); + symbol const birth_parent("birth_parent"); + symbol const birth_split("birth_split"); symbol const birth_suture("birth_suture"); - symbol const birth_split("birth_split"); symbol const dormant_attr("dormant_attr"); + symbol const ident("ident"); symbol const path_mark("path_mark"); symbol const attr_mark("attr_mark"); @@ -78,7 +79,7 @@ roster_t::required_roster_format(marking unsigned int roster_t::required_roster_format(marking_map const & mm) const { - // Format 2 supports birth_cause in the marking_map. This only matters if + // Format 2 supports birth_record in the marking_map. This only matters if // there is a suture or split, so check the marking map for those. unsigned int result = oldest_supported_roster_format; @@ -134,14 +135,27 @@ template <> void } template <> void -dump(std::set const & parents, string & out) +dump(std::set const & val, string & out) { ostringstream oss; + for (std::set::const_iterator i = val.begin(); i != val.end(); i++) + { + oss << *i << " "; + } + out = oss.str(); +} + +template <> void +dump(std::map const & parents, string & out) +{ + ostringstream oss; string tmp; - for (std::set::const_iterator i = parents.begin(); i != parents.end(); i++) + for (std::map::const_iterator i = parents.begin(); i != parents.end(); i++) { - dump(*i, tmp); + dump(i->first, tmp); oss << " " << tmp; + dump(i->second, tmp); + oss << " " << tmp; } out = oss.str(); } @@ -232,12 +246,13 @@ namespace const node_id first_node = 1; const node_id first_temp_node = widen(1) << (sizeof(node_id) * 8 - 1); - inline bool temp_node(node_id n) - { - return n & first_temp_node; - } } +bool +temp_node(node_id n) +{ + return n & first_temp_node; +} node::node(node_id i) : self(i), @@ -1618,14 +1633,19 @@ namespace } void - mark_new_node(revision_id const & new_rid, node_t n, marking_t & new_marking) + mark_new_node(revision_id const & new_rid, + node_t n, + revision_id left_rid, + revision_id right_rid, + marking_t & new_marking) { new_marking.birth_revision = new_rid; if (n->ancestors.first == n->self || n->ancestors.first == the_null_node) new_marking.birth_record = marking_t::birth_record_t(); else - new_marking.birth_record = marking_t::birth_record_t(marking_t::suture, n->ancestors); + new_marking.birth_record = marking_t::birth_record_t + (marking_t::suture, n->ancestors.first, left_rid, n->ancestors.second, right_rid); I(new_marking.parent_name.empty()); new_marking.parent_name.insert(new_rid); @@ -1685,9 +1705,11 @@ namespace } void - mark_merged_node(marking_t const & left_marking, + mark_merged_node(revision_id const & left_rid, + marking_t const & left_marking, set const & left_uncommon_ancestors, node_t ln, + revision_id const & right_rid, marking_t const & right_marking, set const & right_uncommon_ancestors, node_t rn, @@ -1726,8 +1748,32 @@ namespace else { // new suture + std::map parents; new_marking.birth_revision = new_rid; - new_marking.birth_record = marking_t::birth_record_t (marking_t::suture, make_pair(ln->self, rn->self)); + parents.insert(make_pair(ln->self, left_rid)); + parents.insert(make_pair(rn->self, right_rid)); + + if (left_marking.birth_record.cause == marking_t::suture) + { + for (std::map::const_iterator i = left_marking.birth_record.parents.begin(); + i != left_marking.birth_record.parents.end(); + i++) + { + parents.insert(*i); + } + } + + if (right_marking.birth_record.cause == marking_t::suture) + { + for (std::map::const_iterator i = right_marking.birth_record.parents.begin(); + i != right_marking.birth_record.parents.end(); + i++) + { + parents.insert(*i); + } + } + + new_marking.birth_record = marking_t::birth_record_t (marking_t::suture, parents); } } @@ -1811,9 +1857,11 @@ void // those invariants on a roster that involve the structure of the roster's // parents, rather than just the structure of the roster itself. void -mark_merge_roster(roster_t const & left_roster, +mark_merge_roster(revision_id const & left_rid, + roster_t const & left_roster, marking_map const & left_markings, set const & left_uncommon_ancestors, + revision_id const & right_rid, roster_t const & right_roster, marking_map const & right_markings, set const & right_uncommon_ancestors, @@ -1848,18 +1896,17 @@ mark_merge_roster(roster_t const & left_ if (the_null_node == n->ancestors.first) { // not a suture; two-parent workspace - mark_new_node(new_rid, n, new_marking); + mark_new_node(new_rid, n, left_rid, right_rid, new_marking); } else { // suture - node_map::const_iterator left = left_roster.all_nodes().find(n->ancestors.first); - node_map::const_iterator right = right_roster.all_nodes().find(n->ancestors.second); - I(left != left_roster.all_nodes().end()); - I(right != right_roster.all_nodes().end()); - mark_merged_node(safe_get(left_markings, n->ancestors.first), left_uncommon_ancestors, left->second, - safe_get(right_markings, n->ancestors.second), right_uncommon_ancestors, right->second, - new_rid, n, new_marking); + node_t const left_node = left_roster.get_node(n->ancestors.first); + node_t const right_node = right_roster.get_node(n->ancestors.second); + mark_merged_node + (left_rid, safe_get(left_markings, n->ancestors.first), left_uncommon_ancestors, left_node, + right_rid, safe_get(right_markings, n->ancestors.second), right_uncommon_ancestors, right_node, + new_rid, n, new_marking); } } @@ -1903,12 +1950,12 @@ mark_merge_roster(roster_t const & left_ // If one of the ancestor nodes is in right, merge marks with it. // FIXME_SUTURE: handle recursive parents I(left_marking.birth_record.parents.size() == 2); - set::const_iterator left_parents_i = left_marking.birth_record.parents.begin(); - node_id right_nid = *left_parents_i; + map::const_iterator left_parents_i = left_marking.birth_record.parents.begin(); + node_id right_nid = left_parents_i->first; node_map::const_iterator rni = right_roster.all_nodes().find(right_nid); if (rni == right_roster.all_nodes().end()) { - right_nid = *(++left_parents_i); + right_nid = (++left_parents_i)->first; rni = right_roster.all_nodes().find(right_nid); } @@ -1923,9 +1970,10 @@ mark_merge_roster(roster_t const & left_ } else { - mark_merged_node(safe_get(left_markings, n->self), left_uncommon_ancestors, left_node, - safe_get(right_markings, right_nid), right_uncommon_ancestors, rni->second, - new_rid, n, new_marking); + mark_merged_node + (left_rid, safe_get(left_markings, n->self), left_uncommon_ancestors, left_node, + right_rid, safe_get(right_markings, right_nid), right_uncommon_ancestors, rni->second, + new_rid, n, new_marking); } } break; @@ -1939,11 +1987,10 @@ mark_merge_roster(roster_t const & left_ // exists in both left and right parents node_t const & left_node = lni->second; node_t const & right_node = rni->second; - mark_merged_node(safe_get(left_markings, n->self), - left_uncommon_ancestors, left_node, - safe_get(right_markings, n->self), - right_uncommon_ancestors, right_node, - new_rid, n, new_marking); + mark_merged_node + (left_rid, safe_get(left_markings, n->self), left_uncommon_ancestors, left_node, + right_rid, safe_get(right_markings, n->self), right_uncommon_ancestors, right_node, + new_rid, n, new_marking); } safe_insert(new_markings, make_pair(i->first, new_marking)); @@ -1987,7 +2034,7 @@ namespace { virtual node_id create_file_node(file_id const & content, std::pair const ancestors) { - return handle_new(this->editable_roster_base::create_file_node(content)); + return handle_new(this->editable_roster_base::create_file_node(content, ancestors)); } virtual void apply_delta(file_path const & pth, @@ -2018,7 +2065,9 @@ namespace { { node_t n = r.get_node(nid); marking_t new_marking; - mark_new_node(rid, n, new_marking); + // Sutures cannot be created except in merge, and this is a non-merge, + // so don't need parent rev_ids. + mark_new_node(rid, n, revision_id(), revision_id(), new_marking); safe_insert(markings, make_pair(nid, new_marking)); return nid; } @@ -2128,8 +2177,8 @@ namespace { // nodes were modified, and scan only them // load one of the parent markings directly into the new marking map new_markings.clear(); - mark_merge_roster(left_roster, left_markings, left_uncommon_ancestors, - right_roster, right_markings, right_uncommon_ancestors, + mark_merge_roster(left_rid, left_roster, left_markings, left_uncommon_ancestors, + right_rid, right_roster, right_markings, right_uncommon_ancestors, new_rid, new_roster, new_markings); } @@ -2234,7 +2283,8 @@ mark_roster_with_one_parent(roster_t con parent.get_node(i->first), child_rid, i->second, new_marking); else - mark_new_node(child_rid, i->second, new_marking); + // nodes can't be sutured except in merges, so don't need parent rev_ids. + mark_new_node(child_rid, i->second, revision_id(), revision_id(), new_marking); safe_insert(child_markings, make_pair(i->first, new_marking)); } @@ -2814,14 +2864,15 @@ push_marking(basic_io::stanza & st, case marking_t::suture: { I(marking_format > 1); - std::vector data; - for (set::const_iterator parents_i = mark.birth_record.parents.begin(); + st.push_str_pair(syms::birth_cause, syms::birth_suture); + for (map::const_iterator parents_i = mark.birth_record.parents.begin(); parents_i != mark.birth_record.parents.end(); parents_i++) { - data.push_back(lexical_cast(*parents_i)); + st.push_binary_triple(syms::birth_parent, + lexical_cast(parents_i->first), + parents_i->second.inner()); } - st.push_str_multi(syms::birth_cause, syms::birth_suture, data); } break; @@ -2871,9 +2922,8 @@ parse_marking(basic_io::parser & pa, else if (pa.symp(syms::birth_cause)) { // If the current roster_format is 1, there will be no birth_cause - // line, and marking.birth_cause will default to marking_t::add, - // which is correct. - std::string tmp_1, tmp_2; + // or birth_parent lines, and marking.birth_record will default to + // marking_t::add, which is correct. pa.sym(); if (pa.symp (syms::birth_add)) @@ -2884,11 +2934,7 @@ parse_marking(basic_io::parser & pa, else if (pa.symp (syms::birth_suture)) { pa.sym(); - pa.str(tmp_1); - pa.str(tmp_2); - marking.birth_record = marking_t::birth_record_t (marking_t::suture, - make_pair(lexical_cast(tmp_1), - lexical_cast(tmp_2))); + marking.birth_record.cause = marking_t::suture; } else if (pa.symp (syms::birth_split)) { @@ -2899,6 +2945,16 @@ parse_marking(basic_io::parser & pa, E(false, F("unrecognized birth_cause '%s'") % pa.token); } } + else if (pa.symp(syms::birth_parent)) + { + std::string nid; + pa.sym(); + + pa.str(nid); + pa.hex(rev); + marking.birth_record.parents.insert(make_pair(lexical_cast(nid), + decode_hexenc(rev))); + } else if (pa.symp(syms::path_mark)) { pa.sym(); ============================================================ --- roster.hh a6b7d285ee2e18db35b4b7139d5be562a9136fa6 +++ roster.hh b5abbe161a5776c6fdafcc61a6596d47c5c0a442 @@ -31,6 +31,7 @@ template <> void dump(node_id const & va } template <> void dump(node_id const & val, std::string & out); +template <> void dump(std::set const & val, std::string & out); template <> void dump(full_attr_map_t const & val, std::string & out); struct node @@ -146,15 +147,21 @@ struct marking_t struct birth_record_t { birth_cause_t cause; - std::set parents; // parents of sutured nodes, recursive; see ss-existence-merge.text + std::map parents; + // parents of sutured nodes and their birth revision, recursive; see + // ss-existence-merge.text birth_record_t() : cause(add){}; birth_record_t(birth_cause_t cause, - std::set parents) : + std::map parents) : cause(cause), parents(parents){}; birth_record_t(birth_cause_t cause, - std::pair parents) : - cause(cause){this->parents.insert(parents.first); this->parents.insert(parents.second);}; + node_id left_nid, revision_id left_birth_rev, + node_id right_nid, revision_id right_birth_rev) : + cause(cause) + {parents.insert(std::make_pair(left_nid, left_birth_rev)); + parents.insert(std::make_pair(right_nid, right_birth_rev)); + }; }; revision_id birth_revision; @@ -325,6 +332,8 @@ struct temp_node_id_source node_id curr; }; +bool temp_node(node_id n); + template <> void dump(roster_t const & val, std::string & out); // adaptor class to enable cset application on rosters. @@ -398,9 +407,11 @@ void marking_map & child_markings); void -mark_merge_roster(roster_t const & left_roster, +mark_merge_roster(revision_id const & left_rid, + roster_t const & left_roster, marking_map const & left_markings, std::set const & left_uncommon_ancestors, + revision_id const & right_rid, roster_t const & right_roster, marking_map const & right_markings, std::set const & right_uncommon_ancestors, ============================================================ --- roster_merge.cc 797095308451d07094dc20f8791dc529822822a0 +++ roster_merge.cc b58ad49e129c29eb22aaa2eae29ce336aca4ac7a @@ -12,6 +12,7 @@ #include #include "basic_io.hh" +#include "lexical_cast.hh" #include "lua_hooks.hh" #include "vocab.hh" #include "roster_merge.hh" @@ -154,6 +155,32 @@ template <> void } template <> void +dump(suture_drop_conflict const & conflict, string & out) +{ + ostringstream oss; + oss << "suture_drop_conflict: " + << "sutured_node: " << conflict.sutured_nid << " " + << "sutured_side: " << image(conflict.sutured_side) << " " + << "dropped_nodes: "; + + for (set::const_iterator i = conflict.dropped_nids.begin(); i != conflict.dropped_nids.end(); i++) + { + oss << *i << " "; + } + + if (conflict.resolution.first != resolve_conflicts::none) + { + oss << "resolution: " << image(conflict.resolution.first); + if (conflict.resolution.first != resolve_conflicts::ignore_drop) + { + oss << " new_name: " << conflict.resolution.second; + } + } + oss << "\n"; + out = oss.str(); +} + +template <> void dump(attribute_conflict const & conflict, string & out) { ostringstream oss; @@ -1347,15 +1374,13 @@ push_node_id_set(roster_t const & roster symbol const & k, set nids) { - file_path name; - std::vector names; + std::vector string_nids; for (set::const_iterator i = nids.begin(); i != nids.end(); i++) { - roster.get_name(*i, name); - names.push_back(name.as_internal()); + string_nids.push_back(boost::lexical_cast(*i)); } - st.push_str_multi(k, names); + st.push_str_multi(k, string_nids); } void @@ -1748,6 +1773,7 @@ parse_duplicate_name_conflicts(basic_io: left_nid = left_roster.get_node (file_path_internal (left_name))->self; right_nid = right_roster.get_node (file_path_internal (right_name))->self; + // 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); @@ -1874,6 +1900,20 @@ static void } // parse_content_drop_conflicts static void +parse_node_id_set(set & dropped_nids, + int expected_count, + basic_io::parser & pars) +{ + string nid; + + for (int i = 0; i < expected_count; i++) + { + pars.str(nid); + dropped_nids.insert(boost::lexical_cast(nid)); + } +} + +static void parse_suture_drop_conflicts(basic_io::parser & pars, std::vector & conflicts, roster_t const & left_roster, @@ -1901,7 +1941,8 @@ parse_suture_drop_conflicts(basic_io::pa I(tmp == "file"); pars.esym(syms::left_name); pars.str(name); - parse_node_id_set(left_roster, dropped_nids, pars); + pars.esym(syms::dropped); + parse_node_id_set(dropped_nids, conflict.dropped_nids.size(), pars); } else { @@ -1911,7 +1952,7 @@ parse_suture_drop_conflicts(basic_io::pa I(tmp == "file"); pars.esym (syms::right_name); pars.str(name); - parse_node_id_set(right_roster, dropped_nids, pars); + parse_node_id_set(dropped_nids, conflict.dropped_nids.size(), pars); } N(sutured_side == conflict.sutured_side && @@ -2125,6 +2166,18 @@ parse_resolve_conflicts_str(basic_io::pa else N(false, F(error_message_2) % syms::resolved_suture); } + else if (pars.symp (syms::resolved_user)) + { + N(result.file_content_conflicts.size() == 1, + F(error_message_1) % syms::resolved_user); + + file_content_conflict & conflict = *result.file_content_conflicts.begin(); + + conflict.resolution.first = resolve_conflicts::content_user; + pars.sym(); + conflict.resolution.second = file_path_internal (pars.token); + pars.str(); + } else N(false, F("%s is not a supported conflict resolution") % pars.token); @@ -2234,7 +2287,7 @@ roster_merge_result::resolve_duplicate_n void roster_merge_result::resolve_duplicate_name_conflicts(lua_hooks & lua, - temp_node_id_source nis, + temp_node_id_source & nis, roster_t const & left_roster, roster_t const & right_roster, content_merge_adaptor & adaptor) @@ -2701,30 +2754,96 @@ namespace I(false); } - inline void - find_common_ancestor_nodes(set const & birth_parents, + void + find_common_ancestor_nodes(std::map const & birth_parents, marking_map const & markings, set const & uncommon_ancestors, set & result) { - for (set::const_iterator i = birth_parents.begin(); + for (std::map::const_iterator i = birth_parents.begin(); i != birth_parents.end(); i++) { - if (uncommon_ancestors.find(safe_get(markings, *i).birth_revision) == uncommon_ancestors.end()) + if (uncommon_ancestors.find(i->second) == uncommon_ancestors.end()) { - result.insert(*i); + result.insert(i->first); } } } - inline void + bool + is_in(set query, set target) + { + for (set::const_iterator i = query.begin(); i != query.end(); i++) + { + if (target.find(*i) != target.end()) + return true; + } + return false; + } + + bool + is_in(std::map > query, set target) + { + for (std::map >::const_iterator i = query.begin(); i != query.end(); i++) + { + if (is_in(i->second, target)) + return true; + } + return false; + } + + void + check_scalars_modified(node_id sutured_node, + resolve_conflicts::side_t sutured_side, + set const & common_parents, + marking_map const & other_markings, + set const & other_uncommon_ancestors, + roster_merge_result & result) + { + // A scalar is modified if its markings contain a revision in + // other_uncommon_ancestors. + set conflict_nodes; + + for (set::const_iterator i = common_parents.begin(); i != common_parents.end(); i++) + { + marking_t const marking = safe_get(other_markings, *i); + + if (is_in(marking.parent_name, other_uncommon_ancestors) || + is_in(marking.file_content, other_uncommon_ancestors) || + is_in(marking.attrs, other_uncommon_ancestors)) + { + conflict_nodes.insert(*i); + } + } + + if (conflict_nodes.size() > 0) + result.suture_scalar_conflicts.push_back + (suture_scalar_conflict(sutured_node, sutured_side, common_parents, conflict_nodes)); + } + + bool + operator==(std::map left, std::set right) + { + std::set::const_iterator r = right.begin(); + for (std::map::const_iterator l = left.begin(); l != left.end(); l++) + { + if (l->first != *r) + return false; + + r++; + } + return true; + } + + void insert_sutured(node_t const & n, marking_t::birth_record_t const & birth_record, marking_map const & parent_markings, set const & uncommon_ancestors, roster_t const & other_parent_roster, marking_map const & other_markings, + set const & other_uncommon_ancestors, resolve_conflicts::side_t parent_side, temp_node_id_source nis, roster_merge_result & result, @@ -2735,6 +2854,11 @@ namespace set conflict_nodes; set extra_parents; + MM(common_parents); + MM(unfound_parents); + MM(conflict_nodes); + MM(extra_parents); + bool partial_suture = false; // other_parent has sutured node with parents = subset of common_parents bool extra_suture = false; // other_parent has sutured node with some parents in, some not in common_parents @@ -2797,23 +2921,30 @@ namespace { case resolve_conflicts::left_side: result.roster.create_file_node (file_id(), nis, make_pair(n->self, i->first)); - return; + break; case resolve_conflicts::right_side: result.roster.create_file_node (file_id(), nis, make_pair(i->first, n->self)); - return; + break; } + + already_handled.insert(i->first); + + return; } else { conflict_nodes.insert(i->first); - for (set::const_iterator i = this_birth.parents.begin(); i != this_birth.parents.end(); i++) + for (std::map::const_iterator i = this_birth.parents.begin(); + i != this_birth.parents.end(); + i++) { - if (unfound_parents.find(*i) == unfound_parents.end()) - extra_parents.insert(*i); + std::set::iterator found = unfound_parents.find(i->first); + if (found == unfound_parents.end()) + extra_parents.insert(i->first); else - unfound_parents.erase(i); + unfound_parents.erase(found); } } }; // switch this_birth.cause @@ -2836,7 +2967,8 @@ namespace already_handled.insert(*i); } - check_scalars_modified(common_parents, parent_roster, other_parent_roster, result); + check_scalars_modified + (n->self, parent_side, common_parents, other_markings, other_uncommon_ancestors, result); } } else @@ -2853,6 +2985,7 @@ namespace set const & uncommon_ancestors, roster_t const & other_parent_roster, marking_map const & other_parent_markings, + set const & other_uncommon_ancestors, resolve_conflicts::side_t parent_side, temp_node_id_source nis, roster_merge_result & result, @@ -2899,7 +3032,8 @@ namespace case marking_t::suture: // case ib, ic, id, ie; check state of suture parents insert_sutured (n, birth_record, parent_markings, uncommon_ancestors, - other_parent_roster, other_parent_markings, parent_side, nis, result, already_handled); + other_parent_roster, other_parent_markings, other_uncommon_ancestors, + parent_side, nis, result, already_handled); break; } } @@ -3184,7 +3318,7 @@ roster_merge(roster_t const & left_paren roster_t const & right_parent, marking_map const & right_markings, set const & right_uncommon_ancestors, - temp_node_id_source nis, + temp_node_id_source & nis, roster_merge_result & result) { set already_handled; @@ -3199,9 +3333,6 @@ roster_merge(roster_t const & left_paren MM(result); // First handle existence merge (lifecycles). See ss-existence-merge.text. - // FIXME_SUTURE: We don't support user suture yet, so case ii can - // only happen in a conflict resolution, which happens later in the - // process. { // Iterate in reverse order so we see sutured nodes before the // corresponding non-sutured node; see ss-existence-merge.text. @@ -3217,7 +3348,7 @@ roster_merge(roster_t const & left_paren // case ii, iii, iva, va, vc insert_if_unborn_or_sutured(i.left_data(), left_parent, left_markings, left_uncommon_ancestors, - right_parent, right_markings, + right_parent, right_markings, right_uncommon_ancestors, resolve_conflicts::left_side, nis, result, already_handled); break; @@ -3225,7 +3356,7 @@ roster_merge(roster_t const & left_paren // case ii, iii, ivb, vb, vd insert_if_unborn_or_sutured(i.right_data(), right_parent, right_markings, right_uncommon_ancestors, - left_parent, left_markings, + left_parent, left_markings, left_uncommon_ancestors, resolve_conflicts::right_side, nis, result, already_handled); break; @@ -3255,7 +3386,7 @@ roster_merge(roster_t const & left_paren { node_t const left_n = i.left_data(); // We skip nodes that aren't in the result roster (were - // deleted in the lifecycles step above). + // deleted in the existence step above). if (result.roster.has_node(left_n->self)) { @@ -3364,9 +3495,31 @@ roster_merge(roster_t const & left_paren break; } } + I(already_handled.size() == 0); I(left_mi == left_markings.end()); I(right_mi == right_markings.end()); - I(new_i == result.roster.all_nodes().end()); + + // If we automatically sutured some nodes in the existence phase, handle + // them now. + for (; new_i != result.roster.all_nodes().end(); new_i++) + { + I(temp_node(new_i->first)); + + node_t result_n = new_i->second; + node_t left_n = left_parent.get_node(result_n->ancestors.first); + marking_t const & left_marking = safe_get(left_markings, left_n->self); + node_t right_n = right_parent.get_node(result_n->ancestors.second); + marking_t const & right_marking = safe_get(right_markings, right_n->self); + + merge_nodes(left_n, + left_marking, + left_uncommon_ancestors, + right_n, + right_marking, + right_uncommon_ancestors, + result_n, + result); + } } // now check for the possible global problems ============================================================ --- roster_merge.hh d10a16027738710cd50617a340b149a29d102d2f +++ roster_merge.hh f2481eac7fed1d1fb3b1d2e22ee8cf03ea795ee3 @@ -178,8 +178,8 @@ struct suture_drop_conflict {resolution.first = resolve_conflicts::none;}; }; -// files with suture_suture conflicts are left attached in result roster -// (unless unattached for another reason), with sutured parent content hash. +// files with suture_suture conflicts are unattached in result roster, with +// sutured parent content hash. struct suture_suture_conflict { // We don't store the file_id in the conflict, to support suturing and @@ -215,6 +215,39 @@ struct suture_suture_conflict extra_nids(extra_nids) {}; }; +// files with suture_scalar conflicts are unattached in result roster, with +// sutured parent content hash. +struct suture_scalar_conflict +{ + // We don't store the file_id in the conflict, to support suturing and + // conflicting directories in the future. + // + // sutured_nid is in sutured_side roster, not in other roster + // common_parents are parents of suture_nid common to both parent rosters + // conflict_nids are in other roster, have subset of common_parents + node_id sutured_nid; + resolve_conflicts::side_t sutured_side; + std::set common_parents; + std::set conflict_nids; + + // There is no resolution; user must suture the nodes in the other parent + // to match the sutured parent, or change the scalars to match, or undo + // the sutures in the sutured parent to match the other parent. + + suture_scalar_conflict () : + sutured_nid(the_null_node), + sutured_side(resolve_conflicts::left_side) {}; + + suture_scalar_conflict(node_id sutured_nid, + resolve_conflicts::side_t sutured_side, + std::set common_parents, + std::set conflict_nids) : + sutured_nid(sutured_nid), + sutured_side(sutured_side), + common_parents(common_parents), + conflict_nids(conflict_nids) {}; +}; + // nodes with attribute conflicts are left attached in the resulting tree (unless // detached for some other reason), but with the given attribute left out of // their full_attr_map_t. Note that this doesn't actually leave the resulting @@ -278,6 +311,7 @@ struct roster_merge_result // - content_drop conflicts // - suture_drop conflicts // - suture_suture conflicts + // - suture_scalar conflicts // - attribute conflicts // - file content conflicts @@ -291,6 +325,7 @@ struct roster_merge_result std::vector content_drop_conflicts; std::vector suture_drop_conflicts; std::vector suture_suture_conflicts; + std::vector suture_scalar_conflicts; std::vector attribute_conflicts; std::vector file_content_conflicts; @@ -336,7 +371,7 @@ struct roster_merge_result bool const basic_io, std::ostream & output) const; void resolve_duplicate_name_conflicts(lua_hooks & lua, - temp_node_id_source nis, + temp_node_id_source & nis, roster_t const & left_roster, roster_t const & right_roster, content_merge_adaptor & adaptor); @@ -387,7 +422,7 @@ roster_merge(roster_t const & left_paren roster_t const & right_parent, marking_map const & right_markings, std::set const & right_uncommon_ancestors, - temp_node_id_source nis, + temp_node_id_source & nis, roster_merge_result & result); void ============================================================ --- ss-existence-merge.text 4f5e53ab1b719beae34333bfb7417248a8411b92 +++ ss-existence-merge.text a58b78ac10d1436495823576067c8e3387ba6eea @@ -242,12 +242,26 @@ ie) one parent, and is unborn in the other. The parents of node 1 in one parent are also the parents of node 4 in the other. - Nodes 4 and 1 are sutured in the child, and their contents merged. + Nodes 4 and 1 are sutured in the child, and their scalars merged. This is done automatically; it is not a conflict. Note that this is the resolution of case id), after user sutures. + This extends to more complex cases: + D2 E3 F3 G4 D2 E3 + \ / \ / \ / + K5 B4 J7 + \ / / + A1 / + \ / + C8 + + As long as the common parents (in this case, nodes 2 and 3) are + the same in the two revisions being merged, the nodes are sutured + automatically. + + D2 E3 F2 G5 \ / \ / A1 B4 @@ -396,10 +410,10 @@ how a node was born. This is done in the To distinguish among these cases, we need to store information about how a node was born. This is done in the marking map, by storing -birth_cause, a pair containing an enumeral and a set of node_ids. The -enumeral indicates add or suture; if suture, the node_ids indicate all -of the suture parents (including parents of parents that are sutures, -etc). +birth_record, containing an enumeral and a set of node_ids and birth +revisions. The enumeral indicates add or suture; if suture, the +node_ids indicate all of the suture parents (including parents of +parents that are sutures, etc), and their birth revisions. Then when node 1 exists in A but not B, we search A's uncommon ancestors for the birth revision of node 1. If found, we have case i; @@ -411,11 +425,11 @@ revision B. distinguished by the state of the parents of the sutured node in revision B. -First we find the set of common ancestor nodes of node 1; the parents -of the suture that created node 1, and recursively for any other -sutures in the revision tree holding A's uncommon ancestor revisions, -that were born in common ancestors of A and B. This is done by -checking the set of parents in the birth record for revisions in A's +First we find the set of common parent nodes of node 1; the parents of +the suture that created node 1, and recursively for any other sutures +in the revision tree holding A's uncommon ancestor revisions, that +were born in common ancestors of A and B. This is done by checking the +set of parent birth revisions in the birth record for revisions in A's uncommon ancestors. Then we check to see if node 1's common ancestor nodes exist in B,