#
#
# add_file "tests/resolve_duplicate_name_conflict/checkout.sh-merged"
# content [3390893b9a31eaa9ef0e9364b27ee1e617e6891a]
#
# add_file "tests/resolve_duplicate_name_conflict/conflicts-resolved"
# content [9630663631477e886a63eea58ad6898adb81b4a4]
#
# patch "cset.cc"
# from [ee0aba985f36fe78699987b4ea2cf6d9dc870a68]
# to [a2cb867731e1d283fa60165e534157d0b38fb364]
#
# patch "cset.hh"
# from [5120df6bd9c521f03437fc04e5f02ed7dc610b97]
# to [53dcfec8e054e927c0615188e716548390460a3f]
#
# patch "roster.cc"
# from [70af963b64dd2729562ca29ae346392ff299c108]
# to [8befc5f69097a05b8c58ef24bb37d97378b7e7b0]
#
# patch "roster.hh"
# from [0a976adbca1e66062d5bd9fcfb063eef07427f73]
# to [e22fbd6c2eea9615337de4319fca8353c587b13d]
#
# patch "roster_merge.cc"
# from [992ee1d3932808bd0e059c4e8f984c4fcd39b586]
# to [178917d07e72bb9cff7c9616e0e25e0b89eccdfb]
#
# patch "tests/resolve_duplicate_name_conflict/__driver__.lua"
# from [8f477bd075f50532cdb3a519e136122443596aa5]
# to [c6652312c6765180630aff4ebacc7af45e6c8472]
#
============================================================
--- tests/resolve_duplicate_name_conflict/checkout.sh-merged 3390893b9a31eaa9ef0e9364b27ee1e617e6891a
+++ tests/resolve_duplicate_name_conflict/checkout.sh-merged 3390893b9a31eaa9ef0e9364b27ee1e617e6891a
@@ -0,0 +1 @@
+checkout.sh merged
============================================================
--- tests/resolve_duplicate_name_conflict/conflicts-resolved 9630663631477e886a63eea58ad6898adb81b4a4
+++ tests/resolve_duplicate_name_conflict/conflicts-resolved 9630663631477e886a63eea58ad6898adb81b4a4
@@ -0,0 +1,22 @@
+ left [1337cb1059c4bc3e376b14381b43e9383c654da1]
+ right [d5f1dd136c86b5bbd5e71b0c3365667e328af492]
+ancestor [b3ac8a77cee78263b66800c635441ecb1f259a42]
+
+ conflict duplicate_name
+ left_type "added file"
+ left_name "checkout.sh"
+ left_file_id [61b8d4fb0e5d78be111f691b955d523c782fa92e]
+ right_type "added file"
+ right_name "checkout.sh"
+right_file_id [dd6805ae36432d6edcbdff6ea578ea981ffa2144]
+resolved_suture "checkout.sh"
+
+ conflict duplicate_name
+ left_type "added file"
+ left_name "thermostat.c"
+ left_file_id [4cdcec6fa2f9d5c075d5b80d03c708c8e4801196]
+ right_type "added file"
+ right_name "thermostat.c"
+right_file_id [7e9f2712c5d3570815f1546772d9119474d32afc]
+resolved_rename_left "thermostat-westinghouse.c"
+resolved_rename_right "thermostat-honeywell.c"
============================================================
--- cset.cc ee0aba985f36fe78699987b4ea2cf6d9dc870a68
+++ cset.cc a2cb867731e1d283fa60165e534157d0b38fb364
@@ -1,3 +1,4 @@
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2005 Nathaniel Smith
//
// This program is made available under the GNU GPL version 2.0 or
@@ -78,6 +79,7 @@ cset::empty() const
nodes_deleted.empty()
&& dirs_added.empty()
&& files_added.empty()
+ && nodes_sutured.empty()
&& nodes_renamed.empty()
&& deltas_applied.empty()
&& attrs_cleared.empty()
@@ -90,6 +92,7 @@ cset::clear()
nodes_deleted.clear();
dirs_added.clear();
files_added.clear();
+ nodes_sutured.clear();
nodes_renamed.clear();
deltas_applied.clear();
attrs_cleared.clear();
@@ -177,11 +180,24 @@ cset::apply_to(editable_tree & t) const
i != files_added.end(); ++i)
safe_insert(attaches, attach(t.create_file_node(i->second), i->first));
-
// Decompose all path deletion and the first-half of renamings on
// existing paths into the set of pending detaches, to be executed
// bottom-up.
+ for (map::const_iterator i = nodes_sutured.begin();
+ i != nodes_sutured.end(); ++i)
+ {
+ // Sutured node is added
+ safe_insert(attaches, attach(t.create_file_node(i->second.sutured_id), i->first));
+
+ // Ancestor nodes are dropped; detach here, drop later.
+ safe_insert(detaches, detach(i->second.first_ancestor));
+
+ if (!i->second.second_ancestor.empty())
+ safe_insert(detaches, detach(i->second.second_ancestor));
+ }
+
+
for (set::const_iterator i = nodes_deleted.begin();
i != nodes_deleted.end(); ++i)
safe_insert(detaches, detach(*i));
@@ -247,6 +263,9 @@ namespace
symbol const content("content");
symbol const add_file("add_file");
symbol const add_dir("add_dir");
+ symbol const sutured_file("sutured_file");
+ symbol const first_ancestor("first_ancestor");
+ symbol const second_ancestor("second_ancestor");
symbol const patch("patch");
symbol const from("from");
symbol const to("to");
@@ -295,6 +314,17 @@ print_cset(basic_io::printer & printer,
printer.print_stanza(st);
}
+ for (map::const_iterator i = cs.nodes_sutured.begin();
+ i != cs.nodes_sutured.end(); ++i)
+ {
+ basic_io::stanza st;
+ st.push_file_pair(syms::sutured_file, i->first);
+ st.push_file_pair(syms::first_ancestor, i->second.first_ancestor);
+ st.push_file_pair(syms::second_ancestor, i->second.second_ancestor);
+ st.push_binary_pair(syms::content, i->second.sutured_id.inner());
+ printer.print_stanza(st);
+ }
+
for (map >::const_iterator i = cs.deltas_applied.begin();
i != cs.deltas_applied.end(); ++i)
{
@@ -342,7 +372,7 @@ parse_cset(basic_io::parser & parser,
string t1, t2;
MM(t1);
MM(t2);
- file_path p1, p2;
+ file_path p1, p2, p3;
MM(p1);
MM(p2);
@@ -398,6 +428,22 @@ parse_cset(basic_io::parser & parser,
}
prev_path.clear();
+ while (parser.symp(syms::sutured_file))
+ {
+ parser.sym();
+ parse_path(parser, p1);
+ I(prev_path.empty() || prev_path < p1);
+ prev_path = p1;
+ parser.esym(syms::first_ancestor);
+ parse_path(parser, p2);
+ parser.esym(syms::second_ancestor);
+ parse_path(parser, p3);
+ parser.esym(syms::content);
+ parser.hex(t1);
+ safe_insert(cs.nodes_sutured, make_pair(p1, cset::sutured_t(p2, p3, file_id(decode_hexenc(t1)))));
+ }
+
+ prev_path.clear();
while (parser.symp(syms::patch))
{
parser.sym();
============================================================
--- cset.hh 5120df6bd9c521f03437fc04e5f02ed7dc610b97
+++ cset.hh 53dcfec8e054e927c0615188e716548390460a3f
@@ -1,6 +1,7 @@
#ifndef __CSET_HH__
#define __CSET_HH__
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2005 Nathaniel Smith
//
// This program is made available under the GNU GPL version 2.0 or
@@ -56,6 +57,26 @@ struct cset
std::set dirs_added;
std::map files_added;
+ // Sutures.
+ struct sutured_t
+ {
+ // If the suture is resolving a merge conflict, then one ancestor is
+ // from the left side of the merge, and the other ancestor is from the
+ // other side of the merge. However, each changeset only shows one of
+ // these ancestors; there are two changesets for a merged revision. Only
+ // first_ancestor is non-null in this case.
+ //
+ // If the suture is a user command, then both ancestors are from the
+ // same revision, and both are non-null.
+ file_path first_ancestor;
+ file_path second_ancestor;
+ file_id sutured_id;
+
+ sutured_t(file_path the_first, file_path the_second, file_id the_sutured_id) :
+ first_ancestor (the_first), second_ancestor (the_second), sutured_id (the_sutured_id) {};
+ };
+ std::map nodes_sutured;
+
// Pure renames.
std::map nodes_renamed;
============================================================
--- roster.cc 70af963b64dd2729562ca29ae346392ff299c108
+++ roster.cc 8befc5f69097a05b8c58ef24bb37d97378b7e7b0
@@ -1,3 +1,4 @@
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2005 Nathaniel Smith
//
// This program is made available under the GNU GPL version 2.0 or
@@ -263,6 +264,7 @@ dump(node_t const & n, string & out)
string attr_map_s;
dump(n->attrs, attr_map_s);
oss << "attrs:\n" << attr_map_s;
+ oss << "ancestors: " << n->ancestors.first << ' ' << n->ancestors.second << '\n';
oss << "type: ";
if (is_file_t(n))
{
@@ -826,17 +828,23 @@ node_id
// for it into the old_locations member, because there is no old_location to
// forbid
node_id
-roster_t::create_dir_node(node_id_source & nis)
+roster_t::create_dir_node(node_id_source & nis, std::pair ancestors)
{
node_id nid = nis.next();
- create_dir_node(nid);
+ create_dir_node(nid, ancestors);
return nid;
}
void
roster_t::create_dir_node(node_id nid)
{
+ create_dir_node(nid, make_pair(nid, the_null_node));
+}
+void
+roster_t::create_dir_node(node_id nid, std::pair ancestors)
+{
dir_t d = dir_t(new dir_node());
d->self = nid;
+ d->ancestors = ancestors;
safe_insert(nodes, make_pair(nid, d));
}
@@ -845,18 +853,24 @@ node_id
// for it into the old_locations member, because there is no old_location to
// forbid
node_id
-roster_t::create_file_node(file_id const & content, node_id_source & nis)
+roster_t::create_file_node(file_id const & content, node_id_source & nis, std::pair ancestors)
{
node_id nid = nis.next();
- create_file_node(content, nid);
+ create_file_node(content, nid, ancestors);
return nid;
}
void
roster_t::create_file_node(file_id const & content, node_id nid)
{
+ create_file_node(content, nid, make_pair(nid, the_null_node));
+}
+void
+roster_t::create_file_node(file_id const & content, node_id nid, std::pair ancestors)
+{
file_t f = file_t(new file_node());
f->self = nid;
f->content = content;
+ f->ancestors = ancestors;
safe_insert(nodes, make_pair(nid, f));
}
@@ -1376,12 +1390,12 @@ namespace
// this, the two rosters will have identical node_ids at every path.
union_new_nodes(left, left_new, right, right_new, nis);
- // The other thing we need to fix up is attr corpses. Live attrs are made
- // identical by the csets; but if, say, on one side of a fork an attr is
- // added and then deleted, then one of our incoming merge rosters will
- // have a corpse for that attr, and the other will not. We need to make
- // sure at both of them end up with the corpse. This function fixes up
- // that.
+ // The other thing we need to fix up is attr corpses. Live attrs are
+ // made identical by the csets; but if, say, on one side of a fork an
+ // attr is added and then deleted, then one of our incoming merge
+ // rosters will have a corpse for that attr, and the other will not. We
+ // need to make sure that both of them end up with the corpse. This
+ // function does that.
union_corpses(left, right);
}
@@ -2043,26 +2057,84 @@ namespace
node_id nid, node_t n,
cset & cs)
{
+ // Node is deleted; it may have been sutured into another new node
+ //
+ // We cannot easily tell which here - we'd have to search the 'to'
+ // roster for a node with this node as an ancestor. However, we can just
+ // record this node as deleted now, and change it later when the suture
+ // is seen. Note that the suture will be on a later node; nodes are
+ // processed in order, and new nodes occur after deleted nodes.
+
file_path pth;
from.get_name(nid, pth);
safe_insert(cs.nodes_deleted, pth);
}
- void delta_only_in_to(roster_t const & to, node_id nid, node_t n,
+ void delta_only_in_to(roster_t const & from,
+ roster_t const & to,
+ node_id nid,
+ node_t n,
cset & cs)
{
+ MM(nid);
+
+ // Node is new; it may be a suture of two deleted nodes
file_path pth;
to.get_name(nid, pth);
- if (is_file_t(n))
+
+ // Workspace rosters always have ancestors = null. The root node cannot
+ // be sutured.
+ if (n->ancestors.first != the_null_node && n->ancestors.first != nid)
{
- safe_insert(cs.files_added,
- make_pair(pth, downcast_to_file_t(n)->content));
+ // copy not implemented yet; this is a suture
+ I(n->ancestors.second != the_null_node);
+
+ I(is_file_t(n)); // can't suture directories
+
+ file_path first_anc, second_anc;
+
+ // 'from' may be either left or right merge parent, or this could
+ // be a user suture. So either ancestor could be in either 'from' or
+ // the other parent revision of the merge. If it's in the other
+ // parent, it will show up in the other changeset.
+ //
+ // If we only have one ancestor name, it goes in first_anc.
+
+ if (from.has_node(n->ancestors.first))
+ {
+ from.get_name(n->ancestors.first, first_anc);
+ safe_erase(cs.nodes_deleted, first_anc);
+
+ if (from.has_node(n->ancestors.second))
+ {
+ from.get_name (n->ancestors.second, second_anc);
+ safe_erase(cs.nodes_deleted, second_anc);
+ }
+ }
+ else
+ {
+ from.get_name(n->ancestors.second, first_anc);
+ safe_erase(cs.nodes_deleted, first_anc);
+ }
+
+ safe_insert(cs.nodes_sutured,
+ make_pair(pth, cset::sutured_t(first_anc, second_anc, downcast_to_file_t(n)->content)));
}
else
{
- safe_insert(cs.dirs_added, pth);
+ // just added
+ if (is_file_t(n))
+ {
+ safe_insert(cs.files_added,
+ make_pair(pth, downcast_to_file_t(n)->content));
+ }
+ else
+ {
+ safe_insert(cs.dirs_added, pth);
+ }
}
+
for (full_attr_map_t::const_iterator i = n->attrs.begin();
i != n->attrs.end(); ++i)
if (i->second.first)
@@ -2140,6 +2212,8 @@ make_cset(roster_t const & from, roster_
void
make_cset(roster_t const & from, roster_t const & to, cset & cs)
{
+ MM(from);
+ MM(to);
cs.clear();
parallel::iter i(from.all_nodes(), to.all_nodes());
while (i.next())
@@ -2151,13 +2225,13 @@ make_cset(roster_t const & from, roster_
I(false);
case parallel::in_left:
- // deleted
+ // deleted or sutured
delta_only_in_from(from, i.left_key(), i.left_data(), cs);
break;
case parallel::in_right:
- // added
- delta_only_in_to(to, i.right_key(), i.right_data(), cs);
+ // added or sutured
+ delta_only_in_to(from, to, i.right_key(), i.right_data(), cs);
break;
case parallel::in_both:
@@ -2390,6 +2464,14 @@ select_nodes_modified_by_cset(cset const
i != cs.nodes_renamed.end(); ++i)
modified_prestate_nodes.insert(i->first);
+ for (map::const_iterator i = cs.nodes_sutured.begin();
+ i != cs.nodes_sutured.end(); ++i)
+ {
+ modified_prestate_nodes.insert(i->first); // post-state; sutured file added
+ modified_prestate_nodes.insert(i->second.first_ancestor); // pre-state; ancestors deleted
+ modified_prestate_nodes.insert(i->second.second_ancestor);
+ }
+
// Post-state damage
copy(cs.dirs_added.begin(), cs.dirs_added.end(),
============================================================
--- roster.hh 0a976adbca1e66062d5bd9fcfb063eef07427f73
+++ roster.hh e22fbd6c2eea9615337de4319fca8353c587b13d
@@ -1,6 +1,7 @@
#ifndef __ROSTER_HH__
#define __ROSTER_HH__
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2005 Nathaniel Smith
//
// This program is made available under the GNU GPL version 2.0 or
@@ -24,6 +25,7 @@ node_id const the_null_node = 0;
///////////////////////////////////////////////////////////////////
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)
@@ -39,10 +41,24 @@ struct node
node();
node(node_id i);
node_id self;
- node_id parent; // the_null_node iff this is a root dir
+ node_id parent; // directory containing this node; the_null_node iff this is a root dir
path_component name; // the_null_component iff this is a root dir
full_attr_map_t attrs;
+ std::pair ancestors;
+ // new, resurrected: first, second = the_null_node
+ // sutured: first = left, second = right
+ // copied: first = copy source, second = the_null_node
+ // otherwise: first = self, second = the_null_node
+ //
+ // in workspace rosters, ancestors is always null
+ //
+ // We currently only support suture as a merge conflict resolution, so
+ // first and second are from different parent rosters. FIXME: if we add
+ // support for a user suture command, they will be from the same parent
+ // roster; we will need to record that somehow, and indicate it in
+ // changesets.
+
// need a virtual function to make dynamic_cast work
virtual node_t clone() = 0;
virtual ~node() {}
@@ -166,12 +182,18 @@ public:
// editable_tree operations
node_id detach_node(file_path const & src);
void drop_detached_node(node_id nid);
- node_id create_dir_node(node_id_source & nis);
- void create_dir_node(node_id nid);
+ node_id create_dir_node(node_id_source & nis,
+ std::pair const ancestors = null_ancestors);
+ void create_dir_node(node_id nid); // ancestors = (nid, null)
+ void create_dir_node(node_id nid, std::pair const ancestors);
node_id create_file_node(file_id const & content,
- node_id_source & nis);
+ node_id_source & nis,
+ std::pair const ancestors = null_ancestors);
void create_file_node(file_id const & content,
- node_id nid);
+ node_id nid); // ancestors = (nid, null)
+ void create_file_node(file_id const & content,
+ node_id nid,
+ std::pair const ancestors);
void attach_node(node_id nid, file_path const & dst);
void attach_node(node_id nid, node_id parent, path_component name);
void apply_delta(file_path const & pth,
@@ -235,51 +257,29 @@ private:
void do_deep_copy_from(roster_t const & other);
dir_t root_dir;
node_map nodes;
- // This requires some explanation. There is a particular kind of
+ // This requires some explanation. There is a particular kind of
// nonsensical behavior which we wish to discourage -- when a node is
- // detached from some location, and then re-attached at that same location
- // (or similarly, if a new node is created, then immediately deleted -- this
- // is like the previous case, if you think of "does not exist" as a
- // location). In particular, we _must_ error out if a cset attempts to do
+ // detached from some location, and then re-attached at that same
+ // location. In particular, we _must_ error out if a cset attempts to do
// this, because it indicates that the cset had something non-normalized,
- // like "rename a a" in it, and that is illegal. There are two options for
- // detecting this. The more natural approach, perhaps, is to keep a chunk
+ // like "rename a a" in it, and that is illegal. There are two options for
+ // detecting this. The more natural approach, perhaps, is to keep a chunk
// of state around while performing any particular operation (like cset
// application) for which we wish to detect these kinds of redundant
- // computations. The other option is to keep this state directly within the
- // roster, at all times. In the first case, we explicitly turn on checking
- // when we want it; the the latter, we must explicitly turn _off_ checking
- // when we _don't_ want it. We choose the latter, because it is more
- // conservative --- perhaps it will turn out that it is _too_ conservative
- // and causes problems, in which case we should probably switch to the
- // former.
+ // computations. The other option is to keep this state directly within
+ // the roster, at all times. In the first case, we explicitly turn on
+ // checking when we want it; the the latter, we must explicitly turn _off_
+ // checking when we _don't_ want it. We choose the latter, because it is
+ // more conservative --- perhaps it will turn out that it is _too_
+ // conservative and causes problems, in which case we should probably
+ // switch to the former.
//
- // FIXME: This _is_ all a little nasty, because this can be a source of
- // abstraction leak -- for instance, roster_merge's contract is that nodes
- // involved in name-related conflicts will be detached in the roster it returns.
- // Those nodes really should be allowed to be attached anywhere, or dropped,
- // which is not actually expressible right now. Worse, whether or not they
- // are in old_locations map is an implementation detail of roster_merge --
- // it may temporarily attach and then detach the nodes it creates, but this
- // is not deterministic or part of its interface. The main time this would
- // be a _problem_ is if we add interactive resolution of tree rearrangement
- // conflicts -- if someone resolves a rename conflict by saying that one
- // side wins, or by deleting one of the conflicting nodes, and this all
- // happens in memory, then it may trigger a spurious invariant failure here.
- // If anyone ever decides to add this kind of functionality, then it would
- // definitely make sense to move this checking into editable_tree. For now,
- // though, no such functionality is planned, so we'll see what happens.
- //
- // Update; now we are adding precisely these operations! workaround; allow
- // deleting a detached node with no entry in old_locations
- //
// The implementation itself uses the map old_locations. A node can be in
// the following states:
// -- attached, no entry in old_locations map
// -- detached, no entry in old_locations map
// -- create_dir_node, create_file_node put a node into this state
- // -- a node in this state can be attached, anywhere, but may not be
- // deleted. Update; can now be deleted.
+ // -- a node in this state can be attached, anywhere, or deleted.
// -- detached, an entry in old_locations map
// -- detach_node puts a node into this state
// -- a node in this state can be attached anywhere _except_ the
@@ -291,6 +291,10 @@ struct temp_node_id_source
struct temp_node_id_source
: public node_id_source
{
+ // Temp node ids are used for new nodes in rosters. They are converted to
+ // true node ids when the roster is actually written to the database; see
+ // union_new_nodes in roster.cc, ultimately called from
+ // make_roster_for_revision with true_node_id_source
temp_node_id_source();
virtual node_id next();
node_id curr;
============================================================
--- roster_merge.cc 992ee1d3932808bd0e059c4e8f984c4fcd39b586
+++ roster_merge.cc 178917d07e72bb9cff7c9616e0e25e0b89eccdfb
@@ -1440,47 +1440,46 @@ parse_resolve_conflicts_str(basic_io::pa
static void
parse_resolve_conflicts_str(basic_io::parser & pars, roster_merge_result & result)
{
+ char const * error_message = "can't specify a %s conflict resolution for more than one conflict";
+
while (pars.tok.in.lookahead != EOF)
{
if (pars.symp (syms::resolved_suture))
{
- pars.sym();
+ N(result.duplicate_name_conflicts.size() == 1,
+ F(error_message) % syms::resolved_suture);
- for (std::vector::iterator i = result.duplicate_name_conflicts.begin();
- i != result.duplicate_name_conflicts.end();
- ++i)
- {
- duplicate_name_conflict & conflict = *i;
+ duplicate_name_conflict & conflict = *result.duplicate_name_conflicts.begin();
- conflict.left_resolution.first = resolve_conflicts::suture;
- conflict.right_resolution.first = resolve_conflicts::suture;
- }
+ conflict.left_resolution.first = resolve_conflicts::suture;
+ conflict.right_resolution.first = resolve_conflicts::suture;
+ pars.sym();
+ conflict.left_resolution.second = file_path_internal (pars.token);
+ pars.str();
}
else if (pars.symp (syms::resolved_rename_left))
{
- pars.sym();
-
N(result.duplicate_name_conflicts.size() == 1,
- F("can't specify a rename conflict resolution for more than one conflict"));
+ F(error_message) % syms::resolved_rename_left);
duplicate_name_conflict & conflict = *result.duplicate_name_conflicts.begin();
conflict.left_resolution.first = resolve_conflicts::rename;
- pars.str();
+ pars.sym();
conflict.left_resolution.second = file_path_internal (pars.token);
+ pars.str();
}
else if (pars.symp (syms::resolved_rename_right))
{
- pars.sym();
-
N(result.duplicate_name_conflicts.size() == 1,
- F("can't specify a rename conflict resolution for more than one conflict"));
+ F(error_message) % syms::resolved_rename_right);
duplicate_name_conflict & conflict = *result.duplicate_name_conflicts.begin();
conflict.right_resolution.first = resolve_conflicts::rename;
- pars.str();
+ pars.sym();
conflict.right_resolution.second = file_path_internal (pars.token);
+ pars.str();
}
else
N(false, F("%s is not a supported conflict resolution") % pars.token);
@@ -1622,49 +1621,44 @@ roster_merge_result::resolve_duplicate_n
switch (conflict.left_resolution.first)
{
case resolve_conflicts::suture:
- I(conflict.right_resolution.first == resolve_conflicts::suture);
+ {
+ I(conflict.right_resolution.first == resolve_conflicts::suture);
- P(F("suturing %s, %s into %s") % left_name % right_name % conflict.left_resolution.second);
+ N(!is_dir_t(left_roster.get_node (left_nid)), F("can't suture directory : %s") % left_name);
- // Create a single new node, delete the two old ones. FIXME: need to
- // record the links between the nodes somewhere.
- {
+ P(F("suturing %s, %s into %s") % left_name % right_name % conflict.left_resolution.second);
+
+ // Create a single new node, delete the two old ones, set ancestors.
node_id new_nid;
file_path const new_file_name = conflict.left_resolution.second;
- if (is_dir_t(left_roster.get_node (left_nid)))
- new_nid = roster.create_dir_node (nis);
- else
- {
- file_t const left_node = downcast_to_file_t(left_roster.get_node (left_nid));
- file_t const right_node = downcast_to_file_t(right_roster.get_node (right_nid));
+ file_t const left_node = downcast_to_file_t(left_roster.get_node (left_nid));
+ file_t const right_node = downcast_to_file_t(right_roster.get_node (right_nid));
- N(path::file == get_path_status(new_file_name),
- F("%s does not exist or is a directory") % new_file_name);
+ N(path::file == get_path_status(new_file_name),
+ F("%s does not exist or is a directory") % new_file_name);
- file_id const & left_file_id = left_node->content;
- file_id const & right_file_id = right_node->content;
- file_id new_file_id;
- data new_raw_data;
- read_data (new_file_name, new_raw_data);
- file_data new_data (new_raw_data);
- file_data left_data, right_data;
+ file_id const & left_file_id = left_node->content;
+ file_id const & right_file_id = right_node->content;
+ file_id new_file_id;
+ data new_raw_data;
+ read_data (new_file_name, new_raw_data);
+ file_data new_data (new_raw_data);
+ file_data left_data, right_data;
- adaptor.get_version(left_file_id, left_data);
- adaptor.get_version(right_file_id, right_data);
- calculate_ident (new_data, new_file_id);
+ adaptor.get_version(left_file_id, left_data);
+ adaptor.get_version(right_file_id, right_data);
+ calculate_ident (new_data, new_file_id);
- new_nid = roster.create_file_node (new_file_id, nis);
+ new_nid = roster.create_file_node (new_file_id, nis, make_pair(left_nid, right_nid));
- adaptor.record_merge(left_file_id, right_file_id, new_file_id, left_data, right_data, new_data);
- }
+ adaptor.record_merge(left_file_id, right_file_id, new_file_id, left_data, right_data, new_data);
attach_node (lua, roster, new_nid, new_file_name);
roster.drop_detached_node(left_nid);
roster.drop_detached_node(right_nid);
-
}
break;
============================================================
--- tests/resolve_duplicate_name_conflict/__driver__.lua 8f477bd075f50532cdb3a519e136122443596aa5
+++ tests/resolve_duplicate_name_conflict/__driver__.lua c6652312c6765180630aff4ebacc7af45e6c8472
@@ -100,8 +100,5 @@ check("thermostat honeywell" == readfile
-- Verify file contents
check("thermostat westinghouse" == readfile("thermostat-westinghouse.c"))
check("thermostat honeywell" == readfile("thermostat-honeywell.c"))
-
--- This currently fails; the merge during update first adds then drops
--- checkout.sh. Need to change diediedie.
-check("checkout.sh merged" == readfile("checkout.sh"))
+check("checkout.sh merged\n" == readfile("checkout.sh"))
-- end of file