monotone-commits-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Monotone-commits-diffs] net.venge.monotone.issue-209: d4a22d4d54c5ab65e


From: code
Subject: [Monotone-commits-diffs] net.venge.monotone.issue-209: d4a22d4d54c5ab65e491cd43f0f12a3b96b45c78
Date: Mon, 23 Jul 2012 11:01:12 +0200 (CEST)

revision:            d4a22d4d54c5ab65e491cd43f0f12a3b96b45c78
date:                2012-07-16T11:51:51
author:              address@hidden
branch:              net.venge.monotone.issue-209
changelog:
restructure dropped_modified conflicts to handle duplicate_name; first draft. 
compiles, but not complete; commited for backup

* doc/monotone.texi (Conflicts): add user_rename conflict resolution

* src/merge_roster.hh: restructure dropped_modified conflicts to handle
  duplicate_name; store rename in file_resolution_t.

* src/cmd_conflicts.cc:
* src/merge_conflict.cc:
* src/merge_content.cc:
* src/merge_roster.cc: dropped_modified conflicts handle duplicate_name

* test/func/resolve_conflicts_dropped_modified_upstream_vs_local_2: new
  test case that shows duplicate_name/dropped_modified

manifest:
format_version "1"

new_manifest [e25b70e5808ad7e5c4103889103497defff9578f]

old_revision [d6e8f82dc157a9aa5b10b2f11106067683e6f93d]

add_dir "test/func/resolve_conflicts_dropped_modified_upstream_vs_local_2"

add_file 
"test/func/resolve_conflicts_dropped_modified_upstream_vs_local_2/__driver__.lua"
 content [46e918155fe50737229d3c2cb31899cf204ef9fd]

add_file 
"test/func/resolve_conflicts_dropped_modified_upstream_vs_local_2/conflicts_3_2"
 content [bb23f1fbf87e4f1dcb8aa7c4c3fcb90e1fc84fed]

add_file 
"test/func/resolve_conflicts_dropped_modified_upstream_vs_local_2/conflicts_3_2_resolved"
 content [954b8dfc7ee161c1ca809b61d03ad8fd12bfb539]

patch "doc/monotone.texi"
 from [d1225486c4df3ec2023d0e303aac65b8ff10a54e]
   to [0f1e2230272b68614efad33648dbf436e0d6f729]

patch "src/cmd_conflicts.cc"
 from [e1b7feca5acd314d85498cabedf888f5293bf30e]
   to [c4c2c75feb32cce46f146e05aba424c038152ded]

patch "src/merge_conflict.cc"
 from [34fbc891bc8455202d9c0d0a495beb690fd8a5a0]
   to [d653ef4d1d748fa833b2500eed77ecc0107d48fb]

patch "src/merge_content.cc"
 from [662433acaf08fc1f3f3417389a9bc3b3d2bd9bf1]
   to [6a1d7ad7184f88674a585f5065e965f15294f7dc]

patch "src/merge_roster.cc"
 from [5fbc50c114df22f4753f444aa137c06ea0f06195]
   to [b4de8ec7db2c60b3ebb777bfa58d9e0d8986558d]

patch "src/merge_roster.hh"
 from [34d382b0ad333fa0f052e8640a88c1ece03caaa9]
   to [63eab7b216f8839626f9d9d04dc8e58c3eed90bf]
============================================================
--- doc/monotone.texi	d1225486c4df3ec2023d0e303aac65b8ff10a54e
+++ doc/monotone.texi	0f1e2230272b68614efad33648dbf436e0d6f729
@@ -1,4 +1,4 @@
-0\input texinfo   @c -*-texinfo-*-
+\input texinfo   @c -*-texinfo-*-
 @c %**start of header
 @setfilename monotone.info
 @settitle monotone documentation
@@ -5232,6 +5232,16 @@ @subheading Conflict resolutions
 @command{resolved_rename_right @var{filename}} conflict resolution in
 the conflicts file.
 
address@hidden user_rename @var{contents_file} @var{rename_file}
+The file contents are replaced by the contents of the specified file,
+and renamed.
+
+This inserts a @command{resolved_user_left @var{contents_file}} or
address@hidden @var{contents_file}} conflict resolution
+in the conflicts file, and a @command{resolved_rename_left
address@hidden or @command{resolved_rename_right
address@hidden conflict resolution.
+
 @item keep
 The file is kept in the merge.
 
============================================================
--- src/cmd_conflicts.cc	e1b7feca5acd314d85498cabedf888f5293bf30e
+++ src/cmd_conflicts.cc	c4c2c75feb32cce46f146e05aba424c038152ded
@@ -60,7 +60,7 @@ show_conflicts(database & db, conflicts_
     {
       orphaned_node_conflict & conflict = *i;
 
-      if (conflict.resolution.first == resolve_conflicts::none)
+      if (conflict.resolution.resolution == resolve_conflicts::none)
         {
           file_path name;
           if (conflicts.left_roster->has_node(conflict.nid))
@@ -90,83 +90,102 @@ show_conflicts(database & db, conflicts_
     {
       dropped_modified_conflict & conflict = *i;
 
-      if (conflict.resolution.first == resolve_conflicts::none)
+      if (conflict.left_resolution.resolution == resolve_conflicts::none ||
+          conflict.right_resolution.resolution == resolve_conflicts::none)
         {
-          node_id nid;
           file_path modified_name;
 
-          if (conflict.left_nid == the_null_node)
+          switch (conflict.dropped_side)
             {
-              // left side dropped, right side modified
-              nid = conflict.right_nid;
+            case resolve_conflicts::left_side:
               conflicts.right_roster->get_name(conflict.right_nid, modified_name);
-            }
-          else
-            {
-              // left side modified, right side dropped
-              nid = conflict.left_nid;
+              break;
+
+            case resolve_conflicts::right_side:
               conflicts.left_roster->get_name(conflict.left_nid, modified_name);
+              break;
             }
 
           P(F("conflict: file '%s'") % modified_name);
           if (conflict.orphaned)
             {
-              if (conflict.left_nid == the_null_node)
+              switch (conflict.dropped_side)
                 {
+                case resolve_conflicts::left_side:
                   P(F("orphaned on the left"));
                   P(F("modified on the right"));
-                }
-              else
-                {
+                  break;
+
+                case resolve_conflicts::right_side:
                   P(F("modified on the left"));
                   P(F("orphaned on the right"));
                 }
             }
           else
             {
-              if (conflict.left_nid == the_null_node)
+              switch (conflict.dropped_side)
                 {
-                  if (conflict.recreated == the_null_node)
+                case resolve_conflicts::left_side:
+                  if (conflict.left_nid == the_null_node)
                     P(F("dropped on the left"));
                   else
-                    P(F("dropped and recreated on the left"));
+                    {
+                      // we can't distinguish duplicate name from recreated
+                      P(F("dropped and recreated on the left"));
+                    }
 
                   P(F("modified on the right"));
-                }
-              else
-                {
+                  break;
+
+                case resolve_conflicts::right_side:
                   P(F("modified on the left"));
 
-                  if (conflict.recreated == the_null_node)
+                  if (conflict.right_nid == the_null_node)
                     P(F("dropped on the right"));
                   else
-                    P(F("dropped and recreated on the right"));
+                    {
+                      P(F("dropped and recreated on the right"));
+                    }
                 }
             }
 
-          switch (show_case)
-            {
-            case first:
-              P(F("possible resolutions:"));
+          if (show_case == remaining) return;
 
-              if (conflict.recreated == the_null_node)
-                P(F("resolve_first drop"));
+          P(F("possible resolutions:"));
 
-              if (conflict.orphaned)
+          if (conflict.left_nid == the_null_node || conflict.right_nid == the_null_node)
+            {
+              // only one file involved; only need one resolution
+              P(F("resolve_first drop"));
+              P(F("resolve_first rename"));
+              P(F("resolve_first user_rename \"new_content_name\" \"new_file_name\""));
+
+              if (!conflict.orphaned)
                 {
-                  P(F("resolve_first rename"));
-                  P(F("resolve_first user_rename \"new_content_name\" \"new_file_name\""));
-                  return;
-                }
-              else
-                {
                   P(F("resolve_first keep"));
                   P(F("resolve_first user \"name\""));
-                  return;
                 }
+              return;
+            }
+          else
+            {
+              // recreated or repeated duplicate name; need two resolutions
+              P(F("resolve_first_left drop"));
+              P(F("resolve_first_right drop"));
 
-            case remaining:
-              break;
+              P(F("resolve_first_left rename"));
+              P(F("resolve_first_right rename"));
+              P(F("resolve_first_left user_rename \"new_content_name\" \"new_file_name\""));
+              P(F("resolve_first_right user_rename \"new_content_name\" \"new_file_name\""));
+
+              if (!conflict.orphaned)
+                {
+                  P(F("resolve_first_left keep"));
+                  P(F("resolve_first_right keep"));
+                  P(F("resolve_first_left user \"name\""));
+                  P(F("resolve_first_right user \"name\""));
+                }
+              return;
             }
         }
     }
@@ -177,8 +196,8 @@ show_conflicts(database & db, conflicts_
     {
       duplicate_name_conflict & conflict = *i;
 
-      if (conflict.left_resolution.first == resolve_conflicts::none ||
-          conflict.right_resolution.first == resolve_conflicts::none)
+      if (conflict.left_resolution.resolution == resolve_conflicts::none ||
+          conflict.right_resolution.resolution == resolve_conflicts::none)
         {
           file_path left_name;
           conflicts.left_roster->get_name(conflict.left_nid, left_name);
@@ -189,7 +208,7 @@ show_conflicts(database & db, conflicts_
             case first:
               P(F("possible resolutions:"));
 
-              if (conflict.left_resolution.first == resolve_conflicts::none)
+              if (conflict.left_resolution.resolution == resolve_conflicts::none)
                 {
                   P(F("resolve_first_left drop"));
                   P(F("resolve_first_left keep"));
@@ -197,7 +216,7 @@ show_conflicts(database & db, conflicts_
                   P(F("resolve_first_left user \"name\""));
                 }
 
-              if (conflict.right_resolution.first == resolve_conflicts::none)
+              if (conflict.right_resolution.resolution == resolve_conflicts::none)
                 {
                   P(F("resolve_first_right drop"));
                   P(F("resolve_first_right keep"));
@@ -218,7 +237,7 @@ show_conflicts(database & db, conflicts_
     {
       file_content_conflict & conflict = *i;
 
-      if (conflict.resolution.first == resolve_conflicts::none)
+      if (conflict.resolution.resolution == resolve_conflicts::none)
         {
           file_path name;
           conflicts.left_roster->get_name(conflict.nid, name);
@@ -330,48 +349,58 @@ static void
 } // do_interactive_merge
 
 static void
-set_duplicate_name_conflict(resolve_conflicts::file_resolution_t & resolution,
-                            resolve_conflicts::file_resolution_t const & other_resolution,
-                            args_vector const & args)
+set_resolution(resolve_conflicts::file_resolution_t &       resolution,
+               resolve_conflicts::file_resolution_t const & other_resolution,
+               args_vector const &                          args)
 {
   if ("drop" == idx(args, 0)())
     {
       E(args.size() == 1, origin::user, F("too many arguments"));
-      resolution.first = resolve_conflicts::drop;
+      resolution.resolution = resolve_conflicts::drop;
     }
   else if ("keep" == idx(args, 0)())
     {
       E(args.size() == 1, origin::user, F("too many arguments"));
-      E(other_resolution.first == resolve_conflicts::none ||
-        other_resolution.first == resolve_conflicts::drop ||
-        other_resolution.first == resolve_conflicts::rename,
+      E(other_resolution.resolution == resolve_conflicts::none ||
+        other_resolution.resolution == resolve_conflicts::drop ||
+        other_resolution.resolution == resolve_conflicts::rename ||
+        other_resolution.resolution == resolve_conflicts::content_user_rename,
         origin::user,
-        F("other resolution must be 'drop' or 'rename'"));
-      resolution.first = resolve_conflicts::keep;
+        F("other resolution must be 'drop', 'rename', or 'user_rename'"));
+      resolution.resolution = resolve_conflicts::keep;
     }
   else if ("rename" == idx(args, 0)())
     {
       E(args.size() == 2, origin::user, F("wrong number of arguments"));
-      resolution.first  = resolve_conflicts::rename;
-      resolution.second = resolve_conflicts::new_file_path(idx(args,1)());
+      resolution.resolution  = resolve_conflicts::rename;
+      resolution.rename = file_path_external(utf8(idx(args,1)(), origin::user));
     }
   else if ("user" == idx(args, 0)())
     {
       E(args.size() == 2, origin::user, F("wrong number of arguments"));
-      E(other_resolution.first == resolve_conflicts::none ||
-        other_resolution.first == resolve_conflicts::drop ||
-        other_resolution.first == resolve_conflicts::rename,
+      E(other_resolution.resolution == resolve_conflicts::none ||
+        other_resolution.resolution == resolve_conflicts::drop ||
+        other_resolution.resolution == resolve_conflicts::rename ||
+        other_resolution.resolution == resolve_conflicts::content_user_rename,
         origin::user,
-        F("other resolution must be 'drop' or 'rename'"));
+        F("other resolution must be 'drop', 'rename', or 'user_rename'"));
 
-      resolution.first  = resolve_conflicts::content_user;
-      resolution.second = new_optimal_path(idx(args,1)(), false);
+      resolution.resolution  = resolve_conflicts::content_user;
+      resolution.content = new_optimal_path(idx(args,1)(), false);
     }
+  else if ("user_rename" == idx(args,0)())
+    {
+      E(args.size() == 3, origin::user, F("wrong number of arguments"));
+
+      resolution.resolution  = resolve_conflicts::content_user_rename;
+      resolution.content = new_optimal_path(idx(args,1)(), false);
+      resolution.rename = file_path_external(utf8(idx(args,2)(), origin::user));
+    }
   else
     E(false, origin::user,
       F(conflict_resolution_not_supported_msg) % idx(args,0) % "duplicate_name");
 
-} //set_duplicate_name_conflict
+} // set_resolution
 
 static void
 set_first_conflict(database & db,
@@ -384,6 +413,48 @@ set_first_conflict(database & db,
 
   if (side != neither)
     {
+      for (std::vector<dropped_modified_conflict>::iterator i = conflicts.result.dropped_modified_conflicts.begin();
+           i != conflicts.result.dropped_modified_conflicts.end();
+           ++i)
+        {
+          dropped_modified_conflict & conflict = *i;
+
+          // here we only allow two resolutions; single resolutions are handled below
+
+          switch (side)
+            {
+            case left:
+              if (conflict.left_resolution.resolution == resolve_conflicts::none)
+                {
+                  E(conflict.left_nid != the_null_node, origin::user,
+                    F("must specify resolve_first (not _left or _right)"));
+
+                  if ("keep" == idx(args,0)())
+                    E(!conflict.orphaned, origin::user, F("orphaned files must be renamed"));
+
+                  set_resolution(conflict.left_resolution, conflict.right_resolution, args);
+                  return;
+                }
+              break;
+            case right:
+              if (conflict.right_resolution.resolution == resolve_conflicts::none)
+                {
+                  E(conflict.right_nid != the_null_node, origin::user,
+                    F("must specify resolve_first (not _left or _right)"));
+
+                  if ("keep" == idx(args,0)())
+                    E(!conflict.orphaned, origin::user, F("orphaned files must be renamed"));
+
+                  set_resolution(conflict.right_resolution, conflict.left_resolution, args);
+                  return;
+                }
+              break;
+            case neither:
+              // can't get here
+              break;
+            }
+        }
+
       for (std::vector<duplicate_name_conflict>::iterator i = conflicts.result.duplicate_name_conflicts.begin();
            i != conflicts.result.duplicate_name_conflicts.end();
            ++i)
@@ -393,17 +464,17 @@ set_first_conflict(database & db,
           switch (side)
             {
             case left:
-              if (conflict.left_resolution.first == resolve_conflicts::none)
+              if (conflict.left_resolution.resolution == resolve_conflicts::none)
                 {
-                  set_duplicate_name_conflict(conflict.left_resolution, conflict.right_resolution, args);
+                  set_resolution(conflict.left_resolution, conflict.right_resolution, args);
                   return;
                 }
               break;
 
             case right:
-              if (conflict.right_resolution.first == resolve_conflicts::none)
+              if (conflict.right_resolution.resolution == resolve_conflicts::none)
                 {
-                  set_duplicate_name_conflict(conflict.right_resolution, conflict.left_resolution, args);
+                  set_resolution(conflict.right_resolution, conflict.left_resolution, args);
                   return;
                 }
               break;
@@ -422,20 +493,20 @@ set_first_conflict(database & db,
         {
           orphaned_node_conflict & conflict = *i;
 
-          if (conflict.resolution.first == resolve_conflicts::none)
+          if (conflict.resolution.resolution == resolve_conflicts::none)
             {
               if ("drop" == idx(args,0)())
                 {
                   E(args.size() == 1, origin::user, F("wrong number of arguments"));
 
-                  conflict.resolution.first  = resolve_conflicts::drop;
+                  conflict.resolution.resolution  = resolve_conflicts::drop;
                 }
               else if ("rename" == idx(args,0)())
                 {
                   E(args.size() == 2, origin::user, F("wrong number of arguments"));
 
-                  conflict.resolution.first  = resolve_conflicts::rename;
-                  conflict.resolution.second = new_optimal_path(idx(args,1)(), false);
+                  conflict.resolution.resolution  = resolve_conflicts::rename;
+                  conflict.resolution.content = new_optimal_path(idx(args,1)(), false);
                 }
               else
                 {
@@ -452,53 +523,104 @@ set_first_conflict(database & db,
         {
           dropped_modified_conflict & conflict = *i;
 
-          if (conflict.resolution.first == resolve_conflicts::none)
+          // Here we only allow single resolutions; two resolutions are handled above
+
+          switch (conflict.dropped_side)
             {
-              if ("drop" == idx(args,0)())
+            case resolve_conflicts::left_side:
+              if (conflict.left_resolution.resolution == resolve_conflicts::none)
                 {
-                  E(args.size() == 1, origin::user, F("wrong number of arguments"));
-                  E(conflict.recreated == the_null_node, origin::user, F("recreated files may not be dropped"));
+                  if ("drop" == idx(args,0)())
+                    {
+                      E(args.size() == 1, origin::user, F("wrong number of arguments"));
 
-                  conflict.resolution.first  = resolve_conflicts::drop;
-                }
-              else if ("keep" == idx(args,0)())
-                {
+                      conflict.left_resolution.resolution = resolve_conflicts::drop;
+                    }
+                  else if ("keep" == idx(args,0)())
+                    {
                   E(args.size() == 1, origin::user, F("wrong number of arguments"));
                   E(!conflict.orphaned, origin::user, F("orphaned files must be renamed"));
 
-                  conflict.resolution.first  = resolve_conflicts::keep;
+                  conflict.left_resolution.resolution  = resolve_conflicts::keep;
+                    }
+                  else if ("user" == idx(args,0)())
+                    {
+                      E(args.size() == 2, origin::user, F("wrong number of arguments"));
+                      E(!conflict.orphaned, origin::user, F("orphaned files must be renamed"));
+
+                      conflict.left_resolution.resolution  = resolve_conflicts::content_user;
+                      conflict.left_resolution.content = new_optimal_path(idx(args,1)(), false);
+                    }
+                  else if ("rename" == idx(args,0)())
+                    {
+                      E(args.size() == 2, origin::user, F("wrong number of arguments"));
+
+                      conflict.left_resolution.resolution  = resolve_conflicts::rename;
+                      conflict.left_resolution.content = new_optimal_path(idx(args,1)(), false);
+                    }
+                  else if ("user_rename" == idx(args,0)())
+                    {
+                      E(args.size() == 3, origin::user, F("wrong number of arguments"));
+
+                      conflict.left_resolution.resolution  = resolve_conflicts::content_user_rename;
+                      conflict.left_resolution.content = new_optimal_path(idx(args,1)(), false);
+                      conflict.left_resolution.rename = file_path_external(utf8(idx(args,2)(), origin::user));
+                    }
+                  else
+                    {
+                      E(false, origin::user,
+                        F(conflict_resolution_not_supported_msg) % idx(args,0) % "dropped_modified");
+                    }
+                  return;
                 }
-              else if ("user" == idx(args,0)())
+              break;
+            case resolve_conflicts::right_side:
+              if (conflict.right_resolution.resolution == resolve_conflicts::none)
                 {
-                  E(args.size() == 2, origin::user, F("wrong number of arguments"));
+                  if ("drop" == idx(args,0)())
+                    {
+                      E(args.size() == 1, origin::user, F("wrong number of arguments"));
+
+                      conflict.right_resolution.resolution = resolve_conflicts::drop;
+                    }
+                  else if ("keep" == idx(args,0)())
+                    {
+                  E(args.size() == 1, origin::user, F("wrong number of arguments"));
                   E(!conflict.orphaned, origin::user, F("orphaned files must be renamed"));
 
-                  conflict.resolution.first  = resolve_conflicts::content_user;
-                  conflict.resolution.second = new_optimal_path(idx(args,1)(), false);
-                }
-              else if ("rename" == idx(args,0)())
-                {
-                  E(args.size() == 2, origin::user, F("wrong number of arguments"));
-                  E(conflict.orphaned, origin::user, F("non-orphaned files cannot be renamed"));
+                  conflict.right_resolution.resolution  = resolve_conflicts::keep;
+                    }
+                  else if ("user" == idx(args,0)())
+                    {
+                      E(args.size() == 2, origin::user, F("wrong number of arguments"));
+                      E(!conflict.orphaned, origin::user, F("orphaned files must be renamed"));
 
-                  conflict.resolution.first  = resolve_conflicts::rename;
-                  conflict.resolution.second = new_optimal_path(idx(args,1)(), false);
-                }
-              else if ("user_rename" == idx(args,0)())
-                {
-                  E(args.size() == 3, origin::user, F("wrong number of arguments"));
-                  E(conflict.orphaned, origin::user, F("non-orphaned files cannot be renamed"));
+                      conflict.right_resolution.resolution  = resolve_conflicts::content_user;
+                      conflict.right_resolution.content = new_optimal_path(idx(args,1)(), false);
+                    }
+                  else if ("rename" == idx(args,0)())
+                    {
+                      E(args.size() == 2, origin::user, F("wrong number of arguments"));
 
-                  conflict.resolution.first  = resolve_conflicts::content_user_rename;
-                  conflict.resolution.second = new_optimal_path(idx(args,1)(), false);
-                  conflict.rename = file_path_external(utf8(idx(args,2)(), origin::user));
+                      conflict.right_resolution.resolution  = resolve_conflicts::rename;
+                      conflict.right_resolution.content = new_optimal_path(idx(args,1)(), false);
+                    }
+                  else if ("user_rename" == idx(args,0)())
+                    {
+                      E(args.size() == 3, origin::user, F("wrong number of arguments"));
+
+                      conflict.right_resolution.resolution  = resolve_conflicts::content_user_rename;
+                      conflict.right_resolution.content = new_optimal_path(idx(args,1)(), false);
+                      conflict.right_resolution.rename = file_path_external(utf8(idx(args,2)(), origin::user));
+                    }
+                  else
+                    {
+                      E(false, origin::user,
+                        F(conflict_resolution_not_supported_msg) % idx(args,0) % "dropped_modified");
+                    }
+                  return;
                 }
-              else
-                {
-                  E(false, origin::user,
-                    F(conflict_resolution_not_supported_msg) % idx(args,0) % "dropped_modified");
-                }
-              return;
+              break;
             }
         }
 
@@ -508,7 +630,7 @@ set_first_conflict(database & db,
         {
           file_content_conflict & conflict = *i;
 
-          if (conflict.resolution.first == resolve_conflicts::none)
+          if (conflict.resolution.resolution == resolve_conflicts::none)
             {
               if ("interactive" == idx(args,0)())
                 {
@@ -543,8 +665,8 @@ set_first_conflict(database & db,
                   if (do_interactive_merge(db, lua, conflicts, conflict.nid,
                                            conflict.ancestor, conflict.left, conflict.right, result_path))
                     {
-                      conflict.resolution.first  = resolve_conflicts::content_user;
-                      conflict.resolution.second = boost::shared_ptr<any_path>(new bookkeeping_path(result_path));
+                      conflict.resolution.resolution  = resolve_conflicts::content_user;
+                      conflict.resolution.content = boost::shared_ptr<any_path>(new bookkeeping_path(result_path));
                       P(F("interactive merge result saved in '%s'") % result_path.as_internal());
                     }
                   else
@@ -554,8 +676,8 @@ set_first_conflict(database & db,
                 {
                   E(args.size() == 2, origin::user, F("wrong number of arguments"));
 
-                  conflict.resolution.first  = resolve_conflicts::content_user;
-                  conflict.resolution.second = new_optimal_path(idx(args,1)(), false);
+                  conflict.resolution.resolution  = resolve_conflicts::content_user;
+                  conflict.resolution.content = new_optimal_path(idx(args,1)(), false);
                 }
               else
                 {
============================================================
--- src/merge_conflict.cc	34fbc891bc8455202d9c0d0a495beb690fd8a5a0
+++ src/merge_conflict.cc	d653ef4d1d748fa833b2500eed77ecc0107d48fb
@@ -1,4 +1,4 @@
-// Copyright (C) 2005 Nathaniel Smith <address@hidden>
+// Copyright (C) 2005, 2012 Nathaniel Smith <address@hidden>
 //               2008, 2009, 2012 Stephen Leake <address@hidden>
 //
 // This program is made available under the GNU GPL version 2.0 or
@@ -86,11 +86,9 @@ namespace resolve_conflicts
 
 namespace resolve_conflicts
 {
-  shared_ptr<any_path>
-  new_file_path(string path)
+  file_path file_path_external(string path)
   {
-    return shared_ptr<any_path>
-      (new file_path(file_path_external(utf8(path, origin::user))));
+    return file_path_external(utf8(path, origin::user));
   };
 }
 
@@ -396,11 +394,9 @@ put_attr_conflict (basic_io::stanza & st
     }
 }
 
-enum side_t {left_side, right_side};
-
 static void
-put_file_resolution(basic_io::stanza & st,
-                    side_t side,
+put_file_resolution(basic_io::stanza &                           st,
+                    resolve_conflicts::side_t                    side,
                     resolve_conflicts::file_resolution_t const & resolution)
 {
   // We output any resolution for any conflict; only valid resolutions
@@ -408,7 +404,7 @@ put_file_resolution(basic_io::stanza & s
   // resolutions from files we check that the resolution is valid for the
   // conflict. Hence there is no read_resolution.
 
-  switch (resolution.first)
+  switch (resolution.resolution)
     {
     case resolve_conflicts::none:
       break;
@@ -416,12 +412,12 @@ put_file_resolution(basic_io::stanza & s
     case resolve_conflicts::content_user:
       switch (side)
         {
-        case left_side:
-          st.push_str_pair(syms::resolved_user_left, resolution.second->as_external());
+        case resolve_conflicts::left_side:
+          st.push_str_pair(syms::resolved_user_left, resolution.content->as_external());
           break;
 
-        case right_side:
-          st.push_str_pair(syms::resolved_user_right, resolution.second->as_external());
+        case resolve_conflicts::right_side:
+          st.push_str_pair(syms::resolved_user_right, resolution.content->as_external());
           break;
         }
       break;
@@ -429,15 +425,16 @@ put_file_resolution(basic_io::stanza & s
     case resolve_conflicts::content_user_rename:
       switch (side)
         {
-        case left_side:
-          st.push_str_pair(syms::resolved_user_left, resolution.second->as_external());
+        case resolve_conflicts::left_side:
+          st.push_str_pair(syms::resolved_user_left, resolution.content->as_external());
+          st.push_str_pair(syms::resolved_rename_left, resolution.rename.as_external());
           break;
 
-        case right_side:
-          st.push_str_pair(syms::resolved_user_right, resolution.second->as_external());
+        case resolve_conflicts::right_side:
+          st.push_str_pair(syms::resolved_user_right, resolution.content->as_external());
+          st.push_str_pair(syms::resolved_rename_right, resolution.rename.as_external());
           break;
         }
-      // value for rename is put by caller
       break;
 
     case resolve_conflicts::content_internal:
@@ -447,12 +444,12 @@ put_file_resolution(basic_io::stanza & s
     case resolve_conflicts::rename:
       switch (side)
         {
-        case left_side:
-          st.push_str_pair(syms::resolved_rename_left, resolution.second->as_external());
+        case resolve_conflicts::left_side:
+          st.push_str_pair(syms::resolved_rename_left, resolution.rename.as_external());
           break;
 
-        case right_side:
-          st.push_str_pair(syms::resolved_rename_right, resolution.second->as_external());
+        case resolve_conflicts::right_side:
+          st.push_str_pair(syms::resolved_rename_right, resolution.rename.as_external());
           break;
         }
       break;
@@ -460,11 +457,11 @@ put_file_resolution(basic_io::stanza & s
     case resolve_conflicts::drop:
       switch (side)
         {
-        case left_side:
+        case resolve_conflicts::left_side:
           st.push_symbol(syms::resolved_drop_left);
           break;
 
-        case right_side:
+        case resolve_conflicts::right_side:
           st.push_symbol(syms::resolved_drop_right);
           break;
         }
@@ -473,11 +470,11 @@ put_file_resolution(basic_io::stanza & s
     case resolve_conflicts::keep:
       switch (side)
         {
-        case left_side:
+        case resolve_conflicts::left_side:
           st.push_symbol(syms::resolved_keep_left);
           break;
 
-        case right_side:
+        case resolve_conflicts::right_side:
           st.push_symbol(syms::resolved_keep_right);
           break;
         }
@@ -535,7 +532,7 @@ put_content_conflict (basic_io::stanza &
       st.push_file_pair(syms::left_name, left_name);
       st.push_file_pair(syms::right_name, right_name);
     }
-  put_file_resolution (st, left_side, conflict.resolution);
+  put_file_resolution (st, resolve_conflicts::left_side, conflict.resolution);
 }
 
 static void
@@ -1001,7 +998,7 @@ roster_merge_result::report_orphaned_nod
 
       if (basic_io)
         {
-          put_file_resolution (st, left_side, conflict.resolution);
+          put_file_resolution (st, resolve_conflicts::left_side, conflict.resolution);
           put_stanza (st, output);
         }
     }
@@ -1101,21 +1098,21 @@ roster_merge_result::report_dropped_modi
       node_id nid;
       file_path modified_name;
 
-      if (conflict.left_nid == the_null_node)
+      switch (conflict.dropped_side)
         {
-          // left side dropped, right side modified
+        case resolve_conflicts::left_side:
           I(!roster.is_attached(conflict.right_nid));
 
           nid = conflict.right_nid;
           right_roster.get_name(conflict.right_nid, modified_name);
-        }
-      else
-        {
-          // left side modified, right side dropped
+          break;
+
+        case resolve_conflicts::right_side:
           I(!roster.is_attached(conflict.left_nid));
 
           nid = conflict.left_nid;
           left_roster.get_name(conflict.left_nid, modified_name);
+          break;
         }
 
       shared_ptr<roster_t const> lca_roster;
@@ -1137,8 +1134,9 @@ roster_merge_result::report_dropped_modi
           db_adaptor.db.get_file_content (db_adaptor.lca, nid, fid);
           st.push_binary_pair(syms::ancestor_file_id, fid.inner());
 
-          if (conflict.left_nid == the_null_node)
+          switch (conflict.dropped_side)
             {
+            case resolve_conflicts::left_side:
               if (conflict.orphaned)
                 {
                    st.push_str_pair(syms::left_type, "orphaned file");
@@ -1147,7 +1145,7 @@ roster_merge_result::report_dropped_modi
                 }
               else
                 {
-                  if (conflict.recreated == the_null_node)
+                  if (conflict.left_nid == the_null_node)
                     {
                       st.push_str_pair(syms::left_type, "dropped file");
                       push_dropped_details(db_adaptor, syms::left_rev, syms::left_name, syms::left_file_id,
@@ -1157,21 +1155,18 @@ roster_merge_result::report_dropped_modi
                     {
                       st.push_str_pair(syms::left_type, "recreated file");
                       st.push_str_pair(syms::left_name, modified_name.as_external());
-                      db_adaptor.db.get_file_content (db_adaptor.left_rid, conflict.recreated, fid);
+                      db_adaptor.db.get_file_content (db_adaptor.left_rid, conflict.left_nid, fid);
                       st.push_binary_pair(syms::left_file_id, fid.inner());
                     }
                 }
-            }
-          else
-            {
+
               st.push_str_pair(syms::left_type, "modified file");
               st.push_str_pair(syms::left_name, modified_name.as_external());
               db_adaptor.db.get_file_content (db_adaptor.left_rid, nid, fid);
               st.push_binary_pair(syms::left_file_id, fid.inner());
-            }
+              break;
 
-          if (conflict.right_nid == the_null_node)
-            {
+            case resolve_conflicts::right_side:
               if (conflict.orphaned)
                 {
                   st.push_str_pair(syms::right_type, "orphaned file");
@@ -1180,7 +1175,7 @@ roster_merge_result::report_dropped_modi
                 }
               else
                 {
-                  if (conflict.recreated == the_null_node)
+                  if (conflict.right_nid == the_null_node)
                     {
                       st.push_str_pair(syms::right_type, "dropped file");
                       push_dropped_details(db_adaptor, syms::right_rev, syms::right_name, syms::right_file_id,
@@ -1190,59 +1185,44 @@ roster_merge_result::report_dropped_modi
                     {
                       st.push_str_pair(syms::right_type, "recreated file");
                       st.push_str_pair(syms::right_name, modified_name.as_external());
-                      db_adaptor.db.get_file_content (db_adaptor.right_rid, conflict.recreated, fid);
+                      db_adaptor.db.get_file_content (db_adaptor.right_rid, conflict.right_nid, fid);
                       st.push_binary_pair(syms::right_file_id, fid.inner());
                     }
                 }
-            }
-          else
-            {
+
               st.push_str_pair(syms::right_type, "modified file");
               st.push_str_pair(syms::right_name, modified_name.as_external());
               db_adaptor.db.get_file_content (db_adaptor.right_rid, nid, fid);
               st.push_binary_pair(syms::right_file_id, fid.inner());
+              break;
             }
 
-          put_file_resolution (st, left_side, conflict.resolution);
-          if (conflict.orphaned)
-            {
-              switch (conflict.resolution.first)
-                {
-                case resolve_conflicts::none:
-                case resolve_conflicts::rename:
-                case resolve_conflicts::drop:
-                  break;
+          put_file_resolution (st, resolve_conflicts::left_side, conflict.left_resolution);
+          put_file_resolution (st, resolve_conflicts::right_side, conflict.right_resolution);
 
-                case resolve_conflicts::content_user_rename:
-                  st.push_str_pair(syms::resolved_rename_left, conflict.rename.as_external());
-                  break;
-
-                default:
-                  I(false);
-                }
-            }
           put_stanza(st, output);
         }
       else
         {
           P(F("conflict: file '%s' from revision %s") % ancestor_name % lca_rid);
-          if (conflict.left_nid == the_null_node)
+          switch (conflict.dropped_side)
             {
+            case resolve_conflicts::left_side:
               if (conflict.orphaned)
                 {
                   P(F("orphaned on the left"));
                 }
               else
                 {
-                  if (conflict.recreated == the_null_node)
+                  if (conflict.left_nid == the_null_node)
                     P(F("dropped on the left"));
                   else
                     P(F("dropped and recreated on the left"));
                 }
               P(F("modified on the right, named %s") % modified_name);
-            }
-          else
-            {
+              break;
+
+            case resolve_conflicts::right_side:
               P(F("modified on the left, named %s") % modified_name);
               if (conflict.orphaned)
                 {
@@ -1250,43 +1230,45 @@ roster_merge_result::report_dropped_modi
                 }
               else
                 {
-                  if (conflict.recreated == the_null_node)
+                  if (conflict.right_nid == the_null_node)
                     P(F("dropped on the right"));
                   else
                     P(F("dropped and recreated on the right"));
                 }
+              break;
             }
 
-          // We can have a resolution from a mtn:resolve_conflict attribute.
-          switch (conflict.resolution.first)
+          // We can have a resolution from a mtn:resolve_conflict attribute
+          // (so far, that only supports drop).
+          switch (conflict.left_resolution.resolution)
             {
             case resolve_conflicts::none:
-              break;
-
             case resolve_conflicts::content_user:
-              P(F("resolution: user file '%s'") % conflict.resolution.second->as_external());
-              break;
-
             case resolve_conflicts::content_user_rename:
-              P(F("resolution: user '%s' rename '%s'") %
-                conflict.resolution.second->as_external() %
-                conflict.rename.as_external());
-              break;
-
             case resolve_conflicts::rename:
-              P(F("resolution: rename '%s'") % conflict.resolution.second->as_external());
+            case resolve_conflicts::keep:
+            case resolve_conflicts::content_internal:
+              I(false);
               break;
 
             case resolve_conflicts::drop:
-              P(F("resolution: drop"));
+              P(F("left_resolution: drop"));
               break;
-
+            }
+          switch (conflict.right_resolution.resolution)
+            {
+            case resolve_conflicts::none:
+            case resolve_conflicts::content_user:
+            case resolve_conflicts::content_user_rename:
+            case resolve_conflicts::rename:
             case resolve_conflicts::keep:
-              P(F("resolution: keep"));
+            case resolve_conflicts::content_internal:
+              I(false);
               break;
 
-            default:
-              I(false);
+            case resolve_conflicts::drop:
+              P(F("right_resolution: drop"));
+              break;
             }
         }
     }
@@ -1466,8 +1448,8 @@ roster_merge_result::report_duplicate_na
 
       if (basic_io)
         {
-          put_file_resolution (st, left_side, conflict.left_resolution);
-          put_file_resolution (st, right_side, conflict.right_resolution);
+          put_file_resolution (st, resolve_conflicts::left_side, conflict.left_resolution);
+          put_file_resolution (st, resolve_conflicts::right_side, conflict.right_resolution);
           put_stanza(st, output);
         }
     }
@@ -1643,9 +1625,9 @@ roster_merge_result::report_file_content
         {
           basic_io::stanza st;
 
-          if (conflict.resolution.first == resolve_conflicts::none)
+          if (conflict.resolution.resolution == resolve_conflicts::none)
             if (auto_merge_succeeds(lua, conflict, adaptor, left_roster, right_roster))
-              conflict.resolution.first = resolve_conflicts::content_internal;
+              conflict.resolution.resolution = resolve_conflicts::content_internal;
 
           st.push_str_pair(syms::conflict, syms::content);
           put_content_conflict (st, left_roster, right_roster, adaptor, conflict);
@@ -1886,14 +1868,14 @@ read_orphaned_node_conflict(basic_io::pa
     {
       if (pars.symp (syms::resolved_drop_left))
         {
-          conflict.resolution.first = resolve_conflicts::drop;
+          conflict.resolution.resolution = resolve_conflicts::drop;
           pars.sym();
         }
       else if (pars.symp (syms::resolved_rename_left))
         {
-          conflict.resolution.first = resolve_conflicts::rename;
+          conflict.resolution.resolution = resolve_conflicts::rename;
           pars.sym();
-          conflict.resolution.second = new_optimal_path(pars.token, true);
+          conflict.resolution.rename = resolve_conflicts::file_path_external(pars.token);
           pars.str();
         }
       else
@@ -1994,10 +1976,12 @@ static void
 } // read_multiple_name_conflicts
 
 static void
-read_dropped_modified_conflict(basic_io::parser & pars,
+read_dropped_modified_conflict(basic_io::parser &          pars,
                                dropped_modified_conflict & conflict,
-                               roster_t const & left_roster,
-                               roster_t const & right_roster)
+                               revision_id                 left_rid,
+                               roster_t const &            left_roster,
+                               revision_id                 right_rid,
+                               roster_t const &            right_roster)
 {
   string tmp;
 
@@ -2009,28 +1993,38 @@ read_dropped_modified_conflict(basic_io:
 
   if (tmp == "dropped file")
     {
-      pars.esym(syms::left_rev); pars.hex();
+      conflict.dropped_side = resolve_conflicts::left_side;
+
+      pars.esym(syms::left_rev); pars.hex(tmp);
+      conflict.left_rid = decode_hexenc_as<revision_id>(tmp, pars.tok.in.made_from);
       pars.esym(syms::left_name); pars.str();
       pars.esym(syms::left_file_id); pars.hex();
     }
   else if (tmp == "orphaned file")
     {
-      pars.esym(syms::left_rev); pars.hex();
+      conflict.dropped_side = resolve_conflicts::left_side;
+      conflict.orphaned = true;
+
+      pars.esym(syms::left_rev); pars.hex(tmp);
+      conflict.left_rid = decode_hexenc_as<revision_id>(tmp, pars.tok.in.made_from);
       pars.esym(syms::left_name); pars.str();
       pars.esym(syms::left_file_id); pars.hex();
-
-      conflict.orphaned = true;
     }
   else if (tmp == "recreated file")
     {
+      conflict.dropped_side = resolve_conflicts::left_side;
+      conflict.left_rid = left_rid;
+
       pars.esym(syms::left_name); pars.str(tmp);
-      conflict.recreated = left_roster.get_node(file_path_external(utf8(tmp, origin::internal)))->self;
+      conflict.left_nid = left_roster.get_node(resolve_conflicts::file_path_external(tmp))->self;
       pars.esym(syms::left_file_id); pars.hex();
     }
   else if (tmp == "modified file")
     {
+      conflict.left_rid = left_rid;
+
       pars.esym(syms::left_name); pars.str(tmp);
-      conflict.left_nid = left_roster.get_node(file_path_external(utf8(tmp, origin::internal)))->self;
+      conflict.left_nid = left_roster.get_node(resolve_conflicts::file_path_external(tmp))->self;
       pars.esym(syms::left_file_id); pars.hex();
     }
   else
@@ -2041,70 +2035,109 @@ read_dropped_modified_conflict(basic_io:
 
   if (tmp == "dropped file")
     {
-      pars.esym(syms::right_rev); pars.hex();
+      conflict.dropped_side = resolve_conflicts::right_side;
+
+      pars.esym(syms::right_rev); pars.hex(tmp);
+      conflict.right_rid = decode_hexenc_as<revision_id>(tmp, pars.tok.in.made_from);
       pars.esym(syms::right_name); pars.str();
       pars.esym(syms::right_file_id); pars.hex();
     }
   else if (tmp == "orphaned file")
     {
-      pars.esym(syms::right_rev); pars.hex();
+      conflict.dropped_side = resolve_conflicts::right_side;
+      conflict.orphaned = true;
+
+      pars.esym(syms::right_rev); pars.hex(tmp);
+      conflict.right_rid = decode_hexenc_as<revision_id>(tmp, pars.tok.in.made_from);
       pars.esym(syms::right_name); pars.str();
       pars.esym(syms::right_file_id); pars.hex();
-
-      conflict.orphaned = true;
     }
   else if (tmp == "recreated file")
     {
+      conflict.dropped_side = resolve_conflicts::right_side;
+      conflict.right_rid = right_rid;
+
       pars.esym(syms::right_name); pars.str(tmp);
-      conflict.recreated = right_roster.get_node(file_path_external(utf8(tmp, origin::internal)))->self;
+      conflict.right_nid = right_roster.get_node(resolve_conflicts::file_path_external(tmp))->self;
       pars.esym(syms::right_file_id); pars.hex();
     }
   else if (tmp == "modified file")
     {
+      conflict.dropped_side = resolve_conflicts::right_side;
+      conflict.right_rid = right_rid;
+
       pars.esym(syms::right_name); pars.str(tmp);
-      conflict.right_nid = right_roster.get_node(file_path_external(utf8(tmp, origin::internal)))->self;
+      conflict.right_nid = right_roster.get_node(resolve_conflicts::file_path_external(tmp))->self;
       pars.esym(syms::right_file_id); pars.hex();
     }
   else
     I(false);
 
-  // check for a resolution
-  if ((!pars.symp (syms::conflict)) && pars.tok.in.lookahead != EOF)
+  // check for resolutions
+  while ((!pars.symp (syms::conflict)) && pars.tok.in.lookahead != EOF)
     {
       if (pars.symp (syms::resolved_drop_left))
         {
-          conflict.resolution.first = resolve_conflicts::drop;
+          conflict.left_resolution.resolution = resolve_conflicts::drop;
           pars.sym();
         }
+      else if (pars.symp (syms::resolved_drop_right))
+        {
+          conflict.right_resolution.resolution = resolve_conflicts::drop;
+          pars.sym();
+        }
       else if (pars.symp (syms::resolved_keep_left))
         {
           E(!conflict.orphaned, origin::user, F("orphaned files must be renamed"));
 
-          conflict.resolution.first = resolve_conflicts::keep;
+          conflict.left_resolution.resolution = resolve_conflicts::keep;
           pars.sym();
         }
+      else if (pars.symp (syms::resolved_keep_right))
+        {
+          E(!conflict.orphaned, origin::user, F("orphaned files must be renamed"));
+
+          conflict.right_resolution.resolution = resolve_conflicts::keep;
+          pars.sym();
+        }
+      else if (pars.symp (syms::resolved_rename_left))
+        {
+          if (conflict.left_resolution.resolution == resolve_conflicts::content_user)
+            conflict.left_resolution.resolution = resolve_conflicts::content_user_rename;
+          else
+            conflict.left_resolution.resolution = resolve_conflicts::rename;
+          pars.sym();
+          conflict.left_resolution.rename = resolve_conflicts::file_path_external(pars.token);
+          pars.str();
+        }
+      else if (pars.symp (syms::resolved_rename_right))
+        {
+          if (conflict.right_resolution.resolution == resolve_conflicts::content_user)
+            conflict.right_resolution.resolution = resolve_conflicts::content_user_rename;
+          else
+            conflict.right_resolution.resolution = resolve_conflicts::rename;
+          pars.sym();
+          conflict.right_resolution.rename = resolve_conflicts::file_path_external(pars.token);
+          pars.str();
+        }
       else if (pars.symp (syms::resolved_user_left))
         {
-          conflict.resolution.first = resolve_conflicts::content_user;
+          if (conflict.left_resolution.resolution == resolve_conflicts::rename)
+            conflict.left_resolution.resolution = resolve_conflicts::content_user_rename;
+          else
+            conflict.left_resolution.resolution = resolve_conflicts::content_user;
           pars.sym();
-          conflict.resolution.second = new_optimal_path(pars.token, false);
+          conflict.left_resolution.content = new_optimal_path(pars.token, false);
           pars.str();
-
-          if (conflict.orphaned)
-            {
-              pars.esym (syms::resolved_rename_left);
-              conflict.resolution.first = resolve_conflicts::content_user_rename;
-              pars.str(tmp);
-              conflict.rename = file_path_external_ws(utf8(tmp, origin::user));
-            }
         }
-      else if (pars.symp (syms::resolved_rename_left))
+      else if (pars.symp (syms::resolved_user_right))
         {
-          E(conflict.orphaned, origin::user, F("non-orphaned files cannot be renamed"));
-
-          conflict.resolution.first = resolve_conflicts::rename;
+          if (conflict.right_resolution.resolution == resolve_conflicts::rename)
+            conflict.right_resolution.resolution = resolve_conflicts::content_user_rename;
+          else
+            conflict.right_resolution.resolution = resolve_conflicts::content_user;
           pars.sym();
-          conflict.resolution.second = new_optimal_path(pars.token, false);
+          conflict.left_resolution.content = new_optimal_path(pars.token, false);
           pars.str();
         }
       else
@@ -2114,18 +2147,20 @@ static void
 } // read_dropped_modified_conflict
 
 static void
-read_dropped_modified_conflicts(basic_io::parser & pars,
-                             std::vector<dropped_modified_conflict> & conflicts,
-                             roster_t const & left_roster,
-                             roster_t const & right_roster)
+read_dropped_modified_conflicts(basic_io::parser &                       pars,
+                                std::vector<dropped_modified_conflict> & conflicts,
+                                revision_id                              left_rid,
+                                roster_t const &                         left_roster,
+                                revision_id                              right_rid,
+                                roster_t const &                         right_roster)
 {
   while (pars.tok.in.lookahead != EOF && pars.symp(syms::dropped_modified))
     {
-      dropped_modified_conflict conflict(the_null_node, the_null_node);
+      dropped_modified_conflict conflict;
 
       pars.sym();
 
-      read_dropped_modified_conflict(pars, conflict, left_roster, right_roster);
+      read_dropped_modified_conflict(pars, conflict, left_rid, left_roster, right_rid, right_roster);
 
       conflicts.push_back(conflict);
 
@@ -2133,11 +2168,14 @@ read_dropped_modified_conflicts(basic_io
         pars.esym (syms::conflict);
     }
 } // read_dropped_modified_conflicts
+
 static void
-validate_dropped_modified_conflicts(basic_io::parser & pars,
+validate_dropped_modified_conflicts(basic_io::parser &                       pars,
                                     std::vector<dropped_modified_conflict> & conflicts,
-                                    roster_t const & left_roster,
-                                    roster_t const & right_roster)
+                                    revision_id                              left_rid,
+                                    roster_t const &                         left_roster,
+                                    revision_id                              right_rid,
+                                    roster_t const &                         right_roster)
 {
   for (std::vector<dropped_modified_conflict>::iterator i = conflicts.begin();
        i != conflicts.end();
@@ -2148,17 +2186,19 @@ validate_dropped_modified_conflicts(basi
 
       pars.esym(syms::dropped_modified);
 
-      read_dropped_modified_conflict(pars, file_conflict, left_roster, right_roster);
+      read_dropped_modified_conflict(pars, file_conflict, left_rid, left_roster, right_rid, 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 &&
-        merge_conflict.recreated == file_conflict.recreated,
+      E(merge_conflict.dropped_side == file_conflict.dropped_side &&
+        merge_conflict.left_nid == file_conflict.left_nid &&
+        merge_conflict.right_nid == file_conflict.right_nid,
         origin::user,
         F(conflicts_mismatch_msg));
 
-      merge_conflict.resolution = file_conflict.resolution;
-      merge_conflict.rename     = file_conflict.rename;
+      merge_conflict.left_rid         = file_conflict.left_rid;
+      merge_conflict.right_rid        = file_conflict.right_rid;
+      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);
@@ -2185,50 +2225,50 @@ read_duplicate_name_conflict(basic_io::p
     {
       if (pars.symp (syms::resolved_drop_left))
         {
-          conflict.left_resolution.first = resolve_conflicts::drop;
+          conflict.left_resolution.resolution = resolve_conflicts::drop;
           pars.sym();
         }
       else if (pars.symp (syms::resolved_drop_right))
         {
-          conflict.right_resolution.first = resolve_conflicts::drop;
+          conflict.right_resolution.resolution = resolve_conflicts::drop;
           pars.sym();
         }
       else if (pars.symp (syms::resolved_keep_left))
         {
-          conflict.left_resolution.first = resolve_conflicts::keep;
+          conflict.left_resolution.resolution = resolve_conflicts::keep;
           pars.sym();
         }
       else if (pars.symp (syms::resolved_keep_right))
         {
-          conflict.right_resolution.first = resolve_conflicts::keep;
+          conflict.right_resolution.resolution = resolve_conflicts::keep;
           pars.sym();
         }
       else if (pars.symp (syms::resolved_rename_left))
         {
-          conflict.left_resolution.first = resolve_conflicts::rename;
+          conflict.left_resolution.resolution = resolve_conflicts::rename;
           pars.sym();
-          conflict.left_resolution.second = resolve_conflicts::new_file_path(pars.token);
+          conflict.left_resolution.rename = resolve_conflicts::file_path_external(pars.token);
           pars.str();
         }
       else if (pars.symp (syms::resolved_rename_right))
         {
-          conflict.right_resolution.first = resolve_conflicts::rename;
+          conflict.right_resolution.resolution = resolve_conflicts::rename;
           pars.sym();
-          conflict.right_resolution.second = resolve_conflicts::new_file_path(pars.token);
+          conflict.right_resolution.rename = resolve_conflicts::file_path_external(pars.token);
           pars.str();
         }
       else if (pars.symp (syms::resolved_user_left))
         {
-          conflict.left_resolution.first = resolve_conflicts::content_user;
+          conflict.left_resolution.resolution = resolve_conflicts::content_user;
           pars.sym();
-          conflict.left_resolution.second = new_optimal_path(pars.token, true);
+          conflict.left_resolution.content = new_optimal_path(pars.token, true);
           pars.str();
         }
       else if (pars.symp (syms::resolved_user_right))
         {
-          conflict.right_resolution.first = resolve_conflicts::content_user;
+          conflict.right_resolution.resolution = resolve_conflicts::content_user;
           pars.sym();
-          conflict.right_resolution.second = new_optimal_path(pars.token, true);
+          conflict.right_resolution.content = new_optimal_path(pars.token, true);
           pars.str();
         }
       else
@@ -2433,14 +2473,14 @@ read_file_content_conflict(basic_io::par
     {
       if (pars.symp (syms::resolved_internal))
         {
-          conflict.resolution.first = resolve_conflicts::content_internal;
+          conflict.resolution.resolution = resolve_conflicts::content_internal;
           pars.sym();
         }
       else if (pars.symp (syms::resolved_user_left))
         {
-          conflict.resolution.first = resolve_conflicts::content_user;
+          conflict.resolution.resolution = resolve_conflicts::content_user;
           pars.sym();
-          conflict.resolution.second = new_optimal_path(pars.token, true);
+          conflict.resolution.content = new_optimal_path(pars.token, true);
           pars.str();
         }
       else
@@ -2506,7 +2546,9 @@ read_conflict_file_core(basic_io::parser
 
 static void
 read_conflict_file_core(basic_io::parser pars,
+                        revision_id left_rid,
                         roster_t const & left_roster,
+                        revision_id right_rid,
                         roster_t const & right_roster,
                         roster_merge_result & result,
                         bool validate)
@@ -2533,7 +2575,8 @@ read_conflict_file_core(basic_io::parser
       // order as non-validate, below.
 
       validate_orphaned_node_conflicts(pars, result.orphaned_node_conflicts, left_roster, right_roster);
-      validate_dropped_modified_conflicts(pars, result.dropped_modified_conflicts, left_roster, right_roster);
+      validate_dropped_modified_conflicts
+        (pars, result.dropped_modified_conflicts, left_rid, left_roster, right_rid, right_roster);
       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);
     }
@@ -2546,7 +2589,8 @@ read_conflict_file_core(basic_io::parser
       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_dropped_modified_conflicts(pars, result.dropped_modified_conflicts, left_roster, right_roster);
+      read_dropped_modified_conflicts
+        (pars, result.dropped_modified_conflicts, left_rid, left_roster, right_rid, 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);
@@ -2596,7 +2640,7 @@ roster_merge_result::read_conflict_file(
       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);
+      read_conflict_file_core(pars, left_rid, left_roster, right_rid, right_roster, *this, false);
     }
   // else no conflicts
 
@@ -2694,9 +2738,12 @@ parse_resolve_conflicts_opts (options co
           pars.sym();
           pars.hex(temp);
 
+          read_conflict_file_core (pars, left_rid, left_roster, right_rid, right_roster, result, true);
+        }
+      else
+        {
           // if there is no ancestor revision, then left is an ancestor of
           // right, or vice versa, and there can be no conflicts.
-          read_conflict_file_core (pars, left_roster, right_roster, result, true);
         }
     }
   else
@@ -2761,7 +2808,7 @@ roster_merge_result::resolve_orphaned_no
           right_roster.get_name(conflict.nid, name);
         }
 
-      switch (conflict.resolution.first)
+      switch (conflict.resolution.resolution)
         {
         case resolve_conflicts::drop:
           if (is_dir_t(roster.get_node(conflict.nid)))
@@ -2775,9 +2822,8 @@ roster_merge_result::resolve_orphaned_no
           break;
 
         case resolve_conflicts::rename:
-          P(F("renaming '%s' to '%s'") % name % *conflict.resolution.second);
-          attach_node
-            (lua, roster, conflict.nid, file_path_internal (conflict.resolution.second->as_internal()));
+          P(F("renaming '%s' to '%s'") % name % conflict.resolution.rename);
+          attach_node (lua, roster, conflict.nid, conflict.resolution.rename);
           break;
 
         case resolve_conflicts::none:
@@ -2794,34 +2840,102 @@ roster_merge_result::resolve_orphaned_no
   orphaned_node_conflicts.clear();
 }
 
+static node_id
+create_new_node(roster_t const &            parent_roster,
+                node_id const &             parent_nid,
+                roster_t &                  result_roster,
+                boost::shared_ptr<any_path> new_content,
+                content_merge_adaptor &     adaptor,
+                temp_node_id_source &       nis)
+{
+  file_path parent_name;
+  file_id   parent_fid;
+  file_data parent_data;
+
+  parent_roster.get_file_details(parent_nid, parent_fid, parent_name);
+  adaptor.get_version(parent_fid, parent_data);
+
+  P(F("replacing content of '%s' with '%s'") % parent_name % new_content->as_external());
+
+  // FIXME: factor out 'history lost' msg
+  P(F("history for '%s' will be lost; see user manual Merge Conflicts section") % parent_name);
+
+  data result_raw_data;
+  read_data(*new_content, result_raw_data);
+
+  file_data result_data = file_data(result_raw_data);
+  file_id result_fid;
+  calculate_ident(result_data, result_fid);
+
+  // User could specify no changes in content
+  if (result_fid != parent_fid)
+    {
+      adaptor.record_file(parent_fid, result_fid, parent_data, result_data);
+    }
+
+  return result_roster.create_file_node(result_fid, nis);
+}
+
 static void
-resolve_dropped_modified_user(roster_t &                      roster,
-                              node_id   &                     nid,
-                              file_id                         modified_fid,
-                              dropped_modified_conflict const conflict,
-                              content_merge_adaptor &         adaptor,
-                              temp_node_id_source &           nis)
+replace_content(roster_t const &            parent_roster,
+                node_id  const &            nid,
+                roster_t &                  result_roster,
+                boost::shared_ptr<any_path> new_content,
+                content_merge_adaptor &     adaptor)
 {
-  // See comments in keep below on why we drop first
-  roster.drop_detached_node(nid);
+  file_path parent_name;
+  file_id   parent_fid;
 
-  file_data result_data;
+  parent_roster.get_file_details(nid, parent_fid, parent_name);
+
+  P(F("replacing content of '%s' with '%s'") % parent_name % new_content->as_external());
+
+  file_data parent_data;
+  adaptor.get_version(parent_fid, parent_data);
+
   data result_raw_data;
+  read_data(*new_content, result_raw_data);
+
+  file_data result_data = file_data(result_raw_data);
   file_id result_fid;
-  read_data(*conflict.resolution.second, result_raw_data);
+  calculate_ident(result_data, result_fid);
 
-  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_for_update(nid));
+  result_node->content = result_fid;
 
-  nid = roster.create_file_node(result_fid, nis);
+  // User could specify no changes in content
+  if (result_fid != parent_fid)
+    {
+      adaptor.record_file(parent_fid, result_fid, parent_data, result_data);
+    }
+}
 
-  // User could specify no changes
-  if (result_fid != modified_fid)
+static void
+error_extra_left(node_id left_nid, node_id right_nid, roster_t const & right_roster)
+{
+  if (left_nid == the_null_node)
     {
-      adaptor.record_file(result_fid, result_data);
+      file_path right_name;
+      file_id   right_fid;
+      right_roster.get_file_details(right_nid, right_fid, right_name);
+      E(false, origin::user,
+        F("extra left_resolution provided for dropped_modifed '%s'") % right_name);
     }
 }
 
+static void
+error_extra_right(node_id left_nid, node_id right_nid, roster_t const & left_roster)
+{
+  if (right_nid == the_null_node)
+    {
+      file_path left_name;
+      file_id   left_fid;
+      left_roster.get_file_details(left_nid, left_fid, left_name);
+      E(false, origin::user,
+        F("extra right_resolution provided for dropped_modifed '%s'") % left_name);
+    }
+}
+
 void
 roster_merge_result::resolve_dropped_modified_conflicts(lua_hooks & lua,
                                                         roster_t const & left_roster,
@@ -2833,8 +2947,6 @@ roster_merge_result::resolve_dropped_mod
   MM(right_roster);
   MM(this->roster); // New roster
 
-  // Conflict node is absent in the new roster
-
   for (std::vector<dropped_modified_conflict>::const_iterator i = dropped_modified_conflicts.begin();
        i != dropped_modified_conflicts.end();
        ++i)
@@ -2842,107 +2954,93 @@ roster_merge_result::resolve_dropped_mod
       dropped_modified_conflict const & conflict = *i;
       MM(conflict);
 
-      node_id   nid;
-      file_path modified_name;
-      file_id   modified_fid;
-      file_path recreated_name;
-      file_id   recreated_fid;
+      file_path left_name;
+      file_id   left_fid;
+      file_path right_name;
+      file_id   right_fid;
 
-      if (conflict.left_nid == the_null_node)
+      switch (conflict.left_resolution.resolution)
         {
-          nid = conflict.right_nid;
-          right_roster.get_file_details(nid, modified_fid, modified_name);
-
-          if (conflict.recreated != the_null_node)
+        case resolve_conflicts::none:
+          if (conflict.left_nid != the_null_node)
             {
-              roster.get_file_details(conflict.recreated, recreated_fid, recreated_name);
+              // FIXME: use left_name here if it's not too hard
+              right_roster.get_file_details(conflict.right_nid, right_fid, right_name);
+              E(false, origin::user,
+                F("no left_resolution provided for dropped_modifed '%s'") % right_name);
             }
-        }
-      else
-        {
-          nid = conflict.left_nid;
-          left_roster.get_file_details(nid, modified_fid, modified_name);
+          break;
 
-          if (conflict.recreated != the_null_node)
+        case resolve_conflicts::content_user:
+          switch (conflict.dropped_side)
             {
-              roster.get_file_details(conflict.recreated, recreated_fid, recreated_name);
-            }
-        }
+            case resolve_conflicts::left_side:
+              error_extra_left(conflict.left_nid, conflict.right_nid, right_roster);
 
-      switch (conflict.resolution.first)
-        {
-        case resolve_conflicts::none:
-          E(false, origin::user,
-            F("no resolution provided for dropped_modifed '%s'") % modified_name);
-          break;
+              // recreated; replace the contents of the recreated node
+              replace_content(left_roster, conflict.left_nid, roster, conflict.left_resolution.content, adaptor);
+              break;
 
-        case resolve_conflicts::content_user:
-          P(F("replacing content of '%s' with '%s'") %
-            modified_name % conflict.resolution.second->as_external());
-          P(F("history for '%s' will be lost; see user manual Merge Conflicts section") %
-            modified_name);
+            case resolve_conflicts::right_side:
+              error_extra_right(conflict.left_nid, conflict.right_nid, left_roster);
 
-          if (conflict.recreated == the_null_node)
-            {
-              resolve_dropped_modified_user(roster, nid, modified_fid, conflict, adaptor, nis);
-              attach_node(lua, roster, nid, modified_name);
-            }
-          else
-            {
+              // modified; drop and create a new node
               // See comments in keep below on why we drop first
-              roster.drop_detached_node(nid);
+              roster.drop_detached_node(conflict.left_nid);
 
-              file_id result_fid;
-              file_data parent_data, result_data;
-              data result_raw_data;
-              adaptor.get_version(recreated_fid, parent_data);
+              node_id new_nid = create_new_node
+                (left_roster, conflict.left_nid, roster, conflict.left_resolution.content, adaptor, nis);
 
-              read_data(*conflict.resolution.second, result_raw_data);
+              attach_node(lua, roster, new_nid, left_name);
 
-              result_data = file_data(result_raw_data);
-              calculate_ident(result_data, result_fid);
-
-              file_t result_node = downcast_to_file_t(roster.get_node_for_update(conflict.recreated));
-              result_node->content = result_fid;
-
-              adaptor.record_file(recreated_fid, result_fid, parent_data, result_data);
+              break;
             }
           break;
 
-        case resolve_conflicts::content_user_rename:
-          I(conflict.rename.as_external().length() != 0);
-          P(F("replacing content of '%s' (renamed to '%s') with '%s'") %
-            modified_name % conflict.rename.as_external() % conflict.resolution.second->as_external());
-          P(F("history for '%s' will be lost; see user manual Merge Conflicts section") %
-            modified_name);
+        case resolve_conflicts::content_internal:
+          I(false);
 
-          resolve_dropped_modified_user(roster, nid, modified_fid, conflict, adaptor, nis);
-          attach_node(lua, roster, nid, file_path_internal (conflict.rename.as_internal()));
-          break;
+        case resolve_conflicts::drop:
+          switch (conflict.dropped_side)
+            {
+            case resolve_conflicts::left_side:
+              error_extra_left(conflict.left_nid, conflict.right_nid, right_roster);
 
-        case resolve_conflicts::drop:
-          P(F("dropping '%s'") % modified_name);
+              left_roster.get_file_details(conflict.left_nid, left_fid, left_name);
+              P(F("dropping '%s'") % left_name);
 
-          roster.drop_detached_node(nid);
-          break;
+              roster.drop_detached_node(conflict.left_nid);
+              break;
 
-        case resolve_conflicts::rename:
-          P(F("renaming '%s' to '%s'") % modified_name % conflict.resolution.second->as_external());
-          P(F("history for '%s' will be lost; see user manual Merge Conflicts section") %
-            modified_name);
+            case resolve_conflicts::right_side:
+              error_extra_right(conflict.left_nid, conflict.right_nid, left_roster);
 
-          // See comment in keep below on why we drop first.
-          roster.drop_detached_node(nid);
-          nid = roster.create_file_node(modified_fid, nis);
-          attach_node (lua, roster, nid, file_path_internal (conflict.resolution.second->as_internal()));
-          break;
+              right_roster.get_file_details(conflict.right_nid, right_fid, right_name);
+              P(F("dropping '%s'") % right_name);
 
+              roster.drop_detached_node(conflict.right_nid);
+              break;
+            }
+
         case resolve_conflicts::keep:
-          if (conflict.recreated == the_null_node)
+          switch (conflict.dropped_side)
             {
-              P(F("keeping '%s'") % modified_name);
+            case resolve_conflicts::left_side:
+              error_extra_left(conflict.left_nid, conflict.right_nid, right_roster);
+
+              // recreated; keep the recreated contents
+              left_roster.get_file_details(conflict.left_nid, left_fid, left_name);
+              P(F("keeping '%s'") % left_name);
+              attach_node(lua, roster, conflict.left_nid, left_name);
+              break;
+
+            case resolve_conflicts::right_side:
+              // modified; keep the modified contents
+
+              left_roster.get_file_details(conflict.left_nid, left_fid, left_name);
+              P(F("keeping '%s'") % left_name);
               P(F("history for '%s' will be lost; see user manual Merge Conflicts section") %
-                modified_name);
+                left_name);
 
               // We'd like to just attach_node here, but that violates a
               // fundamental design principle of mtn; nodes are born once,
@@ -2953,35 +3051,72 @@ roster_merge_result::resolve_dropped_mod
               // the same contents. That loses history; 'mtn log <path>'
               // will end here, not showing the history of the original
               // node.
-              roster.drop_detached_node(nid);
-              nid = roster.create_file_node(modified_fid, nis);
-              attach_node (lua, roster, nid, modified_name);
+              roster.drop_detached_node(conflict.left_nid);
+              node_id nid = roster.create_file_node(left_fid, nis);
+              attach_node (lua, roster, nid, left_name);
+              break;
             }
-          else
+
+        case resolve_conflicts::rename:
+          switch (conflict.dropped_side)
             {
-              P(F("keeping '%s' from %s") % modified_name % ((conflict.left_nid == the_null_node) ? "right" : "left"));
+            case resolve_conflicts::left_side:
+              error_extra_left(conflict.left_nid, conflict.right_nid, right_roster);
+
+              // recreated; rename the recreated contents
+              left_roster.get_file_details(conflict.left_nid, left_fid, left_name);
+              P(F("renaming '%s' to '%s'") % left_name % conflict.left_resolution.rename.as_external());
+              attach_node(lua, roster, conflict.left_nid, conflict.left_resolution.rename);
+              break;
+
+            case resolve_conflicts::right_side:
+              error_extra_right(conflict.left_nid, conflict.right_nid, left_roster);
+
+              // modified; drop, create new node with the modified contents, rename
+              // See comment in keep above on why we drop first.
+              roster.drop_detached_node(conflict.left_nid);
+
+              left_roster.get_file_details(conflict.left_nid, left_fid, left_name);
+              P(F("renaming '%s' to '%s'") % left_name % conflict.left_resolution.rename.as_external());
               P(F("history for '%s' will be lost; see user manual Merge Conflicts section") %
-                modified_name);
+                left_name);
 
-              roster.drop_detached_node(nid);
+              node_id new_nid = roster.create_file_node(left_fid, nis);
+              attach_node (lua, roster, new_nid, conflict.left_resolution.rename);
+              break;
+            }
 
-              // keep the modified content, not the recreated content
-              file_data parent_data, result_data;
-              adaptor.get_version(recreated_fid, parent_data);
+        case resolve_conflicts::content_user_rename:
+          switch (conflict.dropped_side)
+            {
+            case resolve_conflicts::left_side:
+              error_extra_left(conflict.left_nid, conflict.right_nid, right_roster);
 
-              adaptor.get_version(modified_fid, result_data);
+              // recreated; rename and replace the recreated contents
+              replace_content(left_roster, conflict.left_nid, roster, conflict.left_resolution.content, adaptor);
 
-              file_t result_node = downcast_to_file_t(roster.get_node_for_update(conflict.recreated));
-              result_node->content = modified_fid;
+              left_roster.get_file_details(conflict.left_nid, left_fid, left_name);
+              P(F("renaming '%s' to '%s'") % left_name % conflict.left_resolution.rename.as_external());
 
-              adaptor.record_file(recreated_fid, modified_fid, parent_data, result_data);
-            }
-          break;
+              attach_node (lua, roster, conflict.left_nid, conflict.left_resolution.rename);
+              break;
 
-        default:
-          I(false);
+            case resolve_conflicts::right_side:
+              // modified; drop, rename and replace the modified contents
+              node_id nid = create_new_node
+                (left_roster, conflict.left_nid, roster, conflict.left_resolution.content, adaptor, nis);
+
+              left_roster.get_file_details(conflict.left_nid, left_fid, left_name);
+              P(F("renaming '%s' to '%s'") % left_name % conflict.left_resolution.rename.as_external());
+
+              attach_node(lua, roster, conflict.left_nid, conflict.left_resolution.rename);
+              break;
+            }
         }
 
+      // FIXME: handle right_resolution; factor out resolve_dropped_modified_one_side
+      // add inconsistent left/right resolution checks
+
     } // end for
 
   dropped_modified_conflicts.clear();
@@ -2997,23 +3132,23 @@ resolve_duplicate_name_one_side(lua_hook
                                 content_merge_adaptor & adaptor,
                                 roster_t & result_roster)
 {
-  switch (resolution.first)
+  switch (resolution.resolution)
     {
     case resolve_conflicts::content_user:
       {
-        E(other_resolution.first == resolve_conflicts::drop ||
-          other_resolution.first == resolve_conflicts::rename,
+        E(other_resolution.resolution == resolve_conflicts::drop ||
+          other_resolution.resolution == 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());
+        P(F("replacing content of '%s' with '%s'") % name % resolution.content->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);
+        read_data(*resolution.content, result_raw_data);
 
         result_data = file_data(result_raw_data);
         calculate_ident(result_data, result_fid);
@@ -3039,8 +3174,8 @@ resolve_duplicate_name_one_side(lua_hook
       break;
 
     case resolve_conflicts::keep:
-      E(other_resolution.first == resolve_conflicts::drop ||
-        other_resolution.first == resolve_conflicts::rename,
+      E(other_resolution.resolution == resolve_conflicts::drop ||
+        other_resolution.resolution == resolve_conflicts::rename,
         origin::user,
         F("inconsistent left/right resolutions for '%s'") % name);
 
@@ -3049,9 +3184,8 @@ resolve_duplicate_name_one_side(lua_hook
       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()));
+      P(F("renaming '%s' to '%s'") % name % resolution.rename);
+      attach_node (lua, result_roster, nid, resolution.rename);
       break;
 
     case resolve_conflicts::none:
@@ -3146,7 +3280,7 @@ roster_merge_result::resolve_file_conten
       left_roster.get_name(conflict.nid, left_name);
       right_roster.get_name(conflict.nid, right_name);
 
-      switch (conflict.resolution.first)
+      switch (conflict.resolution.resolution)
         {
           case resolve_conflicts::content_internal:
           case resolve_conflicts::none:
@@ -3168,7 +3302,7 @@ roster_merge_result::resolve_file_conten
           case resolve_conflicts::content_user:
             {
               P(F("replacing content of '%s', '%s' with '%s'") %
-                left_name % right_name % conflict.resolution.second->as_external());
+                left_name % right_name % conflict.resolution.content->as_external());
 
               file_id result_id;
               file_data left_data, right_data, result_data;
@@ -3176,7 +3310,7 @@ roster_merge_result::resolve_file_conten
               adaptor.get_version(conflict.left, left_data);
               adaptor.get_version(conflict.right, right_data);
 
-              read_data(*conflict.resolution.second, result_raw_data);
+              read_data(*conflict.resolution.content, result_raw_data);
 
               result_data = file_data(result_raw_data);
               calculate_ident(result_data, result_id);
============================================================
--- src/merge_content.cc	662433acaf08fc1f3f3417389a9bc3b3d2bd9bf1
+++ src/merge_content.cc	6a1d7ad7184f88674a585f5065e965f15294f7dc
@@ -189,8 +189,9 @@ content_merge_database_adaptor::get_drop
   set<revision_id> parents;
   db.get_revision_parents(rev_id, parents);
 
-  for (set<revision_id>::iterator i = parents.begin(); i != parents.end(); i++)
+  while (parents.begin() != parents.end())
     {
+      set<revision_id>::iterator i = parents.begin();
       roster_t roster;
       marking_map marking_map;
 
@@ -203,6 +204,7 @@ content_merge_database_adaptor::get_drop
         }
       else
         {
+          parents.erase (i);
           set<revision_id> more_parents;
           db.get_revision_parents(*i, more_parents);
           parents.insert(more_parents.begin(), more_parents.end());
============================================================
--- src/merge_roster.cc	5fbc50c114df22f4753f444aa137c06ea0f06195
+++ src/merge_roster.cc	b4de8ec7db2c60b3ebb777bfa58d9e0d8986558d
@@ -49,6 +49,21 @@ image(resolve_conflicts::resolution_t re
   I(false); // keep compiler happy
 }
 
+static string
+image(resolve_conflicts::file_resolution_t res)
+{
+  if (res.resolution == resolve_conflicts::none)
+    return string("");
+  else
+    {
+      ostringstream oss;
+      oss << "resolution: " << image(res.resolution) << " "
+          << "content: " << res.content << " "
+          << "rename: " << res.rename << "\n";
+      return oss.str();
+    }
+}
+
 template <> void
 dump(invalid_name_conflict const & conflict, string & out)
 {
@@ -98,12 +113,8 @@ dump(dropped_modified_conflict const & c
   oss << "dropped_modified_conflict on node: " <<
     conflict.left_nid == the_null_node ? conflict.right_nid : conflict.left_nid;
   oss << " orphaned: " << conflict.orphaned;
-  if (conflict.resolution.first != resolve_conflicts::none)
-    {
-      oss << " resolution: " << image(conflict.resolution.first);
-      oss << " new_content_name: " << conflict.resolution.second;
-      oss << " rename: " << conflict.rename;
-    }
+  oss << " left_resolution: " << image(conflict.left_resolution);
+  oss << " right_resolution: " << image(conflict.left_resolution);
   oss << "\n";
   out = oss.str();
 }
@@ -115,19 +126,10 @@ 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;
-
-  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";
+      << "basename: " << conflict.parent_name.second << " "
+      << "left_resolution: " << " "
+      << "right_resolution: "
+      << "\n";
   out = oss.str();
 }
 
@@ -147,12 +149,7 @@ dump(file_content_conflict const & confl
 {
   ostringstream oss;
   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 << " resolution: " << image(conflict.resolution);
   oss << "\n";
   out = oss.str();
 }
@@ -374,7 +371,7 @@ namespace
                 switch (present_in)
                   {
                   case left_side:
-                      conflict = dropped_modified_conflict(n->self, the_null_node);
+                    conflict = dropped_modified_conflict(n->self, the_null_node);
                     break;
                   case right_side:
                     conflict = dropped_modified_conflict(the_null_node, n->self);
@@ -385,7 +382,15 @@ namespace
                   {
                     if (i->second.second == typecast_vocab<attr_value>(utf8("drop")))
                       {
-                        conflict.resolution.first = resolve_conflicts::drop;
+                        switch (present_in)
+                          {
+                          case left_side:
+                            conflict.left_resolution.resolution = resolve_conflicts::drop;
+                            break;
+                          case right_side:
+                            conflict.right_resolution.resolution = resolve_conflicts::drop;
+                            break;
+                          }
                       }
                     else
                       {
@@ -796,21 +801,65 @@ roster_merge(roster_t const & left_paren
     {
       dropped_modified_conflict & conflict = result.dropped_modified_conflicts[i];
 
+      // If the file name was recreated it may now subject to a
+      // duplicate_name conflict (see
+      // test/func/resolve_conflicts_dropped_modified_upstream_vs_local_2).
+      // In which case modified_name will be present in the other parent,
+      // but not in the result.
+
       file_path modified_name;
-      if (conflict.left_nid == the_null_node)
+      bool duplicate_name = false;
+
+      switch (conflict.dropped_side)
         {
+        case resolve_conflicts::left_side:
           right_parent.get_name(conflict.right_nid, modified_name);
-        }
-      else
-        {
+
+          if (left_parent.has_node (modified_name))
+            {
+              conflict.left_nid = left_parent.get_node(modified_name)->self;
+              duplicate_name = true;
+            }
+          else if (result.roster.has_node(modified_name))
+            {
+              // recreated
+              conflict.left_nid = result.roster.get_node(modified_name)->self;
+            }
+          break;
+
+        case resolve_conflicts::right_side:
           left_parent.get_name(conflict.left_nid, modified_name);
+
+          if (right_parent.has_node (modified_name))
+            {
+              conflict.right_nid = right_parent.get_node(modified_name)->self;
+              duplicate_name = false;
+            }
+
+          else if (result.roster.has_node(modified_name))
+            {
+              // recreated
+              conflict.right_nid = result.roster.get_node(modified_name)->self;
+            }
+          break;
         }
 
-      if (result.roster.has_node(modified_name))
+      if (duplicate_name)
         {
-          conflict.recreated = result.roster.get_node(modified_name)->self;
+          // delete the duplicate name conflict; it will be handled by dropped_modified.
+          for (std::vector<duplicate_name_conflict>::iterator i = result.duplicate_name_conflicts.begin();
+               i != result.duplicate_name_conflicts.end();
+               ++i)
+            {
+              duplicate_name_conflict & dn_conf = *i;
+              if (dn_conf.left_nid == conflict.left_nid || dn_conf.right_nid == conflict.right_nid)
+                {
+                  result.duplicate_name_conflicts.erase(i);
+                  break;
+                }
+            }
         }
-    }
+    } // end dropped_modified loop
 
   // now check for the possible global problems
   if (!result.roster.has_root())
============================================================
--- src/merge_roster.hh	34d382b0ad333fa0f052e8640a88c1ece03caaa9
+++ src/merge_roster.hh	63eab7b216f8839626f9d9d04dc8e58c3eed90bf
@@ -31,10 +31,25 @@ namespace resolve_conflicts
 {
   enum resolution_t {none, content_user, content_internal, drop, keep, rename, content_user_rename};
 
-  typedef std::pair<resolve_conflicts::resolution_t, boost::shared_ptr<any_path> > file_resolution_t;
+  enum side_t {left_side, right_side};
 
-  boost::shared_ptr<any_path> new_file_path(std::string path);
+  struct file_resolution_t
+  {
+    resolution_t resolution;
+    boost::shared_ptr<any_path> content;
+    file_path rename;
 
+    file_resolution_t() :
+      resolution(none),
+      content(),
+      rename()
+      {}
+  };
+
+  // For filename read from conflicts file; converts path to utf8. basic_io
+  // parser should return utf8 in the first place.
+  file_path file_path_external(std::string path);
+
 }
 
 // renaming the root dir allows these:
@@ -94,34 +109,55 @@ struct dropped_modified_conflict
 // roster, with null parent and name fields.
 struct dropped_modified_conflict
 {
-  node_id left_nid, right_nid; // the dropped side is the null node, modified is valid.
+  // A dropped_modified conflict can be the result of a repeated
+  // duplicate_name conflict (see
+  // ../test/func/resolve_conflicts_dropped_modified_upstream_vs_local_2)
+  //
+  // If the user has recreated the dropped node, that also looks like a
+  // duplicate name conflict.
+  //
+  // In either case both nids are non-null.
+  //
+  // Dropped_modified can also be due to a dropped directory, in which case
+  // this looks like an orphaned_node conflict.
+  //
+  // If the resolution for the dropped node is 'keep', we need the revision
+  // that contains the node id, which is an ancestor of the merge parent.
+  // That information is also useful for an external front end, that wants to
+  // retreive the file contents for a merge tool.
 
-  bool orphaned; // if true, the dropped side is due to a dropped parent directory
+  resolve_conflicts::side_t dropped_side;
 
-  node_id recreated; // by user, or in a previous drop/modified resolution
+ // If nid is set, it is in corresponding merge parent.
+  node_id left_nid, right_nid;
 
-  resolve_conflicts::file_resolution_t resolution;
-  file_path rename;
-  // if orphaned is true, the resolutions are 'drop' and 'user rename'; the
-  // latter requires two paths; content in resolution->second, filename in
-  // rename.
+  bool orphaned; // if true, the dropped side is due to a dropped parent directory
 
+  // read_dropped_modified sets rid for dropped, non-recreated file FIXME: can we use this?
+  revision_id                          left_rid, right_rid;
+  resolve_conflicts::file_resolution_t left_resolution, right_resolution;
+
   dropped_modified_conflict(node_id left_nid, node_id right_nid) :
     left_nid(left_nid),
     right_nid(right_nid),
     orphaned(false),
-    recreated(the_null_node)
-    // rename is implicitly null
-  {resolution.first = resolve_conflicts::none;}
+    left_rid(),
+    right_rid(),
+    left_resolution(),
+    right_resolution()
+  {dropped_side = (left_nid == the_null_node ? resolve_conflicts::right_side : resolve_conflicts::left_side);}
 
   dropped_modified_conflict() :
     left_nid(the_null_node),
     right_nid(the_null_node),
     orphaned(false),
-    recreated(the_null_node)
-    // rename is implicitly null
-  {resolution.first = resolve_conflicts::none;}
+    left_rid(),
+    right_rid(),
+    left_resolution(),
+    right_resolution()
+  {}
 
+  // for find
   bool operator==(node_id n) {return left_nid == n || right_nid == n;}
 };
 
@@ -147,9 +183,10 @@ struct duplicate_name_conflict
   // it may be a bookkeeping or system path if resolution is 'user'.
   resolve_conflicts::file_resolution_t left_resolution, right_resolution;
 
-  duplicate_name_conflict ()
-  {left_resolution.first = resolve_conflicts::none;
-    right_resolution.first = resolve_conflicts::none;};
+  duplicate_name_conflict() {};
+
+  // for find
+  bool operator==(node_id n) {return left_nid == n || right_nid == n;}
 };
 
 // nodes with attribute conflicts are left attached in the resulting tree (unless
@@ -174,12 +211,15 @@ struct file_content_conflict
   file_id ancestor, left, right; // ancestor is set only when reading in a conflicts file
   resolve_conflicts::file_resolution_t resolution;
 
-  file_content_conflict () :
-    nid(the_null_node)
-    {resolution.first = resolve_conflicts::none;};
+  file_content_conflict() :
+    nid(the_null_node),
+    resolution()
+  {};
 
   file_content_conflict(node_id nid) :
-    nid(nid) {resolution.first = resolve_conflicts::none;};
+    nid(nid),
+    resolution()
+  {};
 };
 
 template <> void dump(invalid_name_conflict const & conflict, std::string & out);
============================================================
--- /dev/null	
+++ test/func/resolve_conflicts_dropped_modified_upstream_vs_local_2/__driver__.lua	46e918155fe50737229d3c2cb31899cf204ef9fd
@@ -0,0 +1,119 @@
+-- Show a problematic use case involving a dropped_modified conflict,
+-- and how it can be resolved with the 'mtn:resolve_conflict'
+-- attribute.
+--
+-- There is an upstream branch, and a local branch. The local branch
+-- adds a file that the upstream branch adopts. The next merge from
+-- upstream to local encounters a duplicate_name conflict; if we
+-- resolve that the wrong way (keep local instead of upstream), on the
+-- next merge we get a dropped_modified conflict.
+--
+-- In the meantime, we've edited the file locally, illustrating that
+-- the dropped_modified conflict code needs to search thru history for
+-- the rev last containing the dropped node id (this was a bug in an
+-- earlier implementation).
+
+mtn_setup()
+
+addfile("file_1", "file_1 base")
+commit("testbranch", "base")
+base = base_revision()
+
+writefile("file_1", "file_1 upstream 1")
+
+commit("testbranch", "upstream 1")
+upstream_1 = base_revision()
+
+revert_to(base)
+
+addfile("file_2", "file_2 local")
+
+commit("testbranch", "local 1")
+local_1 = base_revision()
+
+revert_to(upstream_1)
+
+addfile("file_2", "file_2 upstream 1")
+
+commit("testbranch", "upstream 2")
+upstream_2 = base_revision()
+
+check(mtn("show_conflicts", upstream_2, local_1), 0, nil, true)
+check(samelines("stderr",
+ {"mtn: [left]     27d41ae9f2b3cb73b130d9845d77574a11021b17",
+  "mtn: [right]    3cae692a68fa6710b1db5e73e3e876994c175925",
+  "mtn: [ancestor] 736498437fa91538540dc5fbad750cbc1472d793",
+  "mtn: conflict: duplicate name 'file_2' for the directory ''",
+  "mtn: added as a new file on the left",
+  "mtn: added as a new file on the right",
+  "mtn: 1 conflict with supported resolutions."}))
+
+check(mtn("conflicts", "store", upstream_2, local_1), 0, nil, true)
+
+-- We should keep upstream, and drop local, but we get it backwards
+check(mtn("conflicts", "resolve_first_left", "drop"), 0, nil, true)
+check(mtn("conflicts", "resolve_first_right", "keep"), 0, nil, true)
+
+check(mtn("explicit_merge", "--resolve-conflicts", upstream_2, local_1, "testbranch"), 0, nil, true)
+check(samelines("stderr",
+ {"mtn: [left]  27d41ae9f2b3cb73b130d9845d77574a11021b17",
+  "mtn: [right] 3cae692a68fa6710b1db5e73e3e876994c175925",
+  "mtn: dropping 'file_2'",
+  "mtn: keeping 'file_2'",
+  "mtn: [merged] fce2fe3c327a294209ad695e0658275f104fe12a"}))
+
+check(mtn("update"), 0, nil, true)
+
+-- One more local mod
+writefile("file_2", "file_2 local 2")
+commit("testbranch", "local 2")
+local_2 = base_revision()
+
+-- round 2; upstream modifies the file again, and we try to merge
+revert_to(upstream_2)
+
+writefile("file_2", "file_2 upstream 2")
+
+commit("testbranch", "upstream 3")
+upstream_3 = base_revision()
+
+check(mtn("show_conflicts", upstream_3, local_2), 0, nil, true)
+check(samelines("stderr",
+ {"mtn: [left]     48b18ebc7b70733133539384e49a2eedb82e32b2",
+  "mtn: [right]    650057e8a81bd41991dc5ff10b2d60343f1032ae",
+  "mtn: [ancestor] 27d41ae9f2b3cb73b130d9845d77574a11021b17",
+  "mtn: conflict: file 'file_2' from revision 27d41ae9f2b3cb73b130d9845d77574a11021b17",
+  "mtn: modified on the left, named file_2",
+  "mtn: dropped and recreated on the right",
+  "mtn: 1 conflict with supported resolutions."}))
+
+check(mtn("conflicts", "store", upstream_3, local_2), 0, nil, true)
+check(samefilestd("conflicts_3_2", "_MTN/conflicts"))
+
+-- 'keep' is ambiguous here, so it gives an error
+check(mtn("conflicts", "resolve_first", "keep"), 1, nil, true)
+check(qgrep("ambiguous; use 'keep_left' or 'keep_right'", "stderr"))
+
+check(mtn("conflicts", "show_first"), 0, nil, true)
+check(samelines("stderr",
+ {"mtn: conflict: file 'file_2' from revision 27d41ae9f2b3cb73b130d9845d77574a11021b17",
+  "mtn: modified on the left, named file_2",
+  "mtn: dropped and recreated on the right",
+  "mtn: modified on the left",
+  "mtn: possible resolutions:",
+  "mtn: resolve_first keep_left",
+  "mtn: resolve_first keep_right",
+  "mtn: resolve_first user_left \"name\"",
+  "mtn: resolve_first user_right \"name\""}))
+
+-- We want to keep the upstream node to avoid future conflicts
+check(mtn("conflicts", "resolve_first", "keep_left"), 0, nil, true)
+check(samefilestd("conflicts_3_2_resolved", "_MTN/conflicts"))
+
+check(mtn("explicit_merge", "--resolve-conflicts", upstream_3, local_2, "testbranch"), 0, nil, true)
+check(qgrep("mtn: dropping 'file_2'", "stderr"))
+
+check(mtn("update"), 0, nil, true)
+check(samelines("file_2", {"file_2 upstream 2"}))
+
+-- end of file
============================================================
--- /dev/null	
+++ test/func/resolve_conflicts_dropped_modified_upstream_vs_local_2/conflicts_3_2	bb23f1fbf87e4f1dcb8aa7c4c3fcb90e1fc84fed
@@ -0,0 +1,13 @@
+    left [48b18ebc7b70733133539384e49a2eedb82e32b2]
+   right [650057e8a81bd41991dc5ff10b2d60343f1032ae]
+ancestor [27d41ae9f2b3cb73b130d9845d77574a11021b17]
+
+        conflict dropped_modified
+   ancestor_name "file_2"
+ancestor_file_id [7fc990de4797bd6534a5c1deb344e11964f6b353]
+       left_type "modified file"
+       left_name "file_2"
+    left_file_id [b7e3240a78dc6afce4507f5a18ab516963e72022]
+      right_type "recreated file"
+      right_name "file_2"
+   right_file_id [6e49d17f382dc2f03d495181490e7653f1a14ad9]
============================================================
--- /dev/null	
+++ test/func/resolve_conflicts_dropped_modified_upstream_vs_local_2/conflicts_3_2_resolved	954b8dfc7ee161c1ca809b61d03ad8fd12bfb539
@@ -0,0 +1,14 @@
+    left [48b18ebc7b70733133539384e49a2eedb82e32b2]
+   right [650057e8a81bd41991dc5ff10b2d60343f1032ae]
+ancestor [27d41ae9f2b3cb73b130d9845d77574a11021b17]
+
+          conflict dropped_modified
+     ancestor_name "file_2"
+  ancestor_file_id [7fc990de4797bd6534a5c1deb344e11964f6b353]
+         left_type "modified file"
+         left_name "file_2"
+      left_file_id [b7e3240a78dc6afce4507f5a18ab516963e72022]
+        right_type "recreated file"
+        right_name "file_2"
+     right_file_id [6e49d17f382dc2f03d495181490e7653f1a14ad9]
+resolved_keep_left

reply via email to

[Prev in Thread] Current Thread [Next in Thread]