#
#
# add_dir "tests/resolve_content_conflict"
#
# add_file "tests/resolve_content_conflict/__driver__.lua"
# content [96bf801b85023fa84035c19133bd3f6fc339a7e1]
#
# add_file "tests/resolve_content_conflict/conflicts-1"
# content [d0d2e52fdf8329d3cf0ba5644454a931a30cacf2]
#
# add_file "tests/resolve_content_conflict/merge-1"
# content [8ab8aad3487a1e247c5edcb01f5f50813f01fa07]
#
# add_file "tests/resolve_content_conflict/resolve-conflicts-1"
# content [8651ee547db93b1ee41e0ee6309144d4e955b9cc]
#
# add_file "tests/resolve_content_conflict/update-1"
# content [4ee9b94a8bc3f20abd7191ea65cbb7cf85ab46c6]
#
# patch "basic_io.cc"
# from [5aacaa8dcf17e4113dbaf2e91b476a9b795ccbf5]
# to [8537b617f85273a7c80736695a983d8a6104c205]
#
# patch "basic_io.hh"
# from [6b126a5b42e3eba99b5aa01b73d3b2f7ffce5b1c]
# to [212d1bb1bb8fed7cb5d123eb9b09f60e993d1018]
#
# patch "cmd_merging.cc"
# from [c00d7dd2a1dd4dda7da812ae6216b6ee391238b9]
# to [88fd8e589c2fbbf1d0eed179260bdafbdf12f77b]
#
# patch "diff_patch.cc"
# from [211424b28fdc3a5dcdae5a781feea97e1e915326]
# to [adf43e9fcc6b0a828e7deb3280ad76b5ae1489e4]
#
# patch "diff_patch.hh"
# from [8386bfd9dc62a30e7173fca60d27adab6d02167b]
# to [1fdf49ab99f9d3a6ca15c08f5c5e7828533e5fa2]
#
# patch "merge.cc"
# from [9059000487b5a7e6e6390175c376b45ca4f251f0]
# to [46e286382febebbed4f099acb5e83b17146cffb8]
#
# patch "merge.hh"
# from [f3613e195ad378f7666d003b959b322abbce97d0]
# to [8cd4bcbd4dfc7b481569c7725003e42bee951969]
#
# patch "monotone.texi"
# from [6a8534e5e1e4045df89160f4d49b69a0fed06077]
# to [2f6f14a68447e9b3418c328cd350db9c54c0d616]
#
# patch "options_list.hh"
# from [cd0ff5a0d051b4644c02a5f81ed262a512c65360]
# to [154daf8d9f4ab4673441dc8e30bf7531fb7fe3eb]
#
# patch "roster.cc"
# from [9cebdc581039f2d117abc2c90c4230b2c9635166]
# to [04ac3afba58ec4c48d56f080d46ed924e51df79c]
#
# patch "roster.hh"
# from [0da49ea4d1bda34fe2875b0e45add9b6918f2df4]
# to [c83fc2636e82ac77a1446c42df169b44bb47197f]
#
# patch "roster_merge.cc"
# from [9bb39c2c87f92180b0a5db5697ff6ea15b82d25b]
# to [2904442eb8835e76182b01daed84ac42f16bc258]
#
# patch "roster_merge.hh"
# from [48d9aa743811732826e3f855a5759c1d61416984]
# to [8f0603774befb2458b9e6a2660a90f31104a9a22]
#
# patch "testlib.lua"
# from [0e8c0415f85924dfb385a6e6a0de8b0f787a611d]
# to [785c976d13cafe3d472fa9d7121ddb7f75a4bae9]
#
============================================================
--- tests/resolve_content_conflict/__driver__.lua 96bf801b85023fa84035c19133bd3f6fc339a7e1
+++ tests/resolve_content_conflict/__driver__.lua 96bf801b85023fa84035c19133bd3f6fc339a7e1
@@ -0,0 +1,42 @@
+-- Demonstrate content conflict resolutions
+mtn_setup()
+
+addfile("foo", "foo")
+addfile("bar", "bar\none\ntwo\nthree")
+addfile("baz", "baz\naaa\nbbb\nccc")
+commit("testbranch", "base")
+base = base_revision()
+
+writefile("foo", "foo\nfirst\nrevision")
+writefile("bar", "bar\nzero\none\ntwo\nthree")
+writefile("baz", "baz\nAAA\nbbb\nccc")
+commit("testbranch", "first")
+first = base_revision()
+
+revert_to(base)
+
+writefile("foo", "foo\nsecond\nrevision")
+writefile("bar", "bar\none\ntwo\nthree\nfour")
+writefile("baz", "baz\naaa\nbbb\nCCC")
+commit("testbranch", "second")
+second = base_revision()
+
+check(mtn("automate", "show_conflicts", first, second), 0, true, nil)
+canonicalize("stdout")
+check(samefilestd("conflicts-1", "stdout"))
+
+writefile("foo", "foo\nmerged\nrevision")
+
+check(get("resolve-conflicts-1", "_MTN/conflicts"))
+check(mtn("merge", "--resolve-conflicts-file=_MTN/conflicts"), 0, nil, true)
+canonicalize("stderr")
+check(samefilestd("merge-1", "stderr"))
+
+check(mtn("update"), 0, nil, true)
+canonicalize("stderr")
+check(samefilestd("update-1", "stderr"))
+
+check(readfile("foo") == "foo\nmerged\nrevision")
+check(readfile("bar") == "bar\nzero\none\ntwo\nthree\nfour\n")
+check(readfile("baz") == "baz\nAAA\nbbb\nCCC\n")
+-- end of file
============================================================
--- tests/resolve_content_conflict/conflicts-1 d0d2e52fdf8329d3cf0ba5644454a931a30cacf2
+++ tests/resolve_content_conflict/conflicts-1 d0d2e52fdf8329d3cf0ba5644454a931a30cacf2
@@ -0,0 +1,32 @@
+ left [c28c3c0f5a7f9cde848be91f79d86a6dd63e88ca]
+ right [9340b41fa26b20293292cb37449cc722b1b55a5a]
+ancestor [4c059d3a8bbe8e624e46a50b298b4f301caf380d]
+
+ conflict content
+ node_type "file"
+ ancestor_name "bar"
+ ancestor_file_id [fc8a40f0b775e86503c7522399f309f6ac298348]
+ left_name "bar"
+ left_file_id [bf227d19bccee7740bb58219910ff0930b6200c1]
+ right_name "bar"
+ right_file_id [5fd4e3cf64e24e969cfcd2380cf244aee9e52d5d]
+resolved_internal
+
+ conflict content
+ node_type "file"
+ ancestor_name "baz"
+ ancestor_file_id [ae708173915e11248629c18d16c96c3a34f87d16]
+ left_name "baz"
+ left_file_id [c438704db55b0d6f819e7e79c1622e5d757b926b]
+ right_name "baz"
+ right_file_id [f0ef173c31428d85dcca6399de12c6352f8b72b3]
+resolved_internal
+
+ conflict content
+ node_type "file"
+ ancestor_name "foo"
+ancestor_file_id [0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33]
+ left_name "foo"
+ left_file_id [841e3d2ada1a56123f9efe9db0d0c045ff9e6d8f]
+ right_name "foo"
+ right_file_id [3506995775abf41d3cdeb1e0417bdd3bcf059395]
============================================================
--- tests/resolve_content_conflict/merge-1 8ab8aad3487a1e247c5edcb01f5f50813f01fa07
+++ tests/resolve_content_conflict/merge-1 8ab8aad3487a1e247c5edcb01f5f50813f01fa07
@@ -0,0 +1,10 @@
+mtn: 2 heads on branch 'testbranch'
+mtn: merge 1 / 1:
+mtn: calculating best pair of heads to merge next
+mtn: [left] 9340b41fa26b20293292cb37449cc722b1b55a5a
+mtn: [right] c28c3c0f5a7f9cde848be91f79d86a6dd63e88ca
+mtn: merged bar, bar
+mtn: merged baz, baz
+mtn: replacing content of foo, foo with foo
+mtn: [merged] c63027af0230a9bf587088ec08fd60318308730d
+mtn: note: your workspaces have not been updated
============================================================
--- tests/resolve_content_conflict/resolve-conflicts-1 8651ee547db93b1ee41e0ee6309144d4e955b9cc
+++ tests/resolve_content_conflict/resolve-conflicts-1 8651ee547db93b1ee41e0ee6309144d4e955b9cc
@@ -0,0 +1,33 @@
+ left [c28c3c0f5a7f9cde848be91f79d86a6dd63e88ca]
+ right [9340b41fa26b20293292cb37449cc722b1b55a5a]
+ancestor [4c059d3a8bbe8e624e46a50b298b4f301caf380d]
+
+ conflict content
+ node_type "file"
+ ancestor_name "bar"
+ ancestor_file_id [fc8a40f0b775e86503c7522399f309f6ac298348]
+ left_name "bar"
+ left_file_id [bf227d19bccee7740bb58219910ff0930b6200c1]
+ right_name "bar"
+ right_file_id [5fd4e3cf64e24e969cfcd2380cf244aee9e52d5d]
+resolved_internal
+
+ conflict content
+ node_type "file"
+ ancestor_name "baz"
+ ancestor_file_id [ae708173915e11248629c18d16c96c3a34f87d16]
+ left_name "baz"
+ left_file_id [c438704db55b0d6f819e7e79c1622e5d757b926b]
+ right_name "baz"
+ right_file_id [f0ef173c31428d85dcca6399de12c6352f8b72b3]
+resolved_internal
+
+ conflict content
+ node_type "file"
+ ancestor_name "foo"
+ancestor_file_id [0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33]
+ left_name "foo"
+ left_file_id [841e3d2ada1a56123f9efe9db0d0c045ff9e6d8f]
+ right_name "foo"
+ right_file_id [3506995775abf41d3cdeb1e0417bdd3bcf059395]
+resolved_user "foo"
============================================================
--- tests/resolve_content_conflict/update-1 4ee9b94a8bc3f20abd7191ea65cbb7cf85ab46c6
+++ tests/resolve_content_conflict/update-1 4ee9b94a8bc3f20abd7191ea65cbb7cf85ab46c6
@@ -0,0 +1,7 @@
+mtn: updating along branch 'testbranch'
+mtn: selected update target c63027af0230a9bf587088ec08fd60318308730d
+mtn: [left] 834637991b7d5e28d893a4e29285b20442d87225
+mtn: [right] c63027af0230a9bf587088ec08fd60318308730d
+mtn: modifying bar
+mtn: modifying baz
+mtn: updated to base revision c63027af0230a9bf587088ec08fd60318308730d
============================================================
--- basic_io.cc 5aacaa8dcf17e4113dbaf2e91b476a9b795ccbf5
+++ basic_io.cc 8537b617f85273a7c80736695a983d8a6104c205
@@ -1,3 +1,4 @@
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2004 Graydon Hoare
//
// This program is made available under the GNU GPL version 2.0 or
@@ -68,6 +69,14 @@ void basic_io::stanza::push_binary_pair(
push_hex_pair(k, hexenc(encode_hexenc(v())));
}
+void
+basic_io::stanza::push_symbol(symbol const & k)
+{
+ entries.push_back(make_pair(k, ""));
+ if (k().size() > indent)
+ indent = k().size();
+}
+
void basic_io::stanza::push_hex_pair(symbol const & k, hexenc const & v)
{
entries.push_back(make_pair(k, ""));
@@ -129,6 +138,22 @@ void basic_io::stanza::push_str_multi(sy
indent = k().size();
}
+void basic_io::stanza::push_str_multi(symbol const & k1,
+ symbol const & k2,
+ vector const & v)
+{
+ string val = k2();
+ for (vector::const_iterator i = v.begin();
+ i != v.end(); ++i)
+ {
+ val += " ";
+ val += escape(*i);
+ }
+ entries.push_back(make_pair(k1, val));
+ if (k1().size() > indent)
+ indent = k1().size();
+}
+
void basic_io::stanza::push_str_triple(symbol const & k,
string const & n,
string const & v)
============================================================
--- basic_io.hh 6b126a5b42e3eba99b5aa01b73d3b2f7ffce5b1c
+++ basic_io.hh 212d1bb1bb8fed7cb5d123eb9b09f60e993d1018
@@ -1,6 +1,7 @@
#ifndef __BASIC_IO_HH__
#define __BASIC_IO_HH__
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2004 Graydon Hoare
//
// This program is made available under the GNU GPL version 2.0 or
@@ -29,29 +30,21 @@ namespace basic_io
namespace
{
- namespace syms
+ namespace syms
{
// general format symbol
symbol const format_version("format_version");
-
- // roster symbols
+
+ // common symbols
symbol const dir("dir");
symbol const file("file");
symbol const content("content");
symbol const attr("attr");
-
- // 'local' roster and marking symbols
- // FIXME: should these be listed as "general" symbols here as well?
- symbol const ident("ident");
- symbol const birth("birth");
- symbol const dormant_attr("dormant_attr");
-
- symbol const path_mark("path_mark");
+
symbol const content_mark("content_mark");
- symbol const attr_mark("attr_mark");
}
}
-
+
typedef enum
{
TOK_SYMBOL,
@@ -77,7 +70,7 @@ namespace basic_io
inline void peek()
{
if (LIKELY(curr != in.end()))
- // we do want to distinguish between EOF and '\xff',
+ // we do want to distinguish between EOF and '\xff',
// so we translate '\xff' to 255u
lookahead = widen(*curr);
else
@@ -162,7 +155,7 @@ namespace basic_io
in.err("non-hex character in hex string");
advance();
}
-
+
store(val);
if (UNLIKELY(static_cast(in.lookahead) != ']'))
@@ -230,13 +223,13 @@ namespace basic_io
}
advance();
}
-
+
store(val);
if (UNLIKELY(static_cast(in.lookahead) != '"'))
in.err("string did not end with '\"'");
in.advance();
-
+
return basic_io::TOK_STRING;
}
else
@@ -253,6 +246,7 @@ namespace basic_io
stanza();
size_t indent;
std::vector > entries;
+ void push_symbol(symbol const & k);
void push_hex_pair(symbol const & k, hexenc const & v);
void push_binary_pair(symbol const & k, id const & v);
void push_binary_triple(symbol const & k, std::string const & n,
@@ -264,10 +258,13 @@ namespace basic_io
void push_file_pair(symbol const & k, file_path const & v);
void push_str_multi(symbol const & k,
std::vector const & v);
+ void push_str_multi(symbol const & k1,
+ symbol const & k2,
+ std::vector const & v);
};
- // Note: printer uses a static buffer; thus only one buffer
+ // Note: printer uses a static buffer; thus only one buffer
// may be referenced (globally). An invariant will be triggered
// if more than one basic_io::printer is instantiated.
struct
============================================================
--- cmd_merging.cc c00d7dd2a1dd4dda7da812ae6216b6ee391238b9
+++ cmd_merging.cc 88fd8e589c2fbbf1d0eed179260bdafbdf12f77b
@@ -143,7 +143,8 @@ CMD(update, "update", "", CMD_REF(worksp
"different revision, preserving uncommitted changes as it does so. "
"If a revision is given, update the workspace to that revision. "
"If not, update the workspace to the head of the branch."),
- options::opts::branch | options::opts::revision)
+ options::opts::branch | options::opts::revision |
+ options::opts::resolve_conflicts_opts)
{
if (args.size() > 0)
throw usage(execid);
@@ -281,8 +282,9 @@ CMD(update, "update", "", CMD_REF(worksp
content_merge_workspace_adaptor wca(db, old_rid, old_roster,
left_markings, right_markings, paths);
wca.cache_roster(working_rid, working_roster);
+ // FIXME_RESOLVE_CONFLICTS: add conflict resolutions here
resolve_merge_conflicts(app.lua, *working_roster, chosen_roster,
- result, wca);
+ result, wca, false);
// Make sure it worked...
I(result.is_clean());
@@ -297,7 +299,7 @@ CMD(update, "update", "", CMD_REF(worksp
make_revision_for_workspace(chosen_rid, chosen_roster,
merged_roster, remaining);
- // small race condition here...
+ // small race condition here... FIXME: what is it?
work.put_work_rev(remaining);
work.update_any_attrs(db);
work.maybe_update_inodeprints(db);
@@ -355,7 +357,7 @@ merge_two(options & opts, lua_hooks & lu
revision_id merged;
transaction_guard guard(project.db);
- interactive_merge_and_store(lua, project.db, left, right, merged);
+ interactive_merge_and_store(lua, project.db, opts, left, right, merged);
project.put_standard_certs_from_options(opts, lua, keys, merged, branch,
utf8(log.str()));
@@ -375,7 +377,16 @@ find_heads_to_merge(database & db, set const heads)
{
- I(heads.size() > 2);
+ I(heads.size() >= 2);
+
+ if (heads.size() == 2)
+ {
+ rid_set_iter i = heads.begin();
+ revision_id left = *i++;
+ revision_id right = *i++;
+ return revpair(left, right);
+ };
+
map heads_for_ancestor;
set ancestors;
@@ -417,7 +428,8 @@ CMD(merge, "merge", "", CMD_REF(tree), "
CMD(merge, "merge", "", CMD_REF(tree), "",
N_("Merges unmerged heads of a branch"),
"",
- options::opts::branch | options::opts::date | options::opts::author)
+ options::opts::branch | options::opts::date | options::opts::author |
+ options::opts::resolve_conflicts_opts)
{
database db(app);
key_store keys(app);
@@ -448,6 +460,12 @@ CMD(merge, "merge", "", CMD_REF(tree), "
size_t pass = 1, todo = heads.size() - 1;
+ if (app.opts.resolve_conflicts_given || app.opts.resolve_conflicts_file_given)
+ {
+ // conflicts and resolutions only apply to first merge, so only do that one.
+ todo = 1;
+ }
+
// If there are more than two heads to be merged, on each iteration we
// merge a pair whose least common ancestor is not an ancestor of any
// other pair's least common ancestor. For example, if the history graph
@@ -460,7 +478,7 @@ CMD(merge, "merge", "", CMD_REF(tree), "
// A B
//
// A and B will be merged first, and then the result will be merged with C.
- while (heads.size() > 2)
+ while (pass <= todo)
{
P(F("merge %d / %d:") % pass % todo);
P(F("calculating best pair of heads to merge next"));
@@ -476,19 +494,9 @@ CMD(merge, "merge", "", CMD_REF(tree), "
pass++;
}
- // Last one.
- I(pass == todo);
- if (todo > 1)
- P(F("merge %d / %d:") % pass % todo);
+ if (heads.size() > 1)
+ P(F("note: branch '%s' still has %s heads; run merge again") % app.opts.branchname % heads.size());
- rid_set_iter i = heads.begin();
- revision_id left = *i++;
- revision_id right = *i++;
- I(i == heads.end());
-
- merge_two(app.opts, app.lua, project, keys,
- left, right, app.opts.branchname, string("merge"),
- std::cout, false);
P(F("note: your workspaces have not been updated"));
}
@@ -496,7 +504,8 @@ CMD(propagate, "propagate", "", CMD_REF(
N_("SOURCE-BRANCH DEST-BRANCH"),
N_("Merges from one branch to another asymmetrically"),
"",
- options::opts::date | options::opts::author | options::opts::message | options::opts::msgfile)
+ options::opts::date | options::opts::author | options::opts::message | options::opts::msgfile |
+ options::opts::resolve_conflicts_opts)
{
if (args.size() != 2)
throw usage(execid);
@@ -535,7 +544,8 @@ CMD(merge_into_dir, "merge_into_dir", ""
N_("SOURCE-BRANCH DEST-BRANCH DIR"),
N_("Merges one branch into a subdirectory in another branch"),
"",
- options::opts::date | options::opts::author | options::opts::message | options::opts::msgfile)
+ options::opts::date | options::opts::author | options::opts::message | options::opts::msgfile |
+ options::opts::resolve_conflicts_opts)
{
database db(app);
key_store keys(app);
@@ -601,8 +611,8 @@ CMD(merge_into_dir, "merge_into_dir", ""
db.get_roster(left_rid, left_roster, left_marking_map);
db.get_roster(right_rid, right_roster, right_marking_map);
db.get_uncommon_ancestors(left_rid, right_rid,
- left_uncommon_ancestors,
- right_uncommon_ancestors);
+ left_uncommon_ancestors,
+ right_uncommon_ancestors);
if (!idx(args,2)().empty())
{
@@ -637,8 +647,12 @@ CMD(merge_into_dir, "merge_into_dir", ""
content_merge_database_adaptor
dba(db, left_rid, right_rid, left_marking_map, right_marking_map);
+ bool resolutions_given;
+
+ parse_resolve_conflicts_opts (app.opts, left_roster, right_roster, result, resolutions_given);
+
resolve_merge_conflicts(app.lua, left_roster, right_roster,
- result, dba);
+ result, dba, false);
{
dir_t moved_root = left_roster.root();
@@ -681,7 +695,8 @@ CMD(merge_into_workspace, "merge_into_wo
"pending changes in the current workspace. Both OTHER-REVISION and "
"the workspace's base revision will be recorded as parents on commit. "
"The workspace's selected branch is not changed."),
- options::opts::none)
+ options::opts::none |
+ options::opts::resolve_conflicts_opts)
{
revision_id left_id, right_id;
cached_roster left, right;
@@ -752,7 +767,8 @@ CMD(merge_into_workspace, "merge_into_wo
content_merge_workspace_adaptor wca(db, lca_id, lca.first,
*left.second, *right.second, paths);
wca.cache_roster(working_rid, working_roster);
- resolve_merge_conflicts(app.lua, *left.first, *right.first, merge_result, wca);
+ // FIXME_RESOLVE_CONFLICTS: add conflict resolutions here
+ resolve_merge_conflicts(app.lua, *left.first, *right.first, merge_result, wca, false);
// Make sure it worked...
I(merge_result.is_clean());
@@ -789,7 +805,8 @@ CMD(explicit_merge, "explicit_merge", ""
N_("Merges two explicitly given revisions"),
N_("The results of the merge are placed on the branch specified by "
"DEST-BRANCH."),
- options::opts::date | options::opts::author)
+ options::opts::date | options::opts::author |
+ options::opts::resolve_conflicts_opts)
{
database db(app);
key_store keys(app);
@@ -832,7 +849,12 @@ static void
}
static void
-show_conflicts_core (database & db, revision_id const & l_id, revision_id const & r_id, bool const basic_io, std::ostream & output)
+show_conflicts_core (database & db,
+ lua_hooks & lua,
+ revision_id const & l_id,
+ revision_id const & r_id,
+ bool const basic_io,
+ std::ostream & output)
{
N(!is_ancestor(db, l_id, r_id),
F("%s is an ancestor of %s; no merge is needed.")
@@ -907,7 +929,7 @@ show_conflicts_core (database & db, revi
result.report_duplicate_name_conflicts(*l_roster, *r_roster, adaptor, basic_io, output);
result.report_attribute_conflicts(*l_roster, *r_roster, adaptor, basic_io, output);
- result.report_file_content_conflicts(*l_roster, *r_roster, adaptor, basic_io, output);
+ result.report_file_content_conflicts(lua, *l_roster, *r_roster, adaptor, basic_io, output);
}
}
@@ -926,7 +948,7 @@ CMD(show_conflicts, "show_conflicts", ""
complete(app.opts, app.lua, project, idx(args,0)(), l_id);
complete(app.opts, app.lua, project, idx(args,1)(), r_id);
- show_conflicts_core(db, l_id, r_id, false, std::cout);
+ show_conflicts_core(db, app.lua, l_id, r_id, false, std::cout);
}
// Name: show_conflicts
@@ -968,19 +990,9 @@ CMD_AUTOMATE(show_conflicts, N_("[LEFT_R
N(heads.size() >= 2,
F("branch '%s' has %d heads; must be at least 2 for show_conflicts") % app.opts.branchname % heads.size());
- if (heads.size() == 2)
- {
- set::const_iterator i = heads.begin();
- l_id = *i;
- ++i;
- r_id = *i;
- }
- else
- {
- revpair p = find_heads_to_merge (db, heads);
- l_id = p.first;
- r_id = p.second;
- }
+ revpair p = find_heads_to_merge (db, heads);
+ l_id = p.first;
+ r_id = p.second;
}
else if (args.size() == 2)
{
@@ -991,7 +1003,7 @@ CMD_AUTOMATE(show_conflicts, N_("[LEFT_R
else
N(false, F("wrong argument count"));
- show_conflicts_core(db, l_id, r_id, true, output);
+ show_conflicts_core(db, app.lua, l_id, r_id, true, output);
}
CMD(pluck, "pluck", "", CMD_REF(workspace), N_("[-r FROM] -r TO [PATH...]"),
@@ -1005,7 +1017,8 @@ CMD(pluck, "pluck", "", CMD_REF(workspac
"compared to its parent.\n"
"If two revisions are given, applies the changes made to get from the "
"first revision to the second."),
- options::opts::revision | options::opts::depth | options::opts::exclude)
+ options::opts::revision | options::opts::depth | options::opts::exclude |
+ options::opts::resolve_conflicts_opts)
{
database db(app);
workspace work(app);
@@ -1135,8 +1148,9 @@ CMD(pluck, "pluck", "", CMD_REF(workspac
// to_roster is not fetched from the db which does not have temporary nids
wca.cache_roster(to_rid, to_roster);
+ // FIXME_RESOLVE_CONFLICTS: add conflict resolutions here
resolve_merge_conflicts(app.lua, *working_roster, *to_roster,
- result, wca);
+ result, wca, false);
I(result.is_clean());
// temporary node ids may appear
============================================================
--- diff_patch.cc 211424b28fdc3a5dcdae5a781feea97e1e915326
+++ diff_patch.cc adf43e9fcc6b0a828e7deb3280ad76b5ae1489e4
@@ -1,3 +1,4 @@
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2002 Graydon Hoare
//
// This program is made available under the GNU GPL version 2.0 or
@@ -767,6 +768,61 @@ bool
}
bool
+content_merger::attempt_auto_merge(file_path const & anc_path, // inputs
+ file_path const & left_path,
+ file_path const & right_path,
+ file_id const & ancestor_id,
+ file_id const & left_id,
+ file_id const & right_id,
+ file_data & left_data, // outputs
+ file_data & right_data,
+ file_data & merge_data)
+{
+ I(left_id != right_id);
+
+ if (attribute_manual_merge(left_path, left_ros) ||
+ attribute_manual_merge(right_path, right_ros))
+ {
+ return false;
+ }
+
+ // both files mergeable by monotone internal algorithm, try to merge
+ // note: the ancestor is not considered for manual merging. Forcing the
+ // user to merge manually just because of an ancestor mistakenly marked
+ // manual seems too harsh
+
+ file_data ancestor_data;
+
+ adaptor.get_version(left_id, left_data);
+ adaptor.get_version(ancestor_id, ancestor_data);
+ adaptor.get_version(right_id, right_data);
+
+ data const left_unpacked = left_data.inner();
+ data const ancestor_unpacked = ancestor_data.inner();
+ data const right_unpacked = right_data.inner();
+
+ string const left_encoding(get_file_encoding(left_path, left_ros));
+ string const anc_encoding(get_file_encoding(anc_path, anc_ros));
+ string const right_encoding(get_file_encoding(right_path, right_ros));
+
+ vector left_lines, ancestor_lines, right_lines, merged_lines;
+ split_into_lines(left_unpacked(), left_encoding, left_lines);
+ split_into_lines(ancestor_unpacked(), anc_encoding, ancestor_lines);
+ split_into_lines(right_unpacked(), right_encoding, right_lines);
+
+ if (merge3(ancestor_lines, left_lines, right_lines, merged_lines))
+ {
+ string tmp;
+
+ join_lines(merged_lines, tmp);
+ merge_data = file_data(tmp);
+ return true;
+ }
+
+ return false;
+}
+
+bool
content_merger::try_auto_merge(file_path const & anc_path,
file_path const & left_path,
file_path const & right_path,
@@ -795,50 +851,19 @@ content_merger::try_auto_merge(file_path
return true;
}
- file_data left_data, right_data, ancestor_data;
- data left_unpacked, ancestor_unpacked, right_unpacked, merged_unpacked;
+ file_data left_data, right_data, merge_data;
- adaptor.get_version(left_id, left_data);
- adaptor.get_version(ancestor_id, ancestor_data);
- adaptor.get_version(right_id, right_data);
-
- left_unpacked = left_data.inner();
- ancestor_unpacked = ancestor_data.inner();
- right_unpacked = right_data.inner();
-
- if (!attribute_manual_merge(left_path, left_ros) &&
- !attribute_manual_merge(right_path, right_ros))
+ if (attempt_auto_merge(anc_path, left_path, right_path,
+ ancestor_id, left_id, right_id,
+ left_data, right_data, merge_data))
{
- // both files mergeable by monotone internal algorithm, try to merge
- // note: the ancestor is not considered for manual merging. Forcing the
- // user to merge manually just because of an ancestor mistakenly marked
- // manual seems too harsh
- string left_encoding, anc_encoding, right_encoding;
- left_encoding = this->get_file_encoding(left_path, left_ros);
- anc_encoding = this->get_file_encoding(anc_path, anc_ros);
- right_encoding = this->get_file_encoding(right_path, right_ros);
+ L(FL("internal 3-way merged ok"));
+ calculate_ident(merge_data, merged_id);
- vector left_lines, ancestor_lines, right_lines, merged_lines;
- split_into_lines(left_unpacked(), left_encoding, left_lines);
- split_into_lines(ancestor_unpacked(), anc_encoding, ancestor_lines);
- split_into_lines(right_unpacked(), right_encoding, right_lines);
+ adaptor.record_merge(left_id, right_id, merged_id,
+ left_data, right_data, merge_data);
- if (merge3(ancestor_lines, left_lines, right_lines, merged_lines))
- {
- file_id tmp_id;
- file_data merge_data;
- string tmp;
-
- L(FL("internal 3-way merged ok"));
- join_lines(merged_lines, tmp);
- merge_data = file_data(tmp);
- calculate_ident(merge_data, merged_id);
-
- adaptor.record_merge(left_id, right_id, merged_id,
- left_data, right_data, merge_data);
-
- return true;
- }
+ return true;
}
return false;
============================================================
--- diff_patch.hh 8386bfd9dc62a30e7173fca60d27adab6d02167b
+++ diff_patch.hh 1fdf49ab99f9d3a6ca15c08f5c5e7828533e5fa2
@@ -1,6 +1,7 @@
#ifndef __DIFF_PATCH_HH__
#define __DIFF_PATCH_HH__
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2002 Graydon Hoare
//
// This program is made available under the GNU GPL version 2.0 or
@@ -179,7 +180,21 @@ struct content_merger
adaptor(adaptor)
{}
- // merge3 on a file (line by line)
+ // Attempt merge3 on a file (line by line). Return true and valid data if
+ // it would succeed; false and invalid data otherwise.
+ bool attempt_auto_merge(file_path const & anc_path, // inputs
+ file_path const & left_path,
+ file_path const & right_path,
+ file_id const & ancestor_id,
+ file_id const & left_id,
+ file_id const & right_id,
+ file_data & left_data, // outputs
+ file_data & right_data,
+ file_data & merge_data);
+
+ // Attempt merge3 on a file (line by line). If it succeeded, store results
+ // in database and return true and valid merged_id; return false
+ // otherwise.
bool try_auto_merge(file_path const & anc_path,
file_path const & left_path,
file_path const & right_path,
============================================================
--- merge.cc 9059000487b5a7e6e6390175c376b45ca4f251f0
+++ merge.cc 46e286382febebbed4f099acb5e83b17146cffb8
@@ -16,6 +16,7 @@
#include "diff_patch.hh"
#include "merge.hh"
+#include "options.hh"
#include "revision.hh"
#include "roster_merge.hh"
#include "safe_map.hh"
@@ -34,17 +35,6 @@ namespace
enum merge_method { auto_merge, user_merge };
void
- get_file_details(roster_t const & ros, node_id nid,
- file_id & fid,
- file_path & pth)
- {
- I(ros.has_node(nid));
- file_t f = downcast_to_file_t(ros.get_node(nid));
- fid = f->content;
- ros.get_name(nid, pth);
- }
-
- void
try_to_merge_files(lua_hooks & lua,
roster_t const & left_roster, roster_t const & right_roster,
roster_merge_result & result, content_merge_adaptor & adaptor,
@@ -71,9 +61,9 @@ namespace
file_id anc_id, left_id, right_id;
file_path anc_path, left_path, right_path;
- get_file_details(*roster_for_file_lca, conflict.nid, anc_id, anc_path);
- get_file_details(left_roster, conflict.nid, left_id, left_path);
- get_file_details(right_roster, conflict.nid, right_id, right_path);
+ roster_for_file_lca->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);
file_id merged_id;
@@ -130,17 +120,32 @@ resolve_merge_conflicts(lua_hooks & lua,
roster_t const & left_roster,
roster_t const & right_roster,
roster_merge_result & result,
- content_merge_adaptor & adaptor)
+ content_merge_adaptor & adaptor,
+ bool resolutions_given)
{
- // FIXME_ROSTERS: we only have code (below) to invoke the
- // line-merger on content conflicts. Other classes of conflict will
- // cause an invariant to trip below. Probably just a bunch of lua
- // hooks for remaining conflict types will be ok.
-
if (!result.is_clean())
- result.log_conflicts();
+ {
+ result.log_conflicts();
+ if (resolutions_given)
+ {
+ // If there are any conflicts for which we don't currently support
+ // resolutions, give a nice error message.
+ char const * const msg = "conflict resolution for %s not yet supported";
+ N(!result.missing_root_dir, F(msg) % "missing_root_dir");
+ N(result.invalid_name_conflicts.size() == 0, F(msg) % "invalid_name_conflicts");
+ N(result.directory_loop_conflicts.size() == 0, F(msg) % "directory_loop_conflicts");
+ N(result.orphaned_node_conflicts.size() == 0, F(msg) % "orphaned_node_conflicts");
+ N(result.multiple_name_conflicts.size() == 0, F(msg) % "multiple_name_conflicts");
+ N(result.attribute_conflicts.size() == 0, F(msg) % "attribute_conflicts");
+
+ // resolve the ones we can.
+ result.resolve_duplicate_name_conflicts(lua, left_roster, right_roster, adaptor);
+ result.resolve_file_content_conflicts(lua, left_roster, right_roster, adaptor);
+ }
+ }
+
if (result.has_non_content_conflicts())
{
result.report_missing_root_conflicts(left_roster, right_roster, adaptor, false, std::cout);
@@ -152,7 +157,7 @@ resolve_merge_conflicts(lua_hooks & lua,
result.report_duplicate_name_conflicts(left_roster, right_roster, adaptor, false, std::cout);
result.report_attribute_conflicts(left_roster, right_roster, adaptor, false, std::cout);
- result.report_file_content_conflicts(left_roster, right_roster, adaptor, false, std::cout);
+ result.report_file_content_conflicts(lua, left_roster, right_roster, adaptor, false, std::cout);
}
else if (result.has_content_conflicts())
{
@@ -170,7 +175,7 @@ resolve_merge_conflicts(lua_hooks & lua,
P(FP("%d content conflict requires user intervention",
"%d content conflicts require user intervention",
remaining) % remaining);
- result.report_file_content_conflicts(left_roster, right_roster, adaptor, false, std::cout);
+ result.report_file_content_conflicts(lua, left_roster, right_roster, adaptor, false, std::cout);
try_to_merge_files(lua, left_roster, right_roster,
result, adaptor, user_merge);
@@ -182,7 +187,9 @@ void
}
void
-interactive_merge_and_store(lua_hooks & lua, database & db,
+interactive_merge_and_store(lua_hooks & lua,
+ database & db,
+ options const & opts,
revision_id const & left_rid,
revision_id const & right_rid,
revision_id & merged_rid)
@@ -202,11 +209,14 @@ interactive_merge_and_store(lua_hooks &
right_roster, right_marking_map, right_uncommon_ancestors,
result);
+ bool resolutions_given;
content_merge_database_adaptor dba(db, left_rid, right_rid,
left_marking_map, right_marking_map);
- resolve_merge_conflicts(lua, left_roster, right_roster,
- result, dba);
+ parse_resolve_conflicts_opts (opts, left_roster, right_roster, result, resolutions_given);
+
+ resolve_merge_conflicts(lua, left_roster, right_roster, result, dba, resolutions_given);
+
// write new files into the db
store_roster_merge_result(db,
left_roster, right_roster, result,
============================================================
--- merge.hh f3613e195ad378f7666d003b959b322abbce97d0
+++ merge.hh 8cd4bcbd4dfc7b481569c7725003e42bee951969
@@ -1,6 +1,7 @@
#ifndef __MERGE_HH__
#define __MERGE_HH__
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2005 Nathaniel Smith
//
// This program is made available under the GNU GPL version 2.0 or
@@ -23,13 +24,15 @@ struct content_merge_adaptor;
struct roster_merge_result;
struct content_merge_adaptor;
+struct options;
void
resolve_merge_conflicts(lua_hooks & lua,
roster_t const & left_roster,
roster_t const & right_roster,
roster_merge_result & result,
- content_merge_adaptor & adaptor);
+ content_merge_adaptor & adaptor,
+ bool const resolutions_given);
// traditional resolve-all-conflicts-as-you-go style merging with 3-way merge
// for file texts
@@ -43,7 +46,9 @@ void
// around the revision and its files not being in the db, and the resulting
// revision and its merged files not being written back to the db
void
-interactive_merge_and_store(lua_hooks & lua, database & db,
+interactive_merge_and_store(lua_hooks & lua,
+ database & db,
+ options const & opts,
revision_id const & left,
revision_id const & right,
revision_id & merged);
============================================================
--- monotone.texi 6a8534e5e1e4045df89160f4d49b69a0fed06077
+++ monotone.texi 2f6f14a68447e9b3418c328cd350db9c54c0d616
@@ -3019,6 +3019,33 @@ @section Merge Conflicts
Unfortunately, these commands can't yet list conflicts between a
database revision and the current workspace.
+All merging commands accept options that specify conflict resolutions:
address@hidden @command
address@hidden address@hidden resolution}
address@hidden address@hidden
address@hidden table
+
+For @command{--resolve-conflicts-file}, the file must contain the output
+of @command{automate show_conflicts}, with conflict resolutions
+appended to each stanza. @file{_MTN/conflicts} is a good place to put
+the file.
+
+For @command{--resolve-conflicts}, a single conflict resolution is given
+in the string; it has the same format as the conflict resolutions in
+the file, and must be applicable to all conflicts in the merge. This
+is most usefull when there is a single conflict.
+
+The @command{merge} command normally will perform as many merges as
+necessary to merge all current heads of a branch. However, when
address@hidden or @command{--resolve-conflicts-file} is given,
+the conflicts and their resolutions apply only to the first merge, so
+the subsequent merges are not done; the @command{merge} command must
+be repeated, possibly with new conflicts and resolutions, to merge the
+remaining heads.
+
+The possible conflict resolutions are discussed with each conflict in
+the following sections.
+
@subsection Conflict Types
Monotone versions both files and directories explicitly and it tracks
@@ -8606,9 +8633,14 @@ @section Automation
workspace is present, the branch may be given by the @var{--branch}
option.
address@hidden Added in:
address@hidden Changes:
-7.1
address@hidden
address@hidden
+FIXME_RESOLVE_CONFLICTS: -- added default resolution for file content conflicts
address@hidden
+7.1 -- initial
address@hidden itemize
@item Purpose:
@@ -8732,14 +8764,15 @@ @section Automation
File content changed (this may be resolvable by the internal line
merger), file also renamed:
@verbatim
- conflict content
- node_type "file"
- ancestor_name "bar"
-ancestor_file_id [f0ef49fe92167fe2a086588019ffcff7ea561786]
- left_name "bar"
- left_file_id [08cd878106a93ce2ef036a32499c1432adb3ee0d]
- right_name "bar"
- right_file_id [0cf419dd93d38b2daaaf1f5e0f3ec647745b9690]
+ conflict content
+ node_type "file"
+ ancestor_name "bar"
+ ancestor_file_id [f0ef49fe92167fe2a086588019ffcff7ea561786]
+ left_name "bar"
+ left_file_id [08cd878106a93ce2ef036a32499c1432adb3ee0d]
+ right_name "bar"
+ right_file_id [0cf419dd93d38b2daaaf1f5e0f3ec647745b9690]
+resolved_internal
conflict content
node_type "file"
@@ -8750,6 +8783,10 @@ @section Automation
right_name "baz"
right_file_id [b966b2d35b99e456cb0c55e4573ef0b1b155b4a9]
@end verbatim
address@hidden is a conflict resolution; see @ref{Merge
+Conflicts}. If the file contents in the two revs can be successfully
+merged by the internal line merger, @code{resolved_internal} is
+output.
File added and/or renamed:
@verbatim
============================================================
--- options_list.hh cd0ff5a0d051b4644c02a5f81ed262a512c65360
+++ options_list.hh 154daf8d9f4ab4673441dc8e30bf7531fb7fe3eb
@@ -1,3 +1,4 @@
+// Copyright 2008 Stephen Leake
// Copyright 2006 Timothy Brownawell
// This is made available under the GNU GPL v2 or later.
@@ -635,6 +636,30 @@ OPTION(automate_inventory_opts, no_corre
}
#endif
+OPTSET(resolve_conflicts_opts)
+OPTVAR(resolve_conflicts_opts, utf8, resolve_conflicts_file, )
+OPTVAR(resolve_conflicts_opts, std::string, resolve_conflicts, )
+
+OPTION(resolve_conflicts_opts, resolve_conflicts_file, true, "resolve-conflicts-file",
+ gettext_noop("use file to resolve conflicts"))
+#ifdef option_bodies
+{
+ N(!resolve_conflicts_given,
+ F("only one of --resolve-conflicts or --resolve-conflicts-file may be given"));
+ resolve_conflicts_file = utf8(arg);
+}
+#endif
+
+OPTION(resolve_conflicts_opts, resolve_conflicts, true, "resolve-conflicts",
+ gettext_noop("use argument to resolve conflicts"))
+#ifdef option_bodies
+{
+ N(!resolve_conflicts_file_given,
+ F("only one of --resolve-conflicts or --resolve-conflicts-file may be given"));
+ resolve_conflicts = arg;
+}
+#endif
+
// Local Variables:
// mode: C++
// fill-column: 76
============================================================
--- roster.cc 9cebdc581039f2d117abc2c90c4230b2c9635166
+++ roster.cc 04ac3afba58ec4c48d56f080d46ed924e51df79c
@@ -1,3 +1,4 @@
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2005 Nathaniel Smith
//
// This program is made available under the GNU GPL version 2.0 or
@@ -46,6 +47,19 @@ using boost::lexical_cast;
///////////////////////////////////////////////////////////////////
+namespace
+{
+ namespace syms
+ {
+ symbol const birth("birth");
+ symbol const dormant_attr("dormant_attr");
+ symbol const ident("ident");
+
+ symbol const path_mark("path_mark");
+ symbol const attr_mark("attr_mark");
+ }
+}
+
template <> void
dump(node_id const & val, string & out)
{
@@ -517,9 +531,8 @@ shallow_equal(node_t a, node_t b,
}
-// FIXME_ROSTERS: why does this do two loops? why does it pass 'true' to
-// shallow_equal?
-// -- njs
+// FIXME_ROSTERS: why does this do two loops? why does it pass 'true' for
+// shallow_compare_dir_children to shallow_equal? -- njs
bool
roster_t::operator==(roster_t const & other) const
{
@@ -847,6 +860,7 @@ roster_t::create_file_node(file_id const
create_file_node(content, nid);
return nid;
}
+
void
roster_t::create_file_node(file_id const & content, node_id nid)
{
@@ -1858,7 +1872,6 @@ namespace {
left_cs.apply_to(from_left_er);
right_cs.apply_to(from_right_er);
- set new_ids;
unify_rosters(new_roster, from_left_er.new_nodes,
from_right_r, from_right_er.new_nodes,
nis);
@@ -2430,6 +2443,17 @@ void
}
void
+roster_t::get_file_details(node_id nid,
+ file_id & fid,
+ file_path & pth) const
+{
+ I(has_node(nid));
+ file_t f = downcast_to_file_t(get_node(nid));
+ fid = f->content;
+ get_name(nid, pth);
+}
+
+void
roster_t::extract_path_set(set & paths) const
{
paths.clear();
@@ -2473,11 +2497,11 @@ push_marking(basic_io::stanza & st,
{
I(!null_id(mark.birth_revision));
- st.push_binary_pair(basic_io::syms::birth, mark.birth_revision.inner());
+ st.push_binary_pair(syms::birth, mark.birth_revision.inner());
for (set::const_iterator i = mark.parent_name.begin();
i != mark.parent_name.end(); ++i)
- st.push_binary_pair(basic_io::syms::path_mark, i->inner());
+ st.push_binary_pair(syms::path_mark, i->inner());
if (is_file)
{
@@ -2493,7 +2517,7 @@ push_marking(basic_io::stanza & st,
{
for (set::const_iterator j = i->second.begin();
j != i->second.end(); ++j)
- st.push_binary_triple(basic_io::syms::attr_mark, i->first(), j->inner());
+ st.push_binary_triple(syms::attr_mark, i->first(), j->inner());
}
}
@@ -2505,13 +2529,13 @@ parse_marking(basic_io::parser & pa,
while (pa.symp())
{
string rev;
- if (pa.symp(basic_io::syms::birth))
+ if (pa.symp(syms::birth))
{
pa.sym();
pa.hex(rev);
marking.birth_revision = revision_id(decode_hexenc(rev));
}
- else if (pa.symp(basic_io::syms::path_mark))
+ else if (pa.symp(syms::path_mark))
{
pa.sym();
pa.hex(rev);
@@ -2523,7 +2547,7 @@ parse_marking(basic_io::parser & pa,
pa.hex(rev);
safe_insert(marking.file_content, revision_id(decode_hexenc(rev)));
}
- else if (pa.symp(basic_io::syms::attr_mark))
+ else if (pa.symp(syms::attr_mark))
{
string k;
pa.sym();
@@ -2573,7 +2597,7 @@ roster_t::print_to(basic_io::printer & p
if (print_local_parts)
{
I(curr->self != the_null_node);
- st.push_str_pair(basic_io::syms::ident, lexical_cast(curr->self));
+ st.push_str_pair(syms::ident, lexical_cast(curr->self));
}
// Push the non-dormant part of the attr map
@@ -2596,7 +2620,7 @@ roster_t::print_to(basic_io::printer & p
if (!j->second.first)
{
I(j->second.second().empty());
- st.push_str_pair(basic_io::syms::dormant_attr, j->first());
+ st.push_str_pair(syms::dormant_attr, j->first());
}
}
@@ -2662,7 +2686,7 @@ roster_t::parse_from(basic_io::parser &
pa.str(pth);
pa.esym(basic_io::syms::content);
pa.hex(content);
- pa.esym(basic_io::syms::ident);
+ pa.esym(syms::ident);
pa.str(ident);
n = file_t(new file_node(read_num(ident),
file_id(decode_hexenc(content))));
@@ -2671,7 +2695,7 @@ roster_t::parse_from(basic_io::parser &
{
pa.sym();
pa.str(pth);
- pa.esym(basic_io::syms::ident);
+ pa.esym(syms::ident);
pa.str(ident);
n = dir_t(new dir_node(read_num(ident)));
}
@@ -2704,7 +2728,7 @@ roster_t::parse_from(basic_io::parser &
}
// Dormant attrs
- while(pa.symp(basic_io::syms::dormant_attr))
+ while(pa.symp(syms::dormant_attr))
{
pa.sym();
string k;
============================================================
--- roster.hh 0da49ea4d1bda34fe2875b0e45add9b6918f2df4
+++ roster.hh c83fc2636e82ac77a1446c42df169b44bb47197f
@@ -1,6 +1,7 @@
#ifndef __ROSTER_HH__
#define __ROSTER_HH__
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2005 Nathaniel Smith
//
// This program is made available under the GNU GPL version 2.0 or
@@ -202,6 +203,10 @@ public:
attr_key const & key,
attr_value & val) const;
+ void get_file_details(node_id nid,
+ file_id & fid,
+ file_path & pth) const;
+
void extract_path_set(std::set & paths) const;
node_map const & all_nodes() const
============================================================
--- roster_merge.cc 9bb39c2c87f92180b0a5db5697ff6ea15b82d25b
+++ roster_merge.cc 2904442eb8835e76182b01daed84ac42f16bc258
@@ -11,14 +11,15 @@
#include "base.hh"
#include
-#include
-
#include "basic_io.hh"
-#include "vocab.hh"
+#include "file_io.hh"
+#include "lua_hooks.hh"
+#include "options.hh"
+#include "parallel_iter.hh"
#include "roster_merge.hh"
-#include "parallel_iter.hh"
#include "safe_map.hh"
#include "transforms.hh"
+#include "vocab.hh"
using boost::shared_ptr;
@@ -28,6 +29,24 @@ using std::string;
using std::set;
using std::string;
+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";
+ default:
+ I(false);
+ }
+}
+
template <> void
dump(invalid_name_conflict const & conflict, string & out)
{
@@ -77,7 +96,19 @@ dump(duplicate_name_conflict const & con
oss << "duplicate_name_conflict between left node: " << conflict.left_nid << " "
<< "and right node: " << conflict.right_nid << " "
<< "parent: " << conflict.parent_name.first << " "
- << "basename: " << conflict.parent_name.second << "\n";
+ << "basename: " << conflict.parent_name.second;
+
+ if (conflict.left_resolution.first != resolve_conflicts::none)
+ {
+ oss << " left_resolution: " << image(conflict.left_resolution.first);
+ oss << " left_name: " << conflict.left_resolution.second;
+ }
+ if (conflict.right_resolution.first != resolve_conflicts::none)
+ {
+ oss << " right_resolution: " << image(conflict.right_resolution.first);
+ oss << " right_name: " << conflict.right_resolution.second;
+ }
+ oss << "\n";
out = oss.str();
}
@@ -96,9 +127,14 @@ dump(file_content_conflict const & confl
dump(file_content_conflict const & conflict, string & out)
{
ostringstream oss;
- oss << "file_content_conflict on node: " << conflict.nid << " "
- << "left: " << conflict.left << " "
- << "right: " << conflict.right << "\n";
+ oss << "file_content_conflict on node: " << conflict.nid;
+
+ if (conflict.resolution.first != resolve_conflicts::none)
+ {
+ oss << " resolution: " << image(conflict.resolution.first);
+ oss << " name: " << conflict.resolution.second;
+ }
+ oss << "\n";
out = oss.str();
}
@@ -178,10 +214,7 @@ namespace
else
I(false);
}
-}
-namespace
-{
namespace syms
{
symbol const ancestor_file_id("ancestor_file_id");
@@ -203,6 +236,10 @@ namespace
symbol const node_type("node_type");
symbol const orphaned_directory("orphaned_directory");
symbol const orphaned_file("orphaned_file");
+ 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 right_attr_state("right_attr_state");
symbol const right_attr_value("right_attr_value");
symbol const right_file_id("right_file_id");
@@ -398,7 +435,6 @@ put_attr_conflict (basic_io::stanza & st
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());
- // FIXME: don't have this. st.push_str_pair(syms::ancestor_attr_value, ???);
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);
@@ -415,7 +451,6 @@ put_attr_conflict (basic_io::stanza & st
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());
- // FIXME: don't have this. st.push_str_pair(syms::ancestor_attr_value, ???);
st.push_file_pair(syms::left_name, left_name);
put_attr_state_left (st, conflict);
st.push_file_pair(syms::right_name, right_name);
@@ -425,6 +460,8 @@ put_content_conflict (basic_io::stanza &
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)
{
@@ -437,18 +474,13 @@ put_content_conflict (basic_io::stanza &
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);
+ 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))
{
@@ -457,14 +489,30 @@ put_content_conflict (basic_io::stanza &
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_file_pair(syms::left_name, left_name);
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_file_pair(syms::right_name, right_name);
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_file_pair(syms::resolved_user, conflict.resolution.second);
+ break;
+
+ default:
+ I(false);
+ }
}
else
{
@@ -472,6 +520,16 @@ put_content_conflict (basic_io::stanza &
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);
+ }
}
}
@@ -1274,28 +1332,63 @@ roster_merge_result::report_attribute_co
}
}
+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(roster_t const & left_roster,
+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) const
+ std::ostream & output)
{
MM(left_roster);
MM(right_roster);
for (size_t i = 0; i < file_content_conflicts.size(); ++i)
{
- file_content_conflict const & conflict = file_content_conflicts[i];
+ file_content_conflict & conflict = file_content_conflicts[i];
MM(conflict);
-
if (basic_io)
{
basic_io::stanza st;
+ if (auto_merge_succeeds(lua, conflict, adaptor, left_roster, right_roster))
+ conflict.resolution = make_pair(resolve_conflicts::content_internal, file_path());
+
st.push_str_pair(syms::conflict, syms::content);
- put_content_conflict (st, adaptor, conflict);
+ put_content_conflict (st, left_roster, right_roster, adaptor, conflict);
put_stanza (st, output);
}
else
@@ -1338,7 +1431,467 @@ roster_merge_result::report_file_content
}
}
+// 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 void
+parse_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 & conflict = *i;
+
+ pars.esym(syms::duplicate_name);
+
+ node_id left_nid, right_nid;
+ string left_name, right_name;
+
+ pars.esym(syms::left_type); pars.str();
+ pars.esym (syms::left_name); pars.str(left_name);
+ pars.esym(syms::left_file_id); pars.hex();
+
+ pars.esym(syms::right_type); pars.str();
+ pars.esym (syms::right_name); pars.str(right_name);
+ pars.esym(syms::right_file_id); pars.hex();
+
+ left_nid = left_roster.get_node (file_path_internal (left_name))->self;
+ right_nid = right_roster.get_node (file_path_internal (right_name))->self;
+
+ // Note that we cannot confirm the file ids.
+ N(left_nid == conflict.left_nid && right_nid == conflict.right_nid,
+ F("conflicts file does not match current conflicts: (duplicate_name, left %s, right %s")
+ % left_name % right_name);
+
+ // check for a resolution
+ while ((!pars.symp (syms::conflict)) && pars.tok.in.lookahead != EOF)
+ {
+ if (pars.symp (syms::resolved_rename_left))
+ {
+ conflict.left_resolution.first = resolve_conflicts::rename;
+ pars.sym();
+ conflict.left_resolution.second = file_path_internal (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 = file_path_internal (pars.token);
+ pars.str();
+ }
+ else
+ N(false, F("%s is not a supported conflict resolution for %s") % pars.token % "duplicate_name");
+ }
+
+ if (pars.tok.in.lookahead != EOF)
+ pars.esym (syms::conflict);
+ else
+ {
+ std::vector::iterator tmp = i;
+ N(++tmp == conflicts.end(), F("conflicts file does not match current conflicts"));
+ }
+ }
+} // parse_duplicate_name_conflicts
+
+static void
+parse_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)
+ {
+ string tmp;
+ node_id left_nid, right_nid;
+ string left_name, right_name, result_name;
+
+ file_content_conflict & conflict = *i;
+
+ pars.esym(syms::content);
+
+ 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();
+
+ pars.esym (syms::left_name); pars.str(left_name);
+ pars.esym(syms::left_file_id); pars.hex();
+
+ pars.esym (syms::right_name); pars.str(right_name);
+ pars.esym(syms::right_file_id); pars.hex();
+
+ left_nid = left_roster.get_node (file_path_internal (left_name))->self;
+ right_nid = right_roster.get_node (file_path_internal (right_name))->self;
+
+ N(left_nid == conflict.nid & right_nid == conflict.nid,
+ F("conflicts file does not match current conflicts: (file_content, left %s, right %s")
+ % left_name % right_name);
+
+ // 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 = file_path_internal (pars.token);
+ pars.str();
+ }
+ else
+ N(false, F("%s is not a supported conflict resolution for %s") % pars.token % "file_content");
+ }
+
+ if (pars.tok.in.lookahead != EOF)
+ pars.esym (syms::conflict);
+ else
+ {
+ std::vector::iterator tmp = i;
+ N(++tmp == conflicts.end(), F("conflicts file does not match current conflicts"));
+ }
+ }
+} // parse_file_content_conflicts
+
+static void
+parse_resolve_conflicts_str(basic_io::parser & pars, roster_merge_result & result)
+{
+ char const * error_message_1 = "can't specify a %s conflict resolution for more than one conflict";
+ char const * error_message_2 = "conflict resolution %s is not appropriate for current conflicts";
+
+ // We don't detect all cases of inappropriate resolutions here; that would
+ // be too hard to maintain as more conflicts and/or resolutions are added.
+ // If the single resolution specified is not appropriate for some
+ // conflict, that conflict will not be resolved, which will be reported
+ // later. Then the user will need to use a conflict resolution file.
+ while (pars.tok.in.lookahead != EOF)
+ {
+ // resolution alphabetical order
+ if (pars.symp (syms::resolved_rename_left))
+ {
+ N(result.duplicate_name_conflicts.size() == 1,
+ F(error_message_1) % syms::resolved_rename_left);
+
+ duplicate_name_conflict & conflict = *result.duplicate_name_conflicts.begin();
+
+ conflict.left_resolution.first = resolve_conflicts::rename;
+ pars.sym();
+ conflict.left_resolution.second = file_path_internal (pars.token);
+ pars.str();
+ }
+ else if (pars.symp (syms::resolved_rename_right))
+ {
+ N(result.duplicate_name_conflicts.size() == 1,
+ F(error_message_1) % syms::resolved_rename_right);
+
+ duplicate_name_conflict & conflict = *result.duplicate_name_conflicts.begin();
+
+ conflict.right_resolution.first = resolve_conflicts::rename;
+ pars.sym();
+ conflict.right_resolution.second = file_path_internal (pars.token);
+ pars.str();
+ }
+ else if (pars.symp (syms::resolved_user))
+ {
+ N(result.file_content_conflicts.size() == 1,
+ F(error_message_1) % syms::resolved_user);
+
+ file_content_conflict & conflict = *result.file_content_conflicts.begin();
+
+ conflict.resolution.first = resolve_conflicts::content_user;
+ pars.sym();
+ conflict.resolution.second = file_path_internal (pars.token);
+ pars.str();
+ }
+ else
+ N(false, F("%s is not a supported conflict resolution") % pars.token);
+
+ } // while
+}
+
void
+parse_resolve_conflicts_opts (options const & opts,
+ roster_t const & left_roster,
+ roster_t const & right_roster,
+ roster_merge_result & result,
+ bool & resolutions_given)
+{
+ if (opts.resolve_conflicts_given)
+ {
+ resolutions_given = true;
+
+ basic_io::input_source src(opts.resolve_conflicts, "resolve_conflicts string");
+ basic_io::tokenizer tok(src);
+ basic_io::parser pars(tok);
+
+ parse_resolve_conflicts_str(pars, result);
+
+ if (src.lookahead != EOF)
+ pars.err("invalid conflict resolution syntax");
+ }
+ else if (opts.resolve_conflicts_file_given)
+ {
+ resolutions_given = true;
+
+ data dat;
+
+ if (opts.resolve_conflicts_file().substr(0, 4) == "_MTN")
+ read_data (bookkeeping_path(opts.resolve_conflicts_file()), dat);
+ else
+ read_data (file_path_external(opts.resolve_conflicts_file), dat);
+
+ basic_io::input_source src(dat(), opts.resolve_conflicts_file());
+ basic_io::tokenizer tok(src);
+ basic_io::parser pars(tok);
+
+ // Skip left, right, ancestor. FIXME_SUTURE: should check these! But don't
+ // see how to access them right now.
+ for (int i = 1; i <= 3; i++)
+ {
+ pars.sym();
+ pars.hex();
+ }
+
+ // Get into the first conflict
+ pars.esym (syms::conflict);
+
+ // 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.
+
+ // 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_dir);
+ 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.
+
+ parse_duplicate_name_conflicts(pars, result.duplicate_name_conflicts, left_roster, right_roster);
+ parse_file_content_conflicts(pars, result.file_content_conflicts, left_roster, right_roster);
+
+ if (src.lookahead != EOF)
+ pars.err("extra conflicts in file");
+ }
+ 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());
+
+ N(!new_roster.has_node(target_path), F("%s already exists") % target_path.as_external());
+ N(new_roster.has_node(target_path.dirname()),
+ 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
+
+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;
+
+ left_roster.get_name(left_nid, left_name);
+ right_roster.get_name(right_nid, right_name);
+
+ switch (conflict.left_resolution.first)
+ {
+ case resolve_conflicts::rename:
+ P(F("renaming %s to %s") % left_name % conflict.left_resolution.second);
+ attach_node (lua, this->roster, left_nid, conflict.left_resolution.second);
+ break;
+
+ case resolve_conflicts::none:
+ N(false, F("no resolution provided for duplicate_name %s") % left_name);
+ break;
+
+ default:
+ N(false, F("%s: invalid resolution for this conflict") % image (conflict.left_resolution.first));
+ }
+
+ switch (conflict.right_resolution.first)
+ {
+ case resolve_conflicts::rename:
+ P(F("renaming %s to %s") % right_name % conflict.right_resolution.second);
+ attach_node (lua, this->roster, right_nid, conflict.right_resolution.second);
+ break;
+
+ case resolve_conflicts::none:
+ // Just keep current name
+ this->roster.attach_node (right_nid, right_name);
+ break;
+
+ default:
+ N(false, F("%s: invalid resolution for this conflict") % image (conflict.right_resolution.first));
+ }
+ } // 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;
+
+ N(resolve_conflicts::do_auto_merge(lua, conflict, adaptor, left_roster,
+ right_roster, this->roster, merged_id),
+ 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);
+
+ 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_dir = false;
@@ -1396,22 +1949,22 @@ namespace
bool left_wins = a_wins(right_marks, right_uncommon_ancestors);
bool right_wins = a_wins(left_marks, left_uncommon_ancestors);
// two bools means 4 cases:
- // left_wins && right_wins
+
// this is ambiguous clean merge, which is theoretically impossible.
I(!(left_wins && right_wins));
- // left_wins && !right_wins
+
if (left_wins && !right_wins)
{
result = left;
return true;
}
- // !left_wins && right_wins
+
if (!left_wins && right_wins)
{
result = right;
return true;
}
- // !left_wins && !right_wins
+
if (!left_wins && !right_wins)
{
conflict_descriptor.left = left;
============================================================
--- roster_merge.hh 48d9aa743811732826e3f855a5759c1d61416984
+++ roster_merge.hh 8f0603774befb2458b9e6a2660a90f31104a9a22
@@ -1,6 +1,7 @@
#ifndef __ROSTER_MERGE_HH__
#define __ROSTER_MERGE_HH__
+// Copyright (C) 2008 Stephen Leake
// Copyright (C) 2005 Nathaniel Smith
//
// This program is made available under the GNU GPL version 2.0 or
@@ -10,6 +11,8 @@
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE.
+#include
+
#include "rev_types.hh"
#include "database.hh"
#include "diff_patch.hh"
@@ -24,6 +27,11 @@
// manifest.)
//
+namespace resolve_conflicts
+{
+ enum resolution_t {none, content_user, content_internal, rename};
+}
+
// renaming the root dir allows these:
// -- _MTN in root
// -- missing root directory
@@ -86,6 +94,11 @@ struct duplicate_name_conflict
{
node_id left_nid, right_nid;
std::pair parent_name;
+ std::pair left_resolution, right_resolution;
+
+ duplicate_name_conflict ()
+ {left_resolution.first = resolve_conflicts::none;
+ right_resolution.first = resolve_conflicts::none;};
};
// nodes with attribute conflicts are left attached in the resulting tree (unless
@@ -107,11 +120,17 @@ struct file_content_conflict
struct file_content_conflict
{
node_id nid;
- file_content_conflict(node_id nid) : nid(nid) {}
file_id left, right;
-};
+ std::pair resolution;
+ file_content_conflict () :
+ nid(the_null_node),
+ resolution(std::make_pair(resolve_conflicts::none, file_path())) {};
+ file_content_conflict(node_id nid) :
+ nid(nid), resolution(std::make_pair(resolve_conflicts::none, file_path())) {};
+};
+
template <> void dump(invalid_name_conflict const & conflict, std::string & out);
template <> void dump(directory_loop_conflict const & conflict, std::string & out);
@@ -180,23 +199,35 @@ struct roster_merge_result
content_merge_adaptor & adaptor,
bool const basic_io,
std::ostream & output) const;
+
void report_duplicate_name_conflicts(roster_t const & left,
roster_t const & right,
content_merge_adaptor & adaptor,
bool const basic_io,
std::ostream & output) const;
+ void resolve_duplicate_name_conflicts(lua_hooks & lua,
+ roster_t const & left_roster,
+ roster_t const & right_roster,
+ content_merge_adaptor & adaptor);
void report_attribute_conflicts(roster_t const & left,
roster_t const & right,
content_merge_adaptor & adaptor,
bool const basic_io,
std::ostream & output) const;
- void report_file_content_conflicts(roster_t const & left,
- roster_t const & right,
+
+ // not 'const' because this sets resolution to 'resolved_internal' if the
+ // internal merger would succeed.
+ void report_file_content_conflicts(lua_hooks & lua,
+ roster_t const & left_roster,
+ roster_t const & right_roster,
content_merge_adaptor & adaptor,
bool const basic_io,
- std::ostream & output) const;
-
+ std::ostream & output);
+ void resolve_file_content_conflicts(lua_hooks & lua,
+ roster_t const & left_roster,
+ roster_t const & right_roster,
+ content_merge_adaptor & adaptor);
void clear();
};
@@ -211,6 +242,12 @@ roster_merge(roster_t const & left_paren
std::set const & right_uncommon_ancestors,
roster_merge_result & result);
+void
+parse_resolve_conflicts_opts (options const & opts,
+ roster_t const & left_roster,
+ roster_t const & right_roster,
+ roster_merge_result & result,
+ bool & resolutions_given);
// Local Variables:
// mode: C++
============================================================
--- testlib.lua 0e8c0415f85924dfb385a6e6a0de8b0f787a611d
+++ testlib.lua 785c976d13cafe3d472fa9d7121ddb7f75a4bae9
@@ -439,6 +439,10 @@ end
end
end
+function samefilestd(left, right)
+ return samefile(testdir .. "/" .. test.name .. "/" .. left, right)
+end
+
function samelines(f, t)
local fl = {}
for l in io.lines(f) do table.insert(fl, l) end