Daniel Shahaf wrote on Tue, May 06, 2014 at 11:08:55 +0000:
> I'm looking into enabling 3-way conflict markers for property conflicts.
...
> That function has some interesting logic around those BASE and Merge-LHS 
> values:

"That function" is libsvn_wc/props.c:prop_conflict_from_skel().

>   /* Pick a suitable base for the conflict diff.
>    * The incoming value is always a change,
>    * but the local value might not have changed. */
>   if (original == NULL)
>     {
>       if (incoming_base)
>         original = incoming_base;
>       else
>         original = svn_string_create_empty(scratch_pool);
>     }
>   else if (incoming_base && svn_string_compare(original, mine))
>     original = incoming_base;
> 
> Here, ORIGINAL is the variable that's eventually passed in the ORIGINAL (aka,
> OLDER) version formal parameter of the diff3 API; however, the code sometimes
> sets the variable "original" to the ORIGINAL version, and sometimes to the
> INCOMING_BASE version.
> 
> I don't quite understand this.  Why does it make sense to set the variable
> 'original' to a different value (other than the empty string) if it is NULL?
> If one of the four sides of the merge happened to be a nonexistent value, then
> that (a null value) is what should be displayed, no?  i.e., the function 
> should
> either always set the variable "original" to the ORIGINAL version,
> or always set that variable to the INCOMING_VERSION version.  Does that sound
> right?

Done in r1595522.  It also affects dir_conflicts files.

Grepping for svn_diff_conflict_display_style_t uses, I found that the
function generate_propconflict(), which underlies the interactive
conflicts resolver API svn_wc_conflict_description3_t, provides the API
consumer with 3 fulltext files and one pre-merged file with conflict
markers.¹  That function, too, has logic which chooses which three out
of the four sides of the conflict to pass into the three sides provided
in the API.

I have two main issues with that:

1. We are encoding information about the 4 sides of the conflict into a
3-sides API in an unpredictable manner: the way we map the 4 sides into
the 3 slots varies depending on the values of the 4 sides. That means
the API consumer receives ambiguous information (it can't tell whether
the "common ancestor" value is the BASE value or the INCOMING_BASE_OLD value).
I think we should always map the 4 sides into the 3 slots in the same
way (and that's what r1595522 did for the accept=postpone case).

2. We are unnecessarily losing a side.  The
svn_wc_conflict_description3_t API is new in 1.9; we could easily extend
it to include all four sides, which would provide API consumers with
more information, while still allowing them to do "only" a diff3 if they
so wish.

So, I suggest the following changes:

1. Make svn_wc_conflict_description3_t have four "full file" members,
rather than three right now.

2. Make generate_propconflict() (in libsvn-wc/conflicts.c) always map
the 4 conflict sides into the 3 sides of the svn_diff_mem_string_diff3()
call the same way, regardless of which sides happen to be NULL --- like
r1595522 did for the accept=postpone case.

WDYT?

Daniel


¹ The cmdline client doesn't use the pre-merged file; it independently
constructs a diff3 representation from the other three, regardless of
whether the merged file uses diff3 or not.
Index: subversion/libsvn_wc/conflicts.c
===================================================================
--- subversion/libsvn_wc/conflicts.c	(revision 1595523)
+++ subversion/libsvn_wc/conflicts.c	(working copy)
@@ -1342,6 +1342,18 @@ generate_propconflict(svn_boolean_t *conflict_rema
          whichever older-value happens to be defined, so that the
          conflict-callback can still attempt a 3-way merge. */
 
+      /* ### Why are we choosing "whichever value happens to exist"?
+         ### If Alice changes "cat" to "dog" and Bob adds "green", we'd pass "cat" as the common ancestor;
+         ### If Alice adds "dog" and Bob changes "red" to "green", we'd pass "red" as the common ancestor.
+         ### API consumers can't tell the difference between these two situations.
+         ###
+         ### Right now, we are arbitrarily/lossily coercing a 4-way conflict into a 3-way one.
+         ### Instead, we should either be passing all four versions to the API consumer and let them sort it out,
+         ### or clearly clarify whether it is WORKING_VAL or INCOMING_NEW_VAL that CDESC->BASE_ABSPATH is associated with.
+         ###
+         ### I vote for the former (with clear documentation about 4-way conflicts, to help API consumers get it right).
+       */
+
       const svn_string_t *conflict_base_val = base_val ? base_val
                                                        : incoming_old_val;
       const char *file_name;
@@ -1375,14 +1387,26 @@ generate_propconflict(svn_boolean_t *conflict_rema
              compare. */
 
           if (working_val && svn_string_compare(base_val, working_val))
+            /* ### Why the condition on working_val != NULL?
+               ### NULL is a legitimate value here; even if one of the four values
+               ### is NULL, that's no reason to prefer one of the other values over it. */
             conflict_base_val = incoming_old_val;
           else
+            /* ### Here, would be better to just set CONFLICT_BASE_VAL to INCOMING_OLD_VAL.
+               ### (which allows folding both this if/else and the if/else containing it
+               ### into the single line "conflict_base_val = incoming_old_val;"),
+               ### since BASE_VAL is already known to the user and already available
+               ### via the BASE tree. */
             conflict_base_val = base_val;
         }
       else
         {
           conflict_base_val = base_val;
         }
+#if 0
+      /* ### I suggest to replace the above if/else by: */
+      conflict_base_val = base_val;
+#endif
 
       SVN_ERR(svn_io_write_unique(&file_name, dirpath, conflict_base_val->data,
                                   conflict_base_val->len,
@@ -1406,7 +1430,9 @@ generate_propconflict(svn_boolean_t *conflict_rema
           SVN_ERR(svn_diff_mem_string_output_merge2(mergestream, diff,
                    conflict_base_val, working_val,
                    incoming_new_val, NULL, NULL, NULL, NULL,
-                   svn_diff_conflict_display_modified_latest, scratch_pool));
+                   /* ### The effect of this can't be seen from the command-line client,
+                      ### but prop_tests.py 36 triggers this codepath. */
+                   svn_diff_conflict_display_modified_original_latest, scratch_pool));
           SVN_ERR(svn_stream_close(mergestream));
         }
     }

Reply via email to