#
#
# add_file "tests/resolve_duplicate_name_conflict/expected-merge-messages-abe_1-beth_1"
# content [8cc5c13905078a96dd1e308964537c69ed78f4e8]
#
# add_file "tests/resolve_duplicate_name_conflict/expected-merge-messages-abe_2-jim_1"
# content [318b3562e18678195b6209aedc9a157a8f2937e2]
#
# add_file "tests/resolve_duplicate_name_conflict/expected-merged-revision-jim_1"
# content [257729bebdb32819cd1fc059806e0fb4144f7ec7]
#
# add_file "tests/resolve_duplicate_name_conflict/expected-update-messages-jim_1"
# content [79eb4985a0fdab8524e19de458835db8b9dcf51c]
#
# patch "basic_io.cc"
# from [5aacaa8dcf17e4113dbaf2e91b476a9b795ccbf5]
# to [f9f5453f817910a7ac6cfc1ec0591b7d938642b5]
#
# patch "basic_io.hh"
# from [df2ce0a862161cd5c0d7061b75fe3804d1cfec87]
# to [6c148df1eafd7316edd472e231541f98c62fcb0b]
#
# patch "cset.cc"
# from [a2cb867731e1d283fa60165e534157d0b38fb364]
# to [f048d2d67e8a0730dcdc0e18e04f5f80a306c2fd]
#
# patch "cset.hh"
# from [53dcfec8e054e927c0615188e716548390460a3f]
# to [5f09b70523f6d8afad6f5b37c8edc90e99d7a979]
#
# patch "rev_types.hh"
# from [9a3a1a69643278a8b2c5c6c85d25623ae079227c]
# to [4cb4ab589c482cdd0d4afb9e23a53d2110011ba6]
#
# patch "roster.cc"
# from [c7cdd1cb8e944b19a946cac03311b025c621bb85]
# to [9eb37f9fb2be303fc606c698427c3450b77c22d6]
#
# patch "roster.hh"
# from [e0d2b16d79c0d818fb951f7b32726d6eaebdb725]
# to [249507b96d4cd90e81b8903cbcbb044f0c40510a]
#
# patch "roster_merge.cc"
# from [178917d07e72bb9cff7c9616e0e25e0b89eccdfb]
# to [7213f45b1eaf58a223235a3569e07ba8d724e4bd]
#
# patch "tests/resolve_duplicate_name_conflict/__driver__.lua"
# from [5bcba012902ee356d5ef041847cef3aa725c53c1]
# to [8bde1a08cec21f13e69bbb1ccae604dc3d5146d7]
#
# patch "work.cc"
# from [0261cdf7dbc3baed0cdfc0e75a3a858f64eefa97]
# to [a01164a4e32079091f126c5018a187cea96e0dcd]
#
============================================================
--- tests/resolve_duplicate_name_conflict/expected-merge-messages-abe_1-beth_1 8cc5c13905078a96dd1e308964537c69ed78f4e8
+++ tests/resolve_duplicate_name_conflict/expected-merge-messages-abe_1-beth_1 8cc5c13905078a96dd1e308964537c69ed78f4e8
@@ -0,0 +1,8 @@
+mtn: 2 heads on branch 'testbranch'
+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] 257729bebdb32819cd1fc059806e0fb4144f7ec7
+mtn: note: your workspaces have not been updated
============================================================
--- tests/resolve_duplicate_name_conflict/expected-merge-messages-abe_2-jim_1 318b3562e18678195b6209aedc9a157a8f2937e2
+++ tests/resolve_duplicate_name_conflict/expected-merge-messages-abe_2-jim_1 318b3562e18678195b6209aedc9a157a8f2937e2
@@ -0,0 +1 @@
+should get suture/edit conflict, or just merge
============================================================
--- tests/resolve_duplicate_name_conflict/expected-merged-revision-jim_1 257729bebdb32819cd1fc059806e0fb4144f7ec7
+++ tests/resolve_duplicate_name_conflict/expected-merged-revision-jim_1 257729bebdb32819cd1fc059806e0fb4144f7ec7
@@ -0,0 +1,29 @@
+format_version "2"
+
+new_manifest [9a3352571f7379d5114aa57fb650fe2acb7588f6]
+
+old_revision [5285636b9d9f988e79b3dcd9a40e64d15fb7fc9f]
+
+rename "thermostat.c"
+ to "thermostat-westinghouse.c"
+
+add_file "thermostat-honeywell.c"
+ content [770bfb0965db64d8b7a267f61ebd50803f27d5e7]
+
+ sutured_file "checkout.sh"
+ first_ancestor "checkout.sh"
+second_ancestor ""
+ content [3390893b9a31eaa9ef0e9364b27ee1e617e6891a]
+
+old_revision [e9ad84a3fc40ef1109251c308428439c21ad1de9]
+
+rename "thermostat.c"
+ to "thermostat-honeywell.c"
+
+add_file "thermostat-westinghouse.c"
+ content [c2f67aa3b29c2bdab4790213c7f3bf73e58440a7]
+
+ sutured_file "checkout.sh"
+ first_ancestor "checkout.sh"
+second_ancestor ""
+ content [3390893b9a31eaa9ef0e9364b27ee1e617e6891a]
============================================================
--- tests/resolve_duplicate_name_conflict/expected-update-messages-jim_1 79eb4985a0fdab8524e19de458835db8b9dcf51c
+++ tests/resolve_duplicate_name_conflict/expected-update-messages-jim_1 79eb4985a0fdab8524e19de458835db8b9dcf51c
@@ -0,0 +1,13 @@
+mtn: updating along branch 'testbranch'
+mtn: selected update target 257729bebdb32819cd1fc059806e0fb4144f7ec7
+mtn: [left] 30065575a95fc60f8deb84b15d9b8c8b3944d37c
+mtn: [right] 257729bebdb32819cd1fc059806e0fb4144f7ec7
+mtn: warning: Content changes to the file 'checkout.sh'
+mtn: warning: will be ignored during this merge as the file has been
+mtn: warning: removed on one side of the merge. Affected revisions include:
+mtn: warning: Revision: 30065575a95fc60f8deb84b15d9b8c8b3944d37c
+mtn: adding checkout.sh
+mtn: renaming thermostat.c to thermostat-honeywell.c
+mtn: adding thermostat-westinghouse.c
+mtn: dropping checkout.sh
+mtn: updated to base revision 257729bebdb32819cd1fc059806e0fb4144f7ec7
============================================================
--- basic_io.cc 5aacaa8dcf17e4113dbaf2e91b476a9b795ccbf5
+++ basic_io.cc f9f5453f817910a7ac6cfc1ec0591b7d938642b5
@@ -1,3 +1,4 @@
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2004 Graydon Hoare
//
// This program is made available under the GNU GPL version 2.0 or
@@ -129,6 +130,22 @@ void basic_io::stanza::push_str_multi(sy
indent = k().size();
}
+void basic_io::stanza::push_str_multi(symbol const & k1,
+ symbol const & k2,
+ vector const & v)
+{
+ string val = k2();
+ for (vector::const_iterator i = v.begin();
+ i != v.end(); ++i)
+ {
+ val += " ";
+ val += escape(*i);
+ }
+ entries.push_back(make_pair(k1, val));
+ if (k1().size() > indent)
+ indent = k1().size();
+}
+
void basic_io::stanza::push_str_triple(symbol const & k,
string const & n,
string const & v)
============================================================
--- basic_io.hh df2ce0a862161cd5c0d7061b75fe3804d1cfec87
+++ basic_io.hh 6c148df1eafd7316edd472e231541f98c62fcb0b
@@ -1,6 +1,7 @@
#ifndef __BASIC_IO_HH__
#define __BASIC_IO_HH__
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2004 Graydon Hoare
//
// This program is made available under the GNU GPL version 2.0 or
@@ -34,21 +35,13 @@ namespace basic_io
// general format symbol
symbol const format_version("format_version");
- // roster symbols
+ // common symbols
symbol const dir("dir");
symbol const file("file");
symbol const content("content");
symbol const attr("attr");
- // 'local' roster and marking symbols
- // FIXME: should these be listed as "general" symbols here as well?
- symbol const ident("ident");
- symbol const birth("birth");
- symbol const dormant_attr("dormant_attr");
-
- symbol const path_mark("path_mark");
symbol const content_mark("content_mark");
- symbol const attr_mark("attr_mark");
}
}
@@ -264,6 +257,9 @@ namespace basic_io
void push_file_pair(symbol const & k, file_path const & v);
void push_str_multi(symbol const & k,
std::vector const & v);
+ void push_str_multi(symbol const & k1,
+ symbol const & k2,
+ std::vector const & v);
};
============================================================
--- cset.cc a2cb867731e1d283fa60165e534157d0b38fb364
+++ cset.cc f048d2d67e8a0730dcdc0e18e04f5f80a306c2fd
@@ -178,7 +178,7 @@ cset::apply_to(editable_tree & t) const
for (map::const_iterator i = files_added.begin();
i != files_added.end(); ++i)
- safe_insert(attaches, attach(t.create_file_node(i->second), i->first));
+ safe_insert(attaches, attach(t.create_file_node(i->second, null_ancestors), i->first));
// Decompose all path deletion and the first-half of renamings on
// existing paths into the set of pending detaches, to be executed
@@ -187,8 +187,43 @@ cset::apply_to(editable_tree & t) const
for (map::const_iterator i = nodes_sutured.begin();
i != nodes_sutured.end(); ++i)
{
+ node_id left_anc_nid;
+ node_id right_anc_nid;
+
+ std::pair ancestors;
+
+ // get_node ("") returns the root node, which is not what we want.
+ if (i->second.first_ancestor.empty())
+ left_anc_nid = the_null_node;
+ else
+ left_anc_nid = t.get_node(i->second.first_ancestor);
+
+ if (i->second.second_ancestor.empty())
+ right_anc_nid = the_null_node;
+ else
+ right_anc_nid = t.get_node(i->second.second_ancestor);
+
+ if (right_anc_nid == the_null_node)
+ {
+ // suture is from merge; t.r is left side
+ ancestors.first = left_anc_nid;
+ ancestors.second = the_null_node;
+ }
+ else if (left_anc_nid == the_null_node)
+ {
+ // suture is from merge; t.r is right side
+ ancestors.first = right_anc_nid;
+ ancestors.second = the_null_node;
+ }
+ else
+ {
+ // user suture
+ ancestors.first = left_anc_nid;
+ ancestors.second = right_anc_nid;
+ }
+
// Sutured node is added
- safe_insert(attaches, attach(t.create_file_node(i->second.sutured_id), i->first));
+ safe_insert(attaches, attach(t.create_file_node(i->second.sutured_id, ancestors), i->first));
// Ancestor nodes are dropped; detach here, drop later.
safe_insert(detaches, detach(i->second.first_ancestor));
============================================================
--- cset.hh 53dcfec8e054e927c0615188e716548390460a3f
+++ cset.hh 5f09b70523f6d8afad6f5b37c8edc90e99d7a979
@@ -15,9 +15,10 @@
#include "paths.hh"
#include "rev_types.hh"
-// Virtual interface to a tree-of-files which you can edit
-// destructively; this may be the filesystem or an in-memory
-// representation (a roster / mfest).
+// Virtual interface to a tree-of-files which you can edit destructively;
+// this may be the filesystem or an in-memory representation (a roster /
+// mfest). The operations maintain both the roster and the marking_map (if
+// any).
struct editable_tree
{
@@ -25,9 +26,11 @@ struct editable_tree
virtual node_id detach_node(file_path const & src) = 0;
virtual void drop_detached_node(node_id nid) = 0;
- // Attaching new nodes (via creation or as the tail end of renaming)
+ // Attaching new nodes (via creation, as the tail end of renaming, suturing, or splitting)
virtual node_id create_dir_node() = 0;
- virtual node_id create_file_node(file_id const & content) = 0;
+ virtual node_id create_file_node(file_id const & content,
+ std::pair const ancestors) = 0;
+ virtual node_id get_node(file_path const &pth) = 0;
virtual void attach_node(node_id nid, file_path const & dst) = 0;
// Modifying elements in-place
@@ -99,7 +102,9 @@ struct cset
;
}
+ // Apply changeset to roster and marking map in tree.
void apply_to(editable_tree & t) const;
+
bool empty() const;
void clear();
};
============================================================
--- rev_types.hh 9a3a1a69643278a8b2c5c6c85d25623ae079227c
+++ rev_types.hh 4cb4ab589c482cdd0d4afb9e23a53d2110011ba6
@@ -1,3 +1,4 @@
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2007 Zack Weinberg
//
// This program is made available under the GNU GPL version 2.0 or
@@ -30,9 +31,13 @@ namespace basic_io
struct stanza;
}
-// full definitions in cset.hh
typedef std::map attr_map_t;
typedef u32 node_id;
+node_id const the_null_node = 0;
+std::pair const null_ancestors = std::pair(the_null_node, the_null_node);
+
+
+// full definitions in cset.hh
struct cset;
struct editable_tree;
============================================================
--- roster.cc c7cdd1cb8e944b19a946cac03311b025c621bb85
+++ roster.cc 9eb37f9fb2be303fc606c698427c3450b77c22d6
@@ -47,6 +47,28 @@ using boost::lexical_cast;
///////////////////////////////////////////////////////////////////
+namespace
+{
+ namespace syms
+ {
+ symbol const ident("ident");
+ symbol const birth("birth");
+ symbol const birth_cause("birth_cause");
+ symbol const birth_add("birth_add");
+ symbol const birth_suture("birth_suture");
+ symbol const birth_split("birth_split");
+ symbol const dormant_attr("dormant_attr");
+
+ symbol const path_mark("path_mark");
+ symbol const attr_mark("attr_mark");
+ }
+}
+
+// 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;
+
template <> void
dump(node_id const & val, string & out)
{
@@ -65,6 +87,37 @@ template <> void
}
template <> void
+dump(std::pair > const & birth_cause, string & out)
+{
+ ostringstream oss;
+ string tmp;
+ switch (birth_cause.first)
+ {
+ case marking_t::invalid:
+ out = "invalid\n";
+ return;
+
+ case marking_t::add:
+ out = syms::birth_add() + "\n";
+ return;
+
+ case marking_t::suture:
+ dump(birth_cause.second.first, tmp);
+ oss << syms::birth_suture() << tmp;
+ dump(birth_cause.second.second, tmp);
+ oss << tmp << '\n';
+ out = oss.str();
+ return;
+
+ case marking_t::split:
+ dump(birth_cause.second.first, tmp);
+ oss << syms::birth_split() << tmp << '\n';
+ out = oss.str();
+ return;
+ }
+}
+
+template <> void
dump(set const & revids, string & out)
{
out.clear();
@@ -85,6 +138,8 @@ dump(marking_t const & marking, string &
ostringstream oss;
string tmp;
oss << "birth_revision: " << marking.birth_revision << '\n';
+ dump(marking.birth_cause, tmp);
+ oss << "birth_cause: " << tmp << '\n';
dump(marking.parent_name, tmp);
oss << "parent_name: " << tmp << '\n';
dump(marking.file_content, tmp);
@@ -1102,6 +1157,7 @@ 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))
@@ -1166,14 +1222,21 @@ node_id
}
node_id
-editable_roster_base::create_file_node(file_id const & content)
+editable_roster_base::create_file_node(file_id const & content,
+ std::pair const ancestors)
{
// L(FL("create_file_node('%s')") % content);
- node_id n = r.create_file_node(content, nis);
+ node_id n = r.create_file_node(content, nis, ancestors);
// L(FL("create_file_node('%s') -> %d") % content % n);
return n;
}
+node_id
+editable_roster_base::get_node(file_path const &pth)
+{
+ return r.get_node(pth)->self;
+}
+
void
editable_roster_base::attach_node(node_id nid, file_path const & dst)
{
@@ -1244,9 +1307,10 @@ namespace
new_nodes.insert(nid);
return nid;
}
- virtual node_id create_file_node(file_id const & content)
+ virtual node_id create_file_node(file_id const & content,
+ std::pair const ancestors = null_ancestors)
{
- node_id nid = this->editable_roster_base::create_file_node(content);
+ node_id nid = this->editable_roster_base::create_file_node(content, ancestors);
new_nodes.insert(nid);
return nid;
}
@@ -1275,9 +1339,11 @@ namespace
// but may not be worth the effort (since it doesn't take that long to
// get out in any case)
a.get_name(aid, p);
- node_id bid = b.get_node(p)->self;
+ node_t bn = b.get_node(p);
+ node_id bid = bn->self;
if (b_new.find(bid) != b_new.end())
{
+ // New in both.
I(temp_node(bid));
node_id new_nid;
do
@@ -1286,6 +1352,18 @@ namespace
a.replace_node_id(aid, new_nid);
b.replace_node_id(bid, new_nid);
+
+ if (bn->ancestors.first != the_null_node)
+ {
+ // This is a suture; unify the ancestors.
+ node_t an = a.get_node(new_nid);
+ an->ancestors.second = bn->ancestors.first;
+
+ node_t new_bn = b.get_node(new_nid);
+ new_bn->ancestors.first = an->ancestors.first;
+ new_bn->ancestors.second = bn->ancestors.first;
+ };
+
b_new.erase(bid);
}
else
@@ -1509,6 +1587,7 @@ namespace
mark_new_node(revision_id const & new_rid, node_t n, marking_t & new_marking)
{
new_marking.birth_revision = new_rid;
+ new_marking.birth_cause = make_pair(marking_t::add, null_ancestors);
I(new_marking.parent_name.empty());
new_marking.parent_name.insert(new_rid);
I(new_marking.file_content.empty());
@@ -1536,6 +1615,7 @@ namespace
I(same_type(parent_n, n) && parent_n->self == n->self);
new_marking.birth_revision = parent_marking.birth_revision;
+ new_marking.birth_cause = parent_marking.birth_cause;
mark_unmerged_scalar(parent_marking.parent_name,
make_pair(parent_n->parent, parent_n->name),
@@ -1577,9 +1657,22 @@ namespace
marking_t & new_marking)
{
I(same_type(ln, n) && same_type(rn, n));
- I(left_marking.birth_revision == right_marking.birth_revision);
- new_marking.birth_revision = left_marking.birth_revision;
+ // birth.
+ if (ln->self == rn->self)
+ {
+ // not a suture; a user add in a two-parent workspace.
+ I(left_marking.birth_revision == right_marking.birth_revision);
+ new_marking.birth_revision = left_marking.birth_revision;
+ new_marking.birth_cause = make_pair (marking_t::add, null_ancestors);
+ }
+ else
+ {
+ // suture.
+ new_marking.birth_revision = new_rid;
+ new_marking.birth_cause = make_pair (marking_t::suture, make_pair(ln->self, rn->self));
+ }
+
// name
mark_merged_scalar(left_marking.parent_name, left_uncommon_ancestors,
make_pair(ln->parent, ln->name),
@@ -1683,13 +1776,45 @@ mark_merge_roster(roster_t const & left_
marking_t new_marking;
if (!exists_in_left && !exists_in_right)
- mark_new_node(new_rid, n, new_marking);
+ {
+ // We have a new node in a merge. There are two cases:
+ //
+ // 1) Adding a file in a two-parent workspace, which is created by
+ // merge_into_workspace
+ //
+ // 2) Doing a suture
+ //
+ // There's a third case; doing a unit test. But we can change the
+ // unit test to handle the real code :).
+ if (the_null_node == n->ancestors.first)
+ {
+ // not a suture; two-parent workspace
+ mark_new_node(new_rid, n, 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);
+ }
+ }
+
else if (!exists_in_left && exists_in_right)
{
+ // FIXME_SUTURE: handle case where n is an ancestor of a sutured node on
+ // the left; beth_3 in test.
node_t const & right_node = rni->second;
marking_t const & right_marking = safe_get(right_markings, n->self);
- // must be unborn on the left (as opposed to dead)
+
+ // Must be unborn on the left (as opposed to dead); otherwise it
+ // would not be in the merge. Therefore the birth revision for
+ // this node must be in the uncommon ancestors on the right:
I(right_uncommon_ancestors.find(right_marking.birth_revision)
!= right_uncommon_ancestors.end());
mark_unmerged_node(right_marking, right_node,
@@ -1697,16 +1822,23 @@ mark_merge_roster(roster_t const & left_
}
else if (exists_in_left && !exists_in_right)
{
+ // FIXME_SUTURE: handle case where n is an ancestor of a sutured node on
+ // the right; abe_3 in test.
node_t const & left_node = lni->second;
marking_t const & left_marking = safe_get(left_markings, n->self);
- // must be unborn on the right (as opposed to dead)
+
+ // Must be unborn on the right (as opposed to dead); otherwise it
+ // would not be in the merge. Therefore the birth revision for
+ // this node must be in the uncommon ancestors on the left:
I(left_uncommon_ancestors.find(left_marking.birth_revision)
!= left_uncommon_ancestors.end());
+
mark_unmerged_node(left_marking, left_node,
new_rid, n, new_marking);
}
else
{
+ // 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),
@@ -1754,7 +1886,8 @@ namespace {
return handle_new(this->editable_roster_base::create_dir_node());
}
- virtual node_id create_file_node(file_id const & content)
+ 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));
}
@@ -2380,7 +2513,7 @@ make_restricted_roster(roster_t const &
if (is_file_t(n->second))
{
file_t const f = downcast_to_file_t(n->second);
- restricted.create_file_node(f->content, f->self);
+ restricted.create_file_node(f->content, f->self, f->ancestors);
}
else
restricted.create_dir_node(n->second->self);
@@ -2559,11 +2692,39 @@ push_marking(basic_io::stanza & st,
{
I(!null_id(mark.birth_revision));
- st.push_binary_pair(basic_io::syms::birth, mark.birth_revision.inner());
+ st.push_binary_pair(syms::birth, mark.birth_revision.inner());
+ 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);
+ break;
+
+ case marking_t::suture:
+ {
+ std::vector data;
+ data.push_back(lexical_cast(mark.birth_cause.second.first));
+ data.push_back(lexical_cast(mark.birth_cause.second.second));
+ st.push_str_multi(syms::birth_cause, syms::birth_suture, data);
+ }
+ break;
+
+ case marking_t::split:
+ {
+ std::vector data;
+ data.push_back(lexical_cast(mark.birth_cause.second.first));
+ st.push_str_multi(syms::birth_cause, syms::birth_split, data);
+ }
+ break;
+ };
+
for (set::const_iterator i = mark.parent_name.begin();
i != mark.parent_name.end(); ++i)
- st.push_binary_pair(basic_io::syms::path_mark, i->inner());
+ st.push_binary_pair(syms::path_mark, i->inner());
if (is_file)
{
@@ -2579,7 +2740,7 @@ push_marking(basic_io::stanza & st,
{
for (set::const_iterator j = i->second.begin();
j != i->second.end(); ++j)
- st.push_binary_triple(basic_io::syms::attr_mark, i->first(), j->inner());
+ st.push_binary_triple(syms::attr_mark, i->first(), j->inner());
}
}
@@ -2591,15 +2752,47 @@ parse_marking(basic_io::parser & pa,
while (pa.symp())
{
string rev;
- if (pa.symp(basic_io::syms::birth))
+ if (pa.symp(syms::birth))
{
pa.sym();
pa.hex(rev);
marking.birth_revision = revision_id(decode_hexenc(rev));
}
- else if (pa.symp(basic_io::syms::path_mark))
+ else if (pa.symp(syms::birth_cause))
{
+ std::string tmp_1, tmp_2;
pa.sym();
+
+ if (pa.symp (syms::birth_add))
+ {
+ pa.sym();
+ marking.birth_cause = make_pair (marking_t::add, null_ancestors);
+ }
+ else if (pa.symp (syms::birth_suture))
+ {
+ pa.sym();
+ pa.str(tmp_1);
+ pa.str(tmp_2);
+ marking.birth_cause = make_pair (marking_t::add,
+ make_pair(lexical_cast(tmp_1),
+ lexical_cast(tmp_2)));
+ }
+ else if (pa.symp (syms::birth_suture))
+ {
+ pa.sym();
+ pa.str(tmp_1);
+ marking.birth_cause = make_pair (marking_t::add,
+ make_pair(lexical_cast(tmp_1),
+ the_null_node));
+ }
+ else
+ {
+ E(false, F("unrecognized birth_cause '%s'") % pa.token);
+ }
+ }
+ else if (pa.symp(syms::path_mark))
+ {
+ pa.sym();
pa.hex(rev);
safe_insert(marking.parent_name, revision_id(decode_hexenc(rev)));
}
@@ -2609,7 +2802,7 @@ parse_marking(basic_io::parser & pa,
pa.hex(rev);
safe_insert(marking.file_content, revision_id(decode_hexenc(rev)));
}
- else if (pa.symp(basic_io::syms::attr_mark))
+ else if (pa.symp(syms::attr_mark))
{
string k;
pa.sym();
@@ -2635,7 +2828,7 @@ roster_t::print_to(basic_io::printer & p
I(has_root());
{
basic_io::stanza st;
- st.push_str_pair(basic_io::syms::format_version, "1");
+ st.push_str_pair(basic_io::syms::format_version, lexical_cast(current_marking_map_format));
pr.print_stanza(st);
}
for (dfs_iter i(root_dir, true); !i.finished(); ++i)
@@ -2659,7 +2852,7 @@ roster_t::print_to(basic_io::printer & p
if (print_local_parts)
{
I(curr->self != the_null_node);
- st.push_str_pair(basic_io::syms::ident, lexical_cast(curr->self));
+ st.push_str_pair(syms::ident, lexical_cast(curr->self));
}
// Push the non-dormant part of the attr map
@@ -2682,7 +2875,7 @@ roster_t::print_to(basic_io::printer & p
if (!j->second.first)
{
I(j->second.second().empty());
- st.push_str_pair(basic_io::syms::dormant_attr, j->first());
+ st.push_str_pair(syms::dormant_attr, j->first());
}
}
@@ -2733,7 +2926,7 @@ roster_t::parse_from(basic_io::parser &
pa.esym(basic_io::syms::format_version);
string vers;
pa.str(vers);
- I(vers == "1");
+ I(vers == lexical_cast(current_marking_map_format));
}
while(pa.symp())
@@ -2748,7 +2941,7 @@ roster_t::parse_from(basic_io::parser &
pa.str(pth);
pa.esym(basic_io::syms::content);
pa.hex(content);
- pa.esym(basic_io::syms::ident);
+ pa.esym(syms::ident);
pa.str(ident);
n = file_t(new file_node(read_num(ident),
file_id(decode_hexenc(content))));
@@ -2757,7 +2950,7 @@ roster_t::parse_from(basic_io::parser &
{
pa.sym();
pa.str(pth);
- pa.esym(basic_io::syms::ident);
+ pa.esym(syms::ident);
pa.str(ident);
n = dir_t(new dir_node(read_num(ident)));
}
@@ -2790,7 +2983,7 @@ roster_t::parse_from(basic_io::parser &
}
// Dormant attrs
- while(pa.symp(basic_io::syms::dormant_attr))
+ while(pa.symp(syms::dormant_attr))
{
pa.sym();
string k;
@@ -3496,6 +3689,8 @@ UNIT_TEST(roster, bad_attr)
// purpose of this section is to systematically and exhaustively test every
// possible case.
//
+// FIXME_SUTURE: reference ss-mark-merge.text, add suture, split tests
+//
// Our underlying merger, *-merge, works on scalars, case-by-case.
// The cases are:
// 0 parent:
============================================================
--- roster.hh e0d2b16d79c0d818fb951f7b32726d6eaebdb725
+++ roster.hh 249507b96d4cd90e81b8903cbcbb044f0c40510a
@@ -24,9 +24,6 @@ struct node_id_source
///////////////////////////////////////////////////////////////////
-node_id const the_null_node = 0;
-std::pair const null_ancestors = std::pair(the_null_node, the_null_node);
-
inline bool
null_node(node_id n)
{
@@ -143,14 +140,20 @@ struct marking_t
struct marking_t
{
+ typedef enum {invalid, add, suture, split} birth_cause_t;
+
revision_id birth_revision;
+ std::pair > birth_cause;
+ // if suture, the node_ids indicate the ancestors. If split, the first
+ // node_id indicates the ancestor.
std::set parent_name;
std::set file_content;
std::map > attrs;
- marking_t() {};
+ marking_t() : birth_cause (std::make_pair (invalid, null_ancestors)) {};
bool operator==(marking_t const & other) const
{
return birth_revision == other.birth_revision
+ && birth_cause == birth_cause
&& parent_name == other.parent_name
&& file_content == other.file_content
&& attrs == other.attrs;
@@ -309,7 +312,9 @@ public:
virtual node_id detach_node(file_path const & src);
virtual void drop_detached_node(node_id nid);
virtual node_id create_dir_node();
- virtual node_id create_file_node(file_id const & content);
+ virtual node_id create_file_node(file_id const & content,
+ std::pair const ancestors = null_ancestors);
+ virtual node_id get_node(file_path const &pth);
virtual void attach_node(node_id nid, file_path const & dst);
virtual void apply_delta(file_path const & pth,
file_id const & old_id,
============================================================
--- roster_merge.cc 178917d07e72bb9cff7c9616e0e25e0b89eccdfb
+++ roster_merge.cc 7213f45b1eaf58a223235a3569e07ba8d724e4bd
@@ -1624,6 +1624,8 @@ roster_merge_result::resolve_duplicate_n
{
I(conflict.right_resolution.first == resolve_conflicts::suture);
+ // There's no inherent reason suturing directories can't be
+ // supported; we just haven't worked on it yet.
N(!is_dir_t(left_roster.get_node (left_nid)), F("can't suture directory : %s") % left_name);
P(F("suturing %s, %s into %s") % left_name % right_name % conflict.left_resolution.second);
@@ -1796,15 +1798,68 @@ namespace
}
inline void
- insert_if_unborn(node_t const & n,
- marking_map const & markings,
- set const & uncommon_ancestors,
- roster_t const & parent_roster,
- roster_t & new_roster)
+ create_node_for(node_t const & n, std::pair const ancestors, roster_t & new_roster)
{
+ if (is_dir_t(n))
+ I(false);
+ else if (is_file_t(n))
+ return new_roster.create_file_node(file_id(), n->self, ancestors);
+ else
+ I(false);
+ }
+
+ inline void
+ insert_if_unborn_or_sutured(node_t const & n,
+ marking_map const & markings,
+ set const & uncommon_ancestors,
+ roster_t const & parent_roster,
+ roster_t & new_roster)
+ {
+ MM(markings);
+ MM(uncommon_ancestors);
+ // See comment in roster_merge below for cases. n is either the left or
+ // right parent node. We are in case iii, iv, or v. We determine which
+ // by searching for the birth revision in uncommon_ancestors.
+
revision_id const & birth = safe_get(markings, n->self).birth_revision;
- if (uncommon_ancestors.find(birth) != uncommon_ancestors.end())
- create_node_for(n, new_roster);
+
+ set::const_iterator const uncommon_birth_i = uncommon_ancestors.find(birth);
+
+ if (uncommon_birth_i != uncommon_ancestors.end())
+ {
+ // case iii or iv
+ std::pair > const & birth_cause =
+ safe_get(markings, n->self).birth_cause;
+
+ switch (birth_cause.first)
+ {
+ case marking_t::invalid:
+ I(false);
+
+ case marking_t::add:
+ // case iv
+ create_node_for(n, new_roster);
+ break;
+
+ case marking_t::suture:
+ case marking_t::split:
+ // case iii
+ {
+ std::pair birth_ancestors = birth_cause.second;
+
+ if (n->self == birth_ancestors.first)
+ {
+ create_node_for(n, make_pair (n->self, birth_ancestors.second), new_roster);
+ }
+ else
+ {
+ I(n->self == birth_ancestors.second);
+ create_node_for(n, make_pair (birth_ancestors.second, n->self), new_roster);
+ }
+ }
+ break;
+ }
+ }
else
{
// In this branch we are NOT inserting the node into the new roster as it
@@ -1965,6 +2020,120 @@ namespace
assign_name(result, n->self, old_n->parent, old_n->name, side);
}
+ void
+ merge_nodes(node_t const left_n,
+ marking_t const & left_marking,
+ set const & left_uncommon_ancestors,
+ node_t const right_n,
+ marking_t const & right_marking,
+ set const & right_uncommon_ancestors,
+ node_t const new_n,
+ roster_merge_result & result)
+ {
+ // merge name
+ pair left_name, right_name, new_name;
+ multiple_name_conflict conflict(new_n->self);
+ left_name = make_pair(left_n->parent, left_n->name);
+ right_name = make_pair(right_n->parent, right_n->name);
+ if (merge_scalar(left_name,
+ left_marking.parent_name,
+ left_uncommon_ancestors,
+ right_name,
+ right_marking.parent_name,
+ right_uncommon_ancestors,
+ new_name, conflict))
+ {
+ side_t winning_side;
+
+ if (new_name == left_name)
+ winning_side = left_side;
+ else if (new_name == right_name)
+ winning_side = right_side;
+ else
+ I(false);
+
+ assign_name(result, new_n->self,
+ new_name.first, new_name.second, winning_side);
+
+ }
+ else
+ {
+ // unsuccessful merge; leave node detached and save
+ // conflict object
+ result.multiple_name_conflicts.push_back(conflict);
+ }
+ // if a file, merge content
+ if (is_file_t(new_n))
+ {
+ file_content_conflict conflict(new_n->self);
+ if (merge_scalar(downcast_to_file_t(left_n)->content,
+ left_marking.file_content,
+ left_uncommon_ancestors,
+ downcast_to_file_t(right_n)->content,
+ right_marking.file_content,
+ right_uncommon_ancestors,
+ downcast_to_file_t(new_n)->content,
+ conflict))
+ {
+ // successful merge
+ }
+ else
+ {
+ downcast_to_file_t(new_n)->content = file_id();
+ result.file_content_conflicts.push_back(conflict);
+ }
+ }
+ // merge attributes
+ {
+ full_attr_map_t::const_iterator left_ai = left_n->attrs.begin();
+ full_attr_map_t::const_iterator right_ai = right_n->attrs.begin();
+ parallel::iter attr_i(left_n->attrs,
+ right_n->attrs);
+ while(attr_i.next())
+ {
+ switch (attr_i.state())
+ {
+ case parallel::invalid:
+ I(false);
+ case parallel::in_left:
+ safe_insert(new_n->attrs, attr_i.left_value());
+ break;
+ case parallel::in_right:
+ safe_insert(new_n->attrs, attr_i.right_value());
+ break;
+ case parallel::in_both:
+ pair new_value;
+ attribute_conflict conflict(new_n->self);
+ conflict.key = attr_i.left_key();
+ I(conflict.key == attr_i.right_key());
+ if (merge_scalar(attr_i.left_data(),
+ safe_get(left_marking.attrs,
+ attr_i.left_key()),
+ left_uncommon_ancestors,
+ attr_i.right_data(),
+ safe_get(right_marking.attrs,
+ attr_i.right_key()),
+ right_uncommon_ancestors,
+ new_value,
+ conflict))
+ {
+ // successful merge
+ safe_insert(new_n->attrs,
+ make_pair(attr_i.left_key(),
+ new_value));
+ }
+ else
+ {
+ // unsuccessful merge
+ // leave out the attr entry entirely, and save the
+ // conflict
+ result.attribute_conflicts.push_back(conflict);
+ }
+ break;
+ }
+ }
+ }
+ }
} // end anonymous namespace
void
@@ -1985,9 +2154,69 @@ roster_merge(roster_t const & left_paren
MM(right_markings);
MM(result);
- // First handle lifecycles, by die-die-die merge -- our result will contain
- // everything that is alive in both parents, or alive in one and unborn in
- // the other, exactly.
+ // First handle lifecycles. Several cases:
+ //
+ // A1 B1
+ // i) \ /
+ // C1
+ //
+ // The node is merged in the child
+ //
+ //
+ // A1 B2
+ // ii) \ /
+ // C3
+ //
+ // The node is sutured in the child. This is only possible in a
+ // conflict resolution, which will occur later in the merge process.
+ // FIXME_SUTURE: support user sutures.
+ //
+ //
+ // A1 B3
+ // iiia) \ /
+ // C3
+ //
+ // The node was born in a suture or split in an uncommon ancestor of B
+ //
+ // A3 B1
+ // iiib) \ /
+ // C3
+ //
+ // The node was born in a suture or split in an uncommon ancestor of A
+ //
+ //
+ // A1 B
+ // iva) \ /
+ // C1
+ //
+ // The node was born new in A's uncommon ancestors
+ //
+ // A B1
+ // ivb) \ /
+ // C1
+ //
+ // The node was born new in B's uncommon ancestors
+ //
+ //
+ // A1 B
+ // v) \ /
+ // C
+ //
+ // The node was deleted in B's uncommon ancestors
+ //
+ // A B1
+ // \ /
+ // C
+ //
+ // The node was deleted in A's uncommon ancestors
+ //
+ //
+ // In monotone 0.4 and earlier, cases ii and iii did not exist.
+ //
+ // In case v, deletion wins over any other change; it might be better to
+ // have deletion conflict with any other change. But that's left for
+ // another time.
+
{
parallel::iter i(left_parent.all_nodes(), right_parent.all_nodes());
while (i.next())
@@ -1998,15 +2227,17 @@ roster_merge(roster_t const & left_paren
I(false);
case parallel::in_left:
- insert_if_unborn(i.left_data(),
- left_markings, left_uncommon_ancestors, left_parent,
- result.roster);
+ // case iii, iva, or va
+ insert_if_unborn_or_sutured(i.left_data(),
+ left_markings, left_uncommon_ancestors, left_parent,
+ result.roster);
break;
case parallel::in_right:
- insert_if_unborn(i.right_data(),
- right_markings, right_uncommon_ancestors, right_parent,
- result.roster);
+ // case iii, ivb, or vb
+ insert_if_unborn_or_sutured(i.right_data(),
+ right_markings, right_uncommon_ancestors, right_parent,
+ result.roster);
break;
case parallel::in_both:
@@ -2033,16 +2264,48 @@ roster_merge(roster_t const & left_paren
case parallel::in_left:
{
- 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)
+ 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), unless they are
+ // parents of a suture.
+
if (result.roster.has_node(left_n->self))
{
- // attach this node from the left roster. this may cause
- // a name collision with the previously attached node from
- // the other side of the merge.
- copy_node_forward(result, new_i->second, left_n, left_side);
- ++new_i;
+ if (left_n->ancestors.first != the_null_node &&
+ left_n->ancestors.first != left_n->self)
+ {
+ // This node is the child of a suture; if right parent
+ // exists in right, merge with it.
+ node_map::const_iterator right_i = right_parent.all_nodes().find (left_n->ancestors.second);
+ if (right_i != right_parent.all_nodes().end())
+ {
+ marking_map::const_iterator right_mi = right_markings.find(right_i->second->ancestors.first);
+
+ // check that iterators are in sync.
+ I(new_i->first == i.left_key());
+ I(left_mi->first == i.left_key());
+
+ merge_nodes(i.left_data(), // left_n
+ left_mi->second, // left_marking
+ left_uncommon_ancestors,
+ right_i->second, // right_n
+ right_mi->second,
+ right_uncommon_ancestors,
+ new_i->second, // new_n
+ result);
+ ++new_i;
+ }
+ }
+ else
+ {
+ // Not child of a suture.
+ //
+ // Attach this node from the left roster. This may cause
+ // a name collision with the previously attached node from
+ // the other side of the merge.
+ copy_node_forward(result, new_i->second, left_n, left_side);
+ ++new_i;
+ }
}
++left_mi;
break;
@@ -2051,14 +2314,46 @@ roster_merge(roster_t const & left_paren
case parallel::in_right:
{
node_t const & right_n = i.right_data();
- // we skip nodes that aren't in the result roster
+ // We skip nodes that aren't in the result roster, unless they are
+ // parents of a suture.
+
if (result.roster.has_node(right_n->self))
{
- // attach this node from the right roster. this may cause
- // a name collision with the previously attached node from
- // the other side of the merge.
- copy_node_forward(result, new_i->second, right_n, right_side);
- ++new_i;
+ if (right_n->ancestors.first != the_null_node &&
+ right_n->ancestors.first != right_n->self)
+ {
+ // This node is the child of a suture; if right parent
+ // exists in right, merge with it.
+ node_map::const_iterator left_i = left_parent.all_nodes().find (right_n->ancestors.first);
+ if (left_i != left_parent.all_nodes().end())
+ {
+ marking_map::const_iterator left_mi = left_markings.find(right_n->ancestors.first);
+
+ // check that iterators are in sync.
+ I(new_i->first == i.right_key());
+ I(right_mi->first == i.right_key());
+
+ merge_nodes(left_i->second, // left_n
+ left_mi->second, // left_marking
+ left_uncommon_ancestors,
+ i.right_data(), // right_n
+ right_mi->second, // right_marking
+ right_uncommon_ancestors,
+ new_i->second, // new_n
+ result);
+ ++new_i;
+ }
+ }
+ else
+ {
+ // Not child of a suture.
+ //
+ // Attach this node from the right roster. This may
+ // cause a name collision with the previously attached
+ // node from the other side of the merge.
+ copy_node_forward(result, new_i->second, right_n, right_side);
+ ++new_i;
+ }
}
++right_mi;
break;
@@ -2069,121 +2364,15 @@ roster_merge(roster_t const & left_paren
I(new_i->first == i.left_key());
I(left_mi->first == i.left_key());
I(right_mi->first == i.right_key());
- node_t const & left_n = i.left_data();
- marking_t const & left_marking = left_mi->second;
- node_t const & right_n = i.right_data();
- marking_t const & right_marking = right_mi->second;
- node_t const & new_n = new_i->second;
- // merge name
- {
- pair left_name, right_name, new_name;
- multiple_name_conflict conflict(new_n->self);
- left_name = make_pair(left_n->parent, left_n->name);
- right_name = make_pair(right_n->parent, right_n->name);
- if (merge_scalar(left_name,
- left_marking.parent_name,
- left_uncommon_ancestors,
- right_name,
- right_marking.parent_name,
- right_uncommon_ancestors,
- new_name, conflict))
- {
- side_t winning_side;
- if (new_name == left_name)
- winning_side = left_side;
- else if (new_name == right_name)
- winning_side = right_side;
- else
- I(false);
-
- // attach this node from the winning side of the merge. if
- // there is a name collision the previously attached node
- // (which is blocking this one) must come from the other
- // side of the merge.
- assign_name(result, new_n->self,
- new_name.first, new_name.second, winning_side);
-
- }
- else
- {
- // unsuccessful merge; leave node detached and save
- // conflict object
- result.multiple_name_conflicts.push_back(conflict);
- }
- }
- // if a file, merge content
- if (is_file_t(new_n))
- {
- file_content_conflict conflict(new_n->self);
- if (merge_scalar(downcast_to_file_t(left_n)->content,
- left_marking.file_content,
- left_uncommon_ancestors,
- downcast_to_file_t(right_n)->content,
- right_marking.file_content,
- right_uncommon_ancestors,
- downcast_to_file_t(new_n)->content,
- conflict))
- {
- // successful merge
- }
- else
- {
- downcast_to_file_t(new_n)->content = file_id();
- result.file_content_conflicts.push_back(conflict);
- }
- }
- // merge attributes
- {
- full_attr_map_t::const_iterator left_ai = left_n->attrs.begin();
- full_attr_map_t::const_iterator right_ai = right_n->attrs.begin();
- parallel::iter attr_i(left_n->attrs,
- right_n->attrs);
- while(attr_i.next())
- {
- switch (attr_i.state())
- {
- case parallel::invalid:
- I(false);
- case parallel::in_left:
- safe_insert(new_n->attrs, attr_i.left_value());
- break;
- case parallel::in_right:
- safe_insert(new_n->attrs, attr_i.right_value());
- break;
- case parallel::in_both:
- pair new_value;
- attribute_conflict conflict(new_n->self);
- conflict.key = attr_i.left_key();
- I(conflict.key == attr_i.right_key());
- if (merge_scalar(attr_i.left_data(),
- safe_get(left_marking.attrs,
- attr_i.left_key()),
- left_uncommon_ancestors,
- attr_i.right_data(),
- safe_get(right_marking.attrs,
- attr_i.right_key()),
- right_uncommon_ancestors,
- new_value,
- conflict))
- {
- // successful merge
- safe_insert(new_n->attrs,
- make_pair(attr_i.left_key(),
- new_value));
- }
- else
- {
- // unsuccessful merge
- // leave out the attr entry entirely, and save the
- // conflict
- result.attribute_conflicts.push_back(conflict);
- }
- break;
- }
-
- }
- }
+ merge_nodes(i.left_data(), // left_n
+ left_mi->second, // left_marking
+ left_uncommon_ancestors,
+ i.right_data(), // right_n
+ right_mi->second, // right_marking
+ right_uncommon_ancestors,
+ new_i->second, // new_n
+ result);
}
++left_mi;
++right_mi;
============================================================
--- tests/resolve_duplicate_name_conflict/__driver__.lua 5bcba012902ee356d5ef041847cef3aa725c53c1
+++ tests/resolve_duplicate_name_conflict/__driver__.lua 8bde1a08cec21f13e69bbb1ccae604dc3d5146d7
@@ -1,11 +1,28 @@
--- Test/demonstrate handling of a duplicate name conflict; Abe and
--- Beth add files with the same names.
+-- Test/demonstrate handling of a duplicate name conflict.
--
--- For checkout.sh, the user intent is that there be
--- one file with that name; the contents should be merged.
+-- In the first step, we have this revision history graph:
--
--- For thermostat.c, there should be two files;
--- thermostat-westinghouse.c and thermostat-honeywell.c
+-- o
+-- / \
+-- a b
+-- \ /
+-- d
+--
+-- 'o' is the base revision. In 'a' and 'b', Abe and Beth each add two
+-- files with the same names:
+--
+-- checkout.sh
+-- the user intent is that there be one file with that name;
+-- the contents should be merged.
+--
+-- thermostat.c
+-- the user intent is that there should be two files;
+-- thermostat-westinghouse.c and thermostat-honeywell.c
+--
+-- in 'd', the duplicate name conflicts are resolved by suturing
+-- checkout.sh and renaming thermostat.c.
+--
+-- After that, we extend the history graph; see below.
mtn_setup()
include ("common/test_utils_inventory.lua")
@@ -16,7 +33,7 @@ base = base_revision()
base = base_revision()
-- Abe adds conflict files
-addfile("thermostat.c", "thermostat westinghouse")
+addfile("thermostat.c", "thermostat westinghouse abe 1")
addfile("checkout.sh", "checkout.sh abe 1")
commit("testbranch", "abe_1")
abe_1 = base_revision()
@@ -24,13 +41,13 @@ revert_to(base)
revert_to(base)
-- Beth adds files, and attempts to merge
-addfile("thermostat.c", "thermostat honeywell")
+addfile("thermostat.c", "thermostat honeywell beth 1")
addfile("checkout.sh", "checkout.sh beth 1")
commit("testbranch", "beth_1")
beth_1 = base_revision()
-- This fails due to duplicate name conflicts
-check(mtn("merge"), 1, false, false)
+check(mtn("merge"), 1, nil, false)
-- Beth uses 'automate show_conflicts' and 'merge --resolve-conflicts-file'
-- to fix the conflicts
@@ -46,7 +63,7 @@ check(mtn("merge"), 1, false, false)
-- _MTN/conflicts, she records the resolution as
-- 'resolved_content_ws'.
-check (mtn("automate", "show_conflicts"), 0, true, false)
+check (mtn("automate", "show_conflicts"), 0, true, nil)
-- Verify that we got the expected revisions, conflicts and file ids
parsed = parse_basic_io(readfile("stdout"))
@@ -65,18 +82,18 @@ check_basic_io_line (15, parsed[15], "le
abe_checkout = parsed[8].values[1]
check_basic_io_line (15, parsed[15], "left_name", "thermostat.c")
-check_basic_io_line (16, parsed[16], "left_file_id", "4cdcec6fa2f9d5c075d5b80d03c708c8e4801196")
+check_basic_io_line (16, parsed[16], "left_file_id", "c2f67aa3b29c2bdab4790213c7f3bf73e58440a7")
abe_thermostat = parsed[16].values[1]
-- Do the filesystem rename for Beth's thermostat.c, and retrieve Abe's version
rename ("thermostat.c", "thermostat-honeywell.c")
-check (mtn ("automate", "get_file", abe_thermostat), 0, true, false)
+check (mtn ("automate", "get_file", abe_thermostat), 0, true, nil)
rename ("stdout", "thermostat-westinghouse.c")
-check ("thermostat westinghouse" == readfile ("thermostat-westinghouse.c"))
+check ("thermostat westinghouse abe 1" == readfile ("thermostat-westinghouse.c"))
-- Do the manual merge for checkout.sh; retrieve Abe's version
-check (mtn ("automate", "get_file", abe_checkout), 0, true, false)
+check (mtn ("automate", "get_file", abe_checkout), 0, true, nil)
rename ("stdout", "checkout.sh-abe")
check ("checkout.sh abe 1" == readfile ("checkout.sh-abe"))
@@ -86,27 +103,87 @@ get ("conflicts-resolved", "_MTN/conflic
get ("conflicts-resolved", "_MTN/conflicts")
-- This succeeds
-check(mtn("merge", "--resolve-conflicts-file=_MTN/conflicts"), 0, true, true)
+check(mtn("merge", "--resolve-conflicts-file=_MTN/conflicts"), 0, nil, true)
+canonicalize("stderr")
+get ("expected-merge-messages-abe_1-beth_1")
+check(samefile("expected-merge-messages-abe_1-beth_1", "stderr"))
--- FIXME: check for reasonable messages from merge
-
-- update fails if thermostat.c is missing, and if
-- thermostat-honeywell.c, thermostat-westinghouse.c are in the way.
-- So clean that up first. FIXME: update needs --ignore-missing,
-- --overwrite-ws or something.
-check(mtn("revert", "--missing"), 0, false, false)
+check(mtn("revert", "--missing"), 0, nil, false)
remove ("thermostat-honeywell.c")
remove ("thermostat-westinghouse.c")
-check(mtn("update"), 0, true, true)
+check(mtn("update"), 0, nil, true)
+-- FIXME: this warns about changes in checkout.sh being lost, even though they aren't; it doesn't understand suture
+canonicalize("stderr")
+get ("expected-update-messages-jim_1")
+check(samefile("expected-update-messages-jim_1", "stderr"))
-- verify that we got revision_format 2
-get ("expected-merged-revision")
-check(mtn("automate", "get_revision", base_revision()), 1, true, nil)
+check(mtn("automate", "get_revision", base_revision()), 0, true, nil)
canonicalize("stdout")
-check_same_file("expected-merged-revision", "stdout")
+get ("expected-merged-revision-jim_1")
+check(samefile("expected-merged-revision-jim_1", "stdout"))
-- Verify file contents
-check("thermostat westinghouse" == readfile("thermostat-westinghouse.c"))
-check("thermostat honeywell" == readfile("thermostat-honeywell.c"))
+check("thermostat westinghouse abe 1" == readfile("thermostat-westinghouse.c"))
+check("thermostat honeywell beth 1" == readfile("thermostat-honeywell.c"))
check("checkout.sh merged\n" == readfile("checkout.sh"))
+
+-- In the second step, we extend the revision history graph:
+--
+-- o
+-- / \
+-- a b
+-- / \ / \
+-- c d e
+-- \ / \ /
+-- g h
+-- \ /
+-- f
+--
+-- Here we assume that the 'd' merge was done by Jim, and Abe and Beth
+-- edit their new files in parallel with the merge. We pretend Abe,
+-- Beth, and Jim have separate development databases, shared via
+-- netsync. Eventually everything is merged properly.
+--
+-- in 'c' and 'e', Abe and Beth edit checkout.sh and thermostat.c
+--
+-- in 'g' and 'h', Abe and Beth each merge their changes with Jim's merge.
+--
+-- in 'f', Jim merges one more time.
+
+jim_1 = base_revision()
+
+-- Abe edits his files and merges
+revert_to(abe_1)
+
+writefile("thermostat.c", "thermostat westinghouse abe 2")
+writefile("checkout.sh", "checkout.sh abe 2")
+commit("testbranch", "abe_2")
+abe_2 = base_revision()
+
+check(mtn("merge"), 0, nil, true)
+canonicalize("stderr")
+get ("expected-merge-messages-abe_2-jim_1")
+check(samefile("expected-merge-messages-abe_2-jim_1", "stderr"))
+
+-- Beth edits her files and merges
+revert_to(beth_1)
+
+writefile("thermostat.c", "thermostat honeywell beth 1")
+writefile("checkout.sh", "checkout.sh beth 1")
+commit("testbranch", "beth_2")
+beth_2 = base_revision()
+
+-- If we just do 'merge', mtn will merge 'e' and 'g', since those are
+-- the current heads. To emulate separate development databases, we
+-- specify the revisions to merge.
+check(mtn("merge", jim_1, beth_2), 0, nil, true)
+canonicalize("stderr")
+get ("expected-merge-messages-jim_1-beth_2")
+check(samefile("expected-merge-messages-jim_1-beth_2", "stderr"))
+
-- end of file
============================================================
--- work.cc 0261cdf7dbc3baed0cdfc0e75a3a858f64eefa97
+++ work.cc a01164a4e32079091f126c5018a187cea96e0dcd
@@ -1,3 +1,4 @@
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2002 Graydon Hoare
//
// This program is made available under the GNU GPL version 2.0 or
@@ -858,7 +859,9 @@ struct editable_working_tree : public ed
virtual void drop_detached_node(node_id nid);
virtual node_id create_dir_node();
- virtual node_id create_file_node(file_id const & content);
+ virtual node_id create_file_node(file_id const & content,
+ std::pair const ancestors = null_ancestors);
+ virtual node_id get_node(file_path const &pth);
virtual void attach_node(node_id nid, file_path const & dst);
virtual void apply_delta(file_path const & pth,
@@ -899,7 +902,9 @@ struct simulated_working_tree : public e
virtual void drop_detached_node(node_id nid);
virtual node_id create_dir_node();
- virtual node_id create_file_node(file_id const & content);
+ virtual node_id create_file_node(file_id const & content,
+ std::pair const ancestors = null_ancestors);
+ virtual node_id get_node(file_path const &pth);
virtual void attach_node(node_id nid, file_path const & dst);
virtual void apply_delta(file_path const & pth,
@@ -1013,8 +1018,10 @@ node_id
}
node_id
-editable_working_tree::create_file_node(file_id const & content)
+editable_working_tree::create_file_node(file_id const & content,
+ std::pair const ancestors)
{
+ I(ancestors == null_ancestors);
node_id nid = next_nid++;
bookkeeping_path pth = path_for_detached_nid(nid);
require_path_is_nonexistent(pth,
@@ -1026,6 +1033,16 @@ editable_working_tree::create_file_node(
return nid;
}
+node_id
+editable_working_tree::get_node(file_path const &pth)
+{
+ // The mapping from node ids to file_paths is not stored anywhere. So we
+ // can't do whatever operation needs this. So far, it's only looking up
+ // ancestors for sutures while applying a changeset; a working tree can't
+ // be an ancestor, so we're ok.
+ I(false);
+}
+
void
editable_working_tree::attach_node(node_id nid, file_path const & dst_pth)
{
@@ -1145,11 +1162,18 @@ node_id
}
node_id
-simulated_working_tree::create_file_node(file_id const & content)
+simulated_working_tree::create_file_node(file_id const & content,
+ std::pair const ancestors)
{
- return workspace.create_file_node(content, nis);
+ return workspace.create_file_node(content, nis, ancestors);
}
+node_id
+simulated_working_tree::get_node(file_path const &pth)
+{
+ return workspace.get_node(pth)->self;
+}
+
void
simulated_working_tree::attach_node(node_id nid, file_path const & dst)
{