You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by pb...@apache.org on 2011/08/23 17:53:40 UTC

svn commit: r1160756 - in /subversion/trunk: ./ subversion/libsvn_client/merge.c subversion/tests/cmdline/merge_authz_tests.py subversion/tests/cmdline/merge_reintegrate_tests.py subversion/tests/cmdline/merge_tests.py

Author: pburba
Date: Tue Aug 23 15:53:39 2011
New Revision: 1160756

URL: http://svn.apache.org/viewvc?rev=1160756&view=rev
Log:
Reintegrate the issue #3975 'adds with explicit mergeinfo don't get
mergeinfo describing merge which added them' branch to trunk.

Modified:
    subversion/trunk/   (props changed)
    subversion/trunk/subversion/libsvn_client/merge.c
    subversion/trunk/subversion/tests/cmdline/merge_authz_tests.py
    subversion/trunk/subversion/tests/cmdline/merge_reintegrate_tests.py
    subversion/trunk/subversion/tests/cmdline/merge_tests.py

Propchange: subversion/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Tue Aug 23 15:53:39 2011
@@ -32,6 +32,7 @@
 /subversion/branches/issue-3242-dev:879653-896436
 /subversion/branches/issue-3334-dirs:875156-875867
 /subversion/branches/issue-3668-3669:1031000-1035744
+/subversion/branches/issue-3975:1152931-1160746
 /subversion/branches/kwallet:870785-871314
 /subversion/branches/log-g-performance:870941-871032
 /subversion/branches/merge-skips-obstructions:874525-874615

Modified: subversion/trunk/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/merge.c?rev=1160756&r1=1160755&r2=1160756&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/merge.c (original)
+++ subversion/trunk/subversion/libsvn_client/merge.c Tue Aug 23 15:53:39 2011
@@ -4480,7 +4480,8 @@ populate_remaining_ranges(apr_array_head
 /* Helper for record_mergeinfo_for_dir_merge().
 
    Adjust, in place, the inheritability of the ranges in RANGELIST to
-   describe a merge of RANGELIST into WC_WCPATH at depth DEPTH.
+   describe a merge of RANGELIST into WC_WCPATH at depth DEPTH.  Set
+   *RANGELIST_INHERITANCE to the inheritability set.
 
    WC_PATH_IS_MERGE_TARGET is true if WC_PATH is the target of the merge,
    otherwise WC_PATH is a subtree.
@@ -4492,6 +4493,7 @@ populate_remaining_ranges(apr_array_head
    Perform any temporary allocations in SCRATCH_POOL. */
 static svn_error_t *
 calculate_merge_inheritance(apr_array_header_t *rangelist,
+                            svn_boolean_t *rangelist_inheritance,
                             const char *local_abspath,
                             svn_boolean_t wc_path_is_merge_target,
                             svn_boolean_t wc_path_has_missing_child,
@@ -4503,6 +4505,10 @@ calculate_merge_inheritance(apr_array_he
 
   SVN_ERR(svn_wc_read_kind(&path_kind, wc_ctx, local_abspath, FALSE,
                            scratch_pool));
+
+  /* Starting assumption. */
+  *rangelist_inheritance = TRUE;
+
   if (path_kind == svn_node_file)
     {
       /* Files *never* have non-inheritable mergeinfo. */
@@ -4515,17 +4521,27 @@ calculate_merge_inheritance(apr_array_he
           if (wc_path_has_missing_child
               || depth == svn_depth_files
               || depth == svn_depth_empty)
-            svn_rangelist__set_inheritance(rangelist, FALSE);
+            {
+              svn_rangelist__set_inheritance(rangelist, FALSE);
+              *rangelist_inheritance = FALSE;
+            }
           else /* depth == svn_depth_files || depth == svn_depth_empty */
-            svn_rangelist__set_inheritance(rangelist, TRUE);
+            {
+              svn_rangelist__set_inheritance(rangelist, TRUE);
+            }
         }
       else /* WC_PATH is a directory subtree of the target. */
         {
           if (wc_path_has_missing_child
               || depth == svn_depth_immediates)
-            svn_rangelist__set_inheritance(rangelist, FALSE);
+            {
+              svn_rangelist__set_inheritance(rangelist, FALSE);
+              *rangelist_inheritance = FALSE;
+            }
           else /* depth == infinity */
-            svn_rangelist__set_inheritance(rangelist, TRUE);
+            {
+              svn_rangelist__set_inheritance(rangelist, TRUE);
+            }
         }
     }
   return SVN_NO_ERROR;
@@ -7600,6 +7616,7 @@ record_mergeinfo_for_dir_merge(svn_merge
       else /* Record mergeinfo on CHILD. */
         {
           svn_boolean_t child_is_deleted;
+          svn_boolean_t rangelist_inheritance;
 
           /* If CHILD is deleted we don't need to set mergeinfo on it. */
           SVN_ERR(svn_wc__node_is_status_deleted(&child_is_deleted,
@@ -7662,6 +7679,7 @@ record_mergeinfo_for_dir_merge(svn_merge
             continue;
 
           SVN_ERR(calculate_merge_inheritance(child_merge_rangelist,
+                                              &rangelist_inheritance,
                                               child->abspath,
                                               i == 0,
                                               child->missing_child,
@@ -7695,6 +7713,66 @@ record_mergeinfo_for_dir_merge(svn_merge
             }
 
           child_merges = apr_hash_make(iterpool);
+
+          /* If CHILD is the merge target we then know that the mergeinfo
+             described by MERGE_SOURCE_PATH:MERGED_RANGE->START-
+             MERGED_RANGE->END describes existent path-revs in the repository,
+             see normalize_merge_sources() and the global comment
+             'MERGEINFO MERGE SOURCE NORMALIZATION'.
+
+             If CHILD is a subtree of the merge target however, then no such
+             guarantee holds.  The mergeinfo described by
+             (MERGE_SOURCE_PATH + CHILD_REPOS_PATH):MERGED_RANGE->START-
+             MERGED_RANGE->END might contain merge sources which don't
+             exist or refer to unrelated lines of history. */
+          if (i > 0
+              && (!merge_b->record_only || merge_b->reintegrate_merge)
+              && (!is_rollback))
+            {
+              svn_opt_revision_t peg_revision;
+              svn_mergeinfo_t subtree_history_as_mergeinfo;
+              apr_array_header_t *child_merge_src_rangelist;
+              const char *old_session_url;
+              const char *subtree_mergeinfo_url =
+                svn_path_url_add_component2(merge_b->repos_root_url,
+                                            child_merge_src_canon_path + 1,
+                                            iterpool);
+
+              /* Confirm that the naive mergeinfo we want to set on
+                 CHILD->ABSPATH both exists and is part of
+                 (MERGE_SOURCE_PATH+CHILD_REPOS_PATH)@MERGED_RANGE->END's
+                 history. */
+              peg_revision.kind = svn_opt_revision_number;
+
+              /* We know MERGED_RANGE->END is younger than MERGE_RANGE->START
+                 because we only do this for forward merges. */
+              peg_revision.value.number = merged_range->end;
+              SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url,
+                                                        merge_b->ra_session2,
+                                                        subtree_mergeinfo_url,
+                                                        iterpool));
+              SVN_ERR(svn_client__get_history_as_mergeinfo(
+                &subtree_history_as_mergeinfo, NULL,
+                subtree_mergeinfo_url, &peg_revision,
+                MAX(merged_range->start, merged_range->end),
+                MIN(merged_range->start, merged_range->end),
+                merge_b->ra_session2, merge_b->ctx, iterpool));
+
+              if (old_session_url)
+                SVN_ERR(svn_ra_reparent(merge_b->ra_session2,
+                                        old_session_url, iterpool));
+              child_merge_src_rangelist = apr_hash_get(
+                subtree_history_as_mergeinfo,
+                child_merge_src_canon_path,
+                APR_HASH_KEY_STRING);
+              SVN_ERR(svn_rangelist_intersect(&child_merge_rangelist,
+                                              child_merge_rangelist,
+                                              child_merge_src_rangelist,
+                                              FALSE, iterpool));
+              if (!rangelist_inheritance)
+                svn_rangelist__set_inheritance(child_merge_rangelist, FALSE);
+            }
+
           apr_hash_set(child_merges, child->abspath, APR_HASH_KEY_STRING,
                        child_merge_rangelist);
           SVN_ERR(update_wc_mergeinfo(result_catalog,
@@ -7750,8 +7828,9 @@ record_mergeinfo_for_dir_merge(svn_merge
 
    Record mergeinfo describing a merge of
    MERGED_RANGE->START:MERGED_RANGE->END from the repository relative path
-   MERGEINFO_PATH to each path in NOTIFY_B->ADDED_ABSPATHS which is the
-   immediate child of a parent with explicit non-inheritable mergeinfo.
+   MERGEINFO_PATH to each path in NOTIFY_B->ADDED_ABSPATHS which has explicit
+   mergeinfo or is the immediate child of a parent with explicit
+   non-inheritable mergeinfo.
 
    DEPTH, NOTIFY_B, MERGE_B, and SQUELCH_MERGEINFO_NOTIFICATIONS, are
    cascaded from do_directory_merge's arguments of the same names.
@@ -7785,34 +7864,48 @@ record_mergeinfo_for_added_subtrees(
       const char *added_abspath = svn__apr_hash_index_key(hi);
       const char *dir_abspath;
       svn_mergeinfo_t parent_mergeinfo;
+      svn_mergeinfo_t added_path_mergeinfo;
       svn_boolean_t inherited; /* used multiple times, but ignored */
 
       apr_pool_clear(iterpool);
       dir_abspath = svn_dirent_dirname(added_abspath, iterpool);
 
-      /* Does ADDED_ABSPATH's immediate parent have non-inheritable
-         mergeinfo? */
-      SVN_ERR(svn_client__get_wc_mergeinfo(&parent_mergeinfo, &inherited,
+      /* Grab the added path's explicit mergeinfo. */
+      SVN_ERR(svn_client__get_wc_mergeinfo(&added_path_mergeinfo, &inherited,
                                            svn_mergeinfo_explicit,
-                                           dir_abspath, NULL, NULL, FALSE,
-                                           merge_b->ctx,
-                                           iterpool, iterpool));
-      if (svn_mergeinfo__is_noninheritable(parent_mergeinfo, iterpool))
+                                           added_abspath, NULL, NULL, FALSE,
+                                           merge_b->ctx, iterpool, iterpool));
+
+      /* If the added path doesn't have explicit mergeinfo, does its immediate
+         parent have non-inheritable mergeinfo? */
+      if (!added_path_mergeinfo)
+        SVN_ERR(svn_client__get_wc_mergeinfo(&parent_mergeinfo, &inherited,
+                                             svn_mergeinfo_explicit,
+                                             dir_abspath, NULL, NULL, FALSE,
+                                             merge_b->ctx,
+                                             iterpool, iterpool));
+
+      if (added_path_mergeinfo
+          || svn_mergeinfo__is_noninheritable(parent_mergeinfo, iterpool))
         {
           svn_client__merge_path_t *target_merge_path =
             APR_ARRAY_IDX(notify_b->children_with_mergeinfo, 0,
                           svn_client__merge_path_t *);
           svn_merge_range_t *rng;
           svn_node_kind_t added_path_kind;
-          svn_mergeinfo_t merge_mergeinfo, added_path_mergeinfo;
+          svn_mergeinfo_t merge_mergeinfo;
+          svn_mergeinfo_t adds_history_as_mergeinfo;
           apr_array_header_t *rangelist;
           const char *rel_added_path;
           const char *added_path_mergeinfo_path;
+          const char *old_session_url;
+          const char *added_path_mergeinfo_url;
+          svn_opt_revision_t peg_revision;
 
           SVN_ERR(svn_wc_read_kind(&added_path_kind, merge_b->ctx->wc_ctx,
                                    added_abspath, FALSE, iterpool));
 
-          /* Calculate the mergeinfo resulting from this merge. */
+          /* Calculate the naive mergeinfo describing the merge. */
           merge_mergeinfo = apr_hash_make(iterpool);
           rangelist = apr_array_make(iterpool, 1, sizeof(svn_merge_range_t *));
           rng = svn_merge_range_dup(merged_range, iterpool);
@@ -7839,14 +7932,46 @@ record_mergeinfo_for_added_subtrees(
           apr_hash_set(merge_mergeinfo, added_path_mergeinfo_path,
                        APR_HASH_KEY_STRING, rangelist);
 
-          /* Get any explicit mergeinfo the added path has. */
-          SVN_ERR(svn_client__get_wc_mergeinfo(
-            &added_path_mergeinfo, &inherited,
-            svn_mergeinfo_explicit, added_abspath,
-            NULL, NULL, FALSE, merge_b->ctx, iterpool, iterpool));
+          /* Don't add new mergeinfo to describe the merge if that mergeinfo
+             contains non-existent merge sources.
+
+             We know that MERGEINFO_PATH/rel_added_path's history does not
+             span MERGED_RANGE->START:MERGED_RANGE->END but rather that it
+             was added at some revions greater than MERGED_RANGE->START
+             (assuming this is a forward merge).  It may have been added,
+             deleted, and re-added many times.  The point is that we cannot
+             blindly apply the naive mergeinfo calculated above because it
+             will describe non-existent merge sources. To avoid this we get
+             take the intersection of the naive mergeinfo with
+             MERGEINFO_PATH/rel_added_path's history. */
+          added_path_mergeinfo_url =
+            svn_path_url_add_component2(merge_b->repos_root_url,
+                                        added_path_mergeinfo_path + 1,
+                                        iterpool);
+          peg_revision.kind = svn_opt_revision_number;
+          peg_revision.value.number = MAX(merged_range->start,
+                                          merged_range->end);
+          SVN_ERR(svn_client__ensure_ra_session_url(
+            &old_session_url, merge_b->ra_session2,
+            added_path_mergeinfo_url, iterpool));
+          SVN_ERR(svn_client__get_history_as_mergeinfo(
+            &adds_history_as_mergeinfo, NULL,
+            added_path_mergeinfo_url, &peg_revision,
+            MAX(merged_range->start, merged_range->end),
+            MIN(merged_range->start, merged_range->end),
+            merge_b->ra_session2, merge_b->ctx, iterpool));
+
+          if (old_session_url)
+            SVN_ERR(svn_ra_reparent(merge_b->ra_session2,
+                                    old_session_url, iterpool));
+
+          SVN_ERR(svn_mergeinfo_intersect2(&merge_mergeinfo,
+                                           merge_mergeinfo,
+                                           adds_history_as_mergeinfo,
+                                           FALSE, iterpool, iterpool));
 
           /* Combine the explict mergeinfo on the added path (if any)
-             with the mergeinfo for this merge. */
+             with the mergeinfo describing this merge. */
           if (added_path_mergeinfo)
             SVN_ERR(svn_mergeinfo_merge(merge_mergeinfo, added_path_mergeinfo,
                                         iterpool));

Modified: subversion/trunk/subversion/tests/cmdline/merge_authz_tests.py
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/merge_authz_tests.py?rev=1160756&r1=1160755&r2=1160756&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/merge_authz_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/merge_authz_tests.py Tue Aug 23 15:53:39 2011
@@ -420,7 +420,7 @@ def mergeinfo_and_skipped_paths(sbox):
                    props={SVN_PROP_MERGEINFO : '/A/D/H/omega:8-9'}),
     'chi'   : Item("This is the file 'chi'.\n"),
     'zeta'  : Item("This is the file 'zeta'.\n",
-                   props={SVN_PROP_MERGEINFO : '/A/D/H/zeta:8-9'}),
+                   props={SVN_PROP_MERGEINFO : '/A/D/H/zeta:9'}),
     })
   expected_skip = wc.State(A_COPY_2_H_path, {})
   svntest.actions.run_and_verify_merge(A_COPY_2_H_path, '7', '9',

Modified: subversion/trunk/subversion/tests/cmdline/merge_reintegrate_tests.py
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/merge_reintegrate_tests.py?rev=1160756&r1=1160755&r2=1160756&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/merge_reintegrate_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/merge_reintegrate_tests.py Tue Aug 23 15:53:39 2011
@@ -486,22 +486,9 @@ def reintegrate_with_rename(sbox):
     ""             : Item(status=' M', wc_rev=9),
   })
   k_expected_disk.tweak('', props={SVN_PROP_MERGEINFO : '/A_COPY:2-9'})
-
-  # Why do we expect mergeinfo of '/A_COPY/D/G/tauprime:2-9' on
-  # A/D/G/tauprime?  Because this --reintegrate merge is effectively a
-  # two URL merge of %URL%/A@9 %URL%/A_COPY@9 to 'A'.  Since %URL%/A@9 and
-  # %URL%/A_COPY@9 have a common ancestor in %URL%/A@1 we expect this 2-URL
-  # merge to record mergeinfo and a component of that mergeinfo describes
-  # the merge of %URL%/A_COPY@2 to %URL%/A_COPY@9.  We see that above on
-  # A.  But we also get it on A's subtrees with explicit mergeinfo, namely
-  # A/D/G/tauprime.  Now I know what you are thinking, "'A_COPY/D/G/tauprime'
-  # doesn't even exist until r9!", and you are quite right.  But this
-  # inheritance of bogus mergeinfo is a known problem, see
-  # http://subversion.tigris.org/issues/show_bug.cgi?id=3157#desc8,
-  # and is not what this test is about, so we won't fail because of it.
   k_expected_disk.add({
     'D/G/tauprime' : Item(props={SVN_PROP_MERGEINFO :
-                                 '/A/D/G/tau:2-7\n/A_COPY/D/G/tauprime:2-9'},
+                                 '/A/D/G/tau:2-7\n/A_COPY/D/G/tauprime:9'},
                           contents="This is the file 'tau'.\n")
     })
   expected_skip = wc.State(A_path, {})
@@ -1269,7 +1256,8 @@ def reintegrate_with_subtree_mergeinfo(s
                            'A    ' + gamma_moved_COPY_path + '\n',
                            'D    ' + gamma_COPY_path + '\n',
                            ' U   ' + A_COPY_path     + '\n',
-                           ' U   ' + D_COPY_path     + '\n',]),
+                           ' U   ' + D_COPY_path     + '\n',
+                           ' U   ' + gamma_moved_COPY_path + '\n']),
     [], 'merge', sbox.repo_url + '/A',  A_COPY_path)
   expected_output = wc.State(
     wc_dir,
@@ -1339,7 +1327,7 @@ def reintegrate_with_subtree_mergeinfo(s
     ''              : Item(status=' U'),
     'mu'            : Item(status=' G'),
     'D'             : Item(status=' U'),
-    'D/gamma_moved' : Item(status=' G'),
+    'D/gamma_moved' : Item(status=' U'),
     })
   expected_elision_output = wc.State(A_path, {
     })
@@ -1385,8 +1373,7 @@ def reintegrate_with_subtree_mergeinfo(s
     'D/G/tau'       : Item("This is the file 'tau'.\n"),
     'D/gamma_moved' : Item(
       "Even newer content", props={SVN_PROP_MERGEINFO :
-                                   '/A/D/gamma_moved:2-15\n'
-                                   '/A_COPY/D/gamma_moved:2-19\n'
+                                   '/A_COPY/D/gamma_moved:17-19\n'
                                    '/A_COPY_3/D/gamma:9'}),
     'D/H'           : Item(),
     'D/H/chi'       : Item("This is the file 'chi'.\n"),
@@ -2024,7 +2011,7 @@ def added_subtrees_with_mergeinfo_break_
     'C'         : Item(),
     'C/nu'      : Item("Trunk work on nu.\n",
                        props={SVN_PROP_MERGEINFO :
-                              '/A_COPY/C/nu:16-18\n'
+                              '/A_COPY/C/nu:13,16-18\n'
                               '/A_COPY_2/C/nu:10'}), # <-- From cyclic
                                                      # merge in r11
     'D'         : Item(),

Modified: subversion/trunk/subversion/tests/cmdline/merge_tests.py
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/merge_tests.py?rev=1160756&r1=1160755&r2=1160756&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/merge_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/merge_tests.py Tue Aug 23 15:53:39 2011
@@ -7681,7 +7681,7 @@ def merge_away_subtrees_noninheritable_r
   expected_output = svntest.verify.UnorderedOutput(
       [A_COPY_path  + ' - /A:2-13*\n',
        mu_COPY_path + ' - /A/mu:2-13\n',
-       nu_COPY_path + ' - /A/nu:2-13\n',])
+       nu_COPY_path + ' - /A/nu:10-13\n',])
   svntest.actions.run_and_verify_svn(None,
                                      expected_output,
                                      [], 'pg', SVN_PROP_MERGEINFO,
@@ -11582,24 +11582,7 @@ def dont_explicitly_record_implicit_merg
   svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
   wc_status.tweak(wc_rev=10)
 
-  # Now do a cherry harvest merge to 'A_copy'.  We should pick up the
-  # change to 'A_copy/D/H/nu' from r10, the mergeinfo on 'A_copy' should
-  # reflect the entire eligible revision range from 'A', r2-10.
-  # 'A_copy/D/H/nu' should have get the equivalent mergeinfo to 'A_copy'
-  # which should elide to the latter, leaving no explicit mergeinfo...
-  #
-  # ...or should it?  For the mergeinfo on ' A_copy/D/H/nu' to elide, it
-  # needs mergeinfo '/A/D/H/nu:2-10', but 'A/D/H/nu' doesn't exist prior to
-  # r6...Anyhow, regardless of what the mergeinfo on ' A_copy/D/H/nu' should
-  # be prior to elision, if we have the following conditions:
-  #
-  #   1) A uniform working revision merge target.
-  #
-  #   2) Explicit mergeinfo on the target/subtrees from the same
-  #      source ('A' in this case).
-  #
-  # Then a cherry harvest from the same source should leave explicit
-  # mergeinfo *only* on the merge target no?
+  # Now do a cherry harvest merge to 'A_copy'.
   expected_output = wc.State(A_copy_path, {
     'D/H/nu' : Item(status='U '),
     })
@@ -11608,7 +11591,6 @@ def dont_explicitly_record_implicit_merg
     'D/H/nu' : Item(status=' U'),
     })
   expected_elision_output = wc.State(A_copy_path, {
-    'D/H/nu' : Item(status=' U'),
     })
   expected_A_copy_status = wc.State(A_copy_path, {
     ''          : Item(status=' M', wc_rev=10),
@@ -11652,7 +11634,8 @@ def dont_explicitly_record_implicit_merg
     'D/H/chi'   : Item("This is the file 'chi'.\n"),
     'D/H/psi'   : Item("This is the file 'psi'.\n"),
     'D/H/omega' : Item("This is the file 'omega'.\n"),
-    'D/H/nu'    : Item("Even nuer content"),
+    'D/H/nu'    : Item("Even nuer content",
+                       props={SVN_PROP_MERGEINFO : '/A/D/H/nu:6-10'}),
     })
   expected_A_copy_skip = wc.State(A_copy_path, {})
   svntest.actions.run_and_verify_merge(A_copy_path, None, None,
@@ -13957,7 +13940,7 @@ def no_self_referential_filtering_on_add
     'B/E/beta'  : Item("New content"),
     'B/lambda'  : Item("This is the file 'lambda'.\n"),
     'B/F'       : Item(),
-    'C_MOVED'   : Item(props={SVN_PROP_MERGEINFO : '/A/C_MOVED:3-10\n' +
+    'C_MOVED'   : Item(props={SVN_PROP_MERGEINFO : '/A/C_MOVED:10\n' +
                               '/A_COPY/C:8\n' +
                               '/A_COPY/C_MOVED:8',
                               'propname' : 'propval'}),
@@ -16711,7 +16694,6 @@ def foreign_repos_prop_conflict(sbox):
 #----------------------------------------------------------------------
 # Test for issue #3975 'adds with explicit mergeinfo don't get mergeinfo
 # describing merge which added them'
-@XFail()
 @Issue(3975)
 @SkipUnless(server_has_mergeinfo)
 def merge_adds_subtree_with_mergeinfo(sbox):
@@ -16767,7 +16749,8 @@ def merge_adds_subtree_with_mergeinfo(sb
     ''          : Item(status=' U'),
     })
   expected_mergeinfo_output = wc.State(A_COPY2_path, {
-    ''   : Item(status=' G'),
+    ''     : Item(status=' G'),
+    'C/nu' : Item(status=' U'),
     })
   expected_elision_output = wc.State(A_COPY2_path, {
     })
@@ -16805,15 +16788,11 @@ def merge_adds_subtree_with_mergeinfo(sb
     'B/lambda'  : Item("This is the file 'lambda'.\n"),
     'B/F'       : Item(),
     'C'         : Item(),
-    # Currently this test fails because A_COPY_2/C/nu gets no
-    # mergeinfo set on it to describe the merge, it only has
-    # the explicit mergeinfo from its copy source (i.e. /A_COPY/C/nu:10
-    # from ^/A/C/nu@11.
     'C/nu'      : Item("This is the file 'nu'.\n" \
                        "More work on the A_COPY branch.\n" \
                        "A faux conflict resolution.\n",
                        props={SVN_PROP_MERGEINFO :
-                              '\/A/C/nu:9-11n/A_COPY/C/nu:10'}),
+                              '/A/C/nu:9-11\n/A_COPY/C/nu:10'}),
     'D'         : Item(),
     'D/G'       : Item(),
     'D/G/pi'    : Item("This is the file 'pi'.\n"),