# # # add_file "tests/resolve_duplicate_name_conflict/expected-merge-messages-abe_2-jim_1-conflicts" # content [c505d776680d4278cfbb27e0cb850ba8486e86bd] # # add_file "tests/resolve_duplicate_name_conflict/merge-abe_2-jim_1-resolve_conflicts" # content [bb50072864e1404e1da5597bca04c486c33bf2ed] # # patch "revision.cc" # from [9b166019d4923fd6219b72fb7563aa914238f81d] # to [f036e8be10fd46153b5d7169b808833631ea743c] # # patch "roster.cc" # from [478a80fbf93015f064209e4222be626726dcbb47] # to [9989ba61bf9d98cdc3551daaa3666cd37a752695] # # patch "roster.hh" # from [249507b96d4cd90e81b8903cbcbb044f0c40510a] # to [e752044dc5fb4183c3118daa8c8da40ef86ee3c7] # # patch "roster_delta.cc" # from [195d0d3dc4bc6a59e8fbb3e46f85f446e731f989] # to [eec178e2824304d6ab5201abab47fdb157c15884] # # patch "roster_merge.cc" # from [cd5f29d931fffca5e7e5f6874f9832c4edb1e0be] # to [4fdb63c868ad20c20d95cde69bddc987316eac0d] # # patch "tests/resolve_duplicate_name_conflict/expected-merge-messages-abe_1-beth_1" # from [b40e0b77db581192a3751a3c267177e1451dfe0e] # to [8cc5c13905078a96dd1e308964537c69ed78f4e8] # ============================================================ --- tests/resolve_duplicate_name_conflict/expected-merge-messages-abe_2-jim_1-conflicts c505d776680d4278cfbb27e0cb850ba8486e86bd +++ tests/resolve_duplicate_name_conflict/expected-merge-messages-abe_2-jim_1-conflicts c505d776680d4278cfbb27e0cb850ba8486e86bd @@ -0,0 +1,13 @@ +mtn: 2 heads on branch 'testbranch' +mtn: [left] 257729bebdb32819cd1fc059806e0fb4144f7ec7 +mtn: [right] 3bb925aabafadcdbdc502699024ee282fef0c512 +mtn: 1 content conflict requires user intervention +mtn: conflict: content conflict on file 'checkout.sh' +mtn: content hash is 3390893b9a31eaa9ef0e9364b27ee1e617e6891a on the left +mtn: content hash is a8acbc7472178c5e87c3f0a6953ac5db954e1205 on the right +mtn: help required for 3-way merge +mtn: [ancestor] checkout.sh +mtn: [ left] checkout.sh +mtn: [ right] checkout.sh +mtn: [ merged] checkout.sh +mtn: error: merge failed due to unresolved conflicts ============================================================ --- tests/resolve_duplicate_name_conflict/merge-abe_2-jim_1-resolve_conflicts bb50072864e1404e1da5597bca04c486c33bf2ed +++ tests/resolve_duplicate_name_conflict/merge-abe_2-jim_1-resolve_conflicts bb50072864e1404e1da5597bca04c486c33bf2ed @@ -0,0 +1,13 @@ + left [257729bebdb32819cd1fc059806e0fb4144f7ec7] + right [3bb925aabafadcdbdc502699024ee282fef0c512] +ancestor [5285636b9d9f988e79b3dcd9a40e64d15fb7fc9f] + + conflict content + node_type "file" + ancestor_name "checkout.sh" +ancestor_file_id [61b8d4fb0e5d78be111f691b955d523c782fa92e] + left_name "checkout.sh" + left_file_id [3390893b9a31eaa9ef0e9364b27ee1e617e6891a] + right_name "checkout.sh" + right_file_id [a8acbc7472178c5e87c3f0a6953ac5db954e1205] +resolved_content "checkout.sh" ============================================================ --- revision.cc 9b166019d4923fd6219b72fb7563aa914238f81d +++ revision.cc f036e8be10fd46153b5d7169b808833631ea743c @@ -1,4 +1,4 @@ -// Copyright (C) 2004 Graydon Hoare +// Copyright (C) 2004, 2008 Graydon Hoare // // This program is made available under the GNU GPL version 2.0 or // greater. See the accompanying file COPYING for details. @@ -67,14 +67,13 @@ using boost::shared_ptr; // monotone release. Note that this is _not_ the database schema format; see // schema_migration for that. // -// We use the oldest format number possible when writing each revision. That -// means that if an old revision is regenerated, it has the same revid -// (revision_format is included in the text that is hashed to compute the -// revid). That maintains history, maximizes interoperability between -// monotone versions, and simplifies maintaining the test suite; there are -// many tests that have hard-coded revision ids, that would spuriously break -// if the revision_format number changed. schema_migration and -// workspace_migration in particular have this problem. +// The revision format number is included in the revision text that is +// hashed to compute the revid. Therefore we use the oldest format number +// possible when writing each revision. That maintains history, maximizes +// interoperability between monotone versions, and simplifies maintaining +// the test suite; there are many tests that have hard-coded revision ids, +// that would spuriously break if the revision_format number changed. +// schema_migration and workspace_migration in particular have this problem. static const unsigned int current_revision_format = 2; static const unsigned int oldest_supported_revision_format = 1; @@ -84,7 +83,7 @@ revision_t::required_revision_format() c // Format 2 supports file suturing, so check the edges to see if there are // any sutures. unsigned int result = oldest_supported_revision_format; - + for (edge_map::const_iterator i = edges.begin(); i != edges.end(); ++i) if (i->second->nodes_sutured.size() > 0) result = 2; ============================================================ --- roster.cc 478a80fbf93015f064209e4222be626726dcbb47 +++ roster.cc 9989ba61bf9d98cdc3551daaa3666cd37a752695 @@ -64,11 +64,37 @@ namespace } } -// The marking_map format number must be incremented whenever any basic_io -// format used for marking_map data in the database changes, but only once -// per monotone release. -static const unsigned int current_marking_map_format = 2; +// The roster format number must be incremented whenever any basic_io format +// used for roster or marking_map data in the database changes, but only +// once per monotone release. +// +// A hash of the manifest text is included in the revision text that is +// hashed to compute the revid, and we want to produce the same revision id +// when an old revision is regenerated. Therefore we use the oldest format +// number possible when writing each manifest. +static const unsigned int current_roster_format = 2; +static const unsigned int oldest_supported_roster_format = 1; +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 + // there is a suture or split, so check the marking map for those. + unsigned int result = oldest_supported_roster_format; + + for (marking_map::const_iterator i = mm.begin(); i != mm.end(); ++i) + if (i->second.birth_cause.first != marking_t::add) + result = 2; + + return result; +} + +unsigned int +roster_current_roster_format() +{ + return current_roster_format; +} + template <> void dump(node_id const & val, string & out) { @@ -93,10 +119,6 @@ dump(std::pairself != b->self) return false; @@ -531,8 +554,13 @@ shallow_equal(node_t a, node_t b, if (a->attrs != b->attrs) return false; - if (a->ancestors != b->ancestors) - return false; + if (compare_ancestors) + // compare_ancestors is set false in unit tests when comparing a merge + // to a parent; in that case, the ancestors may be different; the child + // has the parent as an ancestor, but the parent is probably the birth + // revision, and has no ancestor. + if (a->ancestors != b->ancestors) + return false; if (! same_type(a,b)) return false; @@ -1155,7 +1183,6 @@ roster_t::check_sane_against(marking_map ++ri, ++mi) { I(!null_id(mi->second.birth_revision)); - I(mi->second.birth_cause.first != marking_t::invalid); I(!mi->second.parent_name.empty()); if (is_file_t(ri->second)) @@ -1830,9 +1857,6 @@ mark_merge_roster(roster_t const & left_ switch (left_marking.birth_cause.first) { - case marking_t::invalid: - I(false); - case marking_t::add: // Must be unborn on the right (as opposed to dead); // otherwise it would not be in the merge. Therefore the @@ -2731,7 +2755,8 @@ push_marking(basic_io::stanza & st, void push_marking(basic_io::stanza & st, bool is_file, - marking_t const & mark) + marking_t const & mark, + int const marking_format) { I(!null_id(mark.birth_revision)); @@ -2739,16 +2764,14 @@ push_marking(basic_io::stanza & st, switch (mark.birth_cause.first) { - case marking_t::invalid: - I(false); - break; - case marking_t::add: - st.push_str_pair(syms::birth_cause, syms::birth_add); + if (marking_format > 1) + st.push_str_pair(syms::birth_cause, syms::birth_add); break; case marking_t::suture: { + I(marking_format > 1); std::vector data; data.push_back(lexical_cast(mark.birth_cause.second.first)); data.push_back(lexical_cast(mark.birth_cause.second.second)); @@ -2758,6 +2781,7 @@ push_marking(basic_io::stanza & st, case marking_t::split: { + I(marking_format > 1); std::vector data; data.push_back(lexical_cast(mark.birth_cause.second.first)); st.push_str_multi(syms::birth_cause, syms::birth_split, data); @@ -2803,6 +2827,9 @@ 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; pa.sym(); @@ -2869,9 +2896,11 @@ roster_t::print_to(basic_io::printer & p bool print_local_parts) const { I(has_root()); + + int const marking_format = required_roster_format(mm); { basic_io::stanza st; - st.push_str_pair(basic_io::syms::format_version, lexical_cast(current_marking_map_format)); + st.push_str_pair(basic_io::syms::format_version, lexical_cast(marking_format)); pr.print_stanza(st); } for (dfs_iter i(root_dir, true); !i.finished(); ++i) @@ -2924,7 +2953,7 @@ roster_t::print_to(basic_io::printer & p marking_map::const_iterator m = mm.find(curr->self); I(m != mm.end()); - push_marking(st, is_file_t(curr), m->second); + push_marking(st, is_file_t(curr), m->second, marking_format); } pr.print_stanza(st); @@ -2969,7 +2998,12 @@ roster_t::parse_from(basic_io::parser & pa.esym(basic_io::syms::format_version); string vers; pa.str(vers); - I(vers == lexical_cast(current_marking_map_format)); + unsigned int format = boost::lexical_cast(vers); + E(format <= current_roster_format, + F("encountered a roster with unknown format version %s\n" + "I only understand formats up to version %s\n" + "a newer version of monotone is required to complete this operation") + % vers % current_roster_format); } while(pa.symp()) ============================================================ --- roster.hh 249507b96d4cd90e81b8903cbcbb044f0c40510a +++ roster.hh e752044dc5fb4183c3118daa8c8da40ef86ee3c7 @@ -133,14 +133,16 @@ bool } bool -shallow_equal(node_t a, node_t b, bool shallow_compare_dir_children, - bool compare_file_contents = true); +shallow_equal(node_t a, node_t b, + bool shallow_compare_dir_children, + bool compare_file_contents = true, + bool compare_ancestors = true); template <> void dump(node_t const & n, std::string & out); struct marking_t { - typedef enum {invalid, add, suture, split} birth_cause_t; + typedef enum {add, suture, split} birth_cause_t; revision_id birth_revision; std::pair > birth_cause; @@ -149,7 +151,7 @@ struct marking_t std::set parent_name; std::set file_content; std::map > attrs; - marking_t() : birth_cause (std::make_pair (invalid, null_ancestors)) {}; + marking_t() : birth_cause (std::make_pair (add, null_ancestors)) {}; bool operator==(marking_t const & other) const { return birth_revision == other.birth_revision @@ -242,6 +244,8 @@ public: // marking map void check_sane_against(marking_map const & marks, bool temp_nodes_ok=false) const; + unsigned int required_roster_format(marking_map const & mm) const; + void print_to(basic_io::printer & pr, marking_map const & mm, bool print_local_parts) const; @@ -422,7 +426,8 @@ void calculate_ident(roster_t const & ro manifest_id & ident); // for roster_delta -void push_marking(basic_io::stanza & st, bool is_file, marking_t const & mark); +unsigned int roster_current_roster_format(); +void push_marking(basic_io::stanza & st, bool is_file, marking_t const & mark, int const marking_format); void parse_marking(basic_io::parser & pa, marking_t & marking); #ifdef BUILD_UNIT_TESTS ============================================================ --- roster_delta.cc 195d0d3dc4bc6a59e8fbb3e46f85f446e731f989 +++ roster_delta.cc eec178e2824304d6ab5201abab47fdb157c15884 @@ -1,3 +1,4 @@ +// Copyright (C) 2008 Stephen Leake // Copyright (C) 2006 Nathaniel Smith // // This program is made available under the GNU GPL version 2.0 or @@ -28,7 +29,7 @@ using std::make_pair; using std::pair; using std::make_pair; -namespace +namespace { struct roster_delta_t @@ -82,10 +83,10 @@ namespace // Add the new things. for (dirs_added_t::const_iterator i = dirs_added.begin(); i != dirs_added.end(); ++i) - roster.create_dir_node(i->second); + roster.create_dir_node(i->second, null_ancestors); for (files_added_t::const_iterator i = files_added.begin(); i != files_added.end(); ++i) - roster.create_file_node(i->second.second, i->second.first); + roster.create_file_node(i->second.second, i->second.first, null_ancestors); // Attach everything. for (dirs_added_t::const_iterator @@ -206,17 +207,17 @@ namespace { case parallel::invalid: I(false); - + case parallel::in_left: // deleted safe_insert(d.nodes_deleted, i.left_key()); break; - + case parallel::in_right: // added do_delta_for_node_only_in_dest(i.right_data(), d); break; - + case parallel::in_both: // moved/patched/attribute changes do_delta_for_node_in_both(i.left_data(), i.right_data(), d); @@ -233,17 +234,17 @@ namespace { case parallel::invalid: I(false); - + case parallel::in_left: // deleted; don't need to do anything (will be handled by // nodes_deleted set break; - + case parallel::in_right: // added safe_insert(d.markings_changed, i.right_value()); break; - + case parallel::in_both: // maybe changed if (!(i.left_data() == i.right_data())) @@ -264,7 +265,7 @@ namespace symbol const attr_cleared("attr_cleared"); symbol const attr_changed("attr_changed"); symbol const marking("marking"); - + symbol const content("content"); symbol const location("location"); symbol const attr("attr"); @@ -355,7 +356,7 @@ namespace basic_io::stanza st; push_nid(syms::marking, i->first, st); // ...this second argument is a bit odd... - push_marking(st, !i->second.file_content.empty(), i->second); + push_marking(st, !i->second.file_content.empty(), i->second, roster_current_roster_format()); printer.print_stanza(st); } } @@ -459,7 +460,7 @@ namespace safe_insert(d.markings_changed, make_pair(nid, m)); } } - + } // end anonymous namespace void @@ -536,7 +537,7 @@ try_get_content_from_roster_delta(roster { roster_delta_t d; read_roster_delta(del, d); - + roster_delta_t::deltas_applied_t::const_iterator i = d.deltas_applied.find(nid); if (i != d.deltas_applied.end()) { @@ -563,7 +564,7 @@ try_get_content_from_roster_delta(roster return true; } } - + return false; } ============================================================ --- roster_merge.cc cd5f29d931fffca5e7e5f6874f9832c4edb1e0be +++ roster_merge.cc 4fdb63c868ad20c20d95cde69bddc987316eac0d @@ -1865,9 +1865,6 @@ namespace switch (birth_cause.first) { - case marking_t::invalid: - I(false); - case marking_t::add: // case ii, iv; if ii, conflict will be discovered in next phase create_node_for(n, new_roster); @@ -2572,7 +2569,7 @@ struct base_scalar void make_dir(char const * name, node_id nid, roster_t & r, marking_map & markings) { - r.create_dir_node(nid); + r.create_dir_node(nid, null_ancestors); r.attach_node(nid, file_path_internal(name)); marking_t marking; marking.birth_revision = root_rid; @@ -2583,7 +2580,7 @@ struct base_scalar void make_file(char const * name, node_id nid, roster_t & r, marking_map & markings) { - r.create_file_node(arbitrary_file, nid); + r.create_file_node(arbitrary_file, nid, null_ancestors); r.attach_node(nid, file_path_internal(name)); marking_t marking; marking.birth_revision = root_rid; @@ -2813,7 +2810,9 @@ struct file_content_scalar : public virt break; case scalar_conflict: file_content_conflict const & c = idx(result.file_content_conflicts, 0); - I(c.nid == thing_nid); + I(c.left_nid == thing_nid); + I(c.right_nid == thing_nid); + I(c.result_nid == thing_nid); I(c.left == content_for(left_val)); I(c.right == content_for(right_val)); file_id & content = downcast_to_file_t(result.roster.get_node(thing_nid))->content; @@ -2987,7 +2986,7 @@ make_dir(roster_t & r, marking_map & mar revision_id const & birth_rid, revision_id const & parent_name_rid, string const & name, node_id nid) { - r.create_dir_node(nid); + r.create_dir_node(nid, null_ancestors); r.attach_node(nid, file_path_internal(name)); marking_t marking; marking.birth_revision = birth_rid; @@ -3002,7 +3001,7 @@ make_file(roster_t & r, marking_map & ma string const & name, file_id const & content, node_id nid) { - r.create_file_node(content, nid); + r.create_file_node(content, nid, null_ancestors); r.attach_node(nid, file_path_internal(name)); marking_t marking; marking.birth_revision = birth_rid; @@ -3060,22 +3059,25 @@ UNIT_TEST(roster_merge, node_lifecycle) // 7 = 1 root + 2 common + 2 safe a + 2 safe b I(result.roster.all_nodes().size() == 7); // check that they're the right ones... + MM(result.roster); + MM(a_roster); + MM(b_roster); I(shallow_equal(result.roster.get_node(common_dir_nid), - a_roster.get_node(common_dir_nid), false)); + a_roster.get_node(common_dir_nid), false, true, false)); I(shallow_equal(result.roster.get_node(common_file_nid), - a_roster.get_node(common_file_nid), false)); + a_roster.get_node(common_file_nid), false, true, false)); I(shallow_equal(result.roster.get_node(common_dir_nid), - b_roster.get_node(common_dir_nid), false)); + b_roster.get_node(common_dir_nid), false, true, false)); I(shallow_equal(result.roster.get_node(common_file_nid), - b_roster.get_node(common_file_nid), false)); + b_roster.get_node(common_file_nid), false, true, false)); I(shallow_equal(result.roster.get_node(a_safe_dir_nid), - a_roster.get_node(a_safe_dir_nid), false)); + a_roster.get_node(a_safe_dir_nid), false, true, false)); I(shallow_equal(result.roster.get_node(a_safe_file_nid), - a_roster.get_node(a_safe_file_nid), false)); + a_roster.get_node(a_safe_file_nid), false, true, false)); I(shallow_equal(result.roster.get_node(b_safe_dir_nid), - b_roster.get_node(b_safe_dir_nid), false)); + b_roster.get_node(b_safe_dir_nid), false, true, false)); I(shallow_equal(result.roster.get_node(b_safe_file_nid), - b_roster.get_node(b_safe_file_nid), false)); + b_roster.get_node(b_safe_file_nid), false, true, false)); } UNIT_TEST(roster_merge, attr_lifecycle) ============================================================ --- tests/resolve_duplicate_name_conflict/expected-merge-messages-abe_1-beth_1 b40e0b77db581192a3751a3c267177e1451dfe0e +++ tests/resolve_duplicate_name_conflict/expected-merge-messages-abe_1-beth_1 8cc5c13905078a96dd1e308964537c69ed78f4e8 @@ -1,8 +1,8 @@ mtn: 2 heads on branch 'testbranch' mtn: 2 heads on branch 'testbranch' -mtn: [left] c898aad0501a43b1cc6d5138afdbc7844e4efc91 -mtn: [right] ff5f7c356494ecf4c447701cc0c31b59b14981eb +mtn: [left] 5285636b9d9f988e79b3dcd9a40e64d15fb7fc9f +mtn: [right] e9ad84a3fc40ef1109251c308428439c21ad1de9 mtn: suturing checkout.sh, checkout.sh into checkout.sh mtn: renaming thermostat.c to thermostat-westinghouse.c mtn: renaming thermostat.c to thermostat-honeywell.c -mtn: [merged] 80f7e6f31b93c075aa76eed810925f1c45a593c8 +mtn: [merged] 257729bebdb32819cd1fc059806e0fb4144f7ec7 mtn: note: your workspaces have not been updated