# # patch "roster.cc" # from [fb98f2a7eca88938ce25e5ef7b37d4a64341db2b] # to [cc0748a1ee320b4ff187a1b6d63b90faa94f8c0a] # # patch "roster.hh" # from [6fe9181b0195f97220b667ae96399037c5648156] # to [a8c66ef58534ed14f2d900010f75be41478183af] # # patch "roster_merge.cc" # from [c79d62667810a48d31eb7c380810621c90731bf6] # to [6d7a055092e8af5011d03dcadce47ca59297169a] # # patch "roster_merge.hh" # from [eff5ab8ced6feb7f943b86b425858e4877053ecb] # to [ed68adf1d012e2a3689168d7367bfd5bd3b7be20] # ======================================================================== --- roster.cc fb98f2a7eca88938ce25e5ef7b37d4a64341db2b +++ roster.cc cc0748a1ee320b4ff187a1b6d63b90faa94f8c0a @@ -167,6 +167,12 @@ } +bool +dir_node::has_child(path_component const & pc) const +{ + return children.find(pc) != children.end(); +} + node_t dir_node::get_child(path_component const & pc) const { ======================================================================== --- roster.hh 6fe9181b0195f97220b667ae96399037c5648156 +++ roster.hh a8c66ef58534ed14f2d900010f75be41478183af @@ -77,6 +77,7 @@ dir_node(); dir_node(node_id i); dir_map children; + bool has_child(path_component const & pc) const; node_t get_child(path_component const & pc) const; void attach_child(path_component const & pc, node_t child); node_t detach_child(path_component const & pc); ======================================================================== --- roster_merge.cc c79d62667810a48d31eb7c380810621c90731bf6 +++ roster_merge.cc 6d7a055092e8af5011d03dcadce47ca59297169a @@ -117,16 +117,88 @@ create_node_for(n, new_roster); } + bool + would_make_dir_loop(roster_t const & r, node_id nid, node_id parent) + { + // parent may not be fully attached yet; that's okay. that just means + // we'll run into a node with a null parent somewhere before we hit the + // actual root; whether we hit the actual root or not, hitting a node + // with a null parent will tell us that this particular attachment won't + // create a loop. + for (node_id curr = parent; curr = r.get_node(curr)->parent; !null_node(curr)) + { + if (curr == nid) + return true; + } + return false; + } + void - copy_node_forward(node_t const & n, roster_t & new_roster, + assign_name(roster_merge_result & result, node_id nid, + node_id parent, path_component name) + { + // this function is reponsible for detecting structural conflicts. by the + // time we've gotten here, we have a node that's unambiguously decided on + // a name; but it might be that that name does not exist (because the + // parent dir is gone), or that it's already taken (by another node), or + // that putting this node there would create a directory loop. In all + // such cases, rather than actually attach the node, we write a conflict + // structure and leave it detached. + + // orphan: + if (!result.roster.has_node(parent)) + { + orphaned_node_conflict c; + c.nid = nid; + c.parent_name = std::make_pair(parent, name); + result.orphaned_node_conflicts.push_back(c); + return; + } + + dir_t p = downcast_to_dir_t(result.roster.get_node(parent)); + + // name conflict: + // see the comment in roster_merge.hh for the analysis showing that at + // most two nodes can participate in a rename target conflict. this code + // exploits that; after this code runs, there will be no node at the given + // location in the tree, which means that in principle, if there were a + // third node that _also_ wanted to go here, when we got around to + // attaching it we'd have no way to realize it should be a conflict. but + // that never happens, so we don't have to keep a lookaside set of + // "poisoned locations" or anything. + if (p->has_child(name)) + { + rename_target_conflict c; + c.nid1 = nid; + c.nid2 = p->get_child(name)->self; + c.parent_name = std::make_pair(parent, name); + p->detach_child(name); + result.rename_target_conflicts.push_back(c); + return; + } + + if (would_make_dir_loop(result.roster, nid, parent)) + { + directory_loop_conflict c; + c.nid = nid; + c.parent_name = std::make_pair(parent, name); + result.directory_loop_conflicts.push_back(c); + return; + } + + // hey, we actually made it. attach the node! + result.roster.attach_node(nid, parent, name); + } + + void + copy_node_forward(roster_merge_result & result, node_t const & n, node_t const & old_n) { I(n->self == old_n->self); n->attrs = old_n->attrs; if (is_file_t(n)) downcast_to_file_t(n)->content = downcast_to_file_t(old_n)->content; - // FIXME: this could hit a conflict! (orphan or rename-target) - new_roster.attach_node(n->self, old_n->parent, old_n->name); + assign_name(result, n->self, old_n->parent, old_n->name); } } // end anonymous namespace @@ -195,12 +267,12 @@ I(false); case parallel::in_left: - copy_node_forward(new_i->second, result.roster, i.left_data()); + copy_node_forward(result, new_i->second, i.left_data()); ++left_mi; break; case parallel::in_right: - copy_node_forward(new_i->second, result.roster, i.right_data()); + copy_node_forward(result, new_i->second, i.right_data()); ++right_mi; break; @@ -226,9 +298,8 @@ right_uncommon_ancestors, new_name, conflict)) { - // FIXME: this could hit a conflict! (orphan or rename-target) - result.roster.attach_node(new_n->self, - new_name.first, new_name.second); + assign_name(result, new_n->self, + new_name.first, new_name.second); } else { ======================================================================== --- roster_merge.hh eff5ab8ced6feb7f943b86b425858e4877053ecb +++ roster_merge.hh ed68adf1d012e2a3689168d7367bfd5bd3b7be20 @@ -58,18 +58,12 @@ // manifest.) // -// structural conflicts: -// -- orphans -// -- directory containment loops -// -- multiple nodes with the same name - // orphaned nodes always merged their name cleanly, so we simply put that name // here. the node in the resulting roster is detached. struct orphaned_node_conflict { node_id nid; - node_id dead_parent; - path_component name; + std::pair parent_name; }; // this is when two distinct nodes want to have the same name. these nodes @@ -89,11 +83,13 @@ struct rename_target_conflict { node_id nid1, nid2; - std::pair name; + std::pair parent_name; }; struct directory_loop_conflict { + node_id nid; + std::pair parent_name; }; // renaming the root dir (as we currently do _not_) allows: