# # # patch "cmd_merging.cc" # from [14d137f7034070562dc25354b9829dece065908d] # to [7c7d8852f134cfdc8834adbb204504ff565538a1] # # patch "diff_patch.cc" # from [2016526a3148b6c23a04dd3c50b1af6f5f70a2fd] # to [69b0da5fcfdf80ad434c482841d2d404757c20b3] # # patch "diff_patch.hh" # from [9035f2d57120bd8d9f6c70bf9702e87d1cb42d48] # to [86dc0e6ed925b86091b59948fc4a2779c78ca192] # # patch "merge.cc" # from [ee3fa89c3ccffba1e6d74554f874e7c9e4d528b7] # to [748d0fae376a9804138837a6a1e7006d57f0bdb7] # # patch "paths.hh" # from [f37bdd30f00c3448268723a701924ca5b9450891] # to [7dd47155b2962d3e4f5fe8764b11c955791803fc] # # patch "roster.cc" # from [26d0595c777d04a5fee1da95c1fcef793fdcd578] # to [8630f688a2465d47d34161a4022c0cf5ee0e37c3] # # patch "roster.hh" # from [3d64ba3885d4d9daa5534125d0b58ed00307fb11] # to [379b78e73b1fec2cbac5ada880b97d11a9df6bac] # # patch "roster_merge.cc" # from [e7c50d98e9e6ac59f56b6e8f404ff8c4d68be2ca] # to [2022593f6ae34b8c306d29440264fbfabcdb07e0] # # patch "roster_merge.hh" # from [8d69cc0270c27d6a5d8bca445993a84281ebfaf1] # to [ddb7dc8ed33c6e962fd51eb675c650ec178695c1] # # patch "tests/non_content_conflicts/__driver__.lua" # from [e562b6a47f9b3c0bc84721bff7160d2b70887390] # to [aa81cf5de17933a76239a517c094a700efcbc4c8] # # patch "work.cc" # from [04389e4274f38d0b2bc866c6938d53bbda5e8a2f] # to [bcea6fae446e61352e4a12e0259a9094970a9829] # ============================================================ --- cmd_merging.cc 14d137f7034070562dc25354b9829dece065908d +++ cmd_merging.cc 7c7d8852f134cfdc8834adbb204504ff565538a1 @@ -50,7 +50,7 @@ three_way_merge(roster_t const & ancesto revision_id ancestor_rid(fake_id()); MM(ancestor_rid); revision_id left_rid(fake_id()); MM(left_rid); revision_id right_rid(fake_id()); MM(right_rid); - + // Mark up the ANCESTOR marking_map ancestor_markings; MM(ancestor_markings); mark_roster_with_no_parents(ancestor_rid, ancestor_roster, ancestor_markings); @@ -59,7 +59,7 @@ three_way_merge(roster_t const & ancesto marking_map left_markings; MM(left_markings); mark_roster_with_one_parent(ancestor_roster, ancestor_markings, left_rid, left_roster, left_markings); - + // Mark up the RIGHT roster marking_map right_markings; MM(right_markings); mark_roster_with_one_parent(ancestor_roster, ancestor_markings, @@ -228,7 +228,7 @@ CMD(update, "update", "", CMD_REF(worksp // // we apply the working to merged cset to the workspace // and write the cset from chosen to merged changeset in _MTN/work - + temp_node_id_source nis; // Get the OLD and WORKING rosters @@ -242,7 +242,7 @@ CMD(update, "update", "", CMD_REF(worksp // Get the CHOSEN roster roster_t chosen_roster; MM(chosen_roster); app.db.get_roster(chosen_rid, chosen_roster); - + // And finally do the merge roster_merge_result result; three_way_merge(*old_roster, working_roster, chosen_roster, result); @@ -329,7 +329,7 @@ merge_two(revision_id const & left, revi rsa_keypair_id key; get_user_key(key,app); } - + revision_id merged; transaction_guard guard(app.db); interactive_merge_and_store(left, right, merged, app); @@ -398,7 +398,7 @@ CMD(merge, "merge", "", CMD_REF(tree), " P(F("calculating best pair of heads to merge next")); // For every pair of heads, determine their merge ancestor, and - // remember the ancestor->head mapping. + // remember the ancestor->head mapping. for (rid_set_iter i = heads.begin(); i != heads.end(); ++i) for (rid_set_iter j = i; j != heads.end(); ++j) { @@ -409,7 +409,7 @@ CMD(merge, "merge", "", CMD_REF(tree), " revision_id ancestor; find_common_ancestor_for_merge(*i, *j, ancestor, app); - + // More than one pair might have the same ancestor (e.g. if we // have three heads all with the same parent); as this table // will be recalculated on every pass, we just take the first @@ -417,7 +417,7 @@ CMD(merge, "merge", "", CMD_REF(tree), " if (ancestors.insert(ancestor).second) safe_insert(heads_for_ancestor, std::make_pair(ancestor, revpair(*i, *j))); } - + // Erasing ancestors from ANCESTORS will now produce a set of merge // ancestors each of which is not itself an ancestor of any other // merge ancestor. @@ -427,7 +427,7 @@ CMD(merge, "merge", "", CMD_REF(tree), " // Take the first ancestor from the above set and merge its // corresponding pair of heads. revpair p = heads_for_ancestor[*ancestors.begin()]; - + merge_two(p.first, p.second, app.opts.branchname, string("merge"), app, std::cout, false); ancestors.clear(); @@ -445,7 +445,7 @@ CMD(merge, "merge", "", CMD_REF(tree), " revision_id left = *i++; revision_id right = *i++; I(i == heads.end()); - + merge_two(left, right, app.opts.branchname, string("merge"), app, std::cout, false); P(F("note: your workspaces have not been updated")); } @@ -544,8 +544,8 @@ CMD(merge_into_dir, "merge_into_dir", "" MM(left_roster); MM(right_roster); marking_map left_marking_map, right_marking_map; - set - left_uncommon_ancestors, + set + left_uncommon_ancestors, right_uncommon_ancestors; app.db.get_roster(left_rid, left_roster, left_marking_map); @@ -568,7 +568,7 @@ CMD(merge_into_dir, "merge_into_dir", "" node_t parent = right_roster.get_node(dir); moved_root->parent = parent->self; moved_root->name = base; - marking_map::iterator + marking_map::iterator i = left_marking_map.find(moved_root->self); I(i != left_marking_map.end()); i->second.parent_name.clear(); @@ -576,16 +576,16 @@ CMD(merge_into_dir, "merge_into_dir", "" } roster_merge_result result; - roster_merge(left_roster, - left_marking_map, + roster_merge(left_roster, + left_marking_map, left_uncommon_ancestors, - right_roster, - right_marking_map, + right_roster, + right_marking_map, right_uncommon_ancestors, result); - content_merge_database_adaptor - dba(app, left_rid, right_rid, left_marking_map); + content_merge_database_adaptor + dba(app, left_rid, right_rid, left_marking_map, right_marking_map); resolve_merge_conflicts(left_roster, right_roster, result, dba, app); @@ -742,7 +742,7 @@ CMD(explicit_merge, "explicit_merge", "" merge_two(left, right, branch, string("explicit merge"), app, std::cout, false); } -CMD(show_conflicts, "show_conflicts", "", CMD_REF(informative), N_("REV REV"), +CMD(show_conflicts, "show_conflicts", "", CMD_REF(informative), N_("REV REV"), N_("Shows what conflicts need resolution between two revisions"), N_("The conflicts are calculated based on the two revisions given in " "the REV parameters."), @@ -752,7 +752,7 @@ CMD(show_conflicts, "show_conflicts", "" throw usage(execid); revision_id l_id, r_id; complete(app, idx(args,0)(), l_id); - complete(app, idx(args,1)(), r_id); + complete(app, idx(args,1)(), r_id); N(!is_ancestor(l_id, r_id, app), F("%s is an ancestor of %s; no merge is needed.") % l_id % r_id); N(!is_ancestor(r_id, l_id, app), @@ -770,17 +770,17 @@ CMD(show_conflicts, "show_conflicts", "" r_roster, r_marking, r_uncommon_ancestors, result); - P(F("There are %s divergent_name_conflicts.") + P(F("There are %s divergent_name_conflicts.") % result.divergent_name_conflicts.size()); - P(F("There are %s file_content_conflicts.") + P(F("There are %s file_content_conflicts.") % result.file_content_conflicts.size()); - P(F("There are %s node_attr_conflicts.") + P(F("There are %s node_attr_conflicts.") % result.node_attr_conflicts.size()); - P(F("There are %s orphaned_node_conflicts.") + P(F("There are %s orphaned_node_conflicts.") % result.orphaned_node_conflicts.size()); - P(F("There are %s convergent_name_conflicts.") + P(F("There are %s convergent_name_conflicts.") % result.convergent_name_conflicts.size()); - P(F("There are %s directory_loop_conflicts.") + P(F("There are %s directory_loop_conflicts.") % result.directory_loop_conflicts.size()); } @@ -793,7 +793,7 @@ CMD(pluck, "pluck", "", CMD_REF(workspac "renames, conflicts, and so on.\n" "If one revision is given, applies the changes made in that revision " "compared to its parent.\n" - "If two revisions are given, applies the changes made to get from the " + "If two revisions are given, applies the changes made to get from the " "first revision to the second."), options::opts::revision | options::opts::depth | options::opts::exclude) { @@ -826,7 +826,7 @@ CMD(pluck, "pluck", "", CMD_REF(workspac } else throw usage(execid); - + app.require_workspace(); N(!(from_rid == to_rid), F("no changes to apply")); @@ -834,7 +834,7 @@ CMD(pluck, "pluck", "", CMD_REF(workspac // notionally, we have the situation // // from --> working - // | | + // | | // V V // to --> merged // @@ -915,7 +915,7 @@ CMD(pluck, "pluck", "", CMD_REF(workspac // temporary node ids may appear merged_roster.check_sane(true); - // we apply the working to merged cset to the workspace + // we apply the working to merged cset to the workspace cset update; MM(update); make_cset(working_roster, merged_roster, update); @@ -934,7 +934,7 @@ CMD(pluck, "pluck", "", CMD_REF(workspac // small race condition here... app.work.put_work_rev(remaining); app.work.update_any_attrs(); - + // add a note to the user log file about what we did { utf8 log; @@ -987,13 +987,13 @@ CMD(get_roster, "get_roster", "", CMD_RE { roster_t roster; marking_map mm; - + if (args.size() == 0) { parent_map parents; temp_node_id_source nis; revision_id rid(fake_id()); - + app.require_workspace(); app.work.get_parent_rosters(parents); app.work.get_current_roster_shape(roster, nis); ============================================================ --- diff_patch.cc 2016526a3148b6c23a04dd3c50b1af6f5f70a2fd +++ diff_patch.cc 69b0da5fcfdf80ad434c482841d2d404757c20b3 @@ -490,8 +490,9 @@ content_merge_database_adaptor::content_ content_merge_database_adaptor::content_merge_database_adaptor(app_state & app, revision_id const & left, revision_id const & right, - marking_map const & mm) - : app(app), mm(mm) + marking_map const & left_mm, + marking_map const & right_mm) + : app(app), left_mm(left_mm), right_mm(right_mm) { // FIXME: possibly refactor to run this lazily, as we don't // need to find common ancestors if we're never actually @@ -516,7 +517,7 @@ content_merge_database_adaptor::record_m { delta left_delta; diff(left_data.inner(), merged_data.inner(), left_delta); - app.db.put_file_version(left_ident, merged_ident, file_delta(left_delta)); + app.db.put_file_version(left_ident, merged_ident, file_delta(left_delta)); } if (!(right_ident == merged_ident)) { @@ -547,6 +548,7 @@ content_merge_database_adaptor::get_ance void content_merge_database_adaptor::get_ancestral_roster(node_id nid, + revision_id & rid, shared_ptr & anc) { // Given a file, if the lca is nonzero and its roster contains the file, @@ -554,6 +556,7 @@ content_merge_database_adaptor::get_ance // birth revision, which is the "per-file worst case" lca. // Begin by loading any non-empty file lca roster + rid = lca; if (!lca.inner()().empty()) load_and_cache_roster(lca, rosters, anc, app); @@ -561,9 +564,29 @@ content_merge_database_adaptor::get_ance // then use the file's birth roster. if (!anc || !anc->has_node(nid)) { - marking_map::const_iterator j = mm.find(nid); - I(j != mm.end()); - load_and_cache_roster(j->second.birth_revision, rosters, anc, app); + marking_map::const_iterator lmm = left_mm.find(nid); + marking_map::const_iterator rmm = right_mm.find(nid); + + MM(left_mm); + MM(right_mm); + + if (lmm == left_mm.end()) + { + I(rmm != right_mm.end()); + rid = rmm->second.birth_revision; + } + else if (rmm == right_mm.end()) + { + I(lmm != left_mm.end()); + rid = lmm->second.birth_revision; + } + else + { + I(lmm->second.birth_revision == rmm->second.birth_revision); + rid = lmm->second.birth_revision; + } + + load_and_cache_roster(rid, rosters, anc, app); } I(anc); } @@ -598,11 +621,14 @@ content_merge_workspace_adaptor::get_anc void content_merge_workspace_adaptor::get_ancestral_roster(node_id nid, + revision_id & rid, shared_ptr & anc) { // When doing an update, the base revision is always the ancestor to // use for content merging. anc = base; + + // FIXME: return something for rid } void ============================================================ --- diff_patch.hh 9035f2d57120bd8d9f6c70bf9702e87d1cb42d48 +++ diff_patch.hh 86dc0e6ed925b86091b59948fc4a2779c78ca192 @@ -51,6 +51,7 @@ content_merge_adaptor file_data const & merged_data) = 0; virtual void get_ancestral_roster(node_id nid, + revision_id & rid, boost::shared_ptr & anc) = 0; virtual void get_version(file_id const & ident, @@ -65,12 +66,14 @@ content_merge_database_adaptor { app_state & app; revision_id lca; - marking_map const & mm; + marking_map const & left_mm; + marking_map const & right_mm; std::map > rosters; content_merge_database_adaptor (app_state & app, revision_id const & left, revision_id const & right, - marking_map const & mm); + marking_map const & left_mm, + marking_map const & right_mm); void record_merge(file_id const & left_ident, file_id const & right_ident, file_id const & merged_ident, @@ -79,6 +82,7 @@ content_merge_database_adaptor file_data const & merged_data); void get_ancestral_roster(node_id nid, + revision_id & rid, boost::shared_ptr & anc); void get_version(file_id const & ident, @@ -93,9 +97,9 @@ content_merge_workspace_adaptor app_state & app; boost::shared_ptr base; std::map content_paths; - content_merge_workspace_adaptor (app_state & app, - boost::shared_ptr base, - std::map const & paths) + content_merge_workspace_adaptor(app_state & app, + boost::shared_ptr base, + std::map const & paths) : app(app), base(base), content_paths(paths) {} void record_merge(file_id const & left_ident, @@ -106,6 +110,7 @@ content_merge_workspace_adaptor file_data const & merged_data); void get_ancestral_roster(node_id nid, + revision_id & rid, boost::shared_ptr & anc); void get_version(file_id const & ident, ============================================================ --- merge.cc ee3fa89c3ccffba1e6d74554f874e7c9e4d528b7 +++ merge.cc 748d0fae376a9804138837a6a1e7006d57f0bdb7 @@ -53,9 +53,10 @@ resolve_merge_conflicts(roster_t const & if (!result.is_clean()) result.log_conflicts(); + if (result.has_non_content_conflicts()) { - result.warn_non_content_conflicts(left_roster, right_roster); + result.warn_non_content_conflicts(left_roster, right_roster, adaptor); } else if (result.has_content_conflicts()) { @@ -73,8 +74,11 @@ resolve_merge_conflicts(roster_t const & { file_content_conflict const & conflict = *it; + MM(conflict); + + revision_id rid; shared_ptr roster_for_file_lca; - adaptor.get_ancestral_roster(conflict.nid, roster_for_file_lca); + adaptor.get_ancestral_roster(conflict.nid, rid, roster_for_file_lca); // Now we should certainly have a roster, which has the node. I(roster_for_file_lca); @@ -82,9 +86,9 @@ resolve_merge_conflicts(roster_t const & file_id anc_id, left_id, right_id; file_path anc_path, left_path, right_path; - get_file_details (*roster_for_file_lca, conflict.nid, anc_id, anc_path); - get_file_details (left_roster, conflict.nid, left_id, left_path); - get_file_details (right_roster, conflict.nid, right_id, right_path); + get_file_details(*roster_for_file_lca, conflict.nid, anc_id, anc_path); + get_file_details(left_roster, conflict.nid, left_id, left_path); + get_file_details(right_roster, conflict.nid, right_id, right_path); file_id merged_id; @@ -148,9 +152,10 @@ interactive_merge_and_store(revision_id right_roster, right_marking_map, right_uncommon_ancestors, result); - content_merge_database_adaptor dba(app, left_rid, right_rid, left_marking_map); - resolve_merge_conflicts (left_roster, right_roster, - result, dba, app); + content_merge_database_adaptor dba(app, left_rid, right_rid, + left_marking_map, right_marking_map); + resolve_merge_conflicts(left_roster, right_roster, + result, dba, app); // write new files into the db store_roster_merge_result(left_roster, right_roster, result, ============================================================ --- paths.hh f37bdd30f00c3448268723a701924ca5b9450891 +++ paths.hh 7dd47155b2962d3e4f5fe8764b11c955791803fc @@ -164,7 +164,7 @@ public: // in file_io.cc). any_path operator /(path_component const &) const; any_path dirname() const; - + any_path(any_path const & other) : data(other.data) {} any_path & operator=(any_path const & other) @@ -202,15 +202,18 @@ public: // does dirname() and basename() at the same time, for efficiency void dirname_basename(file_path &, path_component &) const; - + // returns the number of /-separated components of the path. // The empty path has depth zero. unsigned int depth() const; // ordering... - bool operator ==(const file_path & other) const + bool operator==(const file_path & other) const { return data == other.data; } + bool operator!=(const file_path & other) const + { return data != other.data; } + // the ordering on file_path is not exactly that of strings. // see the "ordering" unit test in paths.cc. bool operator <(const file_path & other) const @@ -300,7 +303,7 @@ public: // exposed for the use of walk_tree and friends static bool internal_string_is_bookkeeping_path(utf8 const & path); static bool external_string_is_bookkeeping_path(utf8 const & path); - bool operator ==(const bookkeeping_path & other) const + bool operator==(const bookkeeping_path & other) const { return data == other.data; } bool operator <(const bookkeeping_path & other) const @@ -350,8 +353,8 @@ public: system_path(std::string const & path); system_path(utf8 const & path); - bool operator ==(const system_path & other) const - { return data == other.data; } + bool operator==(const system_path & other) const + { return data== other.data; } system_path operator /(path_component const & to_append) const; system_path operator /(char const * to_append) const; ============================================================ --- roster.cc 26d0595c777d04a5fee1da95c1fcef793fdcd578 +++ roster.cc 8630f688a2465d47d34161a4022c0cf5ee0e37c3 @@ -47,6 +47,12 @@ template <> void /////////////////////////////////////////////////////////////////// template <> void +dump(node_id const & val, string & out) +{ + out = lexical_cast(val); +} + +template <> void dump(full_attr_map_t const & val, string & out) { ostringstream oss; @@ -619,6 +625,21 @@ bool } bool +roster_t::is_attached(node_id n) const +{ + if (!has_root()) + return false; + if (n == root_dir->self) + return true; + if (!has_node(n)) + return false; + + node_t node = get_node(n); + + return !null_node(node->parent); +} + +bool roster_t::has_node(file_path const & p) const { MM(*this); @@ -628,7 +649,7 @@ roster_t::has_node(file_path const & p) return false; if (p.empty()) return true; - + dir_t nd = root_dir; string const & pi = p.as_internal(); string::size_type start = 0, stop; @@ -974,7 +995,7 @@ roster_t::get_attr(file_path const & pth return true; } return false; -} +} template <> void @@ -1951,7 +1972,7 @@ mark_roster_with_one_parent(roster_t con I(!null_id(child_rid)); child_markings.clear(); - + for (node_map::const_iterator i = child.all_nodes().begin(); i != child.all_nodes().end(); ++i) { @@ -2205,7 +2226,7 @@ select_restricted_nodes(roster_t const & case parallel::in_both: // moved/renamed/patched/attribute changes - if (mask.includes(from, i.left_key()) || + if (mask.includes(from, i.left_key()) || mask.includes(to, i.right_key())) selected.insert(make_pair(i.right_key(), i.right_data())); else @@ -2237,17 +2258,17 @@ make_restricted_roster(roster_t const & map::const_iterator n = selected.begin(), p; L(FL("selected node %d %s parent %d") - % n->second->self + % n->second->self % n->second->name % n->second->parent); - while (!null_node(n->second->parent) && + while (!null_node(n->second->parent) && !restricted.has_node(n->second->parent)) { // we can't add this node until its parent has been added L(FL("deferred node %d %s parent %d") - % n->second->self + % n->second->self % n->second->name % n->second->parent); @@ -2265,7 +2286,7 @@ make_restricted_roster(roster_t const & if (p != selected.end()) { L(FL("adding node %d %s parent %d") - % n->second->self + % n->second->self % n->second->name % n->second->parent); @@ -2949,7 +2970,7 @@ tests_on_two_rosters(roster_t const & a, make_cset(b2, a2, b2_to_a2); do_testing_on_two_equivalent_csets(a_to_b, a2_to_b2); do_testing_on_two_equivalent_csets(b_to_a, b2_to_a2); - + { marking_map a_marking; make_fake_marking_for(a, a_marking); @@ -3026,7 +3047,7 @@ bool parent_of(file_path const & p, { string const & ci = c.as_internal(); string const & pi = p.as_internal(); - + string::const_iterator c_anchor = search(ci.begin(), ci.end(), pi.begin(), pi.end()); @@ -4799,7 +4820,7 @@ create_random_unification_task(roster_t randomizer & rng) { size_t n_nodes = 20 + rng.uniform(60); - + // Stick in a root if there isn't one. if (!left.has_root()) { ============================================================ --- roster.hh 3d64ba3885d4d9daa5534125d0b58ed00307fb11 +++ roster.hh 379b78e73b1fec2cbac5ada880b97d11a9df6bac @@ -19,6 +19,7 @@ #include "numeric_vocab.hh" #include "paths.hh" #include "sanity.hh" + #include "vocab.hh" struct node_id_source @@ -52,6 +53,7 @@ typedef hybrid_map node typedef std::map dir_map; typedef hybrid_map node_map; +template <> void dump(node_id const & val, std::string & out); template <> void dump(full_attr_map_t const & val, std::string & out); @@ -182,6 +184,7 @@ public: bool has_node(file_path const & sp) const; bool has_node(node_id nid) const; bool is_root(node_id nid) const; + bool is_attached(node_id nid) const; node_t get_node(file_path const & sp) const; node_t get_node(node_id nid) const; void get_name(node_id nid, file_path & sp) const; @@ -374,7 +377,7 @@ void std::set & nodes_modified); void -get_content_paths(roster_t const & roster, +get_content_paths(roster_t const & roster, std::map & paths); // These functions are for the use of things like 'update' or 'pluck', that @@ -470,4 +473,3 @@ struct testing_node_id_source // vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s: #endif - ============================================================ --- roster_merge.cc e7c50d98e9e6ac59f56b6e8f404ff8c4d68be2ca +++ roster_merge.cc 2022593f6ae34b8c306d29440264fbfabcdb07e0 @@ -10,16 +10,89 @@ #include "base.hh" #include +#include + #include "vocab.hh" #include "roster_merge.hh" #include "parallel_iter.hh" #include "safe_map.hh" +using boost::shared_ptr; + using std::make_pair; +using std::ostringstream; using std::pair; using std::set; using std::string; +template <> void +dump(divergent_name_conflict const & conflict, string & out) +{ + ostringstream oss; + oss << "node: " << conflict.nid << "\n" + << "left: " << conflict.left.first << " " << conflict.left.second << "\n" + << "right: " << conflict.right.first << " " << conflict.right.second << "\n"; + out = oss.str(); +} + +template <> void +dump(file_content_conflict const & conflict, string & out) +{ + ostringstream oss; + oss << "node: " << conflict.nid << "\n" + << "left: " << conflict.left << "\n" + << "right: " << conflict.right << "\n"; + out = oss.str(); +} + +template <> void +dump(node_attr_conflict const & conflict, string & out) +{ + ostringstream oss; + oss << "node: " << conflict.nid << "\n" + << "attr: '" << conflict.key << "'\n" + << "left: " << conflict.left.first << " '" << conflict.left.second << "'\n" + << "right: " << conflict.right.first << " '" << conflict.right.second << "'\n"; + out = oss.str(); +} + +template <> void +dump(orphaned_node_conflict const & conflict, string & out) +{ + ostringstream oss; + oss << "node: " << conflict.nid << "\n" + << "parent: " << conflict.parent_name.first << " '" << conflict.parent_name.second << "'\n"; + out = oss.str(); +} + +template <> void +dump(convergent_name_conflict const & conflict, string & out) +{ + ostringstream oss; + oss << "left: " << conflict.left_nid << "\n" + << "right: " << conflict.right_nid << "\n" + << "parent: " << conflict.parent_name.first << " '" << conflict.parent_name.second << "'\n"; + out = oss.str(); +} + +template <> void +dump(directory_loop_conflict const & conflict, string & out) +{ + ostringstream oss; + oss << "node: " << conflict.nid << "\n" + << "parent: " << conflict.parent_name.first << " '" << conflict.parent_name.second << "'\n"; + out = oss.str(); +} + +template <> void +dump(illegal_name_conflict const & conflict, string & out) +{ + ostringstream oss; + oss << "node: " << conflict.nid << "\n" + << "parent: " << conflict.parent_name.first << " '" << conflict.parent_name.second << "'\n"; + out = oss.str(); +} + bool roster_merge_result::is_clean() const { @@ -171,168 +244,401 @@ namespace else I(false); } - } void -roster_merge_result::warn_non_content_conflicts(roster_t const & left, - roster_t const & right) const +roster_merge_result::warn_non_content_conflicts(roster_t const & left_roster, + roster_t const & right_roster, + content_merge_adaptor & adaptor) const { // TODO: - // - distinguish files and dirs from generic nodes - // - don't include node ids // - W on each conflict type and then P further details - // - get "ancestral" names (if that's reasonable) - // - ensure "left" *is* from the left and "right" *is* from the right // - add a better error macro (D?) for these multiple problem cases + // - need a way to pring blank lines, with no "mtn:" prefix + MM(left_roster); + MM(right_roster); + for (size_t i = 0; i < divergent_name_conflicts.size(); ++i) { + divergent_name_conflict const & conflict = divergent_name_conflicts[i]; + MM(conflict); + + I(!roster.is_attached(conflict.nid)); + file_path left_name, right_name; - left.get_name(divergent_name_conflicts[i].nid, left_name); - right.get_name(divergent_name_conflicts[i].nid, right_name); - /* - mtn: error: divergent name conflict - mtn: file/dir ... in BASE - mtn: renamed to ... on the left - mtn: renamed to ... on the right - */ + left_roster.get_name(conflict.nid, left_name); + right_roster.get_name(conflict.nid, right_name); - W(F("divergent name conflict: one node (%d) wants two names ('%s' and '%s')") - % divergent_name_conflicts[i].nid - % left_name - % right_name); + shared_ptr lca_roster; + revision_id lca_rid; + file_path lca_name; + + adaptor.get_ancestral_roster(conflict.nid, lca_rid, lca_roster); + lca_roster->get_name(conflict.nid, lca_name); + + P(F("conflict: multiple names for %s '%s' from revision %s") + % get_type(*lca_roster, conflict.nid) % lca_name % lca_rid); + P(F("renamed to '%s' on the left") % left_name); + P(F("renamed to '%s' on the right") % right_name); } for (size_t i = 0; i < node_attr_conflicts.size(); ++i) { - file_path left_name, right_name; + node_attr_conflict const & conflict = node_attr_conflicts[i]; + MM(conflict); - string const & type = get_type(left, node_attr_conflicts[i].nid); + string const & type = get_type(roster, conflict.nid); - left.get_name(node_attr_conflicts[i].nid, left_name); - right.get_name(node_attr_conflicts[i].nid, right_name); + if (roster.is_attached(conflict.nid)) + { + file_path name; + roster.get_name(conflict.nid, name); - if (left_name == right_name) - { - P(F("attribute conflict: '%s' on %s '%s'") - % node_attr_conflicts[i].key - % type - % left_name); + P(F("conflict: multiple values for attribute '%s' on %s '%s'") + % conflict.key % type % name); + + if (conflict.left.first) + P(F("set to '%s' on the left") % conflict.left.second); + else + P(F("deleted on the left")); + + if (conflict.right.first) + P(F("set to '%s' on the right") % conflict.right.second); + else + P(F("deleted on the right")); } else { - P(F("attribute conflict: '%s'") - % node_attr_conflicts[i].key); - P(F("left %s '%s'") - % type - % left_name); - P(F("right %s '%s'") - % type - % right_name); - } + // this node isn't attached in the merged roster and there + // isn't really a good name for it so report both the left + // and right names using a slightly different format - if (node_attr_conflicts[i].left.first) - P(F("set to '%s' on the left") - % node_attr_conflicts[i].left.second); - else - P(F("deleted on the left")); + file_path left_name, right_name; + left_roster.get_name(conflict.nid, left_name); + right_roster.get_name(conflict.nid, right_name); - if (node_attr_conflicts[i].right.first) - P(F("set to '%s' on the right") - % node_attr_conflicts[i].right.second); - else - P(F("deleted on the right")); + shared_ptr lca_roster; + revision_id lca_rid; + file_path lca_name; + + adaptor.get_ancestral_roster(conflict.nid, lca_rid, lca_roster); + lca_roster->get_name(conflict.nid, lca_name); + + P(F("conflict: multiple values for attribute '%s' on %s '%s' from revision %s") + % conflict.key % type % lca_name % lca_rid); + + if (conflict.left.first) + P(F("set to '%s' on left %s '%s'") + % conflict.left.second % type % left_name); + else + P(F("deleted from left %s '%s'") + % type % left_name); + + if (conflict.right.first) + P(F("set to '%s' on right %s '%s'") + % conflict.right.second % type % right_name); + else + P(F("deleted from right %s '%s'") + % type % right_name); + } } - for (size_t i = 0; i < orphaned_node_conflicts.size(); ++i) { - file_path fp; - if (left.has_node(orphaned_node_conflicts[i].nid)) - left.get_name(orphaned_node_conflicts[i].nid, fp); - else - right.get_name(orphaned_node_conflicts[i].nid, fp); + orphaned_node_conflict const & conflict = orphaned_node_conflicts[i]; + MM(conflict); - /* - mtn: error: orphaned file/directory conflict - mtn: file/dir ... in base (base/left/right?) - mtn : ...more... - */ + I(!roster.is_attached(conflict.nid)); - W(F("orphaned node conflict: parent of '%s' was removed (node %d parent %d)") - % fp - % orphaned_node_conflicts[i].nid - % orphaned_node_conflicts[i].parent_name.first); + shared_ptr lca_roster, parent_lca_roster; + revision_id lca_rid, parent_lca_rid; + file_path lca_name; + + adaptor.get_ancestral_roster(conflict.nid, lca_rid, lca_roster); + adaptor.get_ancestral_roster(conflict.parent_name.first, + parent_lca_rid, parent_lca_roster); + + lca_roster->get_name(conflict.nid, lca_name); + + string const & type = get_type(*lca_roster, conflict.nid); + + P(F("conflict: orphaned %s '%s' from revision %s") + % type % lca_name % lca_rid); + + if (left_roster.has_node(conflict.parent_name.first) && + !right_roster.has_node(conflict.parent_name.first)) + { + file_path orphan_name, parent_name; + left_roster.get_name(conflict.nid, orphan_name); + left_roster.get_name(conflict.parent_name.first, parent_name); + + // FIXME: we get '' for the root directory here + // also something else is not right in the missing root directory + // case when both sides have pivoted something to the root + // and deleted the other's new root + + P(F("parent directory '%s' was deleted on the right") + % parent_name); + + if (parent_lca_roster->has_node(conflict.nid)) + P(F("%s '%s' was renamed from '%s' on the left") + % type % orphan_name % lca_name); + else + P(F("%s '%s' was added on the left") + % type % orphan_name); + } + else if (!left_roster.has_node(conflict.parent_name.first) && + right_roster.has_node(conflict.parent_name.first)) + { + file_path orphan_name, parent_name; + right_roster.get_name(conflict.nid, orphan_name); + right_roster.get_name(conflict.parent_name.first, parent_name); + + P(F("parent directory '%s' was deleted on the left") + % parent_name); + + if (parent_lca_roster->has_node(conflict.nid)) + P(F("%s '%s' was renamed from '%s' on the right") + % type % orphan_name % lca_name); + else + P(F("%s '%s' was added on the right") + % type % orphan_name); + } + else + I(false); } for (size_t i = 0; i < convergent_name_conflicts.size(); ++i) { convergent_name_conflict const & conflict = convergent_name_conflicts[i]; + MM(conflict); + node_id left_nid, right_nid; + + left_nid = conflict.left_nid; + right_nid = conflict.right_nid; + + I(!roster.is_attached(left_nid)); + I(!roster.is_attached(right_nid)); + file_path left_name, right_name; - left.get_name(conflict.left_nid, left_name); - right.get_name(conflict.right_nid, right_name); + left_roster.get_name(left_nid, left_name); + right_roster.get_name(right_nid, right_name); I(left_name == right_name); - /* - mtn: error: convergent name conflict - mtn: ... 4 different sub cases ... - */ + shared_ptr left_lca_roster, right_lca_roster; + revision_id left_lca_rid, right_lca_rid; - W(F("convergent name conflict: two nodes (%d and %d) want one name ('%s')") - % conflict.left_nid - % conflict.right_nid - % left_name); + adaptor.get_ancestral_roster(left_nid, left_lca_rid, left_lca_roster); + adaptor.get_ancestral_roster(right_nid, right_lca_rid, right_lca_roster); + + P(F("conflict: duplicate name '%s'") % left_name); + + string const & left_type = get_type(left_roster, left_nid); + string const & right_type = get_type(right_roster, right_nid); + + if (!left_lca_roster->has_node(right_nid) && + !right_lca_roster->has_node(left_nid)) + { + P(F("added as a new %s on the left") % left_type); + P(F("added as a new %s on the right") % right_type); + } + else if (!left_lca_roster->has_node(right_nid) && + right_lca_roster->has_node(left_nid)) + { + file_path left_lca_name; + left_lca_roster->get_name(left_nid, left_lca_name); + + P(F("renamed from %s '%s' on the left") % left_type % left_lca_name); + P(F("added as a new %s on the right") % right_type); + } + else if (left_lca_roster->has_node(right_nid) && + !right_lca_roster->has_node(left_nid)) + { + file_path right_lca_name; + right_lca_roster->get_name(right_nid, right_lca_name); + + P(F("added as a new %s on the left") % left_type); + P(F("renamed from %s '%s' on the right") % right_type % right_lca_name); + } + else if (left_lca_roster->has_node(right_nid) && + right_lca_roster->has_node(left_nid)) + { + file_path left_lca_name, right_lca_name; + left_lca_roster->get_name(left_nid, left_lca_name); + right_lca_roster->get_name(right_nid, right_lca_name); + + P(F("renamed from %s '%s' on the left") % left_type % left_lca_name); + P(F("renamed from %s '%s' on the right")% right_type % right_lca_name); + } + else + I(false); } for (size_t i = 0; i < directory_loop_conflicts.size(); ++i) { - file_path left_name, right_name; - left.get_name(directory_loop_conflicts[i].nid, left_name); - right.get_name(directory_loop_conflicts[i].parent_name.first, right_name); + directory_loop_conflict const & conflict = directory_loop_conflicts[i]; + MM(conflict); - /* - mtn: error: directory loop conflict - mtn: ... - */ + I(!roster.is_attached(conflict.nid)); - W(F("directory loop conflict: between '%s' (node %d) and '%s' (node %d)") - % left_name - % directory_loop_conflicts[i].nid - % right_name - % directory_loop_conflicts[i].parent_name.first); + file_path left_name, right_name, left_parent_name, right_parent_name; + + left_roster.get_name(conflict.nid, left_name); + right_roster.get_name(conflict.nid, right_name); + + left_roster.get_name(conflict.parent_name.first, left_parent_name); + right_roster.get_name(conflict.parent_name.first, right_parent_name); + + shared_ptr lca_roster; + revision_id lca_rid; + file_path lca_name, lca_parent_name; + + adaptor.get_ancestral_roster(conflict.nid, lca_rid, lca_roster); + lca_roster->get_name(conflict.nid, lca_name); + lca_roster->get_name(conflict.parent_name.first, lca_parent_name); + + P(F("conflict: directory loop created")); + + if (left_name != lca_name) + P(F("'%s' renamed to '%s' on the left") + % lca_name % left_name); + + if (right_name != lca_name) + P(F("'%s' renamed to '%s' on the right") + % lca_name % right_name); + + if (left_parent_name != lca_parent_name) + P(F("'%s' renamed to '%s' on the left") + % lca_parent_name % left_parent_name); + + if (right_parent_name != lca_parent_name) + P(F("'%s' renamed to '%s' on the right") + % lca_parent_name % right_parent_name); } for (size_t i = 0; i < illegal_name_conflicts.size(); ++i) { - file_path left_name, right_name; - left.get_name(illegal_name_conflicts[i].nid, left_name); - right.get_name(illegal_name_conflicts[i].parent_name.first, right_name); + illegal_name_conflict const & conflict = illegal_name_conflicts[i]; + MM(conflict); - /* - mtn: error: invalid name conflict - mtn: ... - */ + I(!roster.is_attached(conflict.nid)); - W(F("illegal name conflict: between '%s' (node %d) and '%s' (node %d)") - % left_name - % illegal_name_conflicts[i].nid - % right_name - % illegal_name_conflicts[i].parent_name.first); + shared_ptr lca_roster, parent_lca_roster; + revision_id lca_rid, parent_lca_rid; + file_path lca_name, lca_parent_name; + + adaptor.get_ancestral_roster(conflict.nid, lca_rid, lca_roster); + lca_roster->get_name(conflict.nid, lca_name); + lca_roster->get_name(conflict.parent_name.first, lca_parent_name); + + adaptor.get_ancestral_roster(conflict.parent_name.first, + parent_lca_rid, parent_lca_roster); + + P(F("conflict: invalid name _MTN in root directory")); + + if (left_roster.root()->self == conflict.parent_name.first) + { + P(F("'%s' pivoted to root on the left") + % lca_parent_name); + + file_path right_name; + right_roster.get_name(conflict.nid, right_name); + if (parent_lca_roster->has_node(conflict.nid)) + { + P(F("'%s' renamed to '%s' on the right") + % lca_name % right_name); + } + else + { + P(F("'%s' added in revision %s on the right") + % right_name % lca_rid); + } + } + else if (right_roster.root()->self == conflict.parent_name.first) + { + P(F("'%s' pivoted to root on the right") + % lca_parent_name); + + file_path left_name; + left_roster.get_name(conflict.nid, left_name); + if (parent_lca_roster->has_node(conflict.nid)) + { + P(F("'%s' renamed to '%s' on the left") + % lca_name % left_name); + } + else + { + P(F("'%s' added in revision %s on the left") + % left_name % lca_rid); + } + } + else + I(false); } if (missing_root_dir) { - /* - mtn: error: root directory conflict - mtn: ... - */ - W(F("missing root conflict: root directory has been removed")); + node_id left_root, right_root; + left_root = left_roster.root()->self; + right_root = right_roster.root()->self; + + P(F("conflict: missing root directory")); + + if (left_roster.has_node(right_root) && + !right_roster.has_node(left_root)) + { + shared_ptr lca_roster; + revision_id lca_rid; + file_path lca_name; + + adaptor.get_ancestral_roster(left_root, lca_rid, lca_roster); + lca_roster->get_name(left_root, lca_name); + + P(F("directory '%s' pivoted to root on the left") % lca_name); + P(F("directory '%s' deleted on the right") % lca_name); + } + else if (!left_roster.has_node(right_root) && + right_roster.has_node(left_root)) + { + shared_ptr lca_roster; + revision_id lca_rid; + file_path lca_name; + + adaptor.get_ancestral_roster(right_root, lca_rid, lca_roster); + lca_roster->get_name(right_root, lca_name); + + P(F("directory '%s' pivoted to root on the right") % lca_name); + P(F("directory '%s' deleted on the left") % lca_name); + } + else if (!left_roster.has_node(right_root) && + !right_roster.has_node(left_root)) + { + shared_ptr left_lca_roster, right_lca_roster; + revision_id left_lca_rid, right_lca_rid; + file_path left_lca_name, right_lca_name; + + adaptor.get_ancestral_roster(left_root, left_lca_rid, + left_lca_roster); + adaptor.get_ancestral_roster(right_root, right_lca_rid, + right_lca_roster); + + left_lca_roster->get_name(left_root, left_lca_name); + right_lca_roster->get_name(right_root, right_lca_name); + + P(F("directory '%s' pivoted to root on the left") % left_lca_name); + P(F("directory '%s' deleted on the right") % left_lca_name); + + P(F("directory '%s' deleted on the left") % right_lca_name); + P(F("directory '%s' pivoted to root on the right") % right_lca_name); + } + else + I(false); } } @@ -340,7 +646,7 @@ roster_merge_result::clear() roster_merge_result::clear() { divergent_name_conflicts.clear(); - file_content_conflicts.clear(); // first + file_content_conflicts.clear(); node_attr_conflicts.clear(); orphaned_node_conflicts.clear(); convergent_name_conflicts.clear(); ============================================================ --- roster_merge.hh 8d69cc0270c27d6a5d8bca445993a84281ebfaf1 +++ roster_merge.hh ddb7dc8ed33c6e962fd51eb675c650ec178695c1 @@ -10,9 +10,10 @@ // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. - +#include "diff_patch.hh" #include "vocab.hh" #include "roster.hh" +#include "sanity.hh" // our general strategy is to return a (possibly insane) roster, and a list of // conflicts encountered in that roster. Each conflict encountered in merging @@ -50,7 +51,7 @@ struct node_attr_conflict { node_id nid; node_attr_conflict(node_id nid) : nid(nid) {} - attr_key key; + attr_key key; // attr_name? std::pair left, right; }; @@ -110,6 +111,14 @@ struct illegal_name_conflict std::pair parent_name; }; +template <> void dump(divergent_name_conflict const & val, std::string & out); +template <> void dump(file_content_conflict const & val, std::string & out); +template <> void dump(node_attr_conflict const & val, std::string & out); +template <> void dump(orphaned_node_conflict const & val, std::string & out); +template <> void dump(convergent_name_conflict const & val, std::string & out); +template <> void dump(directory_loop_conflict const & val, std::string & out); +template <> void dump(illegal_name_conflict const & val, std::string & out); + struct roster_merge_result { // three main types of conflicts @@ -138,7 +147,8 @@ struct roster_merge_result bool has_non_content_conflicts() const; void log_conflicts() const; void warn_non_content_conflicts(roster_t const & left, - roster_t const & right) const; + roster_t const & right, + content_merge_adaptor & adaptor) const; void clear(); }; ============================================================ --- tests/non_content_conflicts/__driver__.lua e562b6a47f9b3c0bc84721bff7160d2b70887390 +++ tests/non_content_conflicts/__driver__.lua aa81cf5de17933a76239a517c094a700efcbc4c8 @@ -1,8 +1,10 @@ mtn_setup() mtn_setup() -- this test creates the various non-content conflict cases -- and attempts to merge them to check the various messages + + -- divergent name conflict remove("_MTN") @@ -21,7 +23,7 @@ check(mtn("merge", "--branch", "divergen commit("divergent") check(mtn("merge", "--branch", "divergent"), 1, false, true) -check(qgrep("divergent name conflict", "stderr")) +check(qgrep("conflict: multiple names", "stderr")) @@ -34,17 +36,23 @@ base = base_revision() commit("convergent-adds") base = base_revision() -addfile("bar", "convergent add bar1") +addfile("xxx", "convergent add xxx") commit("convergent-adds") +check(mtn("mv", "xxx", "bar"), 0, false, false) +--addfile("bar", "convergent add bar1") +commit("convergent-adds") + revert_to(base) addfile("bar", "convergent add bar2") commit("convergent-adds") check(mtn("merge", "--branch", "convergent-adds"), 1, false, true) -check(qgrep("convergent name conflict", "stderr")) +check(qgrep("conflict: duplicate name", "stderr")) + + -- convergent name conflict (renames) remove("_MTN") @@ -65,8 +73,10 @@ check(mtn("merge", "--branch", "converge commit("convergent-renames") check(mtn("merge", "--branch", "convergent-renames"), 1, false, true) -check(qgrep("convergent name conflict", "stderr")) +check(qgrep("conflict: duplicate name", "stderr")) + + -- convergent name conflict (add-rename) remove("_MTN") @@ -86,7 +96,7 @@ check(mtn("merge", "--branch", "converge commit("convergent-add-rename") check(mtn("merge", "--branch", "convergent-add-rename"), 1, false, true) -check(qgrep("convergent name conflict", "stderr")) +check(qgrep("conflict: duplicate name", "stderr")) @@ -114,61 +124,120 @@ check(mtn("merge", "--branch", "loop"), commit("loop") check(mtn("merge", "--branch", "loop"), 1, false, true) -check(qgrep("directory loop conflict", "stderr")) +check(qgrep("conflict: directory loop", "stderr")) --- orphaned node conflict + +-- orphaned add + remove("_MTN") -check(mtn("setup", ".", "--branch", "orphan"), 0, false, false) +check(mtn("setup", ".", "--branch", "orphaned-add"), 0, false, false) remove("foo") remove("bar") mkdir("foo") -addfile("foo/foo", "orphan foofoo") -commit("orphan") +addfile("foo/foo", "orphaned add foofoo") +commit("orphaned-add") base = base_revision() addfile("foo/bar", "orphan foobar") -addfile("foo/baz", "orphan foobaz") -mkdir("foo/sub") -addfile("foo/sub/bar", "orphan foosubbar") -addfile("foo/sub/baz", "orphan foosubbaz") +commit("orphaned-add") -commit("orphan") +check(mtn("mv", "foo/bar", "foo/baz"), 0, false, false) +commit("orphaned-add") +revert_to(base) +remove("foo") +check(mtn("drop", "--recursive", "foo"), 0, false, false) +commit("orphaned-add") + +check(mtn("merge", "--branch", "orphaned-add"), 1, false, true) +check(qgrep("orphaned file", "stderr")) + + + +-- orphaned rename + +remove("_MTN") +check(mtn("setup", ".", "--branch", "orphaned-rename"), 0, false, false) +remove("foo") +remove("bar") + +mkdir("foo") +addfile("foo/foo", "orphaned rename foofoo") +addfile("bar", "orphaned rename bar") +commit("orphaned-rename") + +base = base_revision() + +check(mtn("mv", "bar", "foo/bar"), 0, false, false) +commit("orphaned-rename") +check(mtn("mv", "foo/bar", "foo/baz"), 0, false, false) +commit("orphaned-rename") + revert_to(base) remove("foo") check(mtn("drop", "--recursive", "foo"), 0, false, false) -commit("orphan") +commit("orphaned-rename") -check(mtn("merge", "--branch", "orphan"), 1, false, true) -check(qgrep("orphaned node conflict", "stderr")) +check(mtn("merge", "--branch", "orphaned-rename"), 1, false, true) +check(qgrep("orphaned file", "stderr")) --- illegal name conflict + +-- invalid name add + remove("_MTN") -check(mtn("setup", ".", "--branch", "illegal"), 0, false, false) +check(mtn("setup", ".", "--branch", "invalid-add"), 0, false, false) mkdir("foo") -addfile("foo/foo", "illegal foofoo") -commit("illegal") +addfile("foo/foo", "invalid add foofoo") +commit("invalid-add") base = base_revision() -check(mtn("co", "--branch", "illegal", "illegal"), 0, false, false) -check(indir("illegal", mtn("pivot_root", "foo", "bar")), 0, true, true) -check(indir("illegal", mtn("commit", "--message", "commit")), 0, false, false) +check(mtn("co", "--branch", "invalid-add", "invalid"), 0, false, false) +check(indir("invalid", mtn("pivot_root", "foo", "bar")), 0, true, true) +check(indir("invalid", mtn("commit", "--message", "commit")), 0, false, false) mkdir("foo/_MTN") -addfile("foo/_MTN/foo", "illegal foo") -addfile("foo/_MTN/bar", "illegal bar") -commit("illegal") +addfile("foo/_MTN/foo", "invalid foo") +addfile("foo/_MTN/bar", "invalid bar") +commit("invalid-add") -check(mtn("merge", "--branch", "illegal"), 1, false, true) -check(qgrep("illegal name conflict", "stderr")) +check(mtn("merge", "--branch", "invalid-add"), 1, false, true) +check(qgrep("conflict: invalid name", "stderr")) + + +-- invalid name rename + +remove("_MTN") +remove("invalid") +check(mtn("setup", ".", "--branch", "invalid-rename"), 0, false, false) + +mkdir("foo") +mkdir("bad") +addfile("foo/foo", "invalid rename foofoo") +addfile("bad/_MTN", "invalid bar") +commit("invalid-rename") + +base = base_revision() + +check(mtn("co", "--branch", "invalid-rename", "invalid"), 0, false, false) +check(indir("invalid", mtn("pivot_root", "foo", "bar")), 0, true, true) +check(indir("invalid", mtn("commit", "--message", "commit")), 0, false, false) + +check(mtn("mv", "bad/_MTN", "foo/_MTN"), 0, false, false) +commit("invalid-rename") + +check(mtn("merge", "--branch", "invalid-rename"), 1, false, true) +check(qgrep("conflict: invalid name", "stderr")) + + + -- missing root conflict remove("_MTN") @@ -182,37 +251,65 @@ check(indir("missing", mtn("pivot_root", check(mtn("co", "--branch", "missing", "missing"), 0, false, false) check(indir("missing", mtn("pivot_root", "foo", "bar")), 0, true, true) -check(indir("missing", mtn("drop", "--recursive", "bar")), 0, true, true) +--check(indir("missing", mtn("drop", "--recursive", "bar")), 0, true, true) check(indir("missing", mtn("commit", "--message", "commit")), 0, false, false) check(mtn("drop", "--recursive", "foo"), 0, false, false) commit("missing") check(mtn("merge", "--branch", "missing"), 1, false, true) -check(qgrep("missing root conflict", "stderr")) +check(qgrep("conflict: missing root directory", "stderr")) --- attribute conflict +-- attribute conflict on attached node + remove("_MTN") -check(mtn("setup", ".", "--branch", "attribute"), 0, false, false) +check(mtn("setup", ".", "--branch", "attribute-attached"), 0, false, false) remove("foo") -addfile("foo", "attribute foo") +addfile("foo", "attribute foo attached") check(mtn("attr", "set", "foo", "attr1", "value1"), 0, false, false) check(mtn("attr", "set", "foo", "attr2", "value2"), 0, false, false) -commit("attribute") +commit("attribute-attached") base = base_revision() check(mtn("attr", "set", "foo", "attr1", "valueX"), 0, false, false) check(mtn("attr", "set", "foo", "attr2", "valueY"), 0, false, false) -commit("attribute") +commit("attribute-attached") revert_to(base) check(mtn("attr", "set", "foo", "attr1", "valueZ"), 0, false, false) check(mtn("attr", "drop", "foo", "attr2"), 0, false, false) -commit("attribute") +commit("attribute-attached") -check(mtn("merge", "--branch", "attribute"), 1, false, true) -check(qgrep("attribute conflict", "stderr")) +check(mtn("merge", "--branch", "attribute-attached"), 1, false, true) +check(qgrep("conflict: multiple values for attribute", "stderr")) +-- attribute conflict on detached node + +remove("_MTN") +check(mtn("setup", ".", "--branch", "attribute-detached"), 0, false, false) +remove("foo") + +addfile("foo", "attribute foo detached") +check(mtn("attr", "set", "foo", "attr1", "value1"), 0, false, false) +check(mtn("attr", "set", "foo", "attr2", "value2"), 0, false, false) +commit("attribute-detached") +base = base_revision() + +check(mtn("attr", "set", "foo", "attr1", "valueX"), 0, false, false) +check(mtn("attr", "set", "foo", "attr2", "valueY"), 0, false, false) +check(mtn("mv", "foo", "bar"), 0, false, false) +commit("attribute-detached") + +revert_to(base) + +check(mtn("attr", "set", "foo", "attr1", "valueZ"), 0, false, false) +check(mtn("attr", "drop", "foo", "attr2"), 0, false, false) +check(mtn("mv", "foo", "baz"), 0, false, false) +commit("attribute-detached") + +check(mtn("merge", "--branch", "attribute-detached"), 1, false, true) +check(qgrep("conflict: multiple values for attribute", "stderr")) + ============================================================ --- work.cc 04389e4274f38d0b2bc866c6938d53bbda5e8a2f +++ work.cc bcea6fae446e61352e4a12e0259a9094970a9829 @@ -183,9 +183,9 @@ workspace::has_changes() bool workspace::has_changes() { - parent_map parents; + parent_map parents; get_parent_rosters(parents); - + // if we have more than one parent roster then this workspace contains // a merge which means this is always a committable change if (parents.size() > 1) @@ -738,7 +738,7 @@ struct content_merge_empty_adaptor : pub file_data const &, file_data const &, file_data const &) { I(false); } - virtual void get_ancestral_roster(node_id, boost::shared_ptr &) + virtual void get_ancestral_roster(node_id, revision_id &, boost::shared_ptr &) { I(false); } };