#
#
# add_file "merge_3way.cc"
# content [ba4b9a47da2894bacbf6b624251d259a11483808]
#
# add_file "merge_conflict.cc"
# content [f00c522c0099d0141849d64a7311d546e61c7008]
#
# patch "Makefile.am"
# from [f9f47a97e59ee658ae1830700e403bd981c8a156]
# to [b2891ae6208641d325a61977637aa21c80dd6842]
#
# patch "merge_roster.cc"
# from [eb800b3f9442d5f00790e1f92d1b560b23714a2c]
# to [2d60c4d0fee6b0b962cab30aeed3838181c8ed08]
#
# patch "po/POTFILES.in"
# from [639dab3c215bfa742d5c757537da5069a3a83744]
# to [80d52ad34536a52ac3fd5e5da3978439e8bbcb2f]
#
============================================================
--- merge_3way.cc ba4b9a47da2894bacbf6b624251d259a11483808
+++ merge_3way.cc ba4b9a47da2894bacbf6b624251d259a11483808
@@ -0,0 +1,646 @@
+// Copyright (C) 2008 Stephen Leake
+// Copyright (C) 2002 Graydon Hoare
+//
+// This program is made available under the GNU GPL version 2.0 or
+// greater. See the accompanying file COPYING for details.
+//
+// This program is distributed WITHOUT ANY WARRANTY; without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE.
+
+#include "base.hh"
+#include "merge_content.hh"
+
+#include "interner.hh"
+#include "lcs.hh"
+#include "paths.hh"
+#include "vector.hh"
+
+using std::min;
+using std::vector;
+using std::string;
+using std::swap;
+
+//
+// a 3-way merge works like this:
+//
+// /----> right
+// ancestor
+// \----> left
+//
+// first you compute the edit list "EDITS(ancestor,left)".
+//
+// then you make an offset table "leftpos" which describes positions in
+// "ancestor" as they map to "left"; that is, for 0 < apos <
+// ancestor.size(), we have
+//
+// left[leftpos[apos]] == ancestor[apos]
+//
+// you do this by walking through the edit list and either jumping the
+// current index ahead an extra position, on an insert, or remaining still,
+// on a delete. on an insert *or* a delete, you push the current index back
+// onto the leftpos array.
+//
+// next you compute the edit list "EDITS(ancestor,right)".
+//
+// you then go through this edit list applying the edits to left, rather
+// than ancestor, and using the table leftpos to map the position of each
+// edit to an appropriate spot in left. this means you walk a "curr_left"
+// index through the edits, and for each edit e:
+//
+// - if e is a delete (and e.pos is a position in ancestor)
+// - increment curr_left without copying anything to "merged"
+//
+// - if e is an insert (and e.pos is a position in right)
+// - copy right[e.pos] to "merged"
+// - leave curr_left alone
+//
+// - when advancing to apos (and apos is a position in ancestor)
+// - copy left[curr_left] to merged while curr_left < leftpos[apos]
+//
+//
+// the practical upshot is that you apply the delta from ancestor->right
+// to the adjusted contexts in left, producing something vaguely like
+// the concatenation of delta(ancestor,left) :: delta(ancestor,right).
+//
+// NB: this is, as far as I can tell, what diff3 does. I don't think I'm
+// infringing on anyone's fancy patents here.
+//
+
+struct conflict {};
+
+typedef enum { preserved = 0, deleted = 1, changed = 2 } edit_t;
+static const char etab[3][10] =
+ {
+ "preserved",
+ "deleted",
+ "changed"
+ };
+
+struct extent
+{
+ extent(size_t p, size_t l, edit_t t)
+ : pos(p), len(l), type(t)
+ {}
+ size_t pos;
+ size_t len;
+ edit_t type;
+};
+
+void calculate_extents(vector const & a_b_edits,
+ vector const & b,
+ vector & prefix,
+ vector & extents,
+ vector & suffix,
+ size_t const a_len,
+ interner & intern)
+{
+ extents.reserve(a_len * 2);
+
+ size_t a_pos = 0, b_pos = 0;
+
+ for (vector::const_iterator i = a_b_edits.begin();
+ i != a_b_edits.end(); ++i)
+ {
+ // L(FL("edit: %d") % *i);
+ if (*i < 0)
+ {
+ // negative elements code the negation of the one-based index into A
+ // of the element to be deleted
+ size_t a_deleted = (-1 - *i);
+
+ // fill positions out to the deletion point
+ while (a_pos < a_deleted)
+ {
+ a_pos++;
+ extents.push_back(extent(b_pos++, 1, preserved));
+ }
+
+ // L(FL(" -- delete at A-pos %d (B-pos = %d)") % a_deleted % b_pos);
+
+ // skip the deleted line
+ a_pos++;
+ extents.push_back(extent(b_pos, 0, deleted));
+ }
+ else
+ {
+ // positive elements code the one-based index into B of the element to
+ // be inserted
+ size_t b_inserted = (*i - 1);
+
+ // fill positions out to the insertion point
+ while (b_pos < b_inserted)
+ {
+ a_pos++;
+ extents.push_back(extent(b_pos++, 1, preserved));
+ }
+
+ // L(FL(" -- insert at B-pos %d (A-pos = %d) : '%s'")
+ // % b_inserted % a_pos % intern.lookup(b.at(b_inserted)));
+
+ // record that there was an insertion, but a_pos did not move.
+ if ((b_pos == 0 && extents.empty())
+ || (b_pos == prefix.size()))
+ {
+ prefix.push_back(b.at(b_pos));
+ }
+ else if (a_len == a_pos)
+ {
+ suffix.push_back(b.at(b_pos));
+ }
+ else
+ {
+ // make the insertion
+ extents.back().type = changed;
+ extents.back().len++;
+ }
+ b_pos++;
+ }
+ }
+
+ while (extents.size() < a_len)
+ extents.push_back(extent(b_pos++, 1, preserved));
+}
+
+void normalize_extents(vector & a_b_map,
+ vector const & a,
+ vector const & b)
+{
+ for (size_t i = 0; i < a_b_map.size(); ++i)
+ {
+ if (i > 0)
+ {
+ size_t j = i;
+ while (j > 0
+ && (a_b_map.at(j-1).type == preserved)
+ && (a_b_map.at(j).type == changed)
+ && (a.at(j) == b.at(a_b_map.at(j).pos + a_b_map.at(j).len - 1)))
+ {
+ // This is implied by (a_b_map.at(j-1).type == preserved)
+ I(a.at(j-1) == b.at(a_b_map.at(j-1).pos));
+
+ // Coming into loop we have:
+ // i
+ // z --pres--> z 0
+ // o --pres--> o 1
+ // a --chng--> a 2 The important thing here is that 'a' in
+ // t the LHS matches with ...
+ // u
+ // v
+ // a ... the a on the RHS here. Hence we can
+ // q --pres--> q 3 'shift' the entire 'changed' block
+ // e --chng--> d 4 upwards, leaving a 'preserved' line
+ // g --pres--> g 5 'a'->'a'
+ //
+ // Want to end up with:
+ // i
+ // z --pres--> z 0
+ // o --chng--> o 1
+ // a
+ // t
+ // u
+ // v
+ // a --pres--> a 2
+ // q --pres--> q 3
+ // e --chng--> d 4
+ // g --pres--> g 5
+ //
+ // Now all the 'changed' extents are normalised to the
+ // earliest possible position.
+
+ L(FL("exchanging preserved extent [%d+%d] with changed extent [%d+%d]")
+ % a_b_map.at(j-1).pos
+ % a_b_map.at(j-1).len
+ % a_b_map.at(j).pos
+ % a_b_map.at(j).len);
+
+ swap(a_b_map.at(j-1).len, a_b_map.at(j).len);
+ swap(a_b_map.at(j-1).type, a_b_map.at(j).type);
+
+ // Adjust position of the later, preserved extent. It should
+ // better point to the second 'a' in the above example.
+ a_b_map.at(j).pos = a_b_map.at(j-1).pos + a_b_map.at(j-1).len;
+
+ --j;
+ }
+ }
+ }
+
+ for (size_t i = 0; i < a_b_map.size(); ++i)
+ {
+ if (i > 0)
+ {
+ size_t j = i;
+ while (j > 0
+ && a_b_map.at(j).type == changed
+ && a_b_map.at(j-1).type == changed
+ && a_b_map.at(j).len > 1
+ && a_b_map.at(j-1).pos + a_b_map.at(j-1).len == a_b_map.at(j).pos)
+ {
+ // step 1: move a chunk from this insert extent to its
+ // predecessor
+ size_t piece = a_b_map.at(j).len - 1;
+ // L(FL("moving change piece of len %d from pos %d to pos %d")
+ // % piece
+ // % a_b_map.at(j).pos
+ // % a_b_map.at(j-1).pos);
+ a_b_map.at(j).len = 1;
+ a_b_map.at(j).pos += piece;
+ a_b_map.at(j-1).len += piece;
+
+ // step 2: if this extent (now of length 1) has become a "changed"
+ // extent identical to its previous state, switch it to a "preserved"
+ // extent.
+ if (b.at(a_b_map.at(j).pos) == a.at(j))
+ {
+ // L(FL("changing normalized 'changed' extent at %d to 'preserved'")
+ // % a_b_map.at(j).pos);
+ a_b_map.at(j).type = preserved;
+ }
+ --j;
+ }
+ }
+ }
+}
+
+
+void merge_extents(vector const & a_b_map,
+ vector const & a_c_map,
+ vector const & b,
+ vector const & c,
+ interner const & in,
+ vector & merged)
+{
+ I(a_b_map.size() == a_c_map.size());
+
+ vector::const_iterator i = a_b_map.begin();
+ vector::const_iterator j = a_c_map.begin();
+ merged.reserve(a_b_map.size() * 2);
+
+ // for (; i != a_b_map.end(); ++i, ++j)
+ // {
+
+ // L(FL("trying to merge: [%s %d %d] vs. [%s %d %d]")
+ // % etab[i->type] % i->pos % i->len
+ // % etab[j->type] % j->pos % j->len);
+ // }
+
+ // i = a_b_map.begin();
+ // j = a_c_map.begin();
+
+ for (; i != a_b_map.end(); ++i, ++j)
+ {
+
+ // L(FL("trying to merge: [%s %d %d] vs. [%s %d %d]")
+ // % etab[i->type] % i->pos % i->len
+ // % etab[j->type] % j->pos % j->len);
+
+ // mutual, identical preserves / inserts / changes
+ if (((i->type == changed && j->type == changed)
+ || (i->type == preserved && j->type == preserved))
+ && i->len == j->len)
+ {
+ for (size_t k = 0; k < i->len; ++k)
+ {
+ if (b.at(i->pos + k) != c.at(j->pos + k))
+ {
+ L(FL("conflicting edits: %s %d[%d] '%s' vs. %s %d[%d] '%s'")
+ % etab[i->type] % i->pos % k % in.lookup(b.at(i->pos + k))
+ % etab[j->type] % j->pos % k % in.lookup(c.at(j->pos + k)));
+ throw conflict();
+ }
+ merged.push_back(b.at(i->pos + k));
+ }
+ }
+
+ // mutual or single-edge deletes
+ else if ((i->type == deleted && j->type == deleted)
+ || (i->type == deleted && j->type == preserved)
+ || (i->type == preserved && j->type == deleted))
+ {
+ // do nothing
+ }
+
+ // single-edge insert / changes
+ else if (i->type == changed && j->type == preserved)
+ for (size_t k = 0; k < i->len; ++k)
+ merged.push_back(b.at(i->pos + k));
+
+ else if (i->type == preserved && j->type == changed)
+ for (size_t k = 0; k < j->len; ++k)
+ merged.push_back(c.at(j->pos + k));
+
+ else
+ {
+ L(FL("conflicting edits: [%s %d %d] vs. [%s %d %d]")
+ % etab[i->type] % i->pos % i->len
+ % etab[j->type] % j->pos % j->len);
+ throw conflict();
+ }
+
+ // if (merged.empty())
+ // L(FL(" --> EMPTY"));
+ // else
+ // L(FL(" --> [%d]: %s") % (merged.size() - 1) % in.lookup(merged.back()));
+ }
+}
+
+
+void merge_via_edit_scripts(vector const & ancestor,
+ vector const & left,
+ vector const & right,
+ vector & merged)
+{
+ vector anc_interned;
+ vector left_interned, right_interned;
+ vector left_edits, right_edits;
+ vector left_prefix, right_prefix;
+ vector left_suffix, right_suffix;
+ vector left_extents, right_extents;
+ vector merged_interned;
+ interner in;
+
+ // for (int i = 0; i < min(min(left.size(), right.size()), ancestor.size()); ++i)
+ // {
+ // cerr << '[' << i << "] " << left[i] << ' ' << ancestor[i] << ' ' << right[i] << '\n';
+ // }
+
+ anc_interned.reserve(ancestor.size());
+ for (vector::const_iterator i = ancestor.begin();
+ i != ancestor.end(); ++i)
+ anc_interned.push_back(in.intern(*i));
+
+ left_interned.reserve(left.size());
+ for (vector::const_iterator i = left.begin();
+ i != left.end(); ++i)
+ left_interned.push_back(in.intern(*i));
+
+ right_interned.reserve(right.size());
+ for (vector::const_iterator i = right.begin();
+ i != right.end(); ++i)
+ right_interned.push_back(in.intern(*i));
+
+ L(FL("calculating left edit script on %d -> %d lines")
+ % anc_interned.size() % left_interned.size());
+
+ edit_script(anc_interned.begin(), anc_interned.end(),
+ left_interned.begin(), left_interned.end(),
+ min(ancestor.size(), left.size()),
+ left_edits);
+
+ L(FL("calculating right edit script on %d -> %d lines")
+ % anc_interned.size() % right_interned.size());
+
+ edit_script(anc_interned.begin(), anc_interned.end(),
+ right_interned.begin(), right_interned.end(),
+ min(ancestor.size(), right.size()),
+ right_edits);
+
+ L(FL("calculating left extents on %d edits") % left_edits.size());
+ calculate_extents(left_edits, left_interned,
+ left_prefix, left_extents, left_suffix,
+ anc_interned.size(), in);
+
+ L(FL("calculating right extents on %d edits") % right_edits.size());
+ calculate_extents(right_edits, right_interned,
+ right_prefix, right_extents, right_suffix,
+ anc_interned.size(), in);
+
+ L(FL("normalizing %d right extents") % right_extents.size());
+ normalize_extents(right_extents, anc_interned, right_interned);
+
+ L(FL("normalizing %d left extents") % left_extents.size());
+ normalize_extents(left_extents, anc_interned, left_interned);
+
+
+ if ((!right_prefix.empty()) && (!left_prefix.empty()))
+ {
+ L(FL("conflicting prefixes"));
+ throw conflict();
+ }
+
+ if ((!right_suffix.empty()) && (!left_suffix.empty()))
+ {
+ L(FL("conflicting suffixes"));
+ throw conflict();
+ }
+
+ L(FL("merging %d left, %d right extents")
+ % left_extents.size() % right_extents.size());
+
+ copy(left_prefix.begin(), left_prefix.end(), back_inserter(merged_interned));
+ copy(right_prefix.begin(), right_prefix.end(), back_inserter(merged_interned));
+
+ merge_extents(left_extents, right_extents,
+ left_interned, right_interned,
+ in, merged_interned);
+
+ copy(left_suffix.begin(), left_suffix.end(), back_inserter(merged_interned));
+ copy(right_suffix.begin(), right_suffix.end(), back_inserter(merged_interned));
+
+ merged.reserve(merged_interned.size());
+ for (vector::const_iterator i = merged_interned.begin();
+ i != merged_interned.end(); ++i)
+ merged.push_back(in.lookup(*i));
+}
+
+
+bool merge3(vector const & ancestor,
+ vector const & left,
+ vector const & right,
+ vector & merged)
+{
+ try
+ {
+ merge_via_edit_scripts(ancestor, left, right, merged);
+ }
+ catch(conflict &)
+ {
+ L(FL("conflict detected. no merge."));
+ return false;
+ }
+ return true;
+}
+
+#ifdef BUILD_UNIT_TESTS
+#include "unit_tests.hh"
+#include "lexical_cast.hh"
+#include "randomfile.hh"
+#include "simplestring_xform.hh"
+
+using std::cerr;
+using std::cout;
+using std::stringstream;
+
+using boost::lexical_cast;
+
+static void dump_incorrect_merge(vector const & expected,
+ vector const & got,
+ string const & prefix)
+{
+ size_t mx = expected.size();
+ if (mx < got.size())
+ mx = got.size();
+ for (size_t i = 0; i < mx; ++i)
+ {
+ cerr << "bad merge: " << i << " [" << prefix << "]\t";
+
+ if (i < expected.size())
+ cerr << '[' << expected[i] << "]\t";
+ else
+ cerr << "[--nil--]\t";
+
+ if (i < got.size())
+ cerr << '[' << got[i] << "]\t";
+ else
+ cerr << "[--nil--]\t";
+
+ cerr << '\n';
+ }
+}
+
+// high tech randomizing test
+UNIT_TEST(diff_patch, randomizing_merge)
+{
+ randomizer rng;
+ for (int i = 0; i < 30; ++i)
+ {
+ vector anc, d1, d2, m1, m2, gm;
+
+ file_randomizer::build_random_fork(anc, d1, d2, gm, (10 + 2 * i), rng);
+
+ UNIT_TEST_CHECK(merge3(anc, d1, d2, m1));
+ if (gm != m1)
+ dump_incorrect_merge (gm, m1, "random_merge 1");
+ UNIT_TEST_CHECK(gm == m1);
+
+ UNIT_TEST_CHECK(merge3(anc, d2, d1, m2));
+ if (gm != m2)
+ dump_incorrect_merge (gm, m2, "random_merge 2");
+ UNIT_TEST_CHECK(gm == m2);
+ }
+}
+
+
+// old boring tests
+UNIT_TEST(diff_patch, merge_prepend)
+{
+ UNIT_TEST_CHECKPOINT("prepend test");
+ vector anc, d1, d2, m1, m2, gm;
+ for (int i = 10; i < 20; ++i)
+ {
+ d2.push_back(lexical_cast(i));
+ gm.push_back(lexical_cast(i));
+ }
+
+ for (int i = 0; i < 10; ++i)
+ {
+ anc.push_back(lexical_cast(i));
+ d1.push_back(lexical_cast(i));
+ d2.push_back(lexical_cast(i));
+ gm.push_back(lexical_cast(i));
+ }
+
+ UNIT_TEST_CHECK(merge3(anc, d1, d2, m1));
+ if (gm != m1)
+ dump_incorrect_merge (gm, m1, "merge_prepend 1");
+ UNIT_TEST_CHECK(gm == m1);
+
+
+ UNIT_TEST_CHECK(merge3(anc, d2, d1, m2));
+ if (gm != m2)
+ dump_incorrect_merge (gm, m2, "merge_prepend 2");
+ UNIT_TEST_CHECK(gm == m2);
+}
+
+UNIT_TEST(diff_patch, merge_append)
+{
+ UNIT_TEST_CHECKPOINT("append test");
+ vector anc, d1, d2, m1, m2, gm;
+ for (int i = 0; i < 10; ++i)
+ anc.push_back(lexical_cast(i));
+
+ d1 = anc;
+ d2 = anc;
+ gm = anc;
+
+ for (int i = 10; i < 20; ++i)
+ {
+ d2.push_back(lexical_cast(i));
+ gm.push_back(lexical_cast(i));
+ }
+
+ UNIT_TEST_CHECK(merge3(anc, d1, d2, m1));
+ if (gm != m1)
+ dump_incorrect_merge (gm, m1, "merge_append 1");
+ UNIT_TEST_CHECK(gm == m1);
+
+ UNIT_TEST_CHECK(merge3(anc, d2, d1, m2));
+ if (gm != m2)
+ dump_incorrect_merge (gm, m2, "merge_append 2");
+ UNIT_TEST_CHECK(gm == m2);
+
+
+}
+
+UNIT_TEST(diff_patch, merge_additions)
+{
+ UNIT_TEST_CHECKPOINT("additions test");
+ string ancestor("I like oatmeal\nI like orange juice\nI like toast");
+ string desc1("I like oatmeal\nI don't like spam\nI like orange juice\nI like toast");
+ string confl("I like oatmeal\nI don't like tuna\nI like orange juice\nI like toast");
+ string desc2("I like oatmeal\nI like orange juice\nI don't like tuna\nI like toast");
+ string good_merge("I like oatmeal\nI don't like spam\nI like orange juice\nI don't like tuna\nI like toast");
+ vector anc, d1, cf, d2, m1, m2, gm;
+
+ split_into_lines(ancestor, anc);
+ split_into_lines(desc1, d1);
+ split_into_lines(confl, cf);
+ split_into_lines(desc2, d2);
+ split_into_lines(good_merge, gm);
+
+ UNIT_TEST_CHECK(merge3(anc, d1, d2, m1));
+ if (gm != m1)
+ dump_incorrect_merge (gm, m1, "merge_addition 1");
+ UNIT_TEST_CHECK(gm == m1);
+
+ UNIT_TEST_CHECK(merge3(anc, d2, d1, m2));
+ if (gm != m2)
+ dump_incorrect_merge (gm, m2, "merge_addition 2");
+ UNIT_TEST_CHECK(gm == m2);
+
+ UNIT_TEST_CHECK(!merge3(anc, d1, cf, m1));
+}
+
+UNIT_TEST(diff_patch, merge_deletions)
+{
+ string ancestor("I like oatmeal\nI like orange juice\nI like toast");
+ string desc2("I like oatmeal\nI like toast");
+
+ vector anc, d1, d2, m1, m2, gm;
+
+ split_into_lines(ancestor, anc);
+ split_into_lines(desc2, d2);
+ d1 = anc;
+ gm = d2;
+
+ UNIT_TEST_CHECK(merge3(anc, d1, d2, m1));
+ if (gm != m1)
+ dump_incorrect_merge (gm, m1, "merge_deletion 1");
+ UNIT_TEST_CHECK(gm == m1);
+
+ UNIT_TEST_CHECK(merge3(anc, d2, d1, m2));
+ if (gm != m2)
+ dump_incorrect_merge (gm, m2, "merge_deletion 2");
+ UNIT_TEST_CHECK(gm == m2);
+}
+
+#endif // BUILD_UNIT_TESTS
+
+// Local Variables:
+// mode: C++
+// fill-column: 76
+// c-file-style: "gnu"
+// indent-tabs-mode: nil
+// End:
+// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:
============================================================
--- merge_conflict.cc f00c522c0099d0141849d64a7311d546e61c7008
+++ merge_conflict.cc f00c522c0099d0141849d64a7311d546e61c7008
@@ -0,0 +1,2411 @@
+// Copyright (C) 2008 Stephen Leake
+// Copyright (C) 2005 Nathaniel Smith
+//
+// This program is made available under the GNU GPL version 2.0 or
+// greater. See the accompanying file COPYING for details.
+//
+// This program is distributed WITHOUT ANY WARRANTY; without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE.
+
+#include "base.hh"
+#include "merge_roster.hh"
+
+#include "basic_io.hh"
+#include "file_io.hh"
+#include "lua_hooks.hh"
+#include "options.hh"
+#include "transforms.hh"
+
+#include
+
+using std::make_pair;
+using std::string;
+using boost::shared_ptr;
+
+namespace
+{
+ enum node_type { file_type, dir_type };
+
+ node_type
+ get_type(roster_t const & roster, node_id const nid)
+ {
+ node_t n = roster.get_node(nid);
+
+ if (is_file_t(n))
+ return file_type;
+ else if (is_dir_t(n))
+ return dir_type;
+ else
+ I(false);
+ }
+
+ namespace syms
+ {
+ symbol const ancestor("ancestor");
+ symbol const ancestor_file_id("ancestor_file_id");
+ symbol const ancestor_name("ancestor_name");
+ symbol const attr_name("attr_name");
+ symbol const attribute("attribute");
+ symbol const conflict("conflict");
+ symbol const content("content");
+ symbol const directory_loop("directory_loop");
+ symbol const duplicate_name("duplicate_name");
+ symbol const invalid_name("invalid_name");
+ symbol const left("left");
+ symbol const left_attr_state("left_attr_state");
+ symbol const left_attr_value("left_attr_value");
+ symbol const left_file_id("left_file_id");
+ symbol const left_name("left_name");
+ symbol const left_type("left_type");
+ symbol const missing_root("missing_root");
+ symbol const multiple_names("multiple_names");
+ symbol const node_type("node_type");
+ symbol const orphaned_directory("orphaned_directory");
+ symbol const orphaned_file("orphaned_file");
+ symbol const resolved_drop_left("resolved_drop_left");
+ symbol const resolved_drop_right("resolved_drop_right");
+ symbol const resolved_internal("resolved_internal");
+ symbol const resolved_rename_left("resolved_rename_left");
+ symbol const resolved_rename_right("resolved_rename_right");
+ symbol const resolved_user("resolved_user");
+ symbol const resolved_user_left("resolved_user_left");
+ symbol const resolved_user_right("resolved_user_right");
+ symbol const right("right");
+ symbol const right_attr_state("right_attr_state");
+ symbol const right_attr_value("right_attr_value");
+ symbol const right_file_id("right_file_id");
+ symbol const right_name("right_name");
+ symbol const right_type("right_type");
+ }
+}
+
+namespace resolve_conflicts
+{
+ shared_ptr
+ new_file_path(string path)
+ {
+ return shared_ptr
+ (new file_path(file_path_external(utf8(path, origin::user))));
+ };
+}
+
+
+static void
+put_added_conflict_left(basic_io::stanza & st,
+ content_merge_adaptor & adaptor,
+ node_id const nid)
+{
+ // We access the roster via the adaptor, to be sure we use the left
+ // roster; avoids typos in long parameter lists.
+
+ // If we get a workspace adaptor here someday, we should add the required
+ // access functions to content_merge_adaptor.
+
+ content_merge_database_adaptor & db_adaptor (dynamic_cast(adaptor));
+ boost::shared_ptr roster(db_adaptor.rosters[db_adaptor.left_rid]);
+ file_path name;
+
+ roster->get_name (nid, name);
+
+ if (file_type == get_type (*roster, nid))
+ {
+ file_id fid;
+ db_adaptor.db.get_file_content (db_adaptor.left_rid, nid, fid);
+ st.push_str_pair(syms::left_type, "added file");
+ st.push_file_pair(syms::left_name, name);
+ st.push_binary_pair(syms::left_file_id, fid.inner());
+ }
+ else
+ {
+ st.push_str_pair(syms::left_type, "added directory");
+ st.push_file_pair(syms::left_name, name);
+ }
+}
+
+static void
+put_added_conflict_right(basic_io::stanza & st,
+ content_merge_adaptor & adaptor,
+ node_id const nid)
+{
+ content_merge_database_adaptor & db_adaptor (dynamic_cast(adaptor));
+ boost::shared_ptr roster(db_adaptor.rosters[db_adaptor.right_rid]);
+ I(0 != roster);
+
+ file_path name;
+
+ roster->get_name (nid, name);
+
+ if (file_type == get_type (*roster, nid))
+ {
+ file_id fid;
+ db_adaptor.db.get_file_content (db_adaptor.right_rid, nid, fid);
+
+ st.push_str_pair(syms::right_type, "added file");
+ st.push_file_pair(syms::right_name, name);
+ st.push_binary_pair(syms::right_file_id, fid.inner());
+ }
+ else
+ {
+ st.push_str_pair(syms::right_type, "added directory");
+ st.push_file_pair(syms::right_name, name);
+ }
+}
+
+static void
+put_rename_conflict_left(basic_io::stanza & st,
+ content_merge_adaptor & adaptor,
+ node_id const nid)
+{
+ content_merge_database_adaptor & db_adaptor (dynamic_cast(adaptor));
+ boost::shared_ptr ancestor_roster(db_adaptor.rosters[db_adaptor.lca]);
+ I(0 != ancestor_roster);
+ boost::shared_ptr left_roster(db_adaptor.rosters[db_adaptor.left_rid]);
+
+ file_path ancestor_name;
+ file_path left_name;
+
+ ancestor_roster->get_name (nid, ancestor_name);
+ left_roster->get_name (nid, left_name);
+
+ if (file_type == get_type (*left_roster, nid))
+ {
+ st.push_str_pair(syms::left_type, "renamed file");
+ file_id ancestor_fid;
+ db_adaptor.db.get_file_content (db_adaptor.lca, nid, ancestor_fid);
+ st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
+ st.push_binary_pair(syms::ancestor_file_id, ancestor_fid.inner());
+ file_id left_fid;
+ db_adaptor.db.get_file_content (db_adaptor.left_rid, nid, left_fid);
+ st.push_file_pair(syms::left_name, left_name);
+ st.push_binary_pair(syms::left_file_id, left_fid.inner());
+ }
+ else
+ {
+ st.push_str_pair(syms::left_type, "renamed directory");
+ st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
+ st.push_file_pair(syms::left_name, left_name);
+ }
+}
+
+static void
+get_nid_name_pair(roster_t const & roster,
+ string const & path,
+ node_id & nid,
+ std::pair & name)
+{
+ node_t const node = roster.get_node(file_path_external(utf8(path, origin::internal)));
+ nid = node->self;
+ name = make_pair (node->parent, node->name);
+}
+
+static void
+read_added_rename_conflict_left(basic_io::parser & pars,
+ roster_t const & roster,
+ node_id & left_nid,
+ std::pair & left_name)
+{
+ string tmp;
+
+ pars.esym(syms::left_type);
+
+ pars.str(tmp);
+
+ if (tmp == "renamed file")
+ {
+ pars.esym(syms::ancestor_name); pars.str();
+ pars.esym(syms::ancestor_file_id); pars.hex();
+
+ pars.esym(syms::left_name); pars.str(tmp);
+ get_nid_name_pair(roster, tmp, left_nid, left_name);
+ pars.esym(syms::left_file_id); pars.hex();
+ }
+ else if (tmp == "renamed directory")
+ {
+ pars.esym(syms::ancestor_name); pars.str();
+ pars.esym(syms::left_name); pars.str(tmp);
+ get_nid_name_pair(roster, tmp, left_nid, left_name);
+ }
+ else if (tmp == "added file")
+ {
+ pars.esym(syms::left_name); pars.str(tmp);
+ get_nid_name_pair(roster, tmp, left_nid, left_name);
+ pars.esym(syms::left_file_id); pars.hex();
+ }
+ else if (tmp == "added directory")
+ {
+ pars.esym(syms::left_name); pars.str(tmp);
+ get_nid_name_pair(roster, tmp, left_nid, left_name);
+ }
+} // read_added_rename_conflict_left
+
+static void
+read_added_rename_conflict_right(basic_io::parser & pars,
+ roster_t const & roster,
+ node_id & right_nid,
+ std::pair & right_name)
+{
+ string tmp;
+
+ pars.esym(syms::right_type);
+
+ pars.str(tmp);
+
+ if (tmp == "renamed file")
+ {
+ pars.esym(syms::ancestor_name); pars.str();
+ pars.esym(syms::ancestor_file_id); pars.hex();
+
+ pars.esym(syms::right_name); pars.str(tmp);
+ get_nid_name_pair(roster, tmp, right_nid, right_name);
+ pars.esym(syms::right_file_id); pars.hex();
+ }
+ else if (tmp == "renamed directory")
+ {
+ pars.esym(syms::ancestor_name); pars.str();
+ pars.esym(syms::right_name); pars.str(tmp);
+ get_nid_name_pair(roster, tmp, right_nid, right_name);
+ }
+ else if (tmp == "added file")
+ {
+ pars.esym(syms::right_name); pars.str(tmp);
+ get_nid_name_pair(roster, tmp, right_nid, right_name);
+ pars.esym(syms::right_file_id); pars.hex();
+ }
+ else if (tmp == "added directory")
+ {
+ pars.esym(syms::right_name); pars.str(tmp);
+ get_nid_name_pair(roster, tmp, right_nid, right_name);
+ }
+}
+
+static void
+put_rename_conflict_right (basic_io::stanza & st,
+ content_merge_adaptor & adaptor,
+ node_id const nid)
+{
+ content_merge_database_adaptor & db_adaptor (dynamic_cast(adaptor));
+ boost::shared_ptr ancestor_roster(db_adaptor.rosters[db_adaptor.lca]);
+ I(0 != ancestor_roster);
+ boost::shared_ptr right_roster(db_adaptor.rosters[db_adaptor.right_rid]);
+ I(0 != right_roster);
+
+ file_path ancestor_name;
+ file_path right_name;
+
+ ancestor_roster->get_name (nid, ancestor_name);
+ right_roster->get_name (nid, right_name);
+
+ if (file_type == get_type (*right_roster, nid))
+ {
+ st.push_str_pair(syms::right_type, "renamed file");
+ file_id ancestor_fid;
+ db_adaptor.db.get_file_content (db_adaptor.lca, nid, ancestor_fid);
+ st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
+ st.push_binary_pair(syms::ancestor_file_id, ancestor_fid.inner());
+ file_id right_fid;
+ db_adaptor.db.get_file_content (db_adaptor.right_rid, nid, right_fid);
+ st.push_file_pair(syms::right_name, right_name);
+ st.push_binary_pair(syms::right_file_id, right_fid.inner());
+ }
+ else
+ {
+ st.push_str_pair(syms::right_type, "renamed directory");
+ st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
+ st.push_file_pair(syms::right_name, right_name);
+ }
+}
+
+static void
+put_attr_state_left (basic_io::stanza & st, attribute_conflict const & conflict)
+{
+ if (conflict.left.first)
+ st.push_str_pair(syms::left_attr_value, conflict.left.second());
+ else
+ st.push_str_pair(syms::left_attr_state, "dropped");
+}
+
+static void
+put_attr_state_right (basic_io::stanza & st, attribute_conflict const & conflict)
+{
+ if (conflict.right.first)
+ st.push_str_pair(syms::right_attr_value, conflict.right.second());
+ else
+ st.push_str_pair(syms::right_attr_state, "dropped");
+}
+
+static void
+put_attr_conflict (basic_io::stanza & st,
+ content_merge_adaptor & adaptor,
+ attribute_conflict const & conflict)
+{
+ // Always report ancestor, left, and right information, for completeness
+
+ content_merge_database_adaptor & db_adaptor (dynamic_cast(adaptor));
+
+ // This ensures that the ancestor roster is computed
+ boost::shared_ptr ancestor_roster;
+ revision_id ancestor_rid;
+ db_adaptor.get_ancestral_roster (conflict.nid, ancestor_rid, ancestor_roster);
+
+ boost::shared_ptr left_roster(db_adaptor.rosters[db_adaptor.left_rid]);
+ I(0 != left_roster);
+ boost::shared_ptr right_roster(db_adaptor.rosters[db_adaptor.right_rid]);
+ I(0 != right_roster);
+
+ file_path ancestor_name;
+ file_path left_name;
+ file_path right_name;
+
+ ancestor_roster->get_name (conflict.nid, ancestor_name);
+ left_roster->get_name (conflict.nid, left_name);
+ right_roster->get_name (conflict.nid, right_name);
+
+ if (file_type == get_type (*ancestor_roster, conflict.nid))
+ {
+ st.push_str_pair(syms::node_type, "file");
+ st.push_str_pair(syms::attr_name, conflict.key());
+ file_id ancestor_fid;
+ db_adaptor.db.get_file_content (db_adaptor.lca, conflict.nid, ancestor_fid);
+ st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
+ st.push_binary_pair(syms::ancestor_file_id, ancestor_fid.inner());
+ file_id left_fid;
+ db_adaptor.db.get_file_content (db_adaptor.left_rid, conflict.nid, left_fid);
+ st.push_file_pair(syms::left_name, left_name);
+ st.push_binary_pair(syms::left_file_id, left_fid.inner());
+ put_attr_state_left (st, conflict);
+ file_id right_fid;
+ db_adaptor.db.get_file_content (db_adaptor.right_rid, conflict.nid, right_fid);
+ st.push_file_pair(syms::right_name, right_name);
+ st.push_binary_pair(syms::right_file_id, right_fid.inner());
+ put_attr_state_right (st, conflict);
+ }
+ else
+ {
+ st.push_str_pair(syms::node_type, "directory");
+ st.push_str_pair(syms::attr_name, conflict.key());
+ st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
+ st.push_file_pair(syms::left_name, left_name);
+ put_attr_state_left (st, conflict);
+ st.push_file_pair(syms::right_name, right_name);
+ put_attr_state_right (st, conflict);
+ }
+}
+
+enum side_t {left_side, right_side};
+
+static void
+put_duplicate_name_resolution(basic_io::stanza & st,
+ side_t side,
+ resolve_conflicts::file_resolution_t const & resolution)
+{
+ switch (resolution.first)
+ {
+ case resolve_conflicts::none:
+ break;
+
+ case resolve_conflicts::content_user:
+ switch (side)
+ {
+ case left_side:
+ st.push_str_pair(syms::resolved_user_left, resolution.second->as_external());
+ break;
+
+ case right_side:
+ st.push_str_pair(syms::resolved_user_right, resolution.second->as_external());
+ break;
+ }
+ break;
+
+ case resolve_conflicts::rename:
+ switch (side)
+ {
+ case left_side:
+ st.push_str_pair(syms::resolved_rename_left, resolution.second->as_external());
+ break;
+
+ case right_side:
+ st.push_str_pair(syms::resolved_rename_right, resolution.second->as_external());
+ break;
+ }
+ break;
+
+ case resolve_conflicts::drop:
+ switch (side)
+ {
+ case left_side:
+ st.push_symbol(syms::resolved_drop_left);
+ break;
+
+ case right_side:
+ st.push_symbol(syms::resolved_drop_right);
+ break;
+ }
+ break;
+
+ default:
+ I(false);
+ }
+}
+
+static void
+put_content_conflict (basic_io::stanza & st,
+ roster_t const & left_roster,
+ roster_t const & right_roster,
+ content_merge_adaptor & adaptor,
+ file_content_conflict const & conflict)
+{
+ // Always report ancestor, left, and right information, for completeness
+
+ content_merge_database_adaptor & db_adaptor (dynamic_cast(adaptor));
+
+ // This ensures that the ancestor roster is computed
+ boost::shared_ptr ancestor_roster;
+ revision_id ancestor_rid;
+ db_adaptor.get_ancestral_roster (conflict.nid, ancestor_rid, ancestor_roster);
+
+ file_path ancestor_name;
+ file_path left_name;
+ file_path right_name;
+
+ ancestor_roster->get_name (conflict.nid, ancestor_name);
+ left_roster.get_name (conflict.nid, left_name);
+ right_roster.get_name (conflict.nid, right_name);
+
+ if (file_type == get_type (*ancestor_roster, conflict.nid))
+ {
+ st.push_str_pair(syms::node_type, "file");
+ file_id ancestor_fid;
+ db_adaptor.db.get_file_content (db_adaptor.lca, conflict.nid, ancestor_fid);
+ st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
+ st.push_binary_pair(syms::ancestor_file_id, ancestor_fid.inner());
+ st.push_file_pair(syms::left_name, left_name);
+ file_id left_fid;
+ db_adaptor.db.get_file_content (db_adaptor.left_rid, conflict.nid, left_fid);
+ st.push_binary_pair(syms::left_file_id, left_fid.inner());
+ st.push_file_pair(syms::right_name, right_name);
+ file_id right_fid;
+ db_adaptor.db.get_file_content (db_adaptor.right_rid, conflict.nid, right_fid);
+ st.push_binary_pair(syms::right_file_id, right_fid.inner());
+ switch (conflict.resolution.first)
+ {
+ case resolve_conflicts::none:
+ break;
+
+ case resolve_conflicts::content_internal:
+ st.push_symbol(syms::resolved_internal);
+ break;
+
+ case resolve_conflicts::content_user:
+ st.push_str_pair(syms::resolved_user, conflict.resolution.second->as_external());
+ break;
+
+ default:
+ I(false);
+ }
+ }
+ else
+ {
+ st.push_str_pair(syms::node_type, "directory");
+ st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
+ st.push_file_pair(syms::left_name, left_name);
+ st.push_file_pair(syms::right_name, right_name);
+
+ switch (conflict.resolution.first)
+ {
+ case resolve_conflicts::none:
+ break;
+
+ default:
+ // not implemented yet
+ I(false);
+ }
+ }
+}
+
+static void
+put_stanza (basic_io::stanza & st,
+ std::ostream & output)
+{
+ // We have to declare the printer here, rather than more globally,
+ // because adaptor.get_ancestral_roster uses a basic_io::printer
+ // internally, and there can only be one active at a time.
+ basic_io::printer pr;
+ output << "\n";
+ pr.print_stanza(st);
+ output.write(pr.buf.data(), pr.buf.size());
+}
+
+void
+roster_merge_result::report_missing_root_conflicts(roster_t const & left_roster,
+ roster_t const & right_roster,
+ content_merge_adaptor & adaptor,
+ bool const basic_io,
+ std::ostream & output) const
+{
+ MM(left_roster);
+ MM(right_roster);
+
+ if (missing_root_conflict)
+ {
+ node_id left_root, right_root;
+ left_root = left_roster.root()->self;
+ right_root = right_roster.root()->self;
+
+ // these must be different for this conflict to happen
+ I(left_root != right_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);
+
+ node_id left_lca_root = left_lca_roster->root()->self;
+ node_id right_lca_root = right_lca_roster->root()->self;
+
+ basic_io::stanza st;
+
+ if (basic_io)
+ st.push_str_pair(syms::conflict, syms::missing_root);
+ else
+ P(F("conflict: missing root directory"));
+
+ if (left_root != left_lca_root && right_root == right_lca_root)
+ {
+ if (basic_io)
+ {
+ st.push_str_pair(syms::left_type, "pivoted root");
+ st.push_str_pair(syms::ancestor_name, left_lca_name.as_external());
+ }
+ else
+ P(F("directory '%s' pivoted to root on the left") % left_lca_name);
+
+ if (!right_roster.has_node(left_root))
+ {
+ if (basic_io)
+ {
+ st.push_str_pair(syms::right_type, "deleted directory");
+ st.push_str_pair(syms::ancestor_name, left_lca_name.as_external());
+ }
+ else
+ P(F("directory '%s' deleted on the right") % left_lca_name);
+ }
+ }
+ else if (left_root == left_lca_root && right_root != right_lca_root)
+ {
+ if (!left_roster.has_node(right_root))
+ {
+ if (basic_io)
+ {
+ st.push_str_pair(syms::left_type, "deleted directory");
+ st.push_str_pair(syms::ancestor_name, right_lca_name.as_external());
+ }
+ else
+ P(F("directory '%s' deleted on the left") % right_lca_name);
+ }
+
+ if (basic_io)
+ {
+ st.push_str_pair(syms::right_type, "pivoted root");
+ st.push_str_pair(syms::ancestor_name, right_lca_name.as_external());
+ }
+ else
+ P(F("directory '%s' pivoted to root on the right") % right_lca_name);
+ }
+ else if (left_root != left_lca_root && right_root != right_lca_root)
+ {
+ if (basic_io)
+ {
+ st.push_str_pair(syms::left_type, "pivoted root");
+ st.push_str_pair(syms::ancestor_name, left_lca_name.as_external());
+ }
+ else
+ P(F("directory '%s' pivoted to root on the left") % left_lca_name);
+
+ if (!right_roster.has_node(left_root))
+ {
+ if (basic_io)
+ {
+ st.push_str_pair(syms::right_type, "deleted directory");
+ st.push_str_pair(syms::ancestor_name, left_lca_name.as_external());
+ }
+ else
+ P(F("directory '%s' deleted on the right") % left_lca_name);
+ }
+
+ if (!left_roster.has_node(right_root))
+ {
+ if (basic_io)
+ {
+ st.push_str_pair(syms::left_type, "deleted directory");
+ st.push_str_pair(syms::ancestor_name, right_lca_name.as_external());
+ }
+ else
+ P(F("directory '%s' deleted on the left") % right_lca_name);
+ }
+
+ if (basic_io)
+ {
+ st.push_str_pair(syms::right_type, "pivoted root");
+ st.push_str_pair(syms::ancestor_name, right_lca_name.as_external());
+ }
+ else
+ P(F("directory '%s' pivoted to root on the right") % right_lca_name);
+ }
+ // else
+ // other conflicts can cause the root dir to be left detached
+ // for example, merging two independently created projects
+ // in these cases don't report anything about pivot_root
+
+ if (basic_io)
+ put_stanza (st, output);
+ }
+}
+
+void
+roster_merge_result::report_invalid_name_conflicts(roster_t const & left_roster,
+ roster_t const & right_roster,
+ content_merge_adaptor & adaptor,
+ bool basic_io,
+ std::ostream & output) const
+{
+ MM(left_roster);
+ MM(right_roster);
+
+ for (size_t i = 0; i < invalid_name_conflicts.size(); ++i)
+ {
+ invalid_name_conflict const & conflict = invalid_name_conflicts[i];
+ MM(conflict);
+
+ I(!roster.is_attached(conflict.nid));
+
+ shared_ptr lca_roster, parent_lca_roster;
+ revision_id lca_rid, parent_lca_rid;
+ file_path lca_name, lca_parent_name;
+ basic_io::stanza st;
+
+ 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);
+
+ if (basic_io)
+ st.push_str_pair(syms::conflict, syms::invalid_name);
+ else
+ P(F("conflict: invalid name _MTN in root directory"));
+
+ if (left_roster.root()->self == conflict.parent_name.first)
+ {
+ if (basic_io)
+ {
+ st.push_str_pair(syms::left_type, "pivoted root");
+ st.push_str_pair(syms::ancestor_name, lca_parent_name.as_external());
+ }
+ else
+ 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))
+ {
+ if (basic_io)
+ put_rename_conflict_right (st, adaptor, conflict.nid);
+ else
+ P(F("'%s' renamed to '%s' on the right")
+ % lca_name % right_name);
+ }
+ else
+ {
+ if (basic_io)
+ put_added_conflict_right (st, adaptor, conflict.nid);
+ 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)
+ {
+ if (basic_io)
+ {
+ st.push_str_pair(syms::right_type, "pivoted root");
+ st.push_str_pair(syms::ancestor_name, lca_parent_name.as_external());
+ }
+ else
+ 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))
+ {
+ if (basic_io)
+ put_rename_conflict_left (st, adaptor, conflict.nid);
+ else
+ P(F("'%s' renamed to '%s' on the left")
+ % lca_name % left_name);
+ }
+ else
+ {
+ if (basic_io)
+ put_added_conflict_left (st, adaptor, conflict.nid);
+ else
+ P(F("'%s' added in revision %s on the left")
+ % left_name % lca_rid);
+ }
+ }
+ else
+ I(false);
+
+ if (basic_io)
+ put_stanza(st, output);
+ }
+}
+
+void
+roster_merge_result::report_directory_loop_conflicts(roster_t const & left_roster,
+ roster_t const & right_roster,
+ content_merge_adaptor & adaptor,
+ bool basic_io,
+ std::ostream & output) const
+{
+ MM(left_roster);
+ MM(right_roster);
+
+ for (size_t i = 0; i < directory_loop_conflicts.size(); ++i)
+ {
+ directory_loop_conflict const & conflict = directory_loop_conflicts[i];
+ MM(conflict);
+
+ I(!roster.is_attached(conflict.nid));
+
+ 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;
+ basic_io::stanza st;
+
+ 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);
+
+ if (basic_io)
+ st.push_str_pair(syms::conflict, syms::directory_loop);
+ else
+ P(F("conflict: directory loop created"));
+
+ if (left_name != lca_name)
+ {
+ if (basic_io)
+ put_rename_conflict_left (st, adaptor, conflict.nid);
+ else
+ P(F("'%s' renamed to '%s' on the left")
+ % lca_name % left_name);
+ }
+
+ if (right_name != lca_name)
+ {
+ if (basic_io)
+ put_rename_conflict_right (st, adaptor, conflict.nid);
+ else
+ P(F("'%s' renamed to '%s' on the right")
+ % lca_name % right_name);
+ }
+
+ if (left_parent_name != lca_parent_name)
+ {
+ if (basic_io)
+ put_rename_conflict_left (st, adaptor, conflict.parent_name.first);
+ else
+ P(F("'%s' renamed to '%s' on the left")
+ % lca_parent_name % left_parent_name);
+ }
+
+ if (right_parent_name != lca_parent_name)
+ {
+ if (basic_io)
+ put_rename_conflict_right (st, adaptor, conflict.parent_name.first);
+ else
+ P(F("'%s' renamed to '%s' on the right")
+ % lca_parent_name % right_parent_name);
+ }
+
+ if (basic_io)
+ put_stanza(st, output);
+ }
+}
+
+void
+roster_merge_result::report_orphaned_node_conflicts(roster_t const & left_roster,
+ roster_t const & right_roster,
+ content_merge_adaptor & adaptor,
+ bool basic_io,
+ std::ostream & output) const
+{
+ MM(left_roster);
+ MM(right_roster);
+
+ for (size_t i = 0; i < orphaned_node_conflicts.size(); ++i)
+ {
+ orphaned_node_conflict const & conflict = orphaned_node_conflicts[i];
+ MM(conflict);
+
+ I(!roster.is_attached(conflict.nid));
+
+ 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);
+
+ node_type type = get_type(*lca_roster, conflict.nid);
+
+ basic_io::stanza st;
+
+ if (type == file_type)
+ if (basic_io)
+ st.push_str_pair(syms::conflict, syms::orphaned_file);
+ else
+ P(F("conflict: orphaned file '%s' from revision %s")
+ % lca_name % lca_rid);
+ else
+ {
+ if (basic_io)
+ st.push_str_pair(syms::conflict, syms::orphaned_directory);
+ else
+ P(F("conflict: orphaned directory '%s' from revision %s")
+ % 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);
+
+ if (basic_io)
+ {
+ st.push_str_pair(syms::right_type, "deleted directory");
+ st.push_str_pair(syms::ancestor_name, parent_name.as_external());
+ }
+ else
+ P(F("parent directory '%s' was deleted on the right")
+ % parent_name);
+
+ if (parent_lca_roster->has_node(conflict.nid))
+ {
+ if (basic_io)
+ put_rename_conflict_left (st, adaptor, conflict.nid);
+ else
+ if (type == file_type)
+ P(F("file '%s' was renamed from '%s' on the left")
+ % orphan_name % lca_name);
+ else
+ P(F("directory '%s' was renamed from '%s' on the left")
+ % orphan_name % lca_name);
+ }
+ else
+ {
+ if (basic_io)
+ put_added_conflict_left (st, adaptor, conflict.nid);
+ else
+ {
+ if (type == file_type)
+ P(F("file '%s' was added on the left")
+ % orphan_name);
+ else
+ P(F("directory '%s' was added on the left")
+ % 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);
+
+ if (basic_io)
+ {
+ st.push_str_pair(syms::left_type, "deleted directory");
+ st.push_str_pair(syms::ancestor_name, parent_name.as_external());
+ }
+ else
+ P(F("parent directory '%s' was deleted on the left")
+ % parent_name);
+
+ if (parent_lca_roster->has_node(conflict.nid))
+ {
+ if (basic_io)
+ put_rename_conflict_right (st, adaptor, conflict.nid);
+ else
+ if (type == file_type)
+ P(F("file '%s' was renamed from '%s' on the right")
+ % orphan_name % lca_name);
+ else
+ P(F("directory '%s' was renamed from '%s' on the right")
+ % orphan_name % lca_name);
+ }
+ else
+ {
+ if (basic_io)
+ put_added_conflict_right (st, adaptor, conflict.nid);
+ else
+ if (type == file_type)
+ P(F("file '%s' was added on the right")
+ % orphan_name);
+ else
+ P(F("directory '%s' was added on the right")
+ % orphan_name);
+ }
+ }
+ else
+ I(false);
+
+ if (basic_io)
+ put_stanza (st, output);
+ }
+}
+
+void
+roster_merge_result::report_multiple_name_conflicts(roster_t const & left_roster,
+ roster_t const & right_roster,
+ content_merge_adaptor & adaptor,
+ bool basic_io,
+ std::ostream & output) const
+{
+ MM(left_roster);
+ MM(right_roster);
+
+ for (size_t i = 0; i < multiple_name_conflicts.size(); ++i)
+ {
+ multiple_name_conflict const & conflict = multiple_name_conflicts[i];
+ MM(conflict);
+
+ I(!roster.is_attached(conflict.nid));
+
+ file_path left_name, right_name;
+
+ left_roster.get_name(conflict.nid, left_name);
+ right_roster.get_name(conflict.nid, 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);
+
+ node_type type = get_type(*lca_roster, conflict.nid);
+
+ basic_io::stanza st;
+
+ if (basic_io)
+ {
+ st.push_str_pair(syms::conflict, syms::multiple_names);
+ put_rename_conflict_left (st, adaptor, conflict.nid);
+ put_rename_conflict_right (st, adaptor, conflict.nid);
+ }
+ else
+ {
+ if (type == file_type)
+ P(F("conflict: multiple names for file '%s' from revision %s")
+ % lca_name % lca_rid);
+ else
+ P(F("conflict: multiple names for directory '%s' from revision %s")
+ % lca_name % lca_rid);
+
+ P(F("renamed to '%s' on the left") % left_name);
+ P(F("renamed to '%s' on the right") % right_name);
+ }
+
+ if (basic_io)
+ put_stanza(st, output);
+ }
+}
+
+void
+roster_merge_result::report_duplicate_name_conflicts(roster_t const & left_roster,
+ roster_t const & right_roster,
+ content_merge_adaptor & adaptor,
+ bool const basic_io,
+ std::ostream & output) const
+{
+ MM(left_roster);
+ MM(right_roster);
+
+ for (size_t i = 0; i < duplicate_name_conflicts.size(); ++i)
+ {
+ duplicate_name_conflict const & conflict = duplicate_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_roster.get_name(left_nid, left_name);
+ right_roster.get_name(right_nid, right_name);
+
+ shared_ptr left_lca_roster, right_lca_roster;
+ revision_id left_lca_rid, right_lca_rid;
+
+ adaptor.get_ancestral_roster(left_nid, left_lca_rid, left_lca_roster);
+ adaptor.get_ancestral_roster(right_nid, right_lca_rid, right_lca_roster);
+
+ // In most cases, the left_name equals the right_name. However, maybe
+ // a parent directory got renamed on one side. In that case, the names
+ // don't match, but it's still the same directory (by node id), to
+ // which we want to add the same file (by name).
+
+ basic_io::stanza st;
+
+ if (basic_io)
+ st.push_str_pair(syms::conflict, syms::duplicate_name);
+ else
+ {
+ if (left_name == right_name)
+ {
+ file_path dir;
+ path_component basename;
+ left_name.dirname_basename(dir, basename);
+ P(F("conflict: duplicate name '%s' for the directory '%s'") % basename % dir);
+ }
+ else
+ {
+ file_path left_dir, right_dir;
+ path_component left_basename, right_basename;
+ left_name.dirname_basename(left_dir, left_basename);
+ right_name.dirname_basename(right_dir, right_basename);
+ I(left_basename == right_basename);
+ P(F("conflict: duplicate name '%s' for the directory\n"
+ " named '%s' on the left and\n"
+ " named '%s' on the right.")
+ % left_basename % left_dir % right_dir);
+ }
+ }
+
+ node_type left_type = get_type(left_roster, left_nid);
+ node_type right_type = get_type(right_roster, right_nid);
+
+ if (!left_lca_roster->has_node(right_nid) &&
+ !right_lca_roster->has_node(left_nid))
+ {
+ if (basic_io)
+ put_added_conflict_left (st, adaptor, left_nid);
+ else
+ {
+ if (left_type == file_type)
+ P(F("added as a new file on the left"));
+ else
+ P(F("added as a new directory on the left"));
+ }
+
+ if (basic_io)
+ put_added_conflict_right (st, adaptor, right_nid);
+ else
+ {
+ if (right_type == file_type)
+ P(F("added as a new file on the right"));
+ else
+ P(F("added as a new directory on the right"));
+ }
+ }
+ 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);
+
+ if (basic_io)
+ put_rename_conflict_left (st, adaptor, left_nid);
+ else
+ if (left_type == file_type)
+ P(F("renamed from file '%s' on the left") % left_lca_name);
+ else
+ P(F("renamed from directory '%s' on the left") % left_lca_name);
+
+ if (basic_io)
+ put_added_conflict_right (st, adaptor, right_nid);
+ else
+ {
+ if (right_type == file_type)
+ P(F("added as a new file on the right"));
+ else
+ P(F("added as a new directory on the right"));
+ }
+ }
+ 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);
+
+ if (basic_io)
+ put_added_conflict_left (st, adaptor, left_nid);
+ else
+ {
+ if (left_type == file_type)
+ P(F("added as a new file on the left"));
+ else
+ P(F("added as a new directory on the left"));
+ }
+
+ if (basic_io)
+ put_rename_conflict_right (st, adaptor, right_nid);
+ else
+ {
+ if (right_type == file_type)
+ P(F("renamed from file '%s' on the right") % right_lca_name);
+ else
+ P(F("renamed from directory '%s' on the right") % 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);
+
+ if (basic_io)
+ put_rename_conflict_left (st, adaptor, left_nid);
+ else
+ {
+ if (left_type == file_type)
+ P(F("renamed from file '%s' on the left") % left_lca_name);
+ else
+ P(F("renamed from directory '%s' on the left") % left_lca_name);
+ }
+
+ if (basic_io)
+ put_rename_conflict_right (st, adaptor, right_nid);
+ else
+ {
+ if (right_type == file_type)
+ P(F("renamed from file '%s' on the right") % right_lca_name);
+ else
+ P(F("renamed from directory '%s' on the right") % right_lca_name);
+ }
+ }
+ else
+ I(false);
+
+ if (basic_io)
+ {
+ put_duplicate_name_resolution (st, left_side, conflict.left_resolution);
+ put_duplicate_name_resolution (st, right_side, conflict.right_resolution);
+ put_stanza(st, output);
+ }
+ }
+}
+
+void
+roster_merge_result::report_attribute_conflicts(roster_t const & left_roster,
+ roster_t const & right_roster,
+ content_merge_adaptor & adaptor,
+ bool basic_io,
+ std::ostream & output) const
+{
+ MM(left_roster);
+ MM(right_roster);
+ MM(roster);
+
+ for (size_t i = 0; i < attribute_conflicts.size(); ++i)
+ {
+ attribute_conflict const & conflict = attribute_conflicts[i];
+ MM(conflict);
+
+ if (basic_io)
+ {
+ basic_io::stanza st;
+
+ st.push_str_pair(syms::conflict, syms::attribute);
+ put_attr_conflict (st, adaptor, conflict);
+ put_stanza (st, output);
+ }
+ else
+ {
+ // this->roster is null when we are called from 'conflicts
+ // show_remaining'; treat as unattached in that case.
+ node_type type = get_type(left_roster, conflict.nid);
+
+ if (roster.all_nodes().size() > 0 && roster.is_attached(conflict.nid))
+ {
+ file_path name;
+ roster.get_name(conflict.nid, name);
+
+ if (type == file_type)
+ P(F("conflict: multiple values for attribute '%s' on file '%s'")
+ % conflict.key % name);
+ else
+ P(F("conflict: multiple values for attribute '%s' on directory '%s'")
+ % conflict.key % 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
+ {
+ // This node isn't attached in the merged roster, due to another
+ // conflict (ie renamed to different names). So report the
+ // ancestor name and the left and right names.
+
+ file_path left_name, right_name;
+ left_roster.get_name(conflict.nid, left_name);
+ right_roster.get_name(conflict.nid, 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);
+
+ if (type == file_type)
+ P(F("conflict: multiple values for attribute '%s' on file '%s' from revision %s")
+ % conflict.key % lca_name % lca_rid);
+ else
+ P(F("conflict: multiple values for attribute '%s' on directory '%s' from revision %s")
+ % conflict.key % lca_name % lca_rid);
+
+ if (conflict.left.first)
+ {
+ if (type == file_type)
+ P(F("set to '%s' on left file '%s'")
+ % conflict.left.second % left_name);
+ else
+ P(F("set to '%s' on left directory '%s'")
+ % conflict.left.second % left_name);
+ }
+ else
+ {
+ if (type == file_type)
+ P(F("deleted from left file '%s'")
+ % left_name);
+ else
+ P(F("deleted from left directory '%s'")
+ % left_name);
+ }
+
+ if (conflict.right.first)
+ {
+ if (type == file_type)
+ P(F("set to '%s' on right file '%s'")
+ % conflict.right.second % right_name);
+ else
+ P(F("set to '%s' on right directory '%s'")
+ % conflict.right.second % right_name);
+ }
+ else
+ {
+ if (type == file_type)
+ P(F("deleted from right file '%s'")
+ % right_name);
+ else
+ P(F("deleted from right directory '%s'")
+ % right_name);
+ }
+ }
+ }
+ }
+}
+
+namespace
+{
+ bool
+ auto_merge_succeeds(lua_hooks & lua,
+ file_content_conflict conflict,
+ content_merge_adaptor & adaptor,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+ {
+ revision_id ancestor_rid;
+ shared_ptr ancestor_roster;
+ adaptor.get_ancestral_roster(conflict.nid, ancestor_rid, ancestor_roster);
+
+ I(ancestor_roster);
+ I(ancestor_roster->has_node(conflict.nid)); // this fails if there is no least common ancestor
+
+ file_id anc_id, left_id, right_id;
+ file_path anc_path, left_path, right_path;
+ ancestor_roster->get_file_details(conflict.nid, anc_id, anc_path);
+ left_roster.get_file_details(conflict.nid, left_id, left_path);
+ right_roster.get_file_details(conflict.nid, right_id, right_path);
+
+ content_merger cm(lua, *ancestor_roster, left_roster, right_roster, adaptor);
+
+ file_data left_data, right_data, merge_data;
+
+ return cm.attempt_auto_merge(anc_path, left_path, right_path,
+ anc_id, left_id, right_id,
+ left_data, right_data, merge_data);
+ }
+}
+
+void
+roster_merge_result::report_file_content_conflicts(lua_hooks & lua,
+ roster_t const & left_roster,
+ roster_t const & right_roster,
+ content_merge_adaptor & adaptor,
+ bool basic_io,
+ std::ostream & output)
+{
+ MM(left_roster);
+ MM(right_roster);
+
+ for (size_t i = 0; i < file_content_conflicts.size(); ++i)
+ {
+ file_content_conflict & conflict = file_content_conflicts[i];
+ MM(conflict);
+
+ if (basic_io)
+ {
+ basic_io::stanza st;
+
+ if (conflict.resolution.first == resolve_conflicts::none)
+ if (auto_merge_succeeds(lua, conflict, adaptor, left_roster, right_roster))
+ conflict.resolution.first = resolve_conflicts::content_internal;
+
+ st.push_str_pair(syms::conflict, syms::content);
+ put_content_conflict (st, left_roster, right_roster, adaptor, conflict);
+ put_stanza (st, output);
+ }
+ else
+ {
+ if (roster.is_attached(conflict.nid))
+ {
+ file_path name;
+ roster.get_name(conflict.nid, name);
+
+ P(F("conflict: content conflict on file '%s'")
+ % name);
+ P(F("content hash is %s on the left") % conflict.left);
+ P(F("content hash is %s on the right") % conflict.right);
+ }
+ else
+ {
+ // 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
+
+ file_path left_name, right_name;
+ left_roster.get_name(conflict.nid, left_name);
+ right_roster.get_name(conflict.nid, 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: content conflict on file '%s' from revision %s")
+ % lca_name % lca_rid);
+ P(F("content hash is %s on the left in file '%s'")
+ % conflict.left % left_name);
+ P(F("content hash is %s on the right in file '%s'")
+ % conflict.right % right_name);
+ }
+ }
+ }
+}
+
+// Resolving non-content conflicts
+
+namespace resolve_conflicts
+{
+ bool
+ do_auto_merge(lua_hooks & lua,
+ file_content_conflict const & conflict,
+ content_merge_adaptor & adaptor,
+ roster_t const & left_roster,
+ roster_t const & right_roster,
+ roster_t const & result_roster,
+ file_id & merged_id)
+ {
+ revision_id ancestor_rid;
+ shared_ptr ancestor_roster;
+ adaptor.get_ancestral_roster(conflict.nid, ancestor_rid, ancestor_roster);
+
+ I(ancestor_roster);
+ I(ancestor_roster->has_node(conflict.nid)); // this fails if there is no least common ancestor
+
+ file_id anc_id, left_id, right_id;
+ file_path anc_path, left_path, right_path, merged_path;
+ ancestor_roster->get_file_details(conflict.nid, anc_id, anc_path);
+ left_roster.get_file_details(conflict.nid, left_id, left_path);
+ right_roster.get_file_details(conflict.nid, right_id, right_path);
+ result_roster.get_file_details(conflict.nid, merged_id, merged_path);
+
+ content_merger cm(lua, *ancestor_roster, left_roster, right_roster, adaptor);
+
+ return cm.try_auto_merge(anc_path, left_path, right_path, merged_path,
+ anc_id, left_id, right_id, merged_id);
+ }
+}
+
+static char const * const conflicts_mismatch_msg = "conflicts file does not match current conflicts";
+static char const * const conflict_resolution_not_supported_msg = "%s is not a supported conflict resolution for %s";
+static char const * const conflict_extra = "extra chars at end of conflict";
+
+static void
+read_missing_root_conflicts(basic_io::parser & pars,
+ bool & missing_root_conflict,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ // There can be only one of these
+ if (pars.tok.in.lookahead != EOF && pars.symp(syms::missing_root))
+ {
+ pars.sym();
+
+ if (pars.symp(syms::left_type))
+ {
+ pars.sym(); pars.str();
+ pars.esym(syms::ancestor_name); pars.str();
+ pars.esym(syms::right_type); pars.str();
+ pars.esym(syms::ancestor_name); pars.str();
+ }
+ // else unrelated projects (branches); nothing else output
+
+ missing_root_conflict = true;
+
+ if (pars.tok.in.lookahead != EOF)
+ pars.esym (syms::conflict);
+ }
+ else
+ {
+ missing_root_conflict = false;
+ }
+} // read_missing_root_conflicts
+
+static void
+read_invalid_name_conflict(basic_io::parser & pars,
+ invalid_name_conflict & conflict,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ if (pars.symp(syms::left_type))
+ {
+ pars.sym();
+ pars.str(); // "pivoted root"
+ pars.esym(syms::ancestor_name); pars.str(); // lca_parent_name
+ read_added_rename_conflict_right (pars, right_roster, conflict.nid, conflict.parent_name);
+ }
+ else
+ {
+ pars.esym(syms::right_type);
+ pars.str(); // "pivoted root"
+ pars.esym(syms::ancestor_name); pars.str(); // lca_parent_name
+ read_added_rename_conflict_left (pars, left_roster, conflict.nid, conflict.parent_name);
+ }
+} // read_invalid_name_conflict
+
+static void
+read_invalid_name_conflicts(basic_io::parser & pars,
+ std::vector & conflicts,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ while (pars.tok.in.lookahead != EOF && pars.symp(syms::invalid_name))
+ {
+ invalid_name_conflict conflict;
+
+ pars.sym();
+
+ read_invalid_name_conflict(pars, conflict, left_roster, right_roster);
+
+ conflicts.push_back(conflict);
+
+ if (pars.tok.in.lookahead != EOF)
+ pars.esym (syms::conflict);
+ }
+} // read_invalid_name_conflicts
+
+static void
+read_directory_loop_conflict(basic_io::parser & pars,
+ directory_loop_conflict & conflict,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ string tmp;
+
+ // syms::directory_loop has been read
+
+ if (pars.symp(syms::left_type))
+ {
+ read_added_rename_conflict_left(pars, left_roster, conflict.nid, conflict.parent_name);
+ }
+ if (pars.symp(syms::right_type))
+ {
+ read_added_rename_conflict_right(pars, right_roster, conflict.nid, conflict.parent_name);
+ }
+
+ if (pars.symp(syms::left_type))
+ {
+ pars.sym();
+ pars.str(); // "renamed directory"
+ pars.esym(syms::ancestor_name); pars.str();
+ pars.esym(syms::left_name); pars.str();
+ }
+ if (pars.symp(syms::right_type))
+ {
+ pars.sym();
+ pars.str(); // "renamed directory"
+ pars.esym(syms::ancestor_name); pars.str();
+ pars.esym(syms::right_name); pars.str();
+ }
+
+} // read_directory_loop_conflict
+
+static void
+read_directory_loop_conflicts(basic_io::parser & pars,
+ std::vector & conflicts,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ while (pars.tok.in.lookahead != EOF && pars.symp(syms::directory_loop))
+ {
+ directory_loop_conflict conflict;
+
+ pars.sym();
+
+ read_directory_loop_conflict(pars, conflict, left_roster, right_roster);
+
+ conflicts.push_back(conflict);
+
+ if (pars.tok.in.lookahead != EOF)
+ pars.esym (syms::conflict);
+ }
+} // read_directory_loop_conflicts
+
+
+static void
+read_orphaned_node_conflict(basic_io::parser & pars,
+ orphaned_node_conflict & conflict,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ if (pars.symp(syms::left_type))
+ {
+ pars.sym(); pars.str(); // "deleted directory | file"
+ pars.esym(syms::ancestor_name); pars.str();
+ read_added_rename_conflict_right(pars, right_roster, conflict.nid, conflict.parent_name);
+ }
+ else
+ {
+ pars.esym(syms::right_type);
+ pars.str(); // "deleted directory | file"
+ pars.esym(syms::ancestor_name); pars.str();
+ read_added_rename_conflict_left(pars, left_roster, conflict.nid, conflict.parent_name);
+ }
+} // read_orphaned_node_conflict
+
+static void
+read_orphaned_node_conflicts(basic_io::parser & pars,
+ std::vector & conflicts,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ while (pars.tok.in.lookahead != EOF && (pars.symp(syms::orphaned_directory) || pars.symp(syms::orphaned_file)))
+ {
+ orphaned_node_conflict conflict;
+
+ pars.sym();
+
+ read_orphaned_node_conflict(pars, conflict, left_roster, right_roster);
+
+ conflicts.push_back(conflict);
+
+ if (pars.tok.in.lookahead != EOF)
+ pars.esym (syms::conflict);
+ }
+} // read_orphaned_node_conflicts
+
+
+static void
+read_multiple_name_conflict(basic_io::parser & pars,
+ multiple_name_conflict & conflict,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ read_added_rename_conflict_left(pars, left_roster, conflict.nid, conflict.left);
+ read_added_rename_conflict_right(pars, right_roster, conflict.nid, conflict.right);
+} // read_multiple_name_conflict
+
+static void
+read_multiple_name_conflicts(basic_io::parser & pars,
+ std::vector & conflicts,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ while (pars.tok.in.lookahead != EOF && pars.symp(syms::multiple_names))
+ {
+ multiple_name_conflict conflict(the_null_node);
+
+ pars.sym();
+
+ read_multiple_name_conflict(pars, conflict, left_roster, right_roster);
+
+ conflicts.push_back(conflict);
+
+ if (pars.tok.in.lookahead != EOF)
+ pars.esym (syms::conflict);
+ }
+} // read_multiple_name_conflicts
+
+static void
+read_duplicate_name_conflict(basic_io::parser & pars,
+ duplicate_name_conflict & conflict,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ read_added_rename_conflict_left(pars, left_roster, conflict.left_nid, conflict.parent_name);
+ read_added_rename_conflict_right(pars, right_roster, conflict.right_nid, conflict.parent_name);
+
+ // check for a resolution
+ while ((!pars.symp (syms::conflict)) && pars.tok.in.lookahead != EOF)
+ {
+ if (pars.symp (syms::resolved_drop_left))
+ {
+ conflict.left_resolution.first = resolve_conflicts::drop;
+ pars.sym();
+ }
+ else if (pars.symp (syms::resolved_drop_right))
+ {
+ conflict.right_resolution.first = resolve_conflicts::drop;
+ pars.sym();
+ }
+ else if (pars.symp (syms::resolved_rename_left))
+ {
+ conflict.left_resolution.first = resolve_conflicts::rename;
+ pars.sym();
+ conflict.left_resolution.second = resolve_conflicts::new_file_path(pars.token);
+ pars.str();
+ }
+ else if (pars.symp (syms::resolved_rename_right))
+ {
+ conflict.right_resolution.first = resolve_conflicts::rename;
+ pars.sym();
+ conflict.right_resolution.second = resolve_conflicts::new_file_path(pars.token);
+ pars.str();
+ }
+ else if (pars.symp (syms::resolved_user_left))
+ {
+ conflict.left_resolution.first = resolve_conflicts::content_user;
+ pars.sym();
+ conflict.left_resolution.second = new_optimal_path(pars.token, true);
+ pars.str();
+ }
+ else if (pars.symp (syms::resolved_user_right))
+ {
+ conflict.right_resolution.first = resolve_conflicts::content_user;
+ pars.sym();
+ conflict.right_resolution.second = new_optimal_path(pars.token, true);
+ pars.str();
+ }
+ else
+ E(false, origin::user,
+ F(conflict_resolution_not_supported_msg) % pars.token % "duplicate_name");
+ }
+
+} // read_duplicate_name_conflict
+
+static void
+read_duplicate_name_conflicts(basic_io::parser & pars,
+ std::vector & conflicts,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ while (pars.tok.in.lookahead != EOF && pars.symp(syms::duplicate_name))
+ {
+ duplicate_name_conflict conflict;
+
+ pars.sym();
+
+ read_duplicate_name_conflict(pars, conflict, left_roster, right_roster);
+
+ conflicts.push_back(conflict);
+
+ if (pars.tok.in.lookahead != EOF)
+ pars.esym (syms::conflict);
+ }
+} // read_duplicate_name_conflicts
+static void
+validate_duplicate_name_conflicts(basic_io::parser & pars,
+ std::vector & conflicts,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ for (std::vector::iterator i = conflicts.begin();
+ i != conflicts.end();
+ ++i)
+ {
+ duplicate_name_conflict & merge_conflict = *i;
+ duplicate_name_conflict file_conflict;
+
+ pars.esym(syms::duplicate_name);
+
+ read_duplicate_name_conflict(pars, file_conflict, left_roster, right_roster);
+
+ // Note that we do not confirm the file ids.
+ E(merge_conflict.left_nid == file_conflict.left_nid &&
+ merge_conflict.right_nid == file_conflict.right_nid,
+ origin::user,
+ F(conflicts_mismatch_msg));
+
+ merge_conflict.left_resolution = file_conflict.left_resolution;
+ merge_conflict.right_resolution = file_conflict.right_resolution;
+
+ if (pars.tok.in.lookahead != EOF)
+ pars.esym (syms::conflict);
+ else
+ {
+ std::vector::iterator tmp = i;
+ E(++tmp == conflicts.end(), origin::user,
+ F(conflicts_mismatch_msg));
+ }
+ }
+} // validate_duplicate_name_conflicts
+
+static void
+read_attr_state_left(basic_io::parser & pars,
+ std::pair & value)
+{
+ string tmp;
+
+ if (pars.symp(syms::left_attr_value))
+ {
+ pars.sym();
+ value.first = true;
+ pars.str(tmp);
+ value.second = attr_value(tmp, pars.tok.in.made_from);
+ }
+ else
+ {
+ pars.esym(syms::left_attr_state);
+ pars.str(tmp);
+ I(tmp == "dropped");
+ value.first = false;
+ }
+} // read_attr_state_left
+
+static void
+read_attr_state_right(basic_io::parser & pars,
+ std::pair & value)
+{
+ string tmp;
+
+ if (pars.symp(syms::right_attr_value))
+ {
+ pars.sym();
+ value.first = true;
+ pars.str(tmp);
+ value.second = attr_value(tmp, pars.tok.in.made_from);
+ }
+ else
+ {
+ pars.esym(syms::right_attr_state);
+ pars.str(tmp);
+ I(tmp == "dropped");
+ value.first = false;
+ }
+} // read_attr_state_right
+
+static void
+read_attribute_conflict(basic_io::parser & pars,
+ attribute_conflict & conflict,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ string tmp;
+
+ pars.esym(syms::node_type);
+
+ pars.str(tmp);
+
+ if (tmp == "file")
+ {
+ pars.esym(syms::attr_name); pars.str(tmp);
+ conflict.key = attr_key(tmp, pars.tok.in.made_from);
+ pars.esym(syms::ancestor_name); pars.str();
+ pars.esym(syms::ancestor_file_id); pars.hex();
+ pars.esym(syms::left_name); pars.str(tmp);
+ conflict.nid = left_roster.get_node(file_path_external(utf8(tmp, pars.tok.in.made_from)))->self;
+ pars.esym(syms::left_file_id); pars.hex();
+ read_attr_state_left(pars, conflict.left);
+ pars.esym(syms::right_name); pars.str();
+ pars.esym(syms::right_file_id); pars.hex();
+ read_attr_state_right(pars, conflict.right);
+ }
+ else if (tmp == "directory")
+ {
+ pars.esym(syms::attr_name); pars.str(tmp);
+ conflict.key = attr_key(tmp, pars.tok.in.made_from);
+ pars.esym(syms::ancestor_name); pars.str();
+ pars.esym(syms::left_name); pars.str(tmp);
+ conflict.nid = left_roster.get_node(file_path_external(utf8(tmp, pars.tok.in.made_from)))->self;
+ read_attr_state_left(pars, conflict.left);
+ pars.esym(syms::right_name); pars.str();
+ read_attr_state_right(pars, conflict.right);
+ }
+ else
+ I(false);
+
+} // read_attribute_conflict
+
+static void
+read_attribute_conflicts(basic_io::parser & pars,
+ std::vector & conflicts,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ while (pars.tok.in.lookahead != EOF && pars.symp(syms::attribute))
+ {
+ attribute_conflict conflict(the_null_node);
+
+ pars.sym();
+
+ read_attribute_conflict(pars, conflict, left_roster, right_roster);
+
+ conflicts.push_back(conflict);
+
+ if (pars.tok.in.lookahead != EOF)
+ pars.esym (syms::conflict);
+ }
+} // read_attribute_conflicts
+
+static void
+read_file_content_conflict(basic_io::parser & pars,
+ file_content_conflict & conflict,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ string tmp;
+ string left_name, right_name, result_name;
+
+ pars.esym(syms::node_type); pars.str(tmp); I(tmp == "file");
+
+ pars.esym (syms::ancestor_name); pars.str();
+ pars.esym (syms::ancestor_file_id); pars.hex(tmp);
+ conflict.ancestor = decode_hexenc_as(tmp, pars.tok.in.made_from);
+
+ pars.esym (syms::left_name); pars.str(left_name);
+ pars.esym(syms::left_file_id); pars.hex(tmp);
+ conflict.left = decode_hexenc_as(tmp, pars.tok.in.made_from);
+
+ pars.esym (syms::right_name); pars.str(right_name);
+ pars.esym(syms::right_file_id); pars.hex(tmp);
+ conflict.right = decode_hexenc_as(tmp, pars.tok.in.made_from);
+
+ conflict.nid = left_roster.get_node (file_path_internal (left_name))->self;
+ I(conflict.nid = right_roster.get_node (file_path_internal (right_name))->self);
+
+ // check for a resolution
+ if ((!pars.symp (syms::conflict)) && pars.tok.in.lookahead != EOF)
+ {
+ if (pars.symp (syms::resolved_internal))
+ {
+ conflict.resolution.first = resolve_conflicts::content_internal;
+ pars.sym();
+ }
+ else if (pars.symp (syms::resolved_user))
+ {
+ conflict.resolution.first = resolve_conflicts::content_user;
+ pars.sym();
+ conflict.resolution.second = new_optimal_path(pars.token, true);
+ pars.str();
+ }
+ else
+ E(false, origin::user,
+ F(conflict_resolution_not_supported_msg) % pars.token % "file_content");
+ }
+
+} // read_file_content_conflict
+
+static void
+read_file_content_conflicts(basic_io::parser & pars,
+ std::vector & conflicts,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ while (pars.tok.in.lookahead != EOF && pars.symp(syms::content))
+ {
+ file_content_conflict conflict;
+
+ pars.sym();
+
+ read_file_content_conflict(pars, conflict, left_roster, right_roster);
+
+ conflicts.push_back(conflict);
+
+ if (pars.tok.in.lookahead != EOF)
+ pars.esym (syms::conflict);
+ }
+} // read_file_content_conflicts
+
+static void
+validate_file_content_conflicts(basic_io::parser & pars,
+ std::vector & conflicts,
+ roster_t const & left_roster,
+ roster_t const & right_roster)
+{
+ for (std::vector::iterator i = conflicts.begin();
+ i != conflicts.end();
+ ++i)
+ {
+ file_content_conflict & merge_conflict = *i;
+ file_content_conflict file_conflict;
+
+ pars.esym(syms::content);
+
+ read_file_content_conflict(pars, file_conflict, left_roster, right_roster);
+
+ E(merge_conflict.nid == file_conflict.nid, origin::user,
+ F(conflicts_mismatch_msg));
+
+ merge_conflict.resolution = file_conflict.resolution;
+
+ if (pars.tok.in.lookahead != EOF)
+ pars.esym (syms::conflict);
+ else
+ {
+ std::vector::iterator tmp = i;
+ E(++tmp == conflicts.end(), origin::user,
+ F("conflicts file does not match current conflicts"));
+ }
+ }
+} // validate_file_content_conflicts
+
+static void
+read_conflict_file_core(basic_io::parser pars,
+ roster_t const & left_roster,
+ roster_t const & right_roster,
+ roster_merge_result & result,
+ bool validate)
+{
+ pars.esym (syms::conflict);
+
+ // If we are validating, there must be one stanza in the file for each
+ // conflict; otherwise something has changed since the file was
+ // regenerated. So we go thru the conflicts in the same order they are
+ // generated; see merge.cc resolve_merge_conflicts.
+
+ if (validate)
+ {
+ // resolve_merge_conflicts should not call us if there are any conflicts
+ // for which we don't currently support resolutions; assert that.
+
+ I(!result.missing_root_conflict);
+ I(result.invalid_name_conflicts.size() == 0);
+ I(result.directory_loop_conflicts.size() == 0);
+ I(result.orphaned_node_conflicts.size() == 0);
+ I(result.multiple_name_conflicts.size() == 0);
+ I(result.attribute_conflicts.size() == 0);
+
+ // These are the ones we know how to resolve.
+
+ validate_duplicate_name_conflicts(pars, result.duplicate_name_conflicts, left_roster, right_roster);
+ validate_file_content_conflicts(pars, result.file_content_conflicts, left_roster, right_roster);
+ }
+ else
+ {
+ // Read in the ones we know how to resolve. Also read in the ones we
+ // don't know how to resolve, so we can report them.
+ read_missing_root_conflicts(pars, result.missing_root_conflict, left_roster, right_roster);
+ read_invalid_name_conflicts(pars, result.invalid_name_conflicts, left_roster, right_roster);
+ read_directory_loop_conflicts(pars, result.directory_loop_conflicts, left_roster, right_roster);
+ read_orphaned_node_conflicts(pars, result.orphaned_node_conflicts, left_roster, right_roster);
+ read_multiple_name_conflicts(pars, result.multiple_name_conflicts, left_roster, right_roster);
+ read_duplicate_name_conflicts(pars, result.duplicate_name_conflicts, left_roster, right_roster);
+ read_attribute_conflicts(pars, result.attribute_conflicts, left_roster, right_roster);
+ read_file_content_conflicts(pars, result.file_content_conflicts, left_roster, right_roster);
+ }
+
+ E(pars.tok.in.lookahead == EOF, pars.tok.in.made_from,
+ F("extra data in file"));
+} // read_conflict_file_core
+
+void
+roster_merge_result::read_conflict_file(database & db,
+ bookkeeping_path const & file_name,
+ revision_id & ancestor_rid,
+ revision_id & left_rid,
+ revision_id & right_rid,
+ roster_t & left_roster,
+ marking_map & left_marking,
+ roster_t & right_roster,
+ marking_map & right_marking)
+{
+ data dat;
+
+ read_data (file_name, dat);
+
+ basic_io::input_source src(dat(), file_name.as_external());
+ src.made_from = origin::user;
+ basic_io::tokenizer tok(src);
+ basic_io::parser pars(tok);
+ std::string temp;
+
+ // Read left, right, ancestor.
+ pars.esym(syms::left);
+ pars.hex(temp);
+ left_rid = decode_hexenc_as(temp, src.made_from);
+ pars.esym(syms::right);
+ pars.hex(temp);
+ right_rid = decode_hexenc_as(temp, src.made_from);
+
+ if (pars.symp(syms::ancestor))
+ {
+ pars.sym();
+ pars.hex(temp);
+ ancestor_rid = decode_hexenc_as(temp, src.made_from);
+
+ // we don't fetch the ancestor roster here, because not every function
+ // needs it.
+ db.get_roster(left_rid, left_roster, left_marking);
+ db.get_roster(right_rid, right_roster, right_marking);
+
+ read_conflict_file_core(pars, left_roster, right_roster, *this, false);
+ }
+ // else no conflicts
+
+} // roster_merge_result::read_conflict_file
+
+void
+roster_merge_result::write_conflict_file(database & db,
+ lua_hooks & lua,
+ bookkeeping_path const & file_name,
+ revision_id const & ancestor_rid,
+ revision_id const & left_rid,
+ revision_id const & right_rid,
+ boost::shared_ptr left_roster,
+ marking_map const & left_marking,
+ boost::shared_ptr right_roster,
+ marking_map const & right_marking)
+{
+ std::ostringstream output;
+
+ content_merge_database_adaptor adaptor(db, left_rid, right_rid,
+ left_marking, right_marking);
+
+ adaptor.cache_roster (left_rid, left_roster);
+ adaptor.cache_roster (right_rid, right_roster);
+ {
+ // match format in cmd_merging.cc show_conflicts_core
+ basic_io::stanza st;
+ basic_io::printer pr;
+ st.push_binary_pair(syms::left, left_rid.inner());
+ st.push_binary_pair(syms::right, right_rid.inner());
+ st.push_binary_pair(syms::ancestor, adaptor.lca.inner());
+ pr.print_stanza(st);
+ output.write(pr.buf.data(), pr.buf.size());
+ }
+
+ report_missing_root_conflicts(*left_roster, *right_roster, adaptor, true, output);
+ report_invalid_name_conflicts(*left_roster, *right_roster, adaptor, true, output);
+ report_directory_loop_conflicts(*left_roster, *right_roster, adaptor, true, output);
+ report_orphaned_node_conflicts(*left_roster, *right_roster, adaptor, true, output);
+ report_multiple_name_conflicts(*left_roster, *right_roster, adaptor, true, output);
+ report_duplicate_name_conflicts(*left_roster, *right_roster, adaptor, true, output);
+ report_attribute_conflicts(*left_roster, *right_roster, adaptor, true, output);
+ report_file_content_conflicts(lua, *left_roster, *right_roster, adaptor, true, output);
+
+ data dat(output.str(), origin::internal);
+ write_data(file_name, dat);
+
+} // roster_merge_result::write_conflict_file
+
+void
+parse_resolve_conflicts_opts (options const & opts,
+ revision_id const & left_rid,
+ roster_t const & left_roster,
+ revision_id const & right_rid,
+ roster_t const & right_roster,
+ roster_merge_result & result,
+ bool & resolutions_given)
+{
+if (opts.resolve_conflicts_given || opts.resolve_conflicts_file_given)
+ {
+ resolutions_given = true;
+
+ data dat;
+
+ read_data (system_path(opts.resolve_conflicts_file), dat);
+
+ basic_io::input_source src(dat(), opts.resolve_conflicts_file.as_external());
+ src.made_from = origin::user;
+ basic_io::tokenizer tok(src);
+ basic_io::parser pars(tok);
+ std::string temp;
+
+ pars.esym(syms::left);
+ pars.hex(temp);
+ E(left_rid == decode_hexenc_as(temp, src.made_from),
+ origin::user,
+ F("left revision id does not match conflict file"));
+
+ pars.esym(syms::right);
+ pars.hex(temp);
+ E(right_rid == decode_hexenc_as(temp, src.made_from),
+ origin::user,
+ F("right revision id does not match conflict file"));
+
+ if (pars.symp(syms::ancestor))
+ {
+ pars.sym();
+ pars.hex(temp);
+
+ read_conflict_file_core (pars, left_roster, right_roster, result, true);
+ }
+ }
+ else
+ resolutions_given = false;
+
+} // parse_resolve_conflicts_opts
+
+static void
+attach_node (lua_hooks & lua,
+ roster_t & new_roster,
+ node_id nid,
+ file_path const target_path)
+{
+ // Simplified from workspace::perform_rename in work.cc
+
+ I(!target_path.empty());
+
+ E(!new_roster.has_node(target_path), origin::user,
+ F("%s already exists") % target_path.as_external());
+ E(new_roster.has_node(target_path.dirname()), origin::user,
+ F("directory %s does not exist or is unknown") % target_path.dirname());
+
+ new_roster.attach_node (nid, target_path);
+
+ node_t node = new_roster.get_node (nid);
+ for (full_attr_map_t::const_iterator attr = node->attrs.begin();
+ attr != node->attrs.end();
+ ++attr)
+ lua.hook_apply_attribute (attr->first(), target_path, attr->second.second());
+
+} // attach_node
+
+static void
+resolve_duplicate_name_one_side(lua_hooks & lua,
+ resolve_conflicts::file_resolution_t const & resolution,
+ resolve_conflicts::file_resolution_t const & other_resolution,
+ file_path const & name,
+ file_id const & fid,
+ node_id const nid,
+ content_merge_adaptor & adaptor,
+ roster_t & result_roster)
+{
+ switch (resolution.first)
+ {
+ case resolve_conflicts::content_user:
+ {
+ E(other_resolution.first == resolve_conflicts::drop ||
+ other_resolution.first == resolve_conflicts::rename,
+ origin::user,
+ F("inconsistent left/right resolutions for %s") % name);
+
+ P(F("replacing content of %s with %s") % name % resolution.second->as_external());
+
+ file_id result_fid;
+ file_data parent_data, result_data;
+ data result_raw_data;
+ adaptor.get_version(fid, parent_data);
+
+ read_data(*resolution.second, result_raw_data);
+
+ result_data = file_data(result_raw_data);
+ calculate_ident(result_data, result_fid);
+
+ file_t result_node = downcast_to_file_t(result_roster.get_node(nid));
+ result_node->content = result_fid;
+
+ adaptor.record_file(fid, result_fid, parent_data, result_data);
+
+ attach_node(lua, result_roster, nid, name);
+ }
+ break;
+
+ case resolve_conflicts::drop:
+ P(F("dropping %s") % name);
+ result_roster.drop_detached_node(nid);
+ break;
+
+ case resolve_conflicts::rename:
+ P(F("renaming %s to %s") % name % *resolution.second);
+ attach_node
+ (lua, result_roster, nid, file_path_internal (resolution.second->as_internal()));
+ break;
+
+ case resolve_conflicts::none:
+ E(false, origin::user,
+ F("no resolution provided for duplicate_name %s") % name);
+ break;
+
+ case resolve_conflicts::content_internal:
+ E(false, origin::user,
+ F("invalid resolution for duplicate_name %s") % name);
+ break;
+
+ default:
+ I(false);
+ }
+} // resolve_duplicate_name_one_side
+
+void
+roster_merge_result::resolve_duplicate_name_conflicts(lua_hooks & lua,
+ roster_t const & left_roster,
+ roster_t const & right_roster,
+ content_merge_adaptor & adaptor)
+{
+ MM(left_roster);
+ MM(right_roster);
+ MM(this->roster); // New roster
+
+ // Conflict nodes are present but detached (without filenames) in the new
+ // roster. The resolution is either to suture the two files together, or to
+ // rename one or both.
+
+ for (std::vector::const_iterator i = duplicate_name_conflicts.begin();
+ i != duplicate_name_conflicts.end();
+ ++i)
+ {
+ duplicate_name_conflict const & conflict = *i;
+ MM(conflict);
+
+ node_id left_nid = conflict.left_nid;
+ node_id right_nid= conflict.right_nid;
+
+ file_path left_name, right_name;
+ file_id left_fid, right_fid;
+
+ left_roster.get_file_details(left_nid, left_fid, left_name);
+ right_roster.get_file_details(right_nid, right_fid, right_name);
+
+ resolve_duplicate_name_one_side
+ (lua, conflict.left_resolution, conflict.right_resolution, left_name, left_fid, left_nid, adaptor, roster);
+
+ resolve_duplicate_name_one_side
+ (lua, conflict.right_resolution, conflict.left_resolution, right_name, right_fid, right_nid, adaptor, roster);
+ } // end for
+
+ duplicate_name_conflicts.clear();
+}
+
+void
+roster_merge_result::resolve_file_content_conflicts(lua_hooks & lua,
+ roster_t const & left_roster,
+ roster_t const & right_roster,
+ content_merge_adaptor & adaptor)
+{
+ MM(left_roster);
+ MM(right_roster);
+ MM(this->roster); // New roster
+
+ // Conflict node is present and attached in the new roster, with a null
+ // file content id. The resolution is to enter the user specified file
+ // content in the database and roster, or let the internal line merger
+ // handle it.
+
+ for (std::vector::const_iterator i = file_content_conflicts.begin();
+ i != file_content_conflicts.end();
+ ++i)
+ {
+ file_content_conflict const & conflict = *i;
+ MM(conflict);
+
+ file_path left_name, right_name;
+
+ left_roster.get_name(conflict.nid, left_name);
+ right_roster.get_name(conflict.nid, right_name);
+
+ switch (conflict.resolution.first)
+ {
+ case resolve_conflicts::content_internal:
+ case resolve_conflicts::none:
+ {
+ file_id merged_id;
+
+ E(resolve_conflicts::do_auto_merge(lua, conflict, adaptor, left_roster,
+ right_roster, this->roster, merged_id),
+ origin::user,
+ F("merge of %s, %s failed") % left_name % right_name);
+
+ P(F("merged %s, %s") % left_name % right_name);
+
+ file_t result_node = downcast_to_file_t(roster.get_node(conflict.nid));
+ result_node->content = merged_id;
+ }
+ break;
+
+ case resolve_conflicts::content_user:
+ {
+ P(F("replacing content of %s, %s with %s") %
+ left_name % right_name % conflict.resolution.second->as_external());
+
+ file_id result_id;
+ file_data left_data, right_data, result_data;
+ data result_raw_data;
+ adaptor.get_version(conflict.left, left_data);
+ adaptor.get_version(conflict.right, right_data);
+
+ read_data(*conflict.resolution.second, result_raw_data);
+
+ result_data = file_data(result_raw_data);
+ calculate_ident(result_data, result_id);
+
+ file_t result_node = downcast_to_file_t(roster.get_node(conflict.nid));
+ result_node->content = result_id;
+
+ adaptor.record_merge(conflict.left, conflict.right, result_id,
+ left_data, right_data, result_data);
+
+ }
+ break;
+
+ default:
+ I(false);
+ }
+
+ } // end for
+
+ file_content_conflicts.clear();
+}
============================================================
--- Makefile.am f9f47a97e59ee658ae1830700e403bd981c8a156
+++ Makefile.am b2891ae6208641d325a61977637aa21c80dd6842
@@ -67,7 +67,7 @@ MOST_SOURCES = \
restrictions.cc restrictions.hh \
hmac.cc hmac.hh \
string_queue.cc string_queue.hh \
- merge_roster.cc merge_roster.hh \
+ merge_roster.cc merge_conflict.cc merge_roster.hh \
merge_content.cc merge_3way.cc merge_content.hh \
legacy.cc legacy.hh uri.cc uri.hh \
graph.cc graph.hh \
@@ -298,34 +298,25 @@ UNIT_TEST_SOURCES = \
# these files contain unit tests
UNIT_TEST_SOURCES = \
basic_io.cc charset.cc commands.cc crypto_tests.cc cset.cc \
- dates.cc merge_3way.cc globish.cc graph.cc netcmd.cc \
- netxx_pipe.cc numeric_vocab.cc option.cc outdated_indicator.cc \
- packet.cc paths.cc refiner.cc restrictions.cc rev_height.cc \
- revision.cc roster.cc merge_roster.cc simplestring_xform.cc \
- string_queue.cc transforms.cc uri.cc vocab.cc xdelta.cc
+ dates.cc globish.cc graph.cc merge_3way.cc merge_roster.cc \
+ netcmd.cc netxx_pipe.cc numeric_vocab.cc option.cc \
+ outdated_indicator.cc packet.cc paths.cc refiner.cc \
+ restrictions.cc rev_height.cc revision.cc roster.cc \
+ simplestring_xform.cc string_queue.cc transforms.cc \
+ unit_tests.cc uri.cc vocab.cc xdelta.cc
# these files do not contain unit tests, but are required for unit testing
# and are not used by the main program
UNIT_TEST_SRC_SUPPORT = \
- randomizer.cc randomizer.hh roster_tests.hh unit_tests.cc
+ randomizer.cc randomizer.hh roster_tests.hh
# these files do not contain unit tests; they are required for unit
-# testing, but can be used "as is" from the main build. (many of
-# these _should_ have unit tests, but they haven't been written yet.)
+# testing, but can be used "as is" from the main build.
UNIT_TEST_OBJ_SUPPORT = \
- mtn-ancestry.$(OBJEXT) mtn-cert.$(OBJEXT) \
- mtn-constants.$(OBJEXT) mtn-database.$(OBJEXT) \
- mtn-epoch.$(OBJEXT) mtn-file_io.$(OBJEXT) mtn-gzip.$(OBJEXT) \
- mtn-hmac.$(OBJEXT) mtn-inodeprint.$(OBJEXT) \
- mtn-key_store.$(OBJEXT) mtn-keys.$(OBJEXT) mtn-lcs.$(OBJEXT) \
- mtn-lua.$(OBJEXT) mtn-lua_hooks.$(OBJEXT) \
- mtn-merge_content.$(OBJEXT) mtn-merkle_tree.$(OBJEXT) \
- mtn-pcrewrap.$(OBJEXT) mtn-project.$(OBJEXT) \
- mtn-roster_delta.$(OBJEXT) mtn-sanity.$(OBJEXT) \
- mtn-schema.$(OBJEXT) mtn-migrate_schema.$(OBJEXT) \
- mtn-specialized_lexical_cast.$(OBJEXT) mtn-ssh_agent.$(OBJEXT) \
- mtn-std_hooks.$(OBJEXT) mtn-ui.$(OBJEXT) mtn-work.$(OBJEXT) \
- mtn-migrate_work.$(OBJEXT)
+ mtn-constants.$(OBJEXT) mtn-file_io.$(OBJEXT) \
+ mtn-gzip.$(OBJEXT) mtn-hmac.$(OBJEXT) mtn-lcs.$(OBJEXT) \
+ mtn-merkle_tree.$(OBJEXT) mtn-roster_delta.$(OBJEXT) \
+ mtn-sanity.$(OBJEXT) mtn-specialized_lexical_cast.$(OBJEXT)
# primaries
============================================================
--- merge_roster.cc eb800b3f9442d5f00790e1f92d1b560b23714a2c
+++ merge_roster.cc 2d60c4d0fee6b0b962cab30aeed3838181c8ed08
@@ -9,52 +9,39 @@
// PURPOSE.
#include "base.hh"
-#include
+#include "merge_roster.hh"
-#include "basic_io.hh"
-#include "file_io.hh"
-#include "lua_hooks.hh"
-#include "options.hh"
+#include "sanity.hh"
+#include "safe_map.hh"
#include "parallel_iter.hh"
-#include "merge_roster.hh"
-#include "safe_map.hh"
-#include "transforms.hh"
-#include "vocab.hh"
-using boost::shared_ptr;
+#include
using std::make_pair;
-using std::ostringstream;
using std::pair;
using std::set;
using std::string;
+using std::ostringstream;
-namespace resolve_conflicts
+enum side_t {left_side, right_side};
+
+static char const *
+image(resolve_conflicts::resolution_t resolution)
{
- boost::shared_ptr
- new_file_path(std::string path)
- {
- return boost::shared_ptr(new file_path(file_path_external(utf8(path, origin::user))));
- };
-
- static char const *
- image(resolve_conflicts::resolution_t resolution)
- {
- switch (resolution)
- {
- case resolve_conflicts::none:
- return "none";
- case resolve_conflicts::content_user:
- return "content_user";
- case resolve_conflicts::content_internal:
- return "content_internal";
- case resolve_conflicts::rename:
- return "rename";
- case resolve_conflicts::drop:
- return "drop";
- }
- I(false); // keep compiler happy
- }
+ switch (resolution)
+ {
+ case resolve_conflicts::none:
+ return "none";
+ case resolve_conflicts::content_user:
+ return "content_user";
+ case resolve_conflicts::content_internal:
+ return "content_internal";
+ case resolve_conflicts::rename:
+ return "rename";
+ case resolve_conflicts::drop:
+ return "drop";
+ }
+ I(false); // keep compiler happy
}
template <> void
@@ -148,6 +135,23 @@ dump(file_content_conflict const & confl
out = oss.str();
}
+void
+roster_merge_result::clear()
+{
+ missing_root_conflict = false;
+ invalid_name_conflicts.clear();
+ directory_loop_conflicts.clear();
+
+ orphaned_node_conflicts.clear();
+ multiple_name_conflicts.clear();
+ duplicate_name_conflicts.clear();
+
+ attribute_conflicts.clear();
+ file_content_conflicts.clear();
+
+ roster = roster_t();
+}
+
bool
roster_merge_result::is_clean() const
{
@@ -188,7 +192,8 @@ dump_conflicts(roster_merge_result const
dump_conflicts(roster_merge_result const & result, string & out)
{
if (result.missing_root_conflict)
- out += (FL("missing_root_conflict: root directory has been removed\n")).str();
+ out += (FL("missing_root_conflict: root directory has been removed\n"))
+ .str();
dump(result.invalid_name_conflicts, out);
dump(result.directory_loop_conflicts, out);
@@ -222,2395 +227,6 @@ namespace
namespace
{
- enum node_type { file_type, dir_type };
-
- node_type
- get_type(roster_t const & roster, node_id const nid)
- {
- node_t n = roster.get_node(nid);
-
- if (is_file_t(n))
- return file_type;
- else if (is_dir_t(n))
- return dir_type;
- else
- I(false);
- }
-
- namespace syms
- {
- symbol const ancestor("ancestor");
- symbol const ancestor_file_id("ancestor_file_id");
- symbol const ancestor_name("ancestor_name");
- symbol const attr_name("attr_name");
- symbol const attribute("attribute");
- symbol const conflict("conflict");
- symbol const content("content");
- symbol const directory_loop("directory_loop");
- symbol const duplicate_name("duplicate_name");
- symbol const invalid_name("invalid_name");
- symbol const left("left");
- symbol const left_attr_state("left_attr_state");
- symbol const left_attr_value("left_attr_value");
- symbol const left_file_id("left_file_id");
- symbol const left_name("left_name");
- symbol const left_type("left_type");
- symbol const missing_root("missing_root");
- symbol const multiple_names("multiple_names");
- symbol const node_type("node_type");
- symbol const orphaned_directory("orphaned_directory");
- symbol const orphaned_file("orphaned_file");
- symbol const resolved_drop_left("resolved_drop_left");
- symbol const resolved_drop_right("resolved_drop_right");
- symbol const resolved_internal("resolved_internal");
- symbol const resolved_rename_left("resolved_rename_left");
- symbol const resolved_rename_right("resolved_rename_right");
- symbol const resolved_user("resolved_user");
- symbol const resolved_user_left("resolved_user_left");
- symbol const resolved_user_right("resolved_user_right");
- symbol const right("right");
- symbol const right_attr_state("right_attr_state");
- symbol const right_attr_value("right_attr_value");
- symbol const right_file_id("right_file_id");
- symbol const right_name("right_name");
- symbol const right_type("right_type");
- }
-}
-
-static void
-put_added_conflict_left(basic_io::stanza & st,
- content_merge_adaptor & adaptor,
- node_id const nid)
-{
- // We access the roster via the adaptor, to be sure we use the left
- // roster; avoids typos in long parameter lists.
-
- // If we get a workspace adaptor here someday, we should add the required
- // access functions to content_merge_adaptor.
-
- content_merge_database_adaptor & db_adaptor (dynamic_cast(adaptor));
- boost::shared_ptr roster(db_adaptor.rosters[db_adaptor.left_rid]);
- file_path name;
-
- roster->get_name (nid, name);
-
- if (file_type == get_type (*roster, nid))
- {
- file_id fid;
- db_adaptor.db.get_file_content (db_adaptor.left_rid, nid, fid);
- st.push_str_pair(syms::left_type, "added file");
- st.push_file_pair(syms::left_name, name);
- st.push_binary_pair(syms::left_file_id, fid.inner());
- }
- else
- {
- st.push_str_pair(syms::left_type, "added directory");
- st.push_file_pair(syms::left_name, name);
- }
-}
-
-static void
-put_added_conflict_right(basic_io::stanza & st,
- content_merge_adaptor & adaptor,
- node_id const nid)
-{
- content_merge_database_adaptor & db_adaptor (dynamic_cast(adaptor));
- boost::shared_ptr roster(db_adaptor.rosters[db_adaptor.right_rid]);
- I(0 != roster);
-
- file_path name;
-
- roster->get_name (nid, name);
-
- if (file_type == get_type (*roster, nid))
- {
- file_id fid;
- db_adaptor.db.get_file_content (db_adaptor.right_rid, nid, fid);
-
- st.push_str_pair(syms::right_type, "added file");
- st.push_file_pair(syms::right_name, name);
- st.push_binary_pair(syms::right_file_id, fid.inner());
- }
- else
- {
- st.push_str_pair(syms::right_type, "added directory");
- st.push_file_pair(syms::right_name, name);
- }
-}
-
-static void
-put_rename_conflict_left(basic_io::stanza & st,
- content_merge_adaptor & adaptor,
- node_id const nid)
-{
- content_merge_database_adaptor & db_adaptor (dynamic_cast(adaptor));
- boost::shared_ptr ancestor_roster(db_adaptor.rosters[db_adaptor.lca]);
- I(0 != ancestor_roster);
- boost::shared_ptr left_roster(db_adaptor.rosters[db_adaptor.left_rid]);
-
- file_path ancestor_name;
- file_path left_name;
-
- ancestor_roster->get_name (nid, ancestor_name);
- left_roster->get_name (nid, left_name);
-
- if (file_type == get_type (*left_roster, nid))
- {
- st.push_str_pair(syms::left_type, "renamed file");
- file_id ancestor_fid;
- db_adaptor.db.get_file_content (db_adaptor.lca, nid, ancestor_fid);
- st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
- st.push_binary_pair(syms::ancestor_file_id, ancestor_fid.inner());
- file_id left_fid;
- db_adaptor.db.get_file_content (db_adaptor.left_rid, nid, left_fid);
- st.push_file_pair(syms::left_name, left_name);
- st.push_binary_pair(syms::left_file_id, left_fid.inner());
- }
- else
- {
- st.push_str_pair(syms::left_type, "renamed directory");
- st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
- st.push_file_pair(syms::left_name, left_name);
- }
-}
-
-static void
-get_nid_name_pair(roster_t const & roster,
- string const & path,
- node_id & nid,
- std::pair & name)
-{
- node_t const node = roster.get_node(file_path_external(utf8(path, origin::internal)));
- nid = node->self;
- name = make_pair (node->parent, node->name);
-}
-
-static void
-read_added_rename_conflict_left(basic_io::parser & pars,
- roster_t const & roster,
- node_id & left_nid,
- std::pair & left_name)
-{
- string tmp;
-
- pars.esym(syms::left_type);
-
- pars.str(tmp);
-
- if (tmp == "renamed file")
- {
- pars.esym(syms::ancestor_name); pars.str();
- pars.esym(syms::ancestor_file_id); pars.hex();
-
- pars.esym(syms::left_name); pars.str(tmp);
- get_nid_name_pair(roster, tmp, left_nid, left_name);
- pars.esym(syms::left_file_id); pars.hex();
- }
- else if (tmp == "renamed directory")
- {
- pars.esym(syms::ancestor_name); pars.str();
- pars.esym(syms::left_name); pars.str(tmp);
- get_nid_name_pair(roster, tmp, left_nid, left_name);
- }
- else if (tmp == "added file")
- {
- pars.esym(syms::left_name); pars.str(tmp);
- get_nid_name_pair(roster, tmp, left_nid, left_name);
- pars.esym(syms::left_file_id); pars.hex();
- }
- else if (tmp == "added directory")
- {
- pars.esym(syms::left_name); pars.str(tmp);
- get_nid_name_pair(roster, tmp, left_nid, left_name);
- }
-} // read_added_rename_conflict_left
-
-static void
-read_added_rename_conflict_right(basic_io::parser & pars,
- roster_t const & roster,
- node_id & right_nid,
- std::pair & right_name)
-{
- string tmp;
-
- pars.esym(syms::right_type);
-
- pars.str(tmp);
-
- if (tmp == "renamed file")
- {
- pars.esym(syms::ancestor_name); pars.str();
- pars.esym(syms::ancestor_file_id); pars.hex();
-
- pars.esym(syms::right_name); pars.str(tmp);
- get_nid_name_pair(roster, tmp, right_nid, right_name);
- pars.esym(syms::right_file_id); pars.hex();
- }
- else if (tmp == "renamed directory")
- {
- pars.esym(syms::ancestor_name); pars.str();
- pars.esym(syms::right_name); pars.str(tmp);
- get_nid_name_pair(roster, tmp, right_nid, right_name);
- }
- else if (tmp == "added file")
- {
- pars.esym(syms::right_name); pars.str(tmp);
- get_nid_name_pair(roster, tmp, right_nid, right_name);
- pars.esym(syms::right_file_id); pars.hex();
- }
- else if (tmp == "added directory")
- {
- pars.esym(syms::right_name); pars.str(tmp);
- get_nid_name_pair(roster, tmp, right_nid, right_name);
- }
-}
-
-static void
-put_rename_conflict_right (basic_io::stanza & st,
- content_merge_adaptor & adaptor,
- node_id const nid)
-{
- content_merge_database_adaptor & db_adaptor (dynamic_cast(adaptor));
- boost::shared_ptr ancestor_roster(db_adaptor.rosters[db_adaptor.lca]);
- I(0 != ancestor_roster);
- boost::shared_ptr right_roster(db_adaptor.rosters[db_adaptor.right_rid]);
- I(0 != right_roster);
-
- file_path ancestor_name;
- file_path right_name;
-
- ancestor_roster->get_name (nid, ancestor_name);
- right_roster->get_name (nid, right_name);
-
- if (file_type == get_type (*right_roster, nid))
- {
- st.push_str_pair(syms::right_type, "renamed file");
- file_id ancestor_fid;
- db_adaptor.db.get_file_content (db_adaptor.lca, nid, ancestor_fid);
- st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
- st.push_binary_pair(syms::ancestor_file_id, ancestor_fid.inner());
- file_id right_fid;
- db_adaptor.db.get_file_content (db_adaptor.right_rid, nid, right_fid);
- st.push_file_pair(syms::right_name, right_name);
- st.push_binary_pair(syms::right_file_id, right_fid.inner());
- }
- else
- {
- st.push_str_pair(syms::right_type, "renamed directory");
- st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
- st.push_file_pair(syms::right_name, right_name);
- }
-}
-
-static void
-put_attr_state_left (basic_io::stanza & st, attribute_conflict const & conflict)
-{
- if (conflict.left.first)
- st.push_str_pair(syms::left_attr_value, conflict.left.second());
- else
- st.push_str_pair(syms::left_attr_state, "dropped");
-}
-
-static void
-put_attr_state_right (basic_io::stanza & st, attribute_conflict const & conflict)
-{
- if (conflict.right.first)
- st.push_str_pair(syms::right_attr_value, conflict.right.second());
- else
- st.push_str_pair(syms::right_attr_state, "dropped");
-}
-
-static void
-put_attr_conflict (basic_io::stanza & st,
- content_merge_adaptor & adaptor,
- attribute_conflict const & conflict)
-{
- // Always report ancestor, left, and right information, for completeness
-
- content_merge_database_adaptor & db_adaptor (dynamic_cast(adaptor));
-
- // This ensures that the ancestor roster is computed
- boost::shared_ptr ancestor_roster;
- revision_id ancestor_rid;
- db_adaptor.get_ancestral_roster (conflict.nid, ancestor_rid, ancestor_roster);
-
- boost::shared_ptr left_roster(db_adaptor.rosters[db_adaptor.left_rid]);
- I(0 != left_roster);
- boost::shared_ptr right_roster(db_adaptor.rosters[db_adaptor.right_rid]);
- I(0 != right_roster);
-
- file_path ancestor_name;
- file_path left_name;
- file_path right_name;
-
- ancestor_roster->get_name (conflict.nid, ancestor_name);
- left_roster->get_name (conflict.nid, left_name);
- right_roster->get_name (conflict.nid, right_name);
-
- if (file_type == get_type (*ancestor_roster, conflict.nid))
- {
- st.push_str_pair(syms::node_type, "file");
- st.push_str_pair(syms::attr_name, conflict.key());
- file_id ancestor_fid;
- db_adaptor.db.get_file_content (db_adaptor.lca, conflict.nid, ancestor_fid);
- st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
- st.push_binary_pair(syms::ancestor_file_id, ancestor_fid.inner());
- file_id left_fid;
- db_adaptor.db.get_file_content (db_adaptor.left_rid, conflict.nid, left_fid);
- st.push_file_pair(syms::left_name, left_name);
- st.push_binary_pair(syms::left_file_id, left_fid.inner());
- put_attr_state_left (st, conflict);
- file_id right_fid;
- db_adaptor.db.get_file_content (db_adaptor.right_rid, conflict.nid, right_fid);
- st.push_file_pair(syms::right_name, right_name);
- st.push_binary_pair(syms::right_file_id, right_fid.inner());
- put_attr_state_right (st, conflict);
- }
- else
- {
- st.push_str_pair(syms::node_type, "directory");
- st.push_str_pair(syms::attr_name, conflict.key());
- st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
- st.push_file_pair(syms::left_name, left_name);
- put_attr_state_left (st, conflict);
- st.push_file_pair(syms::right_name, right_name);
- put_attr_state_right (st, conflict);
- }
-}
-
-enum side_t {left_side, right_side};
-
-static void
-put_duplicate_name_resolution(basic_io::stanza & st,
- side_t side,
- resolve_conflicts::file_resolution_t const & resolution)
-{
- switch (resolution.first)
- {
- case resolve_conflicts::none:
- break;
-
- case resolve_conflicts::content_user:
- switch (side)
- {
- case left_side:
- st.push_str_pair(syms::resolved_user_left, resolution.second->as_external());
- break;
-
- case right_side:
- st.push_str_pair(syms::resolved_user_right, resolution.second->as_external());
- break;
- }
- break;
-
- case resolve_conflicts::rename:
- switch (side)
- {
- case left_side:
- st.push_str_pair(syms::resolved_rename_left, resolution.second->as_external());
- break;
-
- case right_side:
- st.push_str_pair(syms::resolved_rename_right, resolution.second->as_external());
- break;
- }
- break;
-
- case resolve_conflicts::drop:
- switch (side)
- {
- case left_side:
- st.push_symbol(syms::resolved_drop_left);
- break;
-
- case right_side:
- st.push_symbol(syms::resolved_drop_right);
- break;
- }
- break;
-
- default:
- I(false);
- }
-}
-
-static void
-put_content_conflict (basic_io::stanza & st,
- roster_t const & left_roster,
- roster_t const & right_roster,
- content_merge_adaptor & adaptor,
- file_content_conflict const & conflict)
-{
- // Always report ancestor, left, and right information, for completeness
-
- content_merge_database_adaptor & db_adaptor (dynamic_cast(adaptor));
-
- // This ensures that the ancestor roster is computed
- boost::shared_ptr ancestor_roster;
- revision_id ancestor_rid;
- db_adaptor.get_ancestral_roster (conflict.nid, ancestor_rid, ancestor_roster);
-
- file_path ancestor_name;
- file_path left_name;
- file_path right_name;
-
- ancestor_roster->get_name (conflict.nid, ancestor_name);
- left_roster.get_name (conflict.nid, left_name);
- right_roster.get_name (conflict.nid, right_name);
-
- if (file_type == get_type (*ancestor_roster, conflict.nid))
- {
- st.push_str_pair(syms::node_type, "file");
- file_id ancestor_fid;
- db_adaptor.db.get_file_content (db_adaptor.lca, conflict.nid, ancestor_fid);
- st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
- st.push_binary_pair(syms::ancestor_file_id, ancestor_fid.inner());
- st.push_file_pair(syms::left_name, left_name);
- file_id left_fid;
- db_adaptor.db.get_file_content (db_adaptor.left_rid, conflict.nid, left_fid);
- st.push_binary_pair(syms::left_file_id, left_fid.inner());
- st.push_file_pair(syms::right_name, right_name);
- file_id right_fid;
- db_adaptor.db.get_file_content (db_adaptor.right_rid, conflict.nid, right_fid);
- st.push_binary_pair(syms::right_file_id, right_fid.inner());
- switch (conflict.resolution.first)
- {
- case resolve_conflicts::none:
- break;
-
- case resolve_conflicts::content_internal:
- st.push_symbol(syms::resolved_internal);
- break;
-
- case resolve_conflicts::content_user:
- st.push_str_pair(syms::resolved_user, conflict.resolution.second->as_external());
- break;
-
- default:
- I(false);
- }
- }
- else
- {
- st.push_str_pair(syms::node_type, "directory");
- st.push_str_pair(syms::ancestor_name, ancestor_name.as_external());
- st.push_file_pair(syms::left_name, left_name);
- st.push_file_pair(syms::right_name, right_name);
-
- switch (conflict.resolution.first)
- {
- case resolve_conflicts::none:
- break;
-
- default:
- // not implemented yet
- I(false);
- }
- }
-}
-
-static void
-put_stanza (basic_io::stanza & st,
- std::ostream & output)
-{
- // We have to declare the printer here, rather than more globally,
- // because adaptor.get_ancestral_roster uses a basic_io::printer
- // internally, and there can only be one active at a time.
- basic_io::printer pr;
- output << "\n";
- pr.print_stanza(st);
- output.write(pr.buf.data(), pr.buf.size());
-}
-
-void
-roster_merge_result::report_missing_root_conflicts(roster_t const & left_roster,
- roster_t const & right_roster,
- content_merge_adaptor & adaptor,
- bool const basic_io,
- std::ostream & output) const
-{
- MM(left_roster);
- MM(right_roster);
-
- if (missing_root_conflict)
- {
- node_id left_root, right_root;
- left_root = left_roster.root()->self;
- right_root = right_roster.root()->self;
-
- // these must be different for this conflict to happen
- I(left_root != right_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);
-
- node_id left_lca_root = left_lca_roster->root()->self;
- node_id right_lca_root = right_lca_roster->root()->self;
-
- basic_io::stanza st;
-
- if (basic_io)
- st.push_str_pair(syms::conflict, syms::missing_root);
- else
- P(F("conflict: missing root directory"));
-
- if (left_root != left_lca_root && right_root == right_lca_root)
- {
- if (basic_io)
- {
- st.push_str_pair(syms::left_type, "pivoted root");
- st.push_str_pair(syms::ancestor_name, left_lca_name.as_external());
- }
- else
- P(F("directory '%s' pivoted to root on the left") % left_lca_name);
-
- if (!right_roster.has_node(left_root))
- {
- if (basic_io)
- {
- st.push_str_pair(syms::right_type, "deleted directory");
- st.push_str_pair(syms::ancestor_name, left_lca_name.as_external());
- }
- else
- P(F("directory '%s' deleted on the right") % left_lca_name);
- }
- }
- else if (left_root == left_lca_root && right_root != right_lca_root)
- {
- if (!left_roster.has_node(right_root))
- {
- if (basic_io)
- {
- st.push_str_pair(syms::left_type, "deleted directory");
- st.push_str_pair(syms::ancestor_name, right_lca_name.as_external());
- }
- else
- P(F("directory '%s' deleted on the left") % right_lca_name);
- }
-
- if (basic_io)
- {
- st.push_str_pair(syms::right_type, "pivoted root");
- st.push_str_pair(syms::ancestor_name, right_lca_name.as_external());
- }
- else
- P(F("directory '%s' pivoted to root on the right") % right_lca_name);
- }
- else if (left_root != left_lca_root && right_root != right_lca_root)
- {
- if (basic_io)
- {
- st.push_str_pair(syms::left_type, "pivoted root");
- st.push_str_pair(syms::ancestor_name, left_lca_name.as_external());
- }
- else
- P(F("directory '%s' pivoted to root on the left") % left_lca_name);
-
- if (!right_roster.has_node(left_root))
- {
- if (basic_io)
- {
- st.push_str_pair(syms::right_type, "deleted directory");
- st.push_str_pair(syms::ancestor_name, left_lca_name.as_external());
- }
- else
- P(F("directory '%s' deleted on the right") % left_lca_name);
- }
-
- if (!left_roster.has_node(right_root))
- {
- if (basic_io)
- {
- st.push_str_pair(syms::left_type, "deleted directory");
- st.push_str_pair(syms::ancestor_name, right_lca_name.as_external());
- }
- else
- P(F("directory '%s' deleted on the left") % right_lca_name);
- }
-
- if (basic_io)
- {
- st.push_str_pair(syms::right_type, "pivoted root");
- st.push_str_pair(syms::ancestor_name, right_lca_name.as_external());
- }
- else
- P(F("directory '%s' pivoted to root on the right") % right_lca_name);
- }
- // else
- // other conflicts can cause the root dir to be left detached
- // for example, merging two independently created projects
- // in these cases don't report anything about pivot_root
-
- if (basic_io)
- put_stanza (st, output);
- }
-}
-
-void
-roster_merge_result::report_invalid_name_conflicts(roster_t const & left_roster,
- roster_t const & right_roster,
- content_merge_adaptor & adaptor,
- bool basic_io,
- std::ostream & output) const
-{
- MM(left_roster);
- MM(right_roster);
-
- for (size_t i = 0; i < invalid_name_conflicts.size(); ++i)
- {
- invalid_name_conflict const & conflict = invalid_name_conflicts[i];
- MM(conflict);
-
- I(!roster.is_attached(conflict.nid));
-
- shared_ptr lca_roster, parent_lca_roster;
- revision_id lca_rid, parent_lca_rid;
- file_path lca_name, lca_parent_name;
- basic_io::stanza st;
-
- 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);
-
- if (basic_io)
- st.push_str_pair(syms::conflict, syms::invalid_name);
- else
- P(F("conflict: invalid name _MTN in root directory"));
-
- if (left_roster.root()->self == conflict.parent_name.first)
- {
- if (basic_io)
- {
- st.push_str_pair(syms::left_type, "pivoted root");
- st.push_str_pair(syms::ancestor_name, lca_parent_name.as_external());
- }
- else
- 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))
- {
- if (basic_io)
- put_rename_conflict_right (st, adaptor, conflict.nid);
- else
- P(F("'%s' renamed to '%s' on the right")
- % lca_name % right_name);
- }
- else
- {
- if (basic_io)
- put_added_conflict_right (st, adaptor, conflict.nid);
- 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)
- {
- if (basic_io)
- {
- st.push_str_pair(syms::right_type, "pivoted root");
- st.push_str_pair(syms::ancestor_name, lca_parent_name.as_external());
- }
- else
- 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))
- {
- if (basic_io)
- put_rename_conflict_left (st, adaptor, conflict.nid);
- else
- P(F("'%s' renamed to '%s' on the left")
- % lca_name % left_name);
- }
- else
- {
- if (basic_io)
- put_added_conflict_left (st, adaptor, conflict.nid);
- else
- P(F("'%s' added in revision %s on the left")
- % left_name % lca_rid);
- }
- }
- else
- I(false);
-
- if (basic_io)
- put_stanza(st, output);
- }
-}
-
-void
-roster_merge_result::report_directory_loop_conflicts(roster_t const & left_roster,
- roster_t const & right_roster,
- content_merge_adaptor & adaptor,
- bool basic_io,
- std::ostream & output) const
-{
- MM(left_roster);
- MM(right_roster);
-
- for (size_t i = 0; i < directory_loop_conflicts.size(); ++i)
- {
- directory_loop_conflict const & conflict = directory_loop_conflicts[i];
- MM(conflict);
-
- I(!roster.is_attached(conflict.nid));
-
- 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;
- basic_io::stanza st;
-
- 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);
-
- if (basic_io)
- st.push_str_pair(syms::conflict, syms::directory_loop);
- else
- P(F("conflict: directory loop created"));
-
- if (left_name != lca_name)
- {
- if (basic_io)
- put_rename_conflict_left (st, adaptor, conflict.nid);
- else
- P(F("'%s' renamed to '%s' on the left")
- % lca_name % left_name);
- }
-
- if (right_name != lca_name)
- {
- if (basic_io)
- put_rename_conflict_right (st, adaptor, conflict.nid);
- else
- P(F("'%s' renamed to '%s' on the right")
- % lca_name % right_name);
- }
-
- if (left_parent_name != lca_parent_name)
- {
- if (basic_io)
- put_rename_conflict_left (st, adaptor, conflict.parent_name.first);
- else
- P(F("'%s' renamed to '%s' on the left")
- % lca_parent_name % left_parent_name);
- }
-
- if (right_parent_name != lca_parent_name)
- {
- if (basic_io)
- put_rename_conflict_right (st, adaptor, conflict.parent_name.first);
- else
- P(F("'%s' renamed to '%s' on the right")
- % lca_parent_name % right_parent_name);
- }
-
- if (basic_io)
- put_stanza(st, output);
- }
-}
-
-void
-roster_merge_result::report_orphaned_node_conflicts(roster_t const & left_roster,
- roster_t const & right_roster,
- content_merge_adaptor & adaptor,
- bool basic_io,
- std::ostream & output) const
-{
- MM(left_roster);
- MM(right_roster);
-
- for (size_t i = 0; i < orphaned_node_conflicts.size(); ++i)
- {
- orphaned_node_conflict const & conflict = orphaned_node_conflicts[i];
- MM(conflict);
-
- I(!roster.is_attached(conflict.nid));
-
- 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);
-
- node_type type = get_type(*lca_roster, conflict.nid);
-
- basic_io::stanza st;
-
- if (type == file_type)
- if (basic_io)
- st.push_str_pair(syms::conflict, syms::orphaned_file);
- else
- P(F("conflict: orphaned file '%s' from revision %s")
- % lca_name % lca_rid);
- else
- {
- if (basic_io)
- st.push_str_pair(syms::conflict, syms::orphaned_directory);
- else
- P(F("conflict: orphaned directory '%s' from revision %s")
- % 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);
-
- if (basic_io)
- {
- st.push_str_pair(syms::right_type, "deleted directory");
- st.push_str_pair(syms::ancestor_name, parent_name.as_external());
- }
- else
- P(F("parent directory '%s' was deleted on the right")
- % parent_name);
-
- if (parent_lca_roster->has_node(conflict.nid))
- {
- if (basic_io)
- put_rename_conflict_left (st, adaptor, conflict.nid);
- else
- if (type == file_type)
- P(F("file '%s' was renamed from '%s' on the left")
- % orphan_name % lca_name);
- else
- P(F("directory '%s' was renamed from '%s' on the left")
- % orphan_name % lca_name);
- }
- else
- {
- if (basic_io)
- put_added_conflict_left (st, adaptor, conflict.nid);
- else
- {
- if (type == file_type)
- P(F("file '%s' was added on the left")
- % orphan_name);
- else
- P(F("directory '%s' was added on the left")
- % 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);
-
- if (basic_io)
- {
- st.push_str_pair(syms::left_type, "deleted directory");
- st.push_str_pair(syms::ancestor_name, parent_name.as_external());
- }
- else
- P(F("parent directory '%s' was deleted on the left")
- % parent_name);
-
- if (parent_lca_roster->has_node(conflict.nid))
- {
- if (basic_io)
- put_rename_conflict_right (st, adaptor, conflict.nid);
- else
- if (type == file_type)
- P(F("file '%s' was renamed from '%s' on the right")
- % orphan_name % lca_name);
- else
- P(F("directory '%s' was renamed from '%s' on the right")
- % orphan_name % lca_name);
- }
- else
- {
- if (basic_io)
- put_added_conflict_right (st, adaptor, conflict.nid);
- else
- if (type == file_type)
- P(F("file '%s' was added on the right")
- % orphan_name);
- else
- P(F("directory '%s' was added on the right")
- % orphan_name);
- }
- }
- else
- I(false);
-
- if (basic_io)
- put_stanza (st, output);
- }
-}
-
-void
-roster_merge_result::report_multiple_name_conflicts(roster_t const & left_roster,
- roster_t const & right_roster,
- content_merge_adaptor & adaptor,
- bool basic_io,
- std::ostream & output) const
-{
- MM(left_roster);
- MM(right_roster);
-
- for (size_t i = 0; i < multiple_name_conflicts.size(); ++i)
- {
- multiple_name_conflict const & conflict = multiple_name_conflicts[i];
- MM(conflict);
-
- I(!roster.is_attached(conflict.nid));
-
- file_path left_name, right_name;
-
- left_roster.get_name(conflict.nid, left_name);
- right_roster.get_name(conflict.nid, 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);
-
- node_type type = get_type(*lca_roster, conflict.nid);
-
- basic_io::stanza st;
-
- if (basic_io)
- {
- st.push_str_pair(syms::conflict, syms::multiple_names);
- put_rename_conflict_left (st, adaptor, conflict.nid);
- put_rename_conflict_right (st, adaptor, conflict.nid);
- }
- else
- {
- if (type == file_type)
- P(F("conflict: multiple names for file '%s' from revision %s")
- % lca_name % lca_rid);
- else
- P(F("conflict: multiple names for directory '%s' from revision %s")
- % lca_name % lca_rid);
-
- P(F("renamed to '%s' on the left") % left_name);
- P(F("renamed to '%s' on the right") % right_name);
- }
-
- if (basic_io)
- put_stanza(st, output);
- }
-}
-
-void
-roster_merge_result::report_duplicate_name_conflicts(roster_t const & left_roster,
- roster_t const & right_roster,
- content_merge_adaptor & adaptor,
- bool const basic_io,
- std::ostream & output) const
-{
- MM(left_roster);
- MM(right_roster);
-
- for (size_t i = 0; i < duplicate_name_conflicts.size(); ++i)
- {
- duplicate_name_conflict const & conflict = duplicate_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_roster.get_name(left_nid, left_name);
- right_roster.get_name(right_nid, right_name);
-
- shared_ptr left_lca_roster, right_lca_roster;
- revision_id left_lca_rid, right_lca_rid;
-
- adaptor.get_ancestral_roster(left_nid, left_lca_rid, left_lca_roster);
- adaptor.get_ancestral_roster(right_nid, right_lca_rid, right_lca_roster);
-
- // In most cases, the left_name equals the right_name. However, maybe
- // a parent directory got renamed on one side. In that case, the names
- // don't match, but it's still the same directory (by node id), to
- // which we want to add the same file (by name).
-
- basic_io::stanza st;
-
- if (basic_io)
- st.push_str_pair(syms::conflict, syms::duplicate_name);
- else
- {
- if (left_name == right_name)
- {
- file_path dir;
- path_component basename;
- left_name.dirname_basename(dir, basename);
- P(F("conflict: duplicate name '%s' for the directory '%s'") % basename % dir);
- }
- else
- {
- file_path left_dir, right_dir;
- path_component left_basename, right_basename;
- left_name.dirname_basename(left_dir, left_basename);
- right_name.dirname_basename(right_dir, right_basename);
- I(left_basename == right_basename);
- P(F("conflict: duplicate name '%s' for the directory\n"
- " named '%s' on the left and\n"
- " named '%s' on the right.")
- % left_basename % left_dir % right_dir);
- }
- }
-
- node_type left_type = get_type(left_roster, left_nid);
- node_type right_type = get_type(right_roster, right_nid);
-
- if (!left_lca_roster->has_node(right_nid) &&
- !right_lca_roster->has_node(left_nid))
- {
- if (basic_io)
- put_added_conflict_left (st, adaptor, left_nid);
- else
- {
- if (left_type == file_type)
- P(F("added as a new file on the left"));
- else
- P(F("added as a new directory on the left"));
- }
-
- if (basic_io)
- put_added_conflict_right (st, adaptor, right_nid);
- else
- {
- if (right_type == file_type)
- P(F("added as a new file on the right"));
- else
- P(F("added as a new directory on the right"));
- }
- }
- 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);
-
- if (basic_io)
- put_rename_conflict_left (st, adaptor, left_nid);
- else
- if (left_type == file_type)
- P(F("renamed from file '%s' on the left") % left_lca_name);
- else
- P(F("renamed from directory '%s' on the left") % left_lca_name);
-
- if (basic_io)
- put_added_conflict_right (st, adaptor, right_nid);
- else
- {
- if (right_type == file_type)
- P(F("added as a new file on the right"));
- else
- P(F("added as a new directory on the right"));
- }
- }
- 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);
-
- if (basic_io)
- put_added_conflict_left (st, adaptor, left_nid);
- else
- {
- if (left_type == file_type)
- P(F("added as a new file on the left"));
- else
- P(F("added as a new directory on the left"));
- }
-
- if (basic_io)
- put_rename_conflict_right (st, adaptor, right_nid);
- else
- {
- if (right_type == file_type)
- P(F("renamed from file '%s' on the right") % right_lca_name);
- else
- P(F("renamed from directory '%s' on the right") % 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);
-
- if (basic_io)
- put_rename_conflict_left (st, adaptor, left_nid);
- else
- {
- if (left_type == file_type)
- P(F("renamed from file '%s' on the left") % left_lca_name);
- else
- P(F("renamed from directory '%s' on the left") % left_lca_name);
- }
-
- if (basic_io)
- put_rename_conflict_right (st, adaptor, right_nid);
- else
- {
- if (right_type == file_type)
- P(F("renamed from file '%s' on the right") % right_lca_name);
- else
- P(F("renamed from directory '%s' on the right") % right_lca_name);
- }
- }
- else
- I(false);
-
- if (basic_io)
- {
- put_duplicate_name_resolution (st, left_side, conflict.left_resolution);
- put_duplicate_name_resolution (st, right_side, conflict.right_resolution);
- put_stanza(st, output);
- }
- }
-}
-
-void
-roster_merge_result::report_attribute_conflicts(roster_t const & left_roster,
- roster_t const & right_roster,
- content_merge_adaptor & adaptor,
- bool basic_io,
- std::ostream & output) const
-{
- MM(left_roster);
- MM(right_roster);
- MM(roster);
-
- for (size_t i = 0; i < attribute_conflicts.size(); ++i)
- {
- attribute_conflict const & conflict = attribute_conflicts[i];
- MM(conflict);
-
- if (basic_io)
- {
- basic_io::stanza st;
-
- st.push_str_pair(syms::conflict, syms::attribute);
- put_attr_conflict (st, adaptor, conflict);
- put_stanza (st, output);
- }
- else
- {
- // this->roster is null when we are called from 'conflicts
- // show_remaining'; treat as unattached in that case.
- node_type type = get_type(left_roster, conflict.nid);
-
- if (roster.all_nodes().size() > 0 && roster.is_attached(conflict.nid))
- {
- file_path name;
- roster.get_name(conflict.nid, name);
-
- if (type == file_type)
- P(F("conflict: multiple values for attribute '%s' on file '%s'")
- % conflict.key % name);
- else
- P(F("conflict: multiple values for attribute '%s' on directory '%s'")
- % conflict.key % 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
- {
- // This node isn't attached in the merged roster, due to another
- // conflict (ie renamed to different names). So report the
- // ancestor name and the left and right names.
-
- file_path left_name, right_name;
- left_roster.get_name(conflict.nid, left_name);
- right_roster.get_name(conflict.nid, 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);
-
- if (type == file_type)
- P(F("conflict: multiple values for attribute '%s' on file '%s' from revision %s")
- % conflict.key % lca_name % lca_rid);
- else
- P(F("conflict: multiple values for attribute '%s' on directory '%s' from revision %s")
- % conflict.key % lca_name % lca_rid);
-
- if (conflict.left.first)
- {
- if (type == file_type)
- P(F("set to '%s' on left file '%s'")
- % conflict.left.second % left_name);
- else
- P(F("set to '%s' on left directory '%s'")
- % conflict.left.second % left_name);
- }
- else
- {
- if (type == file_type)
- P(F("deleted from left file '%s'")
- % left_name);
- else
- P(F("deleted from left directory '%s'")
- % left_name);
- }
-
- if (conflict.right.first)
- {
- if (type == file_type)
- P(F("set to '%s' on right file '%s'")
- % conflict.right.second % right_name);
- else
- P(F("set to '%s' on right directory '%s'")
- % conflict.right.second % right_name);
- }
- else
- {
- if (type == file_type)
- P(F("deleted from right file '%s'")
- % right_name);
- else
- P(F("deleted from right directory '%s'")
- % right_name);
- }
- }
- }
- }
-}
-
-namespace
-{
- bool
- auto_merge_succeeds(lua_hooks & lua,
- file_content_conflict conflict,
- content_merge_adaptor & adaptor,
- roster_t const & left_roster,
- roster_t const & right_roster)
- {
- revision_id ancestor_rid;
- shared_ptr ancestor_roster;
- adaptor.get_ancestral_roster(conflict.nid, ancestor_rid, ancestor_roster);
-
- I(ancestor_roster);
- I(ancestor_roster->has_node(conflict.nid)); // this fails if there is no least common ancestor
-
- file_id anc_id, left_id, right_id;
- file_path anc_path, left_path, right_path;
- ancestor_roster->get_file_details(conflict.nid, anc_id, anc_path);
- left_roster.get_file_details(conflict.nid, left_id, left_path);
- right_roster.get_file_details(conflict.nid, right_id, right_path);
-
- content_merger cm(lua, *ancestor_roster, left_roster, right_roster, adaptor);
-
- file_data left_data, right_data, merge_data;
-
- return cm.attempt_auto_merge(anc_path, left_path, right_path,
- anc_id, left_id, right_id,
- left_data, right_data, merge_data);
- }
-}
-
-void
-roster_merge_result::report_file_content_conflicts(lua_hooks & lua,
- roster_t const & left_roster,
- roster_t const & right_roster,
- content_merge_adaptor & adaptor,
- bool basic_io,
- std::ostream & output)
-{
- MM(left_roster);
- MM(right_roster);
-
- for (size_t i = 0; i < file_content_conflicts.size(); ++i)
- {
- file_content_conflict & conflict = file_content_conflicts[i];
- MM(conflict);
-
- if (basic_io)
- {
- basic_io::stanza st;
-
- if (conflict.resolution.first == resolve_conflicts::none)
- if (auto_merge_succeeds(lua, conflict, adaptor, left_roster, right_roster))
- conflict.resolution.first = resolve_conflicts::content_internal;
-
- st.push_str_pair(syms::conflict, syms::content);
- put_content_conflict (st, left_roster, right_roster, adaptor, conflict);
- put_stanza (st, output);
- }
- else
- {
- if (roster.is_attached(conflict.nid))
- {
- file_path name;
- roster.get_name(conflict.nid, name);
-
- P(F("conflict: content conflict on file '%s'")
- % name);
- P(F("content hash is %s on the left") % conflict.left);
- P(F("content hash is %s on the right") % conflict.right);
- }
- else
- {
- // 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
-
- file_path left_name, right_name;
- left_roster.get_name(conflict.nid, left_name);
- right_roster.get_name(conflict.nid, 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: content conflict on file '%s' from revision %s")
- % lca_name % lca_rid);
- P(F("content hash is %s on the left in file '%s'")
- % conflict.left % left_name);
- P(F("content hash is %s on the right in file '%s'")
- % conflict.right % right_name);
- }
- }
- }
-}
-
-// Resolving non-content conflicts
-
-namespace resolve_conflicts
-{
- bool
- do_auto_merge(lua_hooks & lua,
- file_content_conflict const & conflict,
- content_merge_adaptor & adaptor,
- roster_t const & left_roster,
- roster_t const & right_roster,
- roster_t const & result_roster,
- file_id & merged_id)
- {
- revision_id ancestor_rid;
- shared_ptr ancestor_roster;
- adaptor.get_ancestral_roster(conflict.nid, ancestor_rid, ancestor_roster);
-
- I(ancestor_roster);
- I(ancestor_roster->has_node(conflict.nid)); // this fails if there is no least common ancestor
-
- file_id anc_id, left_id, right_id;
- file_path anc_path, left_path, right_path, merged_path;
- ancestor_roster->get_file_details(conflict.nid, anc_id, anc_path);
- left_roster.get_file_details(conflict.nid, left_id, left_path);
- right_roster.get_file_details(conflict.nid, right_id, right_path);
- result_roster.get_file_details(conflict.nid, merged_id, merged_path);
-
- content_merger cm(lua, *ancestor_roster, left_roster, right_roster, adaptor);
-
- return cm.try_auto_merge(anc_path, left_path, right_path, merged_path,
- anc_id, left_id, right_id, merged_id);
- }
-}
-
-static char const * const conflicts_mismatch_msg = "conflicts file does not match current conflicts";
-static char const * const conflict_resolution_not_supported_msg = "%s is not a supported conflict resolution for %s";
-static char const * const conflict_extra = "extra chars at end of conflict";
-
-static void
-read_missing_root_conflicts(basic_io::parser & pars,
- bool & missing_root_conflict,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- // There can be only one of these
- if (pars.tok.in.lookahead != EOF && pars.symp(syms::missing_root))
- {
- pars.sym();
-
- if (pars.symp(syms::left_type))
- {
- pars.sym(); pars.str();
- pars.esym(syms::ancestor_name); pars.str();
- pars.esym(syms::right_type); pars.str();
- pars.esym(syms::ancestor_name); pars.str();
- }
- // else unrelated projects (branches); nothing else output
-
- missing_root_conflict = true;
-
- if (pars.tok.in.lookahead != EOF)
- pars.esym (syms::conflict);
- }
- else
- {
- missing_root_conflict = false;
- }
-} // read_missing_root_conflicts
-
-static void
-read_invalid_name_conflict(basic_io::parser & pars,
- invalid_name_conflict & conflict,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- if (pars.symp(syms::left_type))
- {
- pars.sym();
- pars.str(); // "pivoted root"
- pars.esym(syms::ancestor_name); pars.str(); // lca_parent_name
- read_added_rename_conflict_right (pars, right_roster, conflict.nid, conflict.parent_name);
- }
- else
- {
- pars.esym(syms::right_type);
- pars.str(); // "pivoted root"
- pars.esym(syms::ancestor_name); pars.str(); // lca_parent_name
- read_added_rename_conflict_left (pars, left_roster, conflict.nid, conflict.parent_name);
- }
-} // read_invalid_name_conflict
-
-static void
-read_invalid_name_conflicts(basic_io::parser & pars,
- std::vector & conflicts,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- while (pars.tok.in.lookahead != EOF && pars.symp(syms::invalid_name))
- {
- invalid_name_conflict conflict;
-
- pars.sym();
-
- read_invalid_name_conflict(pars, conflict, left_roster, right_roster);
-
- conflicts.push_back(conflict);
-
- if (pars.tok.in.lookahead != EOF)
- pars.esym (syms::conflict);
- }
-} // read_invalid_name_conflicts
-
-static void
-read_directory_loop_conflict(basic_io::parser & pars,
- directory_loop_conflict & conflict,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- string tmp;
-
- // syms::directory_loop has been read
-
- if (pars.symp(syms::left_type))
- {
- read_added_rename_conflict_left(pars, left_roster, conflict.nid, conflict.parent_name);
- }
- if (pars.symp(syms::right_type))
- {
- read_added_rename_conflict_right(pars, right_roster, conflict.nid, conflict.parent_name);
- }
-
- if (pars.symp(syms::left_type))
- {
- pars.sym();
- pars.str(); // "renamed directory"
- pars.esym(syms::ancestor_name); pars.str();
- pars.esym(syms::left_name); pars.str();
- }
- if (pars.symp(syms::right_type))
- {
- pars.sym();
- pars.str(); // "renamed directory"
- pars.esym(syms::ancestor_name); pars.str();
- pars.esym(syms::right_name); pars.str();
- }
-
-} // read_directory_loop_conflict
-
-static void
-read_directory_loop_conflicts(basic_io::parser & pars,
- std::vector & conflicts,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- while (pars.tok.in.lookahead != EOF && pars.symp(syms::directory_loop))
- {
- directory_loop_conflict conflict;
-
- pars.sym();
-
- read_directory_loop_conflict(pars, conflict, left_roster, right_roster);
-
- conflicts.push_back(conflict);
-
- if (pars.tok.in.lookahead != EOF)
- pars.esym (syms::conflict);
- }
-} // read_directory_loop_conflicts
-
-
-static void
-read_orphaned_node_conflict(basic_io::parser & pars,
- orphaned_node_conflict & conflict,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- if (pars.symp(syms::left_type))
- {
- pars.sym(); pars.str(); // "deleted directory | file"
- pars.esym(syms::ancestor_name); pars.str();
- read_added_rename_conflict_right(pars, right_roster, conflict.nid, conflict.parent_name);
- }
- else
- {
- pars.esym(syms::right_type);
- pars.str(); // "deleted directory | file"
- pars.esym(syms::ancestor_name); pars.str();
- read_added_rename_conflict_left(pars, left_roster, conflict.nid, conflict.parent_name);
- }
-} // read_orphaned_node_conflict
-
-static void
-read_orphaned_node_conflicts(basic_io::parser & pars,
- std::vector & conflicts,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- while (pars.tok.in.lookahead != EOF && (pars.symp(syms::orphaned_directory) || pars.symp(syms::orphaned_file)))
- {
- orphaned_node_conflict conflict;
-
- pars.sym();
-
- read_orphaned_node_conflict(pars, conflict, left_roster, right_roster);
-
- conflicts.push_back(conflict);
-
- if (pars.tok.in.lookahead != EOF)
- pars.esym (syms::conflict);
- }
-} // read_orphaned_node_conflicts
-
-
-static void
-read_multiple_name_conflict(basic_io::parser & pars,
- multiple_name_conflict & conflict,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- read_added_rename_conflict_left(pars, left_roster, conflict.nid, conflict.left);
- read_added_rename_conflict_right(pars, right_roster, conflict.nid, conflict.right);
-} // read_multiple_name_conflict
-
-static void
-read_multiple_name_conflicts(basic_io::parser & pars,
- std::vector & conflicts,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- while (pars.tok.in.lookahead != EOF && pars.symp(syms::multiple_names))
- {
- multiple_name_conflict conflict(the_null_node);
-
- pars.sym();
-
- read_multiple_name_conflict(pars, conflict, left_roster, right_roster);
-
- conflicts.push_back(conflict);
-
- if (pars.tok.in.lookahead != EOF)
- pars.esym (syms::conflict);
- }
-} // read_multiple_name_conflicts
-
-static void
-read_duplicate_name_conflict(basic_io::parser & pars,
- duplicate_name_conflict & conflict,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- read_added_rename_conflict_left(pars, left_roster, conflict.left_nid, conflict.parent_name);
- read_added_rename_conflict_right(pars, right_roster, conflict.right_nid, conflict.parent_name);
-
- // check for a resolution
- while ((!pars.symp (syms::conflict)) && pars.tok.in.lookahead != EOF)
- {
- if (pars.symp (syms::resolved_drop_left))
- {
- conflict.left_resolution.first = resolve_conflicts::drop;
- pars.sym();
- }
- else if (pars.symp (syms::resolved_drop_right))
- {
- conflict.right_resolution.first = resolve_conflicts::drop;
- pars.sym();
- }
- else if (pars.symp (syms::resolved_rename_left))
- {
- conflict.left_resolution.first = resolve_conflicts::rename;
- pars.sym();
- conflict.left_resolution.second = resolve_conflicts::new_file_path(pars.token);
- pars.str();
- }
- else if (pars.symp (syms::resolved_rename_right))
- {
- conflict.right_resolution.first = resolve_conflicts::rename;
- pars.sym();
- conflict.right_resolution.second = resolve_conflicts::new_file_path(pars.token);
- pars.str();
- }
- else if (pars.symp (syms::resolved_user_left))
- {
- conflict.left_resolution.first = resolve_conflicts::content_user;
- pars.sym();
- conflict.left_resolution.second = new_optimal_path(pars.token, true);
- pars.str();
- }
- else if (pars.symp (syms::resolved_user_right))
- {
- conflict.right_resolution.first = resolve_conflicts::content_user;
- pars.sym();
- conflict.right_resolution.second = new_optimal_path(pars.token, true);
- pars.str();
- }
- else
- E(false, origin::user,
- F(conflict_resolution_not_supported_msg) % pars.token % "duplicate_name");
- }
-
-} // read_duplicate_name_conflict
-
-static void
-read_duplicate_name_conflicts(basic_io::parser & pars,
- std::vector & conflicts,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- while (pars.tok.in.lookahead != EOF && pars.symp(syms::duplicate_name))
- {
- duplicate_name_conflict conflict;
-
- pars.sym();
-
- read_duplicate_name_conflict(pars, conflict, left_roster, right_roster);
-
- conflicts.push_back(conflict);
-
- if (pars.tok.in.lookahead != EOF)
- pars.esym (syms::conflict);
- }
-} // read_duplicate_name_conflicts
-static void
-validate_duplicate_name_conflicts(basic_io::parser & pars,
- std::vector & conflicts,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- for (std::vector::iterator i = conflicts.begin();
- i != conflicts.end();
- ++i)
- {
- duplicate_name_conflict & merge_conflict = *i;
- duplicate_name_conflict file_conflict;
-
- pars.esym(syms::duplicate_name);
-
- read_duplicate_name_conflict(pars, file_conflict, left_roster, right_roster);
-
- // Note that we do not confirm the file ids.
- E(merge_conflict.left_nid == file_conflict.left_nid &&
- merge_conflict.right_nid == file_conflict.right_nid,
- origin::user,
- F(conflicts_mismatch_msg));
-
- merge_conflict.left_resolution = file_conflict.left_resolution;
- merge_conflict.right_resolution = file_conflict.right_resolution;
-
- if (pars.tok.in.lookahead != EOF)
- pars.esym (syms::conflict);
- else
- {
- std::vector::iterator tmp = i;
- E(++tmp == conflicts.end(), origin::user,
- F(conflicts_mismatch_msg));
- }
- }
-} // validate_duplicate_name_conflicts
-
-static void
-read_attr_state_left(basic_io::parser & pars,
- std::pair & value)
-{
- string tmp;
-
- if (pars.symp(syms::left_attr_value))
- {
- pars.sym();
- value.first = true;
- pars.str(tmp);
- value.second = attr_value(tmp, pars.tok.in.made_from);
- }
- else
- {
- pars.esym(syms::left_attr_state);
- pars.str(tmp);
- I(tmp == "dropped");
- value.first = false;
- }
-} // read_attr_state_left
-
-static void
-read_attr_state_right(basic_io::parser & pars,
- std::pair & value)
-{
- string tmp;
-
- if (pars.symp(syms::right_attr_value))
- {
- pars.sym();
- value.first = true;
- pars.str(tmp);
- value.second = attr_value(tmp, pars.tok.in.made_from);
- }
- else
- {
- pars.esym(syms::right_attr_state);
- pars.str(tmp);
- I(tmp == "dropped");
- value.first = false;
- }
-} // read_attr_state_right
-
-static void
-read_attribute_conflict(basic_io::parser & pars,
- attribute_conflict & conflict,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- string tmp;
-
- pars.esym(syms::node_type);
-
- pars.str(tmp);
-
- if (tmp == "file")
- {
- pars.esym(syms::attr_name); pars.str(tmp);
- conflict.key = attr_key(tmp, pars.tok.in.made_from);
- pars.esym(syms::ancestor_name); pars.str();
- pars.esym(syms::ancestor_file_id); pars.hex();
- pars.esym(syms::left_name); pars.str(tmp);
- conflict.nid = left_roster.get_node(file_path_external(utf8(tmp, pars.tok.in.made_from)))->self;
- pars.esym(syms::left_file_id); pars.hex();
- read_attr_state_left(pars, conflict.left);
- pars.esym(syms::right_name); pars.str();
- pars.esym(syms::right_file_id); pars.hex();
- read_attr_state_right(pars, conflict.right);
- }
- else if (tmp == "directory")
- {
- pars.esym(syms::attr_name); pars.str(tmp);
- conflict.key = attr_key(tmp, pars.tok.in.made_from);
- pars.esym(syms::ancestor_name); pars.str();
- pars.esym(syms::left_name); pars.str(tmp);
- conflict.nid = left_roster.get_node(file_path_external(utf8(tmp, pars.tok.in.made_from)))->self;
- read_attr_state_left(pars, conflict.left);
- pars.esym(syms::right_name); pars.str();
- read_attr_state_right(pars, conflict.right);
- }
- else
- I(false);
-
-} // read_attribute_conflict
-
-static void
-read_attribute_conflicts(basic_io::parser & pars,
- std::vector & conflicts,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- while (pars.tok.in.lookahead != EOF && pars.symp(syms::attribute))
- {
- attribute_conflict conflict(the_null_node);
-
- pars.sym();
-
- read_attribute_conflict(pars, conflict, left_roster, right_roster);
-
- conflicts.push_back(conflict);
-
- if (pars.tok.in.lookahead != EOF)
- pars.esym (syms::conflict);
- }
-} // read_attribute_conflicts
-
-static void
-read_file_content_conflict(basic_io::parser & pars,
- file_content_conflict & conflict,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- string tmp;
- string left_name, right_name, result_name;
-
- pars.esym(syms::node_type); pars.str(tmp); I(tmp == "file");
-
- pars.esym (syms::ancestor_name); pars.str();
- pars.esym (syms::ancestor_file_id); pars.hex(tmp);
- conflict.ancestor = decode_hexenc_as(tmp, pars.tok.in.made_from);
-
- pars.esym (syms::left_name); pars.str(left_name);
- pars.esym(syms::left_file_id); pars.hex(tmp);
- conflict.left = decode_hexenc_as(tmp, pars.tok.in.made_from);
-
- pars.esym (syms::right_name); pars.str(right_name);
- pars.esym(syms::right_file_id); pars.hex(tmp);
- conflict.right = decode_hexenc_as(tmp, pars.tok.in.made_from);
-
- conflict.nid = left_roster.get_node (file_path_internal (left_name))->self;
- I(conflict.nid = right_roster.get_node (file_path_internal (right_name))->self);
-
- // check for a resolution
- if ((!pars.symp (syms::conflict)) && pars.tok.in.lookahead != EOF)
- {
- if (pars.symp (syms::resolved_internal))
- {
- conflict.resolution.first = resolve_conflicts::content_internal;
- pars.sym();
- }
- else if (pars.symp (syms::resolved_user))
- {
- conflict.resolution.first = resolve_conflicts::content_user;
- pars.sym();
- conflict.resolution.second = new_optimal_path(pars.token, true);
- pars.str();
- }
- else
- E(false, origin::user,
- F(conflict_resolution_not_supported_msg) % pars.token % "file_content");
- }
-
-} // read_file_content_conflict
-
-static void
-read_file_content_conflicts(basic_io::parser & pars,
- std::vector & conflicts,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- while (pars.tok.in.lookahead != EOF && pars.symp(syms::content))
- {
- file_content_conflict conflict;
-
- pars.sym();
-
- read_file_content_conflict(pars, conflict, left_roster, right_roster);
-
- conflicts.push_back(conflict);
-
- if (pars.tok.in.lookahead != EOF)
- pars.esym (syms::conflict);
- }
-} // read_file_content_conflicts
-
-static void
-validate_file_content_conflicts(basic_io::parser & pars,
- std::vector & conflicts,
- roster_t const & left_roster,
- roster_t const & right_roster)
-{
- for (std::vector::iterator i = conflicts.begin();
- i != conflicts.end();
- ++i)
- {
- file_content_conflict & merge_conflict = *i;
- file_content_conflict file_conflict;
-
- pars.esym(syms::content);
-
- read_file_content_conflict(pars, file_conflict, left_roster, right_roster);
-
- E(merge_conflict.nid == file_conflict.nid, origin::user,
- F(conflicts_mismatch_msg));
-
- merge_conflict.resolution = file_conflict.resolution;
-
- if (pars.tok.in.lookahead != EOF)
- pars.esym (syms::conflict);
- else
- {
- std::vector::iterator tmp = i;
- E(++tmp == conflicts.end(), origin::user,
- F("conflicts file does not match current conflicts"));
- }
- }
-} // validate_file_content_conflicts
-
-static void
-read_conflict_file_core(basic_io::parser pars,
- roster_t const & left_roster,
- roster_t const & right_roster,
- roster_merge_result & result,
- bool validate)
-{
- pars.esym (syms::conflict);
-
- // If we are validating, there must be one stanza in the file for each
- // conflict; otherwise something has changed since the file was
- // regenerated. So we go thru the conflicts in the same order they are
- // generated; see merge.cc resolve_merge_conflicts.
-
- if (validate)
- {
- // resolve_merge_conflicts should not call us if there are any conflicts
- // for which we don't currently support resolutions; assert that.
-
- I(!result.missing_root_conflict);
- I(result.invalid_name_conflicts.size() == 0);
- I(result.directory_loop_conflicts.size() == 0);
- I(result.orphaned_node_conflicts.size() == 0);
- I(result.multiple_name_conflicts.size() == 0);
- I(result.attribute_conflicts.size() == 0);
-
- // These are the ones we know how to resolve.
-
- validate_duplicate_name_conflicts(pars, result.duplicate_name_conflicts, left_roster, right_roster);
- validate_file_content_conflicts(pars, result.file_content_conflicts, left_roster, right_roster);
- }
- else
- {
- // Read in the ones we know how to resolve. Also read in the ones we
- // don't know how to resolve, so we can report them.
- read_missing_root_conflicts(pars, result.missing_root_conflict, left_roster, right_roster);
- read_invalid_name_conflicts(pars, result.invalid_name_conflicts, left_roster, right_roster);
- read_directory_loop_conflicts(pars, result.directory_loop_conflicts, left_roster, right_roster);
- read_orphaned_node_conflicts(pars, result.orphaned_node_conflicts, left_roster, right_roster);
- read_multiple_name_conflicts(pars, result.multiple_name_conflicts, left_roster, right_roster);
- read_duplicate_name_conflicts(pars, result.duplicate_name_conflicts, left_roster, right_roster);
- read_attribute_conflicts(pars, result.attribute_conflicts, left_roster, right_roster);
- read_file_content_conflicts(pars, result.file_content_conflicts, left_roster, right_roster);
- }
-
- E(pars.tok.in.lookahead == EOF, pars.tok.in.made_from,
- F("extra data in file"));
-} // read_conflict_file_core
-
-void
-roster_merge_result::read_conflict_file(database & db,
- bookkeeping_path const & file_name,
- revision_id & ancestor_rid,
- revision_id & left_rid,
- revision_id & right_rid,
- roster_t & left_roster,
- marking_map & left_marking,
- roster_t & right_roster,
- marking_map & right_marking)
-{
- data dat;
-
- read_data (file_name, dat);
-
- basic_io::input_source src(dat(), file_name.as_external());
- src.made_from = origin::user;
- basic_io::tokenizer tok(src);
- basic_io::parser pars(tok);
- std::string temp;
-
- // Read left, right, ancestor.
- pars.esym(syms::left);
- pars.hex(temp);
- left_rid = decode_hexenc_as(temp, src.made_from);
- pars.esym(syms::right);
- pars.hex(temp);
- right_rid = decode_hexenc_as(temp, src.made_from);
-
- if (pars.symp(syms::ancestor))
- {
- pars.sym();
- pars.hex(temp);
- ancestor_rid = decode_hexenc_as(temp, src.made_from);
-
- // we don't fetch the ancestor roster here, because not every function
- // needs it.
- db.get_roster(left_rid, left_roster, left_marking);
- db.get_roster(right_rid, right_roster, right_marking);
-
- read_conflict_file_core(pars, left_roster, right_roster, *this, false);
- }
- // else no conflicts
-
-} // roster_merge_result::read_conflict_file
-
-void
-roster_merge_result::write_conflict_file(database & db,
- lua_hooks & lua,
- bookkeeping_path const & file_name,
- revision_id const & ancestor_rid,
- revision_id const & left_rid,
- revision_id const & right_rid,
- boost::shared_ptr left_roster,
- marking_map const & left_marking,
- boost::shared_ptr right_roster,
- marking_map const & right_marking)
-{
- std::ostringstream output;
-
- content_merge_database_adaptor adaptor(db, left_rid, right_rid,
- left_marking, right_marking);
-
- adaptor.cache_roster (left_rid, left_roster);
- adaptor.cache_roster (right_rid, right_roster);
- {
- // match format in cmd_merging.cc show_conflicts_core
- basic_io::stanza st;
- basic_io::printer pr;
- st.push_binary_pair(syms::left, left_rid.inner());
- st.push_binary_pair(syms::right, right_rid.inner());
- st.push_binary_pair(syms::ancestor, adaptor.lca.inner());
- pr.print_stanza(st);
- output.write(pr.buf.data(), pr.buf.size());
- }
-
- report_missing_root_conflicts(*left_roster, *right_roster, adaptor, true, output);
- report_invalid_name_conflicts(*left_roster, *right_roster, adaptor, true, output);
- report_directory_loop_conflicts(*left_roster, *right_roster, adaptor, true, output);
- report_orphaned_node_conflicts(*left_roster, *right_roster, adaptor, true, output);
- report_multiple_name_conflicts(*left_roster, *right_roster, adaptor, true, output);
- report_duplicate_name_conflicts(*left_roster, *right_roster, adaptor, true, output);
- report_attribute_conflicts(*left_roster, *right_roster, adaptor, true, output);
- report_file_content_conflicts(lua, *left_roster, *right_roster, adaptor, true, output);
-
- data dat(output.str(), origin::internal);
- write_data(file_name, dat);
-
-} // roster_merge_result::write_conflict_file
-
-void
-parse_resolve_conflicts_opts (options const & opts,
- revision_id const & left_rid,
- roster_t const & left_roster,
- revision_id const & right_rid,
- roster_t const & right_roster,
- roster_merge_result & result,
- bool & resolutions_given)
-{
-if (opts.resolve_conflicts_given || opts.resolve_conflicts_file_given)
- {
- resolutions_given = true;
-
- data dat;
-
- read_data (system_path(opts.resolve_conflicts_file), dat);
-
- basic_io::input_source src(dat(), opts.resolve_conflicts_file.as_external());
- src.made_from = origin::user;
- basic_io::tokenizer tok(src);
- basic_io::parser pars(tok);
- std::string temp;
-
- pars.esym(syms::left);
- pars.hex(temp);
- E(left_rid == decode_hexenc_as(temp, src.made_from),
- origin::user,
- F("left revision id does not match conflict file"));
-
- pars.esym(syms::right);
- pars.hex(temp);
- E(right_rid == decode_hexenc_as(temp, src.made_from),
- origin::user,
- F("right revision id does not match conflict file"));
-
- if (pars.symp(syms::ancestor))
- {
- pars.sym();
- pars.hex(temp);
-
- read_conflict_file_core (pars, left_roster, right_roster, result, true);
- }
- }
- else
- resolutions_given = false;
-
-} // parse_resolve_conflicts_opts
-
-static void
-attach_node (lua_hooks & lua,
- roster_t & new_roster,
- node_id nid,
- file_path const target_path)
-{
- // Simplified from workspace::perform_rename in work.cc
-
- I(!target_path.empty());
-
- E(!new_roster.has_node(target_path), origin::user,
- F("%s already exists") % target_path.as_external());
- E(new_roster.has_node(target_path.dirname()), origin::user,
- F("directory %s does not exist or is unknown") % target_path.dirname());
-
- new_roster.attach_node (nid, target_path);
-
- node_t node = new_roster.get_node (nid);
- for (full_attr_map_t::const_iterator attr = node->attrs.begin();
- attr != node->attrs.end();
- ++attr)
- lua.hook_apply_attribute (attr->first(), target_path, attr->second.second());
-
-} // attach_node
-
-static void
-resolve_duplicate_name_one_side(lua_hooks & lua,
- resolve_conflicts::file_resolution_t const & resolution,
- resolve_conflicts::file_resolution_t const & other_resolution,
- file_path const & name,
- file_id const & fid,
- node_id const nid,
- content_merge_adaptor & adaptor,
- roster_t & result_roster)
-{
- switch (resolution.first)
- {
- case resolve_conflicts::content_user:
- {
- E(other_resolution.first == resolve_conflicts::drop ||
- other_resolution.first == resolve_conflicts::rename,
- origin::user,
- F("inconsistent left/right resolutions for %s") % name);
-
- P(F("replacing content of %s with %s") % name % resolution.second->as_external());
-
- file_id result_fid;
- file_data parent_data, result_data;
- data result_raw_data;
- adaptor.get_version(fid, parent_data);
-
- read_data(*resolution.second, result_raw_data);
-
- result_data = file_data(result_raw_data);
- calculate_ident(result_data, result_fid);
-
- file_t result_node = downcast_to_file_t(result_roster.get_node(nid));
- result_node->content = result_fid;
-
- adaptor.record_file(fid, result_fid, parent_data, result_data);
-
- attach_node(lua, result_roster, nid, name);
- }
- break;
-
- case resolve_conflicts::drop:
- P(F("dropping %s") % name);
- result_roster.drop_detached_node(nid);
- break;
-
- case resolve_conflicts::rename:
- P(F("renaming %s to %s") % name % *resolution.second);
- attach_node
- (lua, result_roster, nid, file_path_internal (resolution.second->as_internal()));
- break;
-
- case resolve_conflicts::none:
- E(false, origin::user,
- F("no resolution provided for duplicate_name %s") % name);
- break;
-
- default:
- E(false, origin::user,
- F("%s: invalid resolution for duplicate_name %s") % image (resolution.first) % name);
- }
-} // resolve_duplicate_name_one_side
-
-void
-roster_merge_result::resolve_duplicate_name_conflicts(lua_hooks & lua,
- roster_t const & left_roster,
- roster_t const & right_roster,
- content_merge_adaptor & adaptor)
-{
- MM(left_roster);
- MM(right_roster);
- MM(this->roster); // New roster
-
- // Conflict nodes are present but detached (without filenames) in the new
- // roster. The resolution is either to suture the two files together, or to
- // rename one or both.
-
- for (std::vector::const_iterator i = duplicate_name_conflicts.begin();
- i != duplicate_name_conflicts.end();
- ++i)
- {
- duplicate_name_conflict const & conflict = *i;
- MM(conflict);
-
- node_id left_nid = conflict.left_nid;
- node_id right_nid= conflict.right_nid;
-
- file_path left_name, right_name;
- file_id left_fid, right_fid;
-
- left_roster.get_file_details(left_nid, left_fid, left_name);
- right_roster.get_file_details(right_nid, right_fid, right_name);
-
- resolve_duplicate_name_one_side
- (lua, conflict.left_resolution, conflict.right_resolution, left_name, left_fid, left_nid, adaptor, roster);
-
- resolve_duplicate_name_one_side
- (lua, conflict.right_resolution, conflict.left_resolution, right_name, right_fid, right_nid, adaptor, roster);
- } // end for
-
- duplicate_name_conflicts.clear();
-}
-
-void
-roster_merge_result::resolve_file_content_conflicts(lua_hooks & lua,
- roster_t const & left_roster,
- roster_t const & right_roster,
- content_merge_adaptor & adaptor)
-{
- MM(left_roster);
- MM(right_roster);
- MM(this->roster); // New roster
-
- // Conflict node is present and attached in the new roster, with a null
- // file content id. The resolution is to enter the user specified file
- // content in the database and roster, or let the internal line merger
- // handle it.
-
- for (std::vector::const_iterator i = file_content_conflicts.begin();
- i != file_content_conflicts.end();
- ++i)
- {
- file_content_conflict const & conflict = *i;
- MM(conflict);
-
- file_path left_name, right_name;
-
- left_roster.get_name(conflict.nid, left_name);
- right_roster.get_name(conflict.nid, right_name);
-
- switch (conflict.resolution.first)
- {
- case resolve_conflicts::content_internal:
- case resolve_conflicts::none:
- {
- file_id merged_id;
-
- E(resolve_conflicts::do_auto_merge(lua, conflict, adaptor, left_roster,
- right_roster, this->roster, merged_id),
- origin::user,
- F("merge of %s, %s failed") % left_name % right_name);
-
- P(F("merged %s, %s") % left_name % right_name);
-
- file_t result_node = downcast_to_file_t(roster.get_node(conflict.nid));
- result_node->content = merged_id;
- }
- break;
-
- case resolve_conflicts::content_user:
- {
- P(F("replacing content of %s, %s with %s") %
- left_name % right_name % conflict.resolution.second->as_external());
-
- file_id result_id;
- file_data left_data, right_data, result_data;
- data result_raw_data;
- adaptor.get_version(conflict.left, left_data);
- adaptor.get_version(conflict.right, right_data);
-
- read_data(*conflict.resolution.second, result_raw_data);
-
- result_data = file_data(result_raw_data);
- calculate_ident(result_data, result_id);
-
- file_t result_node = downcast_to_file_t(roster.get_node(conflict.nid));
- result_node->content = result_id;
-
- adaptor.record_merge(conflict.left, conflict.right, result_id,
- left_data, right_data, result_data);
-
- }
- break;
-
- default:
- I(false);
- }
-
- } // end for
-
- file_content_conflicts.clear();
-}
-
-void
-roster_merge_result::clear()
-{
- missing_root_conflict = false;
- invalid_name_conflicts.clear();
- directory_loop_conflicts.clear();
-
- orphaned_node_conflicts.clear();
- multiple_name_conflicts.clear();
- duplicate_name_conflicts.clear();
-
- attribute_conflicts.clear();
- file_content_conflicts.clear();
-
- roster = roster_t();
-}
-
-namespace
-{
// a wins if *(b) > a. Which is to say that all members of b_marks are
// ancestors of a. But all members of b_marks are ancestors of the
// _b_, so the previous statement is the same as saying that _no_
@@ -3111,9 +727,8 @@ roster_merge(roster_t const & left_paren
#ifdef BUILD_UNIT_TESTS
#include "unit_tests.hh"
+#include "roster_tests.hh"
#include "constants.hh"
-#include "roster_delta.hh"
-#include "roster_tests.hh"
// cases for testing:
//
============================================================
--- po/POTFILES.in 639dab3c215bfa742d5c757537da5069a3a83744
+++ po/POTFILES.in 80d52ad34536a52ac3fd5e5da3978439e8bbcb2f
@@ -85,6 +85,7 @@ merge_3way.cc
lua_hooks.cc
lua_hooks.hh
merge_3way.cc
+merge_conflict.cc
merge_content.cc
merge_content.hh
merge_roster.cc