Over the early part of this year Mike Pilato and other Collabnet
employees met with many of our customers to get feeback on Subversion,
see 
http://svn.apache.org/repos/asf/subversion/trunk/notes/feedback/cmpilato-user-calls

One merge related item that came up was: '--reintegration should
tolerate sparse checkouts that aren't affected'.

I created issue #3603 to track this.

The good news is that simply removing the check for a sparse WC works
perfectly in the case where the 'missing' subtrees in the reintegrate
target are unaffected by the merge: The merge succeeds, no new subtree
mergeinfo is created, no errors, it just DTRT.

If the reintegrate target is missing a subtree that *is* affected by
the merge, then a tree-conflict occurs.  Subtree mergeinfo is also set
to partition the missing subtree so it doesn't inherit the mergeinfo
set on the root (exactly like a non-reintegrate merge does already).

My question is this: Is that sufficient?

I think it is.  It's not as if reintegrate merges can't result in tree
conflicts already (e.g. if work continues on trunk after the last sync
merge to a branch, the reintegration of the branch back to trunk can
easily cause this).

In a perfect world maybe we'd give a error along the lines of 'hey,
you are trying to reintegrate into a shallow WC and some of the paths
affected by the merge aren't present, you are going to get tree
conflicts, is this really what you want? :-)'

But going this route adds more merge special casing and obviously has
a performance penalty, two things we definitely don't need more of.  I
think that if you are reintegrating into a shallow WC we should simply
permit it and assume this this is what you really want.  Anyone
disagree?  If not, I'll be applying the attached patch RSN.

[[[
Fix issue #3603 'allow reintegrate merges into WCs with missing subtrees'.

* subversion/libsvn_client/merge.c

  (ensure_wc_reflects_repository_subtree): Consider shallow WCs as ready
   to reintegrate to.  That's it!  Nothing special, it works as you'd
   expect.

* subversion/tests/cmdline/merge_tests.py

  (reintegrate_fail_on_shallow_wc): Rename to...

  (reintegrate_on_shallow_wc): ...this.  Reimplement the test to expect
   success when reintegrating into a shallow WC.

  (test_list): Reflect test rename.

]]]

Paul
Index: subversion/libsvn_client/merge.c
===================================================================
--- subversion/libsvn_client/merge.c    (revision 933937)
+++ subversion/libsvn_client/merge.c    (working copy)
@@ -8614,9 +8614,9 @@
 }
 
 
-/* If TARGET_WCPATH does not reflect a single-revision,
-   svn_depth_infinity, pristine, unswitched working copy -- in other
-   words, a subtree found in a single revision -- raise
+/* If TARGET_WCPATH does not reflect a single-revision, pristine,
+   unswitched working copy -- in other words, a subtree found in a
+   single revision (although sparse checkouts are permitted) -- raise
    SVN_ERR_CLIENT_NOT_READY_TO_MERGE. */
 static svn_error_t *
 ensure_wc_reflects_repository_subtree(const char *target_abspath,
@@ -8635,11 +8635,6 @@
                             _("Cannot reintegrate into a working copy "
                               "with a switched subtree"));
 
-  if (wc_stat->sparse_checkout)
-    return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
-                            _("Cannot reintegrate into a working copy "
-                              "not entirely at infinite depth"));
-
   if (wc_stat->modified)
     return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
                             _("Cannot reintegrate into a working copy "
Index: subversion/tests/cmdline/merge_tests.py
===================================================================
--- subversion/tests/cmdline/merge_tests.py     (revision 933937)
+++ subversion/tests/cmdline/merge_tests.py     (working copy)
@@ -11026,28 +11026,120 @@
     ".*Cannot reintegrate into a working copy with a switched subtree.*",
     None, None, None, None, True, False, '--reintegrate')
 
-def reintegrate_fail_on_shallow_wc(sbox):
-  "merge --reintegrate should fail in shallow wc"
+
+# Test for issue #3603 'allow reintegrate merges into WCs with
+# missing subtrees'.
+def reintegrate_on_shallow_wc(sbox):
+  "merge --reintegrate in shallow wc"
+
+  # Create a standard greek tree, branch A to A_COPY in r2.
   sbox.build()
   wc_dir = sbox.wc_dir
-  expected_disk, expected_status = set_up_branch(sbox)
-  A_path = os.path.join(wc_dir, "A")
-  G_path = os.path.join(A_path, "D", "G")
-  # Our default checkout doesn't have any subdirs at non-infinite
-  # depth, so we'll have to create one the old-fashioned way: remove a
-  # tree, then "update" it back into existence at a shallower depth.
-  svntest.main.safe_rmtree(G_path)
-  svntest.actions.run_and_verify_svn(None, None, [], 'update', G_path,
-                                     '--depth=files')
-  # Even though everything is actually present (as G has no subdirs
-  # anyway), the reintegration should fail, because G's depth is other
-  # than infinity.
-  svntest.actions.run_and_verify_merge(
-    A_path, None, None, sbox.repo_url + '/A_COPY', None, None, None, None,
-    None, None, None,
-    ".*Cannot reintegrate into a working copy not.*at infinite depth.*",
-    None, None, None, None, True, False, '--reintegrate')
+  expected_disk, expected_status = set_up_branch(sbox, branch_only = True)
 
+  # Some paths we'll care about
+  A_path         = os.path.join(wc_dir, "A")
+  A_D_path       = os.path.join(wc_dir, "A", "D")
+  mu_COPY_path   = os.path.join(wc_dir, "A_COPY", "mu")
+  psi_COPY_path  = os.path.join(wc_dir, "A_COPY", "D", "H", "psi")
+  A_COPY_path    = os.path.join(wc_dir, "A_COPY")
+
+  # r3 - Make a change on the A_COPY branch that will be
+  # reintegrated back to A.
+  svntest.main.file_write(mu_COPY_path, "branch work")
+  svntest.main.run_svn(None, 'commit', '-m',
+                       'Some work on the A_COPY branch', wc_dir)
+
+  # First try a reintegrate where the target WC has a shallow subtree
+  # that is not affected by the reintegrate.  In this case we set the
+  # depth of A/D to empty.  Since the only change made on the branch
+  # since the branch point is to A_COPY/mu, the reintegrate should
+  # simply work and update A/mu with the branch's contents.
+  svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
+  svntest.actions.run_and_verify_svn(None, None, [], 'up',
+                                     '--set-depth', 'empty', A_D_path)
+  expected_output = wc.State(A_path, {
+    'mu' : Item(status='U '),
+    })
+  expected_mergeinfo_output = wc.State(A_path, {
+    '' : Item(status=' U'),
+    })
+  expected_elision_output = wc.State(A_path, {
+    })
+  expected_A_status = wc.State(A_path, {
+    ''          : Item(status=' M'),
+    'B'         : Item(status='  '),
+    'mu'        : Item(status='M '),
+    'B/E'       : Item(status='  '),
+    'B/E/alpha' : Item(status='  '),
+    'B/E/beta'  : Item(status='  '),
+    'B/lambda'  : Item(status='  '),
+    'B/F'       : Item(status='  '),
+    'C'         : Item(status='  '),
+    'D'         : Item(status='  '), # Don't expect anything under D,
+                                     # its depth is empty!
+    })
+  expected_A_status.tweak(wc_rev=3)
+  expected_A_disk = wc.State('', {
+    ''          : Item(props={SVN_PROP_MERGEINFO : '/A_COPY:2-3'}),
+    'B'         : Item(),
+    'mu'        : Item("branch work"),
+    'B/E'       : Item(),
+    'B/E/alpha' : Item("This is the file 'alpha'.\n"),
+    'B/E/beta'  : Item("This is the file 'beta'.\n"),
+    'B/lambda'  : Item("This is the file 'lambda'.\n"),
+    'B/F'       : Item(),
+    'C'         : Item(),
+    'D'         : Item(), # Don't expect anything under D, its depth is empty!
+    })
+  expected_A_skip = wc.State(A_COPY_path, {})
+  svntest.actions.run_and_verify_merge(A_path, None, None,
+                                       sbox.repo_url + '/A_COPY', None,
+                                       expected_output,
+                                       expected_mergeinfo_output,
+                                       expected_elision_output,
+                                       expected_A_disk,
+                                       expected_A_status,
+                                       expected_A_skip,
+                                       None, None, None, None,
+                                       None, 1, 1, "--reintegrate")
+
+  # Now revert the reintegrate and make a second change on the
+  # branch in r4, but this time change a subtree that corresponds
+  # to the missing (shallow) portion of the source.  The reintegrate
+  # should still succeed, albeit with a tree-conflict.
+  svntest.actions.run_and_verify_svn(None, None, [], 'revert', '-R', wc_dir)
+  svntest.main.file_write(psi_COPY_path, "more branch work")
+  svntest.main.run_svn(None, 'commit', '-m',
+                       'Some more work on the A_COPY branch', wc_dir)
+  # Reuse the same expectations as the prior merge, except that...
+  #
+  # ...a tree conflict occurs...
+  expected_output.add({
+      'D/H' : Item(status='  ', treeconflict='C')
+      })
+  expected_A_status.add({
+      'D/H' : Item(status='! ', treeconflict='C')
+      })
+  # ...non-inheritable mergeinfo is set on the root of the missing subtree...
+  expected_mergeinfo_output.add({
+      'D' : Item(status=' U')
+      })
+  expected_A_status.tweak('D', status=' M')
+  expected_A_disk.tweak('D', props={SVN_PROP_MERGEINFO : '/A_COPY/D:2-4*'})
+  # ...the mergeinfo on the target root includes the latest rev on the branch.
+  expected_A_disk.tweak('', props={SVN_PROP_MERGEINFO : '/A_COPY:2-4'})
+  svntest.actions.run_and_verify_merge(A_path, None, None,
+                                       sbox.repo_url + '/A_COPY', None,
+                                       expected_output,
+                                       expected_mergeinfo_output,
+                                       expected_elision_output,
+                                       expected_A_disk,
+                                       expected_A_status,
+                                       expected_A_skip,
+                                       None, None, None, None,
+                                       None, 1, 1, "--reintegrate")
+
 def reintegrate_fail_on_stale_source(sbox):
   "merge --reintegrate should fail on stale source"
   sbox.build()
@@ -19032,7 +19124,7 @@
               reintegrate_fail_on_modified_wc,
               reintegrate_fail_on_mixed_rev_wc,
               reintegrate_fail_on_switched_wc,
-              reintegrate_fail_on_shallow_wc,
+              reintegrate_on_shallow_wc,
               SkipUnless(reintegrate_fail_on_stale_source,
                          server_has_mergeinfo),
               SkipUnless(dont_add_mergeinfo_from_own_history,

Reply via email to