You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by hw...@apache.org on 2010/12/11 01:16:08 UTC

svn commit: r1044548 [6/39] - in /subversion/branches/ignore-mergeinfo: ./ build/ build/ac-macros/ build/generator/ build/generator/templates/ build/win32/ contrib/client-side/ contrib/hook-scripts/ contrib/server-side/ notes/api-errata/ notes/api-erra...

Modified: subversion/branches/ignore-mergeinfo/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/ignore-mergeinfo/subversion/libsvn_client/merge.c?rev=1044548&r1=1044547&r2=1044548&view=diff
==============================================================================
--- subversion/branches/ignore-mergeinfo/subversion/libsvn_client/merge.c (original)
+++ subversion/branches/ignore-mergeinfo/subversion/libsvn_client/merge.c Sat Dec 11 00:15:55 2010
@@ -1121,8 +1121,6 @@ merge_props_changed(const char *local_di
      definition, 'svn merge' shouldn't touch any data within .svn/  */
   if (props->nelts)
     {
-      int i;
-
       /* If this is a forward merge then don't add new mergeinfo to
          PATH that is already part of PATH's own history, see
          http://svn.haxx.se/dev/archive-2008-09/0006.shtml.  If the
@@ -1150,6 +1148,8 @@ merge_props_changed(const char *local_di
          is having its existing mergeinfo deleted. */
       if (!merge_b->dry_run)
         {
+          int i;
+
           for (i = 0; i < props->nelts; ++i)
             {
               svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
@@ -1723,13 +1723,14 @@ merge_file_added(const char *local_dir_a
                    copying 'yours' to 'mine', isn't enough; we need to get
                    the whole text-base and props installed too, just as if
                    we had called 'svn cp wc wc'. */
-                /* ### would be nice to have cancel/notify funcs to pass */
                 SVN_ERR(svn_wc_add_repos_file4(
                             merge_b->ctx->wc_ctx, mine_abspath,
                             new_base_contents, new_contents,
                             new_base_props, new_props,
                             copyfrom_url, copyfrom_rev,
-                            NULL, NULL, NULL, NULL, subpool));
+                            merge_b->ctx->cancel_func,
+                            merge_b->ctx->cancel_baton,
+                            subpool));
 
                 /* ### delete 'yours' ? */
               }
@@ -3289,6 +3290,110 @@ fix_deleted_subtree_ranges(const char *u
 
 /*** Determining What Remains To Be Merged ***/
 
+
+/* Attempt to determine if a working copy path inherits any invalid
+   mergeinfo.
+
+   Query the repository for the mergeinfo TARGET_ABSPATH inherits at its
+   base revision and set *VALIDATED to indicate to the caller if we can
+   determine what portions of that inherited mergeinfo are invalid.
+
+   If no mergeinfo is inherited set *INVALID_INHERITED_MERGEINFO to NULL and
+   *VALIDATED to true.
+
+   If only empty mergeinfo is inherited set *INVALID_INHERITED_MERGEINFO to
+   and empty hash and *VALIDATED to true.
+
+   If non-empty inherited mergeinfo is inherited then, if the server (1.7+)
+   supports inherited mergeinfo validation, remove all valid path-revisions
+   from the raw inherited mergeinfo, set *INVALID_INHERITED_MERGEINFO to the
+   remainder, and set *VALIDATED to true.  If the server doesn't support
+   inherited mergeinfo validation then set *INVALID_INHERITED_MERGEINFO to
+   NULL and set *VALIDATED to false.
+
+   Note that if validation occurs, but all inherited mergeinfo describes
+   non-existent paths, then *INVALID_INHERITED_MERGEINFO is set to an empty
+   hash.
+
+   RA_SESSION is an open session that points to TARGET_ABSPATH's repository
+   location or to the location of one of TARGET_ABSPATH's parents.  It may
+   be temporarily reparented.
+
+   RESULT_POOL is used to allocate *INVALID_INHERITED_MERGEINFO, SCRATCH_POOL
+   is used for any temporary allocations. */
+static svn_error_t *
+get_invalid_inherited_mergeinfo(svn_mergeinfo_t *invalid_inherited_mergeinfo,
+                                svn_boolean_t *validated,
+                                svn_ra_session_t *ra_session,
+                                const char *target_abspath,
+                                svn_client_ctx_t *ctx,
+                                apr_pool_t *result_pool,
+                                apr_pool_t *scratch_pool)
+{
+  svn_mergeinfo_t repos_raw_inherited;
+  svn_mergeinfo_t repos_validated_inherited;
+  svn_revnum_t base_revision;
+  svn_boolean_t validate_inherited_mergeinfo;
+
+  /* Our starting assumptions. */ 
+  *invalid_inherited_mergeinfo = NULL;
+  *validated = TRUE;
+
+  SVN_ERR(svn_wc__node_get_base_rev(&base_revision, ctx->wc_ctx,
+                                    target_abspath, scratch_pool));
+
+  /* If there is no base revision then TARGET_ABSPATH doesn't exist
+     in the repository yet, so we're done. */
+  if (SVN_IS_VALID_REVNUM(base_revision))
+    {
+      const char *target_url;
+      const char *session_url;
+
+      /* Reparent RA_SESSION if necessary. */
+      SVN_ERR(svn_wc__node_get_url(&target_url, ctx->wc_ctx, target_abspath,
+                                   scratch_pool, scratch_pool));
+      SVN_ERR(svn_client__ensure_ra_session_url(&session_url, ra_session,
+                                                target_url, scratch_pool));
+
+      /* Contact the repository to derive the portion of
+         TARGET_ABSPATH's inherited mergeinfo which is non-existent
+         and remove it from */
+      validate_inherited_mergeinfo = FALSE;
+      SVN_ERR(svn_client__get_repos_mergeinfo(
+        ra_session, &repos_raw_inherited, "", base_revision,
+        svn_mergeinfo_inherited, TRUE,
+        &validate_inherited_mergeinfo,
+        scratch_pool));
+
+      if (repos_raw_inherited == NULL)
+        {
+          *invalid_inherited_mergeinfo = NULL;
+        }
+      else if (apr_hash_count(repos_raw_inherited) == 0)
+        {
+          *invalid_inherited_mergeinfo = apr_hash_make(result_pool);
+        }
+      else
+        {
+          SVN_ERR(svn_client__get_repos_mergeinfo(
+            ra_session, &repos_validated_inherited, "", base_revision,
+            svn_mergeinfo_inherited, TRUE,
+            validated,
+            scratch_pool));
+          if (*validated)
+            SVN_ERR(svn_mergeinfo_remove2(invalid_inherited_mergeinfo,
+                                          repos_validated_inherited,
+                                          repos_raw_inherited, FALSE,
+                                          result_pool, scratch_pool));
+          else
+            *invalid_inherited_mergeinfo = NULL;
+        }
+      SVN_ERR(svn_client__ensure_ra_session_url(&session_url, ra_session,
+                                                session_url, scratch_pool));
+    }
+  return SVN_NO_ERROR;
+}
+
 /* Get explicit and/or implicit mergeinfo for the working copy path
    TARGET_ABSPATH.
 
@@ -3299,6 +3404,10 @@ fix_deleted_subtree_ranges(const char *u
    If IMPLICIT_MERGEINFO is not NULL then set *IMPLICIT_MERGEINFO
    to TARGET_ABSPATH's implicit mergeinfo (a.k.a. natural history).
 
+   If both RECORDED_MERGEINFO and IMPLICIT_MERGEINFO are not NULL and
+   *RECORDED_MERGEINFO is inherited, then *IMPLICIT_MERGEINFO will be
+   removed from *RECORDED_MERGEINFO.
+
    If INDIRECT is not NULL set *INDIRECT to TRUE if *RECORDED_MERGEINFO
    is inherited and not explicit.  If RECORDED_MERGEINFO is NULL then
    INDIRECT is ignored.
@@ -3309,6 +3418,9 @@ fix_deleted_subtree_ranges(const char *u
    is older than START, then the base revision is used as the younger
    bound in place of START.
 
+   RA_SESSION is an open session that may be temporarily reparented as
+   needed by this function.
+
    Allocate *RECORDED_MERGEINFO and *IMPLICIT_MERGEINFO in RESULT_POOL.
    Use SCRATCH_POOL for any temporary allocations. */
 static svn_error_t *
@@ -3324,11 +3436,14 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
                    apr_pool_t *result_pool,
                    apr_pool_t *scratch_pool)
 {
-  /* First, we get the real mergeinfo. */
+  svn_boolean_t inherited = FALSE;
+  svn_boolean_t inherited_validated = FALSE;
+
+  /* First, we get the real mergeinfo.  We use SCRATCH_POOL throughout this
+     block because we'll make a final copy of *RECORDED_MERGEINFO only after
+     removing any self-referential mergeinfo. */
   if (recorded_mergeinfo)
     {
-      svn_boolean_t inherited;
-
       /* ### FIXME: There's probably an RA session we could/should be
          ### using here instead of having this function possibly spawn
          ### yet another one.  */
@@ -3336,9 +3451,29 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
                                                     &inherited, FALSE,
                                                     inherit, NULL,
                                                     target_abspath,
-                                                    ctx, result_pool));
+                                                    ctx, scratch_pool));
       if (indirect)
         *indirect = inherited;
+
+      /* Issue #3669: Remove any non-existent mergeinfo sources
+         from TARGET_ABSPATH's inherited mergeinfo. */
+      if (inherited)
+        {
+          svn_mergeinfo_t invalid_inherited_mergeinfo;
+
+          SVN_ERR(get_invalid_inherited_mergeinfo(
+            &invalid_inherited_mergeinfo, &inherited_validated,
+            ra_session, target_abspath, ctx,
+            scratch_pool, scratch_pool));
+
+          if (inherited_validated
+              && invalid_inherited_mergeinfo
+              && apr_hash_count(invalid_inherited_mergeinfo))
+            SVN_ERR(svn_mergeinfo_remove2(recorded_mergeinfo,
+                                          invalid_inherited_mergeinfo,
+                                          *recorded_mergeinfo, FALSE,
+                                          scratch_pool, scratch_pool));
+        }
     }
 
   if (implicit_mergeinfo)
@@ -3346,7 +3481,6 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
       const char *session_url = NULL, *url;
       svn_revnum_t target_rev;
       svn_opt_revision_t peg_revision;
-      apr_pool_t *sesspool = NULL;
       svn_error_t *err;
 
       /* Assert that we have sane input. */
@@ -3354,10 +3488,12 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
                  && SVN_IS_VALID_REVNUM(end)
                  && (start > end));
 
+      *implicit_mergeinfo = NULL;
+
       peg_revision.kind = svn_opt_revision_working;
       err = svn_client__derive_location(&url, &target_rev, target_abspath,
                                         &peg_revision, ra_session,
-                                        ctx, result_pool, scratch_pool);
+                                        ctx, scratch_pool, scratch_pool);
 
       if (err)
         {
@@ -3370,7 +3506,6 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
                * mergeinfo is empty. */
               svn_error_clear(err);
               *implicit_mergeinfo = apr_hash_make(result_pool);
-              return SVN_NO_ERROR;
             }
           else
             return svn_error_return(err);
@@ -3381,52 +3516,60 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
           /* We're asking about a range outside our natural history
              altogether.  That means our implicit mergeinfo is empty. */
           *implicit_mergeinfo = apr_hash_make(result_pool);
-          return SVN_NO_ERROR;
         }
 
-      /* Temporarily point our RA_SESSION at our target URL so we can
-         fetch so-called "implicit mergeinfo" (that is, natural history). */
-      if (ra_session)
+      if (*implicit_mergeinfo == NULL)
         {
-          SVN_ERR(svn_client__ensure_ra_session_url(&session_url, ra_session,
-                                                    url, scratch_pool));
-        }
-      else
-        {
-          SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, url,
-                                                       NULL, NULL,
-                                                       FALSE, TRUE,
-                                                       ctx, scratch_pool));
-        }
-
-      /* Do not ask for implicit mergeinfo from TARGET_ABSPATH's future.
-         TARGET_ABSPATH might not even exist, and even if it does the
-         working copy is *at* TARGET_REV so its implicit history ends
-         at TARGET_REV! */
-      if (target_rev < start)
-        start = target_rev;
-
-      /* Fetch the implicit mergeinfo. */
-      peg_revision.kind = svn_opt_revision_number;
-      peg_revision.value.number = target_rev;
-      SVN_ERR(svn_client__get_history_as_mergeinfo(implicit_mergeinfo, url,
-                                                   &peg_revision, start, end,
-                                                   ra_session, ctx,
-                                                   result_pool));
+          /* Temporarily point our RA_SESSION at our target URL so we can
+             fetch so-called "implicit mergeinfo" (that is, natural
+             history). */
+          SVN_ERR(svn_client__ensure_ra_session_url(&session_url,
+                                                    ra_session, url,
+                                                    scratch_pool));
 
-      /* If we created an RA_SESSION above, destroy it.  Otherwise, if
-         reparented an existing session, point it back where it was when
-         we were called. */
-      if (sesspool)
-        {
-          svn_pool_destroy(sesspool);
-        }
-      else if (session_url)
-        {
-          SVN_ERR(svn_ra_reparent(ra_session, session_url, scratch_pool));
+          /* Do not ask for implicit mergeinfo from TARGET_ABSPATH's future.
+             TARGET_ABSPATH might not even exist, and even if it does the
+             working copy is *at* TARGET_REV so its implicit history ends
+             at TARGET_REV! */
+          if (target_rev < start)
+            start = target_rev;
+
+          /* Fetch the implicit mergeinfo. */
+          peg_revision.kind = svn_opt_revision_number;
+          peg_revision.value.number = target_rev;
+          SVN_ERR(svn_client__get_history_as_mergeinfo(implicit_mergeinfo,
+                                                       url, &peg_revision,
+                                                       start, end,
+                                                       ra_session, ctx,
+                                                       result_pool));
+
+          /* Return RA_SESSION back to where it was when we were called. */
+         SVN_ERR(svn_client__ensure_ra_session_url(&session_url,
+                                                   ra_session, session_url,
+                                                   scratch_pool)); 
         }
     } /*if (implicit_mergeinfo) */
 
+
+  if (recorded_mergeinfo && *recorded_mergeinfo)
+    {
+      /* Issue #3668: Remove any self-referential mergeinfo from that
+         which TARGET_ABSPATH inherited; but only do this if we were able to
+         validate inherited mergeinfo (issue #3669) or otherwise we end
+         up with fragmented mergeinfo, see
+         http://subversion.tigris.org/issues/show_bug.cgi?id=3668#desc5 */
+      if (implicit_mergeinfo
+          && inherited
+          && inherited_validated)
+        SVN_ERR(svn_mergeinfo_remove2(recorded_mergeinfo,
+                                      *implicit_mergeinfo,
+                                      *recorded_mergeinfo, FALSE,
+                                      result_pool, scratch_pool));
+      else
+        *recorded_mergeinfo = svn_mergeinfo_dup(*recorded_mergeinfo,
+          result_pool);
+    }
+
   return SVN_NO_ERROR;
 }
 
@@ -3778,8 +3921,8 @@ filter_merged_revisions(svn_client__merg
             child->remaining_ranges = svn_rangelist_dup(explicit_rangelist,
                                                         pool);
         }
-    }
 #endif
+    }
 
   svn_pool_destroy(subpool);
   return SVN_NO_ERROR;
@@ -4257,7 +4400,7 @@ populate_remaining_ranges(apr_array_head
         svn_mergeinfo_inherited, ra_session,
         child->abspath,
         MAX(revision1, revision2),
-        MIN(revision1, revision2),
+        0, /* Get all implicit mergeinfo */
         merge_b->ctx, pool, pool));
 
       /* If CHILD isn't the merge target find its parent. */
@@ -4313,7 +4456,7 @@ populate_remaining_ranges(apr_array_head
           /* If this is a reverse merge reorder CHILD->REMAINING_RANGES
               so it will work with the svn_rangelist_* APIs below. */
           if (revision1 > revision2)
-            svn_rangelist_reverse(child->remaining_ranges, iterpool);
+            SVN_ERR(svn_rangelist_reverse(child->remaining_ranges, iterpool));
 
           for (j = 0; j < child->remaining_ranges->nelts; j++)
             {
@@ -4355,7 +4498,7 @@ populate_remaining_ranges(apr_array_head
             }
 
           if (revision1 > revision2) /* Reverse merge */
-            svn_rangelist_reverse(child->remaining_ranges, iterpool);
+            SVN_ERR(svn_rangelist_reverse(child->remaining_ranges, iterpool));
         }
     }
 
@@ -5449,18 +5592,16 @@ get_mergeinfo_walk_cb(const char *local_
 
   /* TODO(#2843) How to deal with a excluded item on merge? */
 
+  SVN_ERR(svn_wc__get_mergeinfo_walk_info(&is_present, &deleted, &absent,
+                                          &depth,
+                                          wb->ctx->wc_ctx, local_abspath,
+                                          scratch_pool));
+
   /* Ignore LOCAL_ABSPATH if its parent thinks it exists, but it is not
      actually present. */
-  SVN_ERR(svn_wc__node_is_status_present(&is_present, wb->ctx->wc_ctx,
-                                         local_abspath, scratch_pool));
   if (!is_present)
     return SVN_NO_ERROR;
 
-  SVN_ERR(svn_wc__node_is_status_deleted(&deleted, wb->ctx->wc_ctx,
-                                         local_abspath, scratch_pool));
-  SVN_ERR(svn_wc__node_is_status_absent(&absent, wb->ctx->wc_ctx,
-                                        local_abspath, scratch_pool));
-
    if (deleted || absent)
     {
       propval = NULL;
@@ -5484,8 +5625,6 @@ get_mergeinfo_walk_cb(const char *local_
 
   SVN_ERR(svn_wc_read_kind(&kind, wb->ctx->wc_ctx, local_abspath, TRUE,
                            scratch_pool));
-  SVN_ERR(svn_wc__node_get_depth(&depth, wb->ctx->wc_ctx, local_abspath,
-                                 scratch_pool));
 
   immediate_child_dir = ((wb->depth == svn_depth_immediates)
                          &&(kind == svn_node_dir)
@@ -6596,7 +6735,6 @@ do_file_merge(svn_mergeinfo_catalog_t re
   svn_merge_range_t range;
   svn_mergeinfo_t target_mergeinfo;
   svn_merge_range_t *conflicted_range = NULL;
-  int i;
   svn_boolean_t indirect = FALSE;
   apr_pool_t *subpool;
   svn_boolean_t is_rollback = (revision1 > revision2);
@@ -6643,7 +6781,7 @@ do_file_merge(svn_mergeinfo_catalog_t re
                                  &indirect, svn_mergeinfo_inherited,
                                  merge_b->ra_session1, target_abspath,
                                  MAX(revision1, revision2),
-                                 MIN(revision1, revision2),
+                                 0, /* Get all implicit mergeinfo */
                                  ctx, scratch_pool, scratch_pool));
 
       SVN_ERR(svn_ra_reparent(merge_b->ra_session1, url1, scratch_pool));
@@ -6676,6 +6814,7 @@ do_file_merge(svn_mergeinfo_catalog_t re
   if (!merge_b->record_only)
     {
       apr_array_header_t *ranges_to_merge = remaining_ranges;
+      int i;
 
       /* If we have ancestrally related sources and more than one
          range to merge, eliminate no-op ranges before going through
@@ -7244,11 +7383,11 @@ get_inoperative_immediate_children(apr_h
         apr_hash_set(*immediate_children,
                      apr_pstrdup(result_pool, child->abspath),
                      APR_HASH_KEY_STRING,
-                     svn_uri_join(merge_source_repos_abspath,
-                                  svn_dirent_is_child(merge_target_abspath,
-                                                      child->abspath,
-                                                      iterpool),
-                                  result_pool));
+                     svn_fspath__join(merge_source_repos_abspath,
+                                      svn_dirent_is_child(merge_target_abspath,
+                                                          child->abspath,
+                                                          iterpool),
+                                      result_pool));
     }
 
   svn_pool_destroy(iterpool);
@@ -7424,9 +7563,9 @@ record_mergeinfo_for_dir_merge(svn_merge
           if (!child_repos_path)
             child_repos_path = "";
 
-          child_merge_src_canon_path = svn_uri_join(mergeinfo_path,
-                                                    child_repos_path,
-                                                    iterpool);
+          child_merge_src_canon_path = svn_fspath__join(mergeinfo_path,
+                                                        child_repos_path,
+                                                        iterpool);
           /* Filter any ranges from each child's natural history before
              setting mergeinfo describing the merge. */
           SVN_ERR(filter_natural_history_from_mergeinfo(
@@ -7734,7 +7873,7 @@ log_noop_revs(void *baton,
       /* Adjust REL_PATH so it is relative to the merge source then use it to
          calculate what path in the merge target would be affected by this
          revision. */
-      rel_path = svn_uri_skip_ancestor(log_gap_baton->source_repos_abs, path);
+      rel_path = svn_fspath__skip_ancestor(log_gap_baton->source_repos_abs, path);
       cwmi_path = svn_dirent_join(log_gap_baton->merge_b->target_abspath,
                                   rel_path, pool);
 
@@ -8028,7 +8167,18 @@ do_directory_merge(svn_mergeinfo_catalog
                    apr_pool_t *pool)
 {
   svn_error_t *err = SVN_NO_ERROR;
+
+  /* The range defining the mergeinfo we will record to describe the merge
+     (assuming we are recording mergeinfo
+
+     Note: This may be a subset of REVISION1:REVISION2 if
+     populate_remaining_ranges() determines that some part of
+     REVISION1:REVISION2 has already been wholly merged to TARGET_ABSPATH.
+     Also, the actual editor drive(s) may be a subset of RANGE, if
+     remove_noop_subtree_ranges() and/or fix_deleted_subtree_ranges()
+     further tweak things. */
   svn_merge_range_t range;
+
   svn_ra_session_t *ra_session;
   svn_client__merge_path_t *target_merge_path;
   svn_boolean_t is_rollback = (revision1 > revision2);
@@ -8101,17 +8251,22 @@ do_directory_merge(svn_mergeinfo_catalog
 
   if (honor_mergeinfo && !merge_b->reintegrate_merge)
     {
-      svn_revnum_t start_rev, end_rev;
+      svn_revnum_t new_range_start, start_rev, end_rev;
       apr_pool_t *iterpool = svn_pool_create(pool);
 
-      /* The merge target target_wcpath and/or its subtrees may not need all
+      /* The merge target TARGET_ABSPATH and/or its subtrees may not need all
          of REVISION1:REVISION2 applied.  So examine
          NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the oldest starting
          revision that actually needs to be merged (for reverse merges this is
-         the youngest starting revision). */
-      start_rev =
-        get_most_inclusive_start_rev(notify_b->children_with_mergeinfo,
-                                     is_rollback);
+         the youngest starting revision).
+         
+         We'll do this twice, right now for the start of the mergeinfo we will
+         ultimately record to describe this merge and then later for the 
+         start of the actual editor drive. */
+      new_range_start = get_most_inclusive_start_rev(
+        notify_b->children_with_mergeinfo, is_rollback);
+      if (SVN_IS_VALID_REVNUM(new_range_start))
+        range.start = new_range_start;
 
       /* Remove inoperative ranges from any subtrees' remaining_ranges
          to spare the expense of noop editor drives. */
@@ -8121,16 +8276,20 @@ do_directory_merge(svn_mergeinfo_catalog
                                            notify_b, merge_b,
                                            pool, iterpool));
 
+      /* Adjust subtrees' remaining_ranges to deal with issue #3067 */
       SVN_ERR(fix_deleted_subtree_ranges(url1, revision1, url2, revision2,
                                          ra_session, notify_b, merge_b, pool));
 
+      /* remove_noop_subtree_ranges() and/or fix_deleted_subtree_range()
+         may have further refined the starting revision for our editor
+         drive. */
+      start_rev =
+        get_most_inclusive_start_rev(notify_b->children_with_mergeinfo,
+                                     is_rollback);
+
       /* Is there anything to merge? */
       if (SVN_IS_VALID_REVNUM(start_rev))
         {
-          /* Adjust range to describe the start of our most
-             inclusive merge. */
-          range.start = start_rev;
-
           /* Now examine NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the youngest
              ending revision that actually needs to be merged (for reverse
              merges this is the oldest starting revision). */
@@ -8170,6 +8329,33 @@ do_directory_merge(svn_mergeinfo_catalog
               svn_revnum_t next_end_rev;
               const char *real_url1 = url1, *real_url2 = url2;
               const char *old_sess1_url = NULL, *old_sess2_url = NULL;
+              svn_merge_range_t *first_target_range = APR_ARRAY_IDX(
+                target_merge_path->remaining_ranges, 0, svn_merge_range_t *);
+
+              /* Issue #3324: Stop editor abuse!  Don't call
+                 drive_merge_report_editor() in such a way that we request an
+                 editor with svn_client__get_diff_editor() for some rev X,
+                 then call svn_ra_do_diff3() for some revision Y, and then
+                 call reporter->set_path(PATH=="") to set the root revision
+                 for the editor drive to revision Z where
+                 (X != Z && X < Z < Y).  This is bogus because the server will
+                 send us the diff between X:Y but the client is expecting the
+                 diff between Y:Z.  See issue #3324 for full details on the
+                 problems this can cause. */
+              if (first_target_range
+                  && start_rev != first_target_range->start)
+                {
+                  if (is_rollback)
+                    {
+                      if (end_rev < first_target_range->start)
+                        end_rev = first_target_range->start;
+                    }
+                  else
+                    {
+                      if (end_rev > first_target_range->start)
+                        end_rev = first_target_range->start;
+                    }
+                }
 
               svn_pool_clear(iterpool);
 
@@ -8468,11 +8654,11 @@ do_merge(apr_hash_t **modified_subtrees,
 
   SVN_ERR(svn_wc_read_kind(&target_kind, ctx->wc_ctx, target_abspath, FALSE,
                            pool));
+
   if (target_kind != svn_node_dir && target_kind != svn_node_file)
-    return svn_error_return(svn_error_createf(
-                              SVN_ERR_ILLEGAL_TARGET, NULL,
-                              _("Merge target '%s' does not exist in the "
-                                "working copy"), target_abspath));
+    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+                             _("Merge target '%s' does not exist in the "
+                               "working copy"), target_abspath);
 
   /* Ensure a known depth. */
   if (depth == svn_depth_unknown)
@@ -8815,6 +9001,73 @@ merge_cousins_and_supplement_mergeinfo(c
   return SVN_NO_ERROR;
 }
 
+/* Perform checks to determine whether of the working copy at TARGET_ABSPATH
+ * can safely be used as a merge target. Checks are performed according to
+ * the ALLOW_MIXED_REV, ALLOW_LOCAL_MODS, and ALLOW_SWITCHED_SUBTREES
+ * parameters. If any checks fail, raise SVN_ERR_CLIENT_NOT_READY_TO_MERGE.
+ *
+ * E.g. if all the ALLOW_* parameters are FALSE, TARGET_ABSPATH must
+ * be a single-revision, pristine, unswitched working copy.
+ * In other words, it must reflect a subtree of the repostiory as found
+ * at single revision -- although sparse checkouts are permitted. */
+static svn_error_t *
+ensure_wc_is_suitable_merge_target(const char *target_abspath,
+                                   svn_client_ctx_t *ctx,
+                                   svn_boolean_t allow_mixed_rev,
+                                   svn_boolean_t allow_local_mods,
+                                   svn_boolean_t allow_switched_subtrees,
+                                   apr_pool_t *scratch_pool)
+{
+  svn_wc_revision_status_t *wc_stat;
+
+  /* Avoid the following status crawl if we don't need it. */
+  if (allow_mixed_rev && allow_local_mods && allow_switched_subtrees)
+    return SVN_NO_ERROR;
+
+  /* Get a WC summary with min/max revisions set to the BASE revision. */
+  SVN_ERR(svn_wc_revision_status2(&wc_stat, ctx->wc_ctx, target_abspath, NULL,
+                                  FALSE, ctx->cancel_func, ctx->cancel_baton,
+                                  scratch_pool, scratch_pool));
+
+  if (! allow_switched_subtrees && wc_stat->switched)
+    return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
+                            _("Cannot merge into a working copy "
+                              "with a switched subtree"));
+
+  if (! allow_local_mods && wc_stat->modified)
+    return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
+                            _("Cannot merge into a working copy "
+                              "that has local modifications"));
+
+  if (! allow_mixed_rev)
+    {
+      if (! (SVN_IS_VALID_REVNUM(wc_stat->min_rev)
+             && SVN_IS_VALID_REVNUM(wc_stat->max_rev)))
+        {
+          svn_boolean_t is_added;
+
+          /* Allow merge into added nodes. */
+          SVN_ERR(svn_wc__node_is_added(&is_added, ctx->wc_ctx, target_abspath,
+                                        scratch_pool));
+          if (is_added)
+            return SVN_NO_ERROR;
+          else
+            return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
+                                    _("Cannot determine revision of working "
+                                      "copy"));
+        }
+
+      if (wc_stat->min_rev != wc_stat->max_rev)
+        return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
+                                 _("Cannot merge into mixed-revision working "
+                                   "copy [%lu:%lu]; try updating first"),
+                                   wc_stat->min_rev, wc_stat->max_rev);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+
 /*-----------------------------------------------------------------------*/
 
 /*** Public APIs ***/
@@ -8830,6 +9083,7 @@ merge_locked(const char *source1,
              svn_boolean_t force,
              svn_boolean_t record_only,
              svn_boolean_t dry_run,
+             svn_boolean_t allow_mixed_rev,
              const apr_array_header_t *merge_options,
              svn_client_ctx_t *ctx,
              apr_pool_t *scratch_pool)
@@ -8866,10 +9120,11 @@ merge_locked(const char *source1,
       || (revision2->kind == svn_opt_revision_unspecified))
     return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
                             _("Not all required revisions are specified"));
+
   if (svn_path_is_url(source1) != svn_path_is_url(source2))
-    return svn_error_return(svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
-                                             _("Merge sources must both be "
-                                               "either paths or URLs")));
+    return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
+                            _("Merge sources must both be "
+                              "either paths or URLs"));
 
   /* ### FIXME: This function really ought to do a history check on
      the left and right sides of the merge source, and -- if one is an
@@ -8899,11 +9154,16 @@ merge_locked(const char *source1,
 
   SVN_ERR(svn_wc_read_kind(&target_kind, ctx->wc_ctx, target_abspath, FALSE,
                            scratch_pool));
+
   if (target_kind != svn_node_dir && target_kind != svn_node_file)
-    return svn_error_return(svn_error_createf(
-                              SVN_ERR_ILLEGAL_TARGET, NULL,
-                              _("Merge target '%s' does not exist in the "
-                                "working copy"), target_abspath));
+    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+                             _("Merge target '%s' does not exist in the "
+                               "working copy"), target_abspath);
+
+  /* Do not allow merges into mixed-revision working copies. */
+  SVN_ERR(ensure_wc_is_suitable_merge_target(target_abspath, ctx,
+                                             allow_mixed_rev, TRUE, TRUE,
+                                             scratch_pool));
 
   /* Determine the working copy target's repository root URL. */
   working_rev.kind = svn_opt_revision_working;
@@ -9103,6 +9363,7 @@ struct merge_baton {
   svn_boolean_t force;
   svn_boolean_t record_only;
   svn_boolean_t dry_run;
+  svn_boolean_t allow_mixed_rev;
   const apr_array_header_t *merge_options;
   svn_client_ctx_t *ctx;
 };
@@ -9116,7 +9377,8 @@ merge_cb(void *baton, apr_pool_t *result
   SVN_ERR(merge_locked(b->source1, b->revision1, b->source2, b->revision2,
                        b->target_abspath, b->depth, b->ignore_ancestry,
                        b->force, b->record_only, b->dry_run,
-                       b->merge_options, b->ctx, scratch_pool));
+                       b->allow_mixed_rev, b->merge_options, b->ctx,
+                       scratch_pool));
 
   return SVN_NO_ERROR;
 }
@@ -9144,7 +9406,7 @@ get_target_and_lock_abspath(const char *
 }
 
 svn_error_t *
-svn_client_merge3(const char *source1,
+svn_client_merge4(const char *source1,
                   const svn_opt_revision_t *revision1,
                   const char *source2,
                   const svn_opt_revision_t *revision2,
@@ -9154,6 +9416,7 @@ svn_client_merge3(const char *source1,
                   svn_boolean_t force,
                   svn_boolean_t record_only,
                   svn_boolean_t dry_run,
+                  svn_boolean_t allow_mixed_rev,
                   const apr_array_header_t *merge_options,
                   svn_client_ctx_t *ctx,
                   apr_pool_t *pool)
@@ -9174,6 +9437,7 @@ svn_client_merge3(const char *source1,
   baton.force = force;
   baton.record_only = record_only;
   baton.dry_run = dry_run;
+  baton.allow_mixed_rev = allow_mixed_rev;
   baton.merge_options = merge_options;
   baton.ctx = ctx;
 
@@ -9188,45 +9452,6 @@ svn_client_merge3(const char *source1,
 }
 
 
-/* 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,
-                                      svn_client_ctx_t *ctx,
-                                      apr_pool_t *scratch_pool)
-{
-  svn_wc_revision_status_t *wc_stat;
-
-  /* Get a WC summary with min/max revisions set to the BASE revision. */
-  SVN_ERR(svn_wc_revision_status2(&wc_stat, ctx->wc_ctx, target_abspath, NULL,
-                                  FALSE, ctx->cancel_func, ctx->cancel_baton,
-                                  scratch_pool, scratch_pool));
-
-  if (wc_stat->switched)
-    return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
-                            _("Cannot reintegrate into a working copy "
-                              "with a switched subtree"));
-
-  if (wc_stat->modified)
-    return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
-                            _("Cannot reintegrate into a working copy "
-                              "that has local modifications"));
-
-  if (! (SVN_IS_VALID_REVNUM(wc_stat->min_rev)
-         && SVN_IS_VALID_REVNUM(wc_stat->max_rev)))
-    return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
-                            _("Cannot determine revision of working copy"));
-
-  if (wc_stat->min_rev != wc_stat->max_rev)
-    return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
-                            _("Cannot reintegrate into mixed-revision "
-                              "working copy; try updating first"));
-
-  return SVN_NO_ERROR;
-}
-
 /* Check if mergeinfo for a given path is described explicitly or via
    inheritance in a mergeinfo catalog.
 
@@ -9351,10 +9576,10 @@ log_find_operative_revs(void *baton,
       svn_mergeinfo_t log_entry_as_mergeinfo;
 
       /* Easy out: The path is not within the tree of interest. */
-      if (!svn_uri_is_ancestor(log_baton->target_abspath, path))
+      if (!svn_fspath__is_ancestor(log_baton->target_abspath, path))
         continue;
 
-      rel_path = svn_uri_skip_ancestor(log_baton->target_abspath, path);
+      rel_path = svn_fspath__skip_ancestor(log_baton->target_abspath, path);
       source_rel_path = svn_relpath_join(log_baton->source_repos_rel_path,
                                          rel_path, pool);
 
@@ -9383,8 +9608,8 @@ log_find_operative_revs(void *baton,
               && strcmp(subtree_missing_this_rev, source_rel_path))
             {
               const char *suffix =
-                svn_uri_skip_ancestor(subtree_missing_this_rev,
-                                      source_rel_path);
+                svn_relpath_skip_ancestor(subtree_missing_this_rev,
+                                          source_rel_path);
               missing_path = apr_pstrmemdup(pool, path,
                                             strlen(path) - strlen(suffix) - 1);
             }
@@ -9436,9 +9661,9 @@ log_find_operative_revs(void *baton,
    of the unmerged revisions in UNMERGED_CATALOG's mergeinfos are "phantoms",
    that is, one of the following conditions holds:
 
-     1) The revision effects no corresponding paths in SOURCE_REPOS_REL_PATH.
+     1) The revision affects no corresponding paths in SOURCE_REPOS_REL_PATH.
 
-     2) The revision effects corresponding paths in SOURCE_REPOS_REL_PATH,
+     2) The revision affects corresponding paths in SOURCE_REPOS_REL_PATH,
         but based on the mergeinfo in MERGED_CATALOG, the change was
         previously merged.
 
@@ -9489,7 +9714,7 @@ find_unsynced_ranges(const char *source_
         }
     }
 
-  /* Find any unmerged revisions which both effect the source and
+  /* Find any unmerged revisions which both affect the source and
      are not yet merged to it. */
   if (potentially_unmerged_ranges)
     {
@@ -9615,10 +9840,10 @@ find_unmerged_mergeinfo(svn_mergeinfo_ca
       svn_pool_clear(iterpool);
 
       source_path = path + strlen(target_repos_rel_path);
-      if (source_path[0] == '/') /* Remove leading '/' for svn_uri_join. */
+      if (source_path[0] == '/')  /* Remove leading '/'. */
         source_path++;
-      source_path = svn_uri_join(source_repos_rel_path, source_path,
-                                 iterpool);
+      source_path = svn_relpath_join(source_repos_rel_path, source_path,
+                                     iterpool);
       source_path_rel_to_session =
         svn_relpath_skip_ancestor(source_repos_rel_path, source_path);
 
@@ -9769,7 +9994,7 @@ find_unmerged_mergeinfo(svn_mergeinfo_ca
           svn_pool_clear(iterpool);
 
           target_path = source_path + strlen(source_repos_rel_path);
-          if (target_path[0] == '/') /* Remove leading '/' for svn_uri_join. */
+          if (target_path[0] == '/')  /* Remove leading '/'. */
             target_path++;
           err = svn_client__repos_location_segments(&segments,
                                                     target_ra_session,
@@ -10174,8 +10399,11 @@ merge_reintegrate_locked(const char *sou
                              svn_dirent_local_style(target_abspath,
                                                     scratch_pool));
 
-  SVN_ERR(ensure_wc_reflects_repository_subtree(target_abspath, ctx,
-                                                scratch_pool));
+  /* A reintegrate merge requires the merge target to reflect a subtree
+   * of the repository as found at a single revision. */
+  SVN_ERR(ensure_wc_is_suitable_merge_target(target_abspath, ctx,
+                                             FALSE, FALSE, FALSE,
+                                             scratch_pool));
   SVN_ERR(svn_wc__node_get_base_rev(&target_base_rev, ctx->wc_ctx,
                                     target_abspath, scratch_pool));
 
@@ -10391,6 +10619,7 @@ merge_peg_locked(const char *source,
                  svn_boolean_t force,
                  svn_boolean_t record_only,
                  svn_boolean_t dry_run,
+                 svn_boolean_t allow_mixed_rev,
                  const apr_array_header_t *merge_options,
                  svn_client_ctx_t *ctx,
                  apr_pool_t *scratch_pool)
@@ -10432,6 +10661,10 @@ merge_peg_locked(const char *source,
                               _("Merge target '%s' does not exist in the "
                                 "working copy"), target_abspath));
 
+  SVN_ERR(ensure_wc_is_suitable_merge_target(target_abspath, ctx,
+                                             allow_mixed_rev, TRUE, TRUE,
+                                             scratch_pool));
+
   /* Determine the working copy target's repository root URL. */
   working_rev.kind = svn_opt_revision_working;
   SVN_ERR(svn_client__get_repos_root(&wc_repos_root, target_abspath,
@@ -10494,6 +10727,7 @@ struct merge_peg_baton {
   svn_boolean_t force;
   svn_boolean_t record_only;
   svn_boolean_t dry_run;
+  svn_boolean_t allow_mixed_rev;
   const apr_array_header_t *merge_options;
   svn_client_ctx_t *ctx;
 };
@@ -10507,13 +10741,14 @@ merge_peg_cb(void *baton, apr_pool_t *re
   SVN_ERR(merge_peg_locked(b->source, b->ranges_to_merge, b->peg_revision,
                            b->target_abspath, b->depth, b->ignore_ancestry,
                            b->force, b->record_only, b->dry_run,
-                           b->merge_options, b->ctx, scratch_pool));
+                           b->allow_mixed_rev, b->merge_options, b->ctx,
+                           scratch_pool));
 
   return SVN_NO_ERROR;
 }
 
 svn_error_t *
-svn_client_merge_peg3(const char *source,
+svn_client_merge_peg4(const char *source,
                       const apr_array_header_t *ranges_to_merge,
                       const svn_opt_revision_t *peg_revision,
                       const char *target_wcpath,
@@ -10522,6 +10757,7 @@ svn_client_merge_peg3(const char *source
                       svn_boolean_t force,
                       svn_boolean_t record_only,
                       svn_boolean_t dry_run,
+                      svn_boolean_t allow_mixed_rev,
                       const apr_array_header_t *merge_options,
                       svn_client_ctx_t *ctx,
                       apr_pool_t *pool)
@@ -10545,6 +10781,7 @@ svn_client_merge_peg3(const char *source
   baton.force = force;
   baton.record_only = record_only;
   baton.dry_run = dry_run;
+  baton.allow_mixed_rev = allow_mixed_rev;
   baton.merge_options = merge_options;
   baton.ctx = ctx;
 

Modified: subversion/branches/ignore-mergeinfo/subversion/libsvn_client/mergeinfo.c
URL: http://svn.apache.org/viewvc/subversion/branches/ignore-mergeinfo/subversion/libsvn_client/mergeinfo.c?rev=1044548&r1=1044547&r2=1044548&view=diff
==============================================================================
--- subversion/branches/ignore-mergeinfo/subversion/libsvn_client/mergeinfo.c (original)
+++ subversion/branches/ignore-mergeinfo/subversion/libsvn_client/mergeinfo.c Sat Dec 11 00:15:55 2010
@@ -156,7 +156,7 @@ svn_client__adjust_mergeinfo_source_path
 
       /* Copy inherited mergeinfo into our output hash, adjusting the
          merge source as appropriate. */
-      path = svn_uri_join(merge_source, rel_path, pool);
+      path = svn_fspath__join(merge_source, rel_path, pool);
       copied_rangelist = svn_rangelist_dup(rangelist, pool);
       apr_hash_set(adjusted_mergeinfo, path, APR_HASH_KEY_STRING,
                    copied_rangelist);
@@ -442,17 +442,16 @@ svn_client__get_repos_mergeinfo(svn_ra_s
                                 svn_revnum_t rev,
                                 svn_mergeinfo_inheritance_t inherit,
                                 svn_boolean_t squelch_incapable,
+                                svn_boolean_t *validate_inherited_mergeinfo,
                                 apr_pool_t *pool)
 {
   svn_mergeinfo_catalog_t tgt_mergeinfo_cat;
 
   *target_mergeinfo = NULL;
 
-  SVN_ERR(svn_client__get_repos_mergeinfo_catalog(&tgt_mergeinfo_cat,
-                                                  ra_session,
-                                                  rel_path, rev, inherit,
-                                                  squelch_incapable, FALSE,
-                                                  pool, pool));
+  SVN_ERR(svn_client__get_repos_mergeinfo_catalog(
+    &tgt_mergeinfo_cat, ra_session, rel_path, rev, inherit,
+    squelch_incapable, FALSE, validate_inherited_mergeinfo, pool, pool));
 
   if (tgt_mergeinfo_cat && apr_hash_count(tgt_mergeinfo_cat))
     {
@@ -468,15 +467,17 @@ svn_client__get_repos_mergeinfo(svn_ra_s
 }
 
 svn_error_t *
-svn_client__get_repos_mergeinfo_catalog(svn_mergeinfo_catalog_t *mergeinfo_cat,
-                                        svn_ra_session_t *ra_session,
-                                        const char *rel_path,
-                                        svn_revnum_t rev,
-                                        svn_mergeinfo_inheritance_t inherit,
-                                        svn_boolean_t squelch_incapable,
-                                        svn_boolean_t include_descendants,
-                                        apr_pool_t *result_pool,
-                                        apr_pool_t *scratch_pool)
+svn_client__get_repos_mergeinfo_catalog(
+  svn_mergeinfo_catalog_t *mergeinfo_cat,
+  svn_ra_session_t *ra_session,
+  const char *rel_path,
+  svn_revnum_t rev,
+  svn_mergeinfo_inheritance_t inherit,
+  svn_boolean_t squelch_incapable,
+  svn_boolean_t include_descendants,
+  svn_boolean_t *validate_inherited_mergeinfo,
+  apr_pool_t *result_pool,
+  apr_pool_t *scratch_pool)
 {
   svn_error_t *err;
   svn_mergeinfo_t repos_mergeinfo;
@@ -486,8 +487,9 @@ svn_client__get_repos_mergeinfo_catalog(
   APR_ARRAY_PUSH(rel_paths, const char *) = rel_path;
 
   /* Fetch the mergeinfo. */
-  err = svn_ra_get_mergeinfo(ra_session, &repos_mergeinfo, rel_paths, rev,
-                             inherit, include_descendants, result_pool);
+  err = svn_ra_get_mergeinfo2(ra_session, &repos_mergeinfo, rel_paths, rev,
+                              inherit, validate_inherited_mergeinfo,
+                              include_descendants, result_pool);
   if (err)
     {
       if (squelch_incapable && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
@@ -606,6 +608,7 @@ svn_client__get_wc_or_repos_mergeinfo_ca
             {
               const char *session_url = NULL;
               apr_pool_t *sesspool = NULL;
+              svn_boolean_t validate_inherited_mergeinfo = FALSE;
 
               if (ra_session)
                 {
@@ -624,7 +627,8 @@ svn_client__get_wc_or_repos_mergeinfo_ca
               SVN_ERR(svn_client__get_repos_mergeinfo_catalog(
                         target_mergeinfo_catalog, ra_session,
                         "", target_rev, inherit,
-                        TRUE, FALSE, result_pool, scratch_pool));
+                        TRUE, FALSE, &validate_inherited_mergeinfo,
+                        result_pool, scratch_pool));
 
               if (*target_mergeinfo_catalog
                   && apr_hash_get(*target_mergeinfo_catalog, "",
@@ -672,7 +676,7 @@ svn_client__mergeinfo_from_segments(svn_
         continue;
 
       /* Prepend a leading slash to our path. */
-      source_path = apr_pstrcat(pool, "/", segment->path, NULL);
+      source_path = apr_pstrcat(pool, "/", segment->path, (char *)NULL);
 
       /* See if we already stored ranges for this path.  If not, make
          a new list.  */
@@ -1027,6 +1031,7 @@ get_mergeinfo(svn_mergeinfo_catalog_t *m
   if (is_url)
     {
       svn_mergeinfo_catalog_t tmp_catalog;
+      svn_boolean_t validate_inherited_mergeinfo = FALSE;
 
       SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", scratch_pool));
       SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL,
@@ -1037,14 +1042,10 @@ get_mergeinfo(svn_mergeinfo_catalog_t *m
                                               local_abspath, ra_session,
                                               &peg_rev, scratch_pool));
       SVN_ERR(svn_ra_get_repos_root2(ra_session, repos_root, scratch_pool));
-      SVN_ERR(svn_client__get_repos_mergeinfo_catalog(&tmp_catalog,
-                                                      ra_session,
-                                                      "", rev,
-                                                      svn_mergeinfo_inherited,
-                                                      FALSE,
-                                                      include_descendants,
-                                                      result_pool,
-                                                      scratch_pool));
+      SVN_ERR(svn_client__get_repos_mergeinfo_catalog(
+        &tmp_catalog, ra_session, "", rev, svn_mergeinfo_inherited,
+        FALSE, include_descendants, &validate_inherited_mergeinfo,
+        result_pool, scratch_pool));
 
       /* If we're not querying the root of the repository, the catalog
          we fetched will be keyed on paths relative to the session
@@ -1258,11 +1259,12 @@ static svn_mergeinfo_t
 find_nearest_ancestor(const apr_array_header_t *depth_first_catalog_index,
                       const char *abs_repos_path)
 {
-  int i;
   int ancestor_index = -1;
 
   if (depth_first_catalog_index)
     {
+      int i;
+
       for (i = 0; i < depth_first_catalog_index->nelts; i++)
         {
           svn_sort__item_t item = APR_ARRAY_IDX(depth_first_catalog_index, i,
@@ -1393,7 +1395,7 @@ filter_log_entry_with_rangelist(void *ba
             {
               merge_source_path =
                 APR_ARRAY_IDX(fleb->merge_source_paths, i, const char *);
-              if (svn_uri_is_ancestor(merge_source_path, path))
+              if (svn_fspath__is_ancestor(merge_source_path, path))
                 {
                   /* If MERGE_SOURCE was itself deleted, replaced, or added
                      in LOG_ENTRY->REVISION then ignore this PATH since you
@@ -1410,11 +1412,11 @@ filter_log_entry_with_rangelist(void *ba
             continue;
 
           /* Calculate the target path which PATH would affect if merged. */
-          merge_source_rel_target = svn_uri_skip_ancestor(merge_source_path,
-                                                          path);
-          target_path_affected = svn_uri_join(fleb->abs_repos_target_path,
-                                              merge_source_rel_target,
-                                              iterpool);
+          merge_source_rel_target = svn_fspath__skip_ancestor(merge_source_path,
+                                                              path);
+          target_path_affected = svn_fspath__join(fleb->abs_repos_target_path,
+                                                  merge_source_rel_target,
+                                                  iterpool);
 
           nearest_ancestor_mergeinfo =
             find_nearest_ancestor(fleb->depth_first_catalog_index,

Modified: subversion/branches/ignore-mergeinfo/subversion/libsvn_client/mergeinfo.h
URL: http://svn.apache.org/viewvc/subversion/branches/ignore-mergeinfo/subversion/libsvn_client/mergeinfo.h?rev=1044548&r1=1044547&r2=1044548&view=diff
==============================================================================
--- subversion/branches/ignore-mergeinfo/subversion/libsvn_client/mergeinfo.h (original)
+++ subversion/branches/ignore-mergeinfo/subversion/libsvn_client/mergeinfo.h Sat Dec 11 00:15:55 2010
@@ -155,7 +155,20 @@ svn_client__get_wc_mergeinfo_catalog(svn
 
    If there is no mergeinfo available for REL_PATH, or if the server
    doesn't support a mergeinfo capability and SQUELCH_INCAPABLE is
-   TRUE, set *TARGET_MERGEINFO to NULL. */
+   TRUE, set *TARGET_MERGEINFO to NULL.
+
+   If the *TARGET_MERGEINFO for REL_PATH path is inherited and
+   *VALIDATE_INHERITED_MERGEINFO is TRUE, then *TARGET_MERGEINFO
+   will only contain merge source path-revisions that actually
+   exist in repository.
+
+   If the *TARGET_MERGEINFO for REL_PATH path is inherited and
+   *VALIDATE_INHERITED_MERGEINFO is TRUE, then request that the server
+   validate the mergeinfo in *TARGET_MERGEINFO, so it contains only merge
+   source path-revisions that actually exist in repository.  If validation
+   is requested and the server supports it, then set
+   *VALIDATE_INHERITED_MERGEINFO to TRUE on return.  Set it to FALSE in
+   all other cases. */
 svn_error_t *
 svn_client__get_repos_mergeinfo(svn_ra_session_t *ra_session,
                                 svn_mergeinfo_t *target_mergeinfo,
@@ -163,6 +176,7 @@ svn_client__get_repos_mergeinfo(svn_ra_s
                                 svn_revnum_t rev,
                                 svn_mergeinfo_inheritance_t inherit,
                                 svn_boolean_t squelch_incapable,
+                                svn_boolean_t *validate_inherited_mergeinfo,
                                 apr_pool_t *pool);
 
 /* If INCLUDE_DESCENDANTS is FALSE, behave exactly like
@@ -176,15 +190,17 @@ svn_client__get_repos_mergeinfo(svn_ra_s
    paths of the subtrees.  If no mergeinfo is found, then
    *TARGET_MERGEINFO_CAT is set to NULL. */
 svn_error_t *
-svn_client__get_repos_mergeinfo_catalog(svn_mergeinfo_catalog_t *mergeinfo_cat,
-                                        svn_ra_session_t *ra_session,
-                                        const char *rel_path,
-                                        svn_revnum_t rev,
-                                        svn_mergeinfo_inheritance_t inherit,
-                                        svn_boolean_t squelch_incapable,
-                                        svn_boolean_t include_descendants,
-                                        apr_pool_t *result_pool,
-                                        apr_pool_t *scratch_pool);
+svn_client__get_repos_mergeinfo_catalog(
+  svn_mergeinfo_catalog_t *mergeinfo_cat,
+  svn_ra_session_t *ra_session,
+  const char *rel_path,
+  svn_revnum_t rev,
+  svn_mergeinfo_inheritance_t inherit,
+  svn_boolean_t squelch_incapable,
+  svn_boolean_t include_descendants,
+  svn_boolean_t *validate_inherited_mergeinfo,
+  apr_pool_t *result_pool,
+  apr_pool_t *scratch_pool);
 
 /* Retrieve the direct mergeinfo for the TARGET_WCPATH from the WC's
    mergeinfo prop, or that inherited from its nearest ancestor if the

Modified: subversion/branches/ignore-mergeinfo/subversion/libsvn_client/patch.c
URL: http://svn.apache.org/viewvc/subversion/branches/ignore-mergeinfo/subversion/libsvn_client/patch.c?rev=1044548&r1=1044547&r2=1044548&view=diff
==============================================================================
--- subversion/branches/ignore-mergeinfo/subversion/libsvn_client/patch.c (original)
+++ subversion/branches/ignore-mergeinfo/subversion/libsvn_client/patch.c Sat Dec 11 00:15:55 2010
@@ -203,6 +203,9 @@ typedef struct patch_target_t {
   /* True if the patch changed any of the properties of the target. */
   svn_boolean_t has_prop_changes;
 
+  /* True if the patch contained a svn:special property. */
+  svn_boolean_t is_special;
+
   /* All the information that is specific to the content of the target. */
   target_content_info_t *content_info;
 
@@ -235,7 +238,7 @@ strip_path(const char **result, const ch
   apr_array_header_t *stripped;
 
   components = svn_path_decompose(path, scratch_pool);
-  if (strip_count >= components->nelts)
+  if (strip_count > components->nelts)
     return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL,
                              _("Cannot strip %u components from '%s'"),
                              strip_count,
@@ -346,9 +349,7 @@ resolve_target_path(patch_target_t *targ
   target->canon_path_from_patchfile = svn_dirent_internal_style(
                                         path_from_patchfile, result_pool);
 
-  /* We allow properties to be set on the wc root dir.
-   * ### Do we need to check for empty paths here, shouldn't the parser
-   * ### guarentee that the paths returned are non-empty? */
+  /* We allow properties to be set on the wc root dir. */
   if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0')
     {
       /* An empty patch target path? What gives? Skip this. */
@@ -498,7 +499,7 @@ init_prop_target(prop_patch_target_t **p
   new_prop_target->content_info = content_info;
 
   err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name, 
-                           result_pool, scratch_pool);
+                         result_pool, scratch_pool);
   if (err)
     {
       if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
@@ -528,12 +529,40 @@ init_prop_target(prop_patch_target_t **p
   return SVN_NO_ERROR;
 }
 
+/* Return a suitable filename for the target of PATCH.
+ * Examine the ``old'' and ``new'' file names, and choose the file name
+ * with the fewest path components, the shortest basename, and the shortest
+ * total file name length (in that order). In case of a tie, return the new
+ * filename. This heuristic is also used by Larry Wall's UNIX patch (except
+ * that it prompts for a filename in case of a tie). */
+static const char *
+choose_target_filename(const svn_patch_t *patch)
+{
+  apr_size_t old;
+  apr_size_t new;
+
+  old = svn_path_component_count(patch->old_filename);
+  new = svn_path_component_count(patch->new_filename);
+
+  if (old == new)
+    {
+      old = strlen(svn_dirent_basename(patch->old_filename, NULL));
+      new = strlen(svn_dirent_basename(patch->new_filename, NULL));
+
+      if (old == new)
+        {
+          old = strlen(patch->old_filename);
+          new = strlen(patch->new_filename);
+        }
+    }
+
+  return (old < new) ? patch->old_filename : patch->new_filename;
+}
+
 /* Attempt to initialize a *PATCH_TARGET structure for a target file
  * described by PATCH. Use working copy context WC_CTX.
  * STRIP_COUNT specifies the number of leading path components
  * which should be stripped from target paths in the patch.
- * OLD_PATCH_TARGET_NAMES indicates whether the old target's name parsed
- * from the patch file should be preferred over the new target's name.
  * The patch target structure is allocated in RESULT_POOL, but if the target
  * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be
  * treated as not fully initialized, e.g. the caller should not not do any
@@ -545,9 +574,7 @@ static svn_error_t *
 init_patch_target(patch_target_t **patch_target,
                   const svn_patch_t *patch,
                   const char *base_dir,
-                  svn_wc_context_t *wc_ctx,
-                  int strip_count,
-                  svn_boolean_t old_patch_target_names,
+                  svn_wc_context_t *wc_ctx, int strip_count,
                   svn_boolean_t remove_tempfiles,
                   apr_pool_t *result_pool, apr_pool_t *scratch_pool)
 {
@@ -593,8 +620,7 @@ init_patch_target(patch_target_t **patch
   target->prop_targets = apr_hash_make(result_pool);
   target->pool = result_pool;
 
-  SVN_ERR(resolve_target_path(target, old_patch_target_names ?
-                                patch->old_filename : patch->new_filename,
+  SVN_ERR(resolve_target_path(target, choose_target_filename(patch),
                               base_dir, strip_count, prop_changes_only,
                               wc_ctx, result_pool, scratch_pool));
   if (! target->skipped)
@@ -631,13 +657,16 @@ init_patch_target(patch_target_t **patch
                                                    scratch_pool));
         }
 
-      /* ### Is it ok to set target->added here? Isn't the target supposed to
-       * ### be marked as added after it's been proven that it can be added?
-       * ### One alternative is to include a git_added flag. Or maybe we
-       * ### should have kept the patch field in patch_target_t? Then we
-       * ### could have checked for target->patch->operation == added */
+      /* ### Is it ok to set the operation of the target already here? Isn't
+       * ### the target supposed to be marked with an operation after we have
+       * ### determined that the changes will apply cleanly to the WC? Maybe
+       * ### we should have kept the patch field in patch_target_t to be
+       * ### able to distinguish between 'what the patch says we should do' 
+       * ### and 'what we can do with the given state of our WC'. */
       if (patch->operation == svn_diff_op_added)
         target->added = TRUE;
+      else if (patch->operation == svn_diff_op_deleted)
+        target->deleted = TRUE;
 
       SVN_ERR(svn_stream_open_unique(&patched_raw,
                                      &target->patched_path, NULL,
@@ -869,7 +898,7 @@ match_hunk(svn_boolean_t *matched, targe
       /* If the last line doesn't have a newline, we get EOF but still
        * have a non-empty line to compare. */
       if ((hunk_eof && hunk_line->len == 0) ||
-          (content_info->eof && strlen(target_line) == 0))
+          (content_info->eof && *target_line == 0))
         break;
 
       /* Leading/trailing fuzzy lines always match. */
@@ -910,9 +939,9 @@ match_hunk(svn_boolean_t *matched, targe
  * Return the line at which HUNK was matched in *MATCHED_LINE.
  * If the hunk did not match at all, set *MATCHED_LINE to zero.
  * If the hunk matched multiple times, and MATCH_FIRST is TRUE,
- * return the line number at which the first match occured in *MATCHED_LINE.
+ * return the line number at which the first match occurred in *MATCHED_LINE.
  * If the hunk matched multiple times, and MATCH_FIRST is FALSE,
- * return the line number at which the last match occured in *MATCHED_LINE.
+ * return the line number at which the last match occurred in *MATCHED_LINE.
  * If IGNORE_WHITESPACE is set, ignore whitespace during the matching.
  * If MATCH_MODIFIED is TRUE, match the modified hunk text,
  * rather than the original hunk text.
@@ -936,7 +965,6 @@ scan_for_match(svn_linenum_t *matched_li
          ! content_info->eof)
     {
       svn_boolean_t matched;
-      int i;
 
       svn_pool_clear(iterpool);
 
@@ -948,6 +976,7 @@ scan_for_match(svn_linenum_t *matched_li
       if (matched)
         {
           svn_boolean_t taken = FALSE;
+          int i;
 
           /* Don't allow hunks to match at overlapping locations. */
           for (i = 0; i < content_info->hunks->nelts; i++)
@@ -1235,24 +1264,6 @@ get_hunk_info(hunk_info_t **hi, patch_ta
   return SVN_NO_ERROR;
 }
 
-/* Attempt to write LEN bytes of DATA to STREAM, the underlying file
- * of which is at ABSPATH. Fail if not all bytes could be written to
- * the stream. Do temporary allocations in POOL. */
-static svn_error_t *
-try_stream_write(svn_stream_t *stream, const char *abspath,
-                 const char *data, apr_size_t len, apr_pool_t *pool)
-{
-  apr_size_t written;
-
-  written = len;
-  SVN_ERR(svn_stream_write(stream, data, &written));
-  if (written != len)
-    return svn_error_createf(SVN_ERR_IO_WRITE_ERROR, NULL,
-                             _("Error writing to '%s'"),
-                             svn_dirent_local_style(abspath, pool));
-  return SVN_NO_ERROR;
-}
-
 /* Copy lines to the patched stream until the specified LINE has been
  * reached. Indicate in *EOF whether end-of-file was encountered while
  * reading from the target.
@@ -1269,28 +1280,29 @@ copy_lines_to_target(target_content_info
          && ! content_info->eof)
     {
       const char *target_line;
+      apr_size_t len;
 
       svn_pool_clear(iterpool);
 
       SVN_ERR(read_line(content_info, &target_line, iterpool, iterpool));
       if (! content_info->eof)
         target_line = apr_pstrcat(iterpool, target_line, content_info->eol_str,
-                                  NULL);
-
-      SVN_ERR(try_stream_write(content_info->patched, patched_path,
-                               target_line, strlen(target_line), iterpool));
+                                  (char *)NULL);
+      len = strlen(target_line);
+      SVN_ERR(svn_stream_write(content_info->patched, target_line, &len));
     }
   svn_pool_destroy(iterpool);
 
   return SVN_NO_ERROR;
 }
 
-/* Write the diff text of the hunk described by HI to the
- * reject stream of CONTENT_INFO, and mark TARGET as having had rejects.
+/* Write the diff text of HUNK to the reject stream of CONTENT_INFO,
+ * and mark TARGET as having had rejects.
  * Do temporary allocations in POOL. */
 static svn_error_t *
 reject_hunk(patch_target_t *target, target_content_info_t *content_info, 
-            hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
+            const svn_diff_hunk_t *hunk, const char *prop_name,
+            apr_pool_t *pool)
 {
   const char *hunk_header;
   apr_size_t len;
@@ -1301,7 +1313,7 @@ reject_hunk(patch_target_t *target, targ
     {
       const char *prop_header;
         
-      /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Propperty'.
+      /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'.
        */
       prop_header = apr_psprintf(pool, "Property: %s\n", prop_name);
       len = strlen(prop_header);
@@ -1311,18 +1323,18 @@ reject_hunk(patch_target_t *target, targ
       /* ### What about just setting a variable to either "@@" or "##",
        * ### and merging with the else clause below? */
       hunk_header = apr_psprintf(pool, "## -%lu,%lu +%lu,%lu ##%s",
-                                 svn_diff_hunk_get_original_start(hi->hunk),
-                                 svn_diff_hunk_get_original_length(hi->hunk),
-                                 svn_diff_hunk_get_modified_start(hi->hunk),
-                                 svn_diff_hunk_get_modified_length(hi->hunk),
+                                 svn_diff_hunk_get_original_start(hunk),
+                                 svn_diff_hunk_get_original_length(hunk),
+                                 svn_diff_hunk_get_modified_start(hunk),
+                                 svn_diff_hunk_get_modified_length(hunk),
                                  APR_EOL_STR);
     }
   else
     hunk_header = apr_psprintf(pool, "@@ -%lu,%lu +%lu,%lu @@%s",
-                               svn_diff_hunk_get_original_start(hi->hunk),
-                               svn_diff_hunk_get_original_length(hi->hunk),
-                               svn_diff_hunk_get_modified_start(hi->hunk),
-                               svn_diff_hunk_get_modified_length(hi->hunk),
+                               svn_diff_hunk_get_original_start(hunk),
+                               svn_diff_hunk_get_original_length(hunk),
+                               svn_diff_hunk_get_modified_start(hunk),
+                               svn_diff_hunk_get_modified_length(hunk),
                                APR_EOL_STR);
   len = strlen(hunk_header);
   SVN_ERR(svn_stream_write(content_info->reject, hunk_header, &len));
@@ -1335,17 +1347,22 @@ reject_hunk(patch_target_t *target, targ
 
       svn_pool_clear(iterpool);
 
-      SVN_ERR(svn_diff_hunk_readline_diff_text(hi->hunk, &hunk_line, &eol_str,
+      SVN_ERR(svn_diff_hunk_readline_diff_text(hunk, &hunk_line, &eol_str,
                                                &eof, iterpool, iterpool));
       if (! eof)
         {
           if (hunk_line->len >= 1)
-            SVN_ERR(try_stream_write(content_info->reject, target->reject_path,
-                                     hunk_line->data, hunk_line->len,
-                                     iterpool));
+            {
+              len = hunk_line->len;
+              SVN_ERR(svn_stream_write(content_info->reject, hunk_line->data,
+                                       &len));
+            }
+
           if (eol_str)
-            SVN_ERR(try_stream_write(content_info->reject, target->reject_path,
-                                     eol_str, strlen(eol_str), iterpool));
+            {
+              len = strlen(eol_str);
+              SVN_ERR(svn_stream_write(content_info->reject, eol_str, &len));
+            }
         }
     }
   while (! eof);
@@ -1394,7 +1411,7 @@ apply_hunk(patch_target_t *target, targe
         {
           /* Seek failed, reject this hunk. */
           hi->rejected = TRUE;
-          SVN_ERR(reject_hunk(target, content_info, hi, prop_name, pool));
+          SVN_ERR(reject_hunk(target, content_info, hi->hunk, prop_name, pool));
           return SVN_NO_ERROR;
         }
     }
@@ -1418,11 +1435,15 @@ apply_hunk(patch_target_t *target, targe
       if (! eof && lines_read > hi->fuzz &&
           lines_read <= svn_diff_hunk_get_modified_length(hi->hunk) - hi->fuzz)
         {
+          apr_size_t len;
+
           if (hunk_line->len >= 1)
-            SVN_ERR(try_stream_write(content_info->patched, 
-                                     target->patched_path,
-                                     hunk_line->data, hunk_line->len,
-                                     iterpool));
+            {
+              len = hunk_line->len;
+              SVN_ERR(svn_stream_write(content_info->patched, hunk_line->data,
+                                       &len));
+            }
+
           if (eol_str)
             {
               /* Use the EOL as it was read from the patch file,
@@ -1430,9 +1451,8 @@ apply_hunk(patch_target_t *target, targe
               if (content_info->eol_style != svn_subst_eol_style_none)
                 eol_str = content_info->eol_str;
 
-              SVN_ERR(try_stream_write(content_info->patched,
-                                       target->patched_path, eol_str,
-                                       strlen(eol_str), iterpool));
+              len = strlen(eol_str);
+              SVN_ERR(svn_stream_write(content_info->patched, eol_str, &len));
             }
         }
     }
@@ -1592,53 +1612,12 @@ send_patch_notification(const patch_targ
   return SVN_NO_ERROR;
 }
 
-/* Close the streams of the TARGET so that their content is flushed
- * to disk. This will also close underlying streams and files. Use POOL for
- * temporary allocations. */
-static svn_error_t *
-close_target_streams(const patch_target_t *target,
-                     apr_pool_t *pool)
-{
-  apr_hash_index_t *hi;
-  target_content_info_t *prop_content_info;
-
-   /* First the streams belonging to properties .. */
-      for (hi = apr_hash_first(pool, target->prop_targets);
-           hi;
-           hi = apr_hash_next(hi))
-        {
-          prop_patch_target_t *prop_target;
-          prop_target = svn__apr_hash_index_val(hi);
-          prop_content_info = prop_target->content_info;
-
-          /* ### If the prop did not exist pre-patching we'll not have a
-           * ### stream to read from. Find a better way to store info on
-           * ### the existence of the target prop. */
-          if (prop_content_info->stream)
-            SVN_ERR(svn_stream_close(prop_content_info->stream));
-
-          SVN_ERR(svn_stream_close(prop_content_info->patched));
-        }
-
-
-   /* .. And then streams associted with the file. The reject stream is
-    * shared between all target_content_info structures. */
-  if (target->kind_on_disk == svn_node_file)
-    SVN_ERR(svn_stream_close(target->content_info->stream));
-  SVN_ERR(svn_stream_close(target->content_info->patched));
-  SVN_ERR(svn_stream_close(target->content_info->reject));
-
-  return SVN_NO_ERROR;
-}
-
 /* Apply a PATCH to a working copy at ABS_WC_PATH and put the result
  * into temporary files, to be installed in the working copy later.
  * Return information about the patch target in *PATCH_TARGET, allocated
  * in RESULT_POOL. Use WC_CTX as the working copy context.
  * STRIP_COUNT specifies the number of leading path components
  * which should be stripped from target paths in the patch.
- * OLD_PATCH_TARGET_NAMES indicates whether the old filename parsed
- * from the patch file should be preferred over the new filename.
  * REMOVE_TEMPFILES, PATCH_FUNC, and PATCH_BATON as in svn_client_patch().
  * IGNORE_WHITESPACE tells whether whitespace should be considered when
  * doing the matching.
@@ -1648,7 +1627,6 @@ static svn_error_t *
 apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch,
                 const char *abs_wc_path, svn_wc_context_t *wc_ctx,
                 int strip_count,
-                svn_boolean_t old_patch_target_names,
                 svn_boolean_t ignore_whitespace,
                 svn_boolean_t remove_tempfiles,
                 svn_client_patch_func_t patch_func,
@@ -1662,9 +1640,9 @@ apply_one_patch(patch_target_t **patch_t
   int i;
   static const int MAX_FUZZ = 2;
   apr_hash_index_t *hash_index;
+  target_content_info_t *prop_content_info;
 
-  SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx,
-                            strip_count, old_patch_target_names,
+  SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
                             remove_tempfiles, result_pool, scratch_pool));
   if (target->skipped)
     {
@@ -1727,7 +1705,7 @@ apply_one_patch(patch_target_t **patch_t
       if (hi->already_applied)
         continue;
       else if (hi->rejected)
-        SVN_ERR(reject_hunk(target, target->content_info, hi,
+        SVN_ERR(reject_hunk(target, target->content_info, hi->hunk,
                             NULL /* prop_name */, 
                             iterpool));
       else
@@ -1761,6 +1739,9 @@ apply_one_patch(patch_target_t **patch_t
       prop_name = svn__apr_hash_index_key(hash_index);
       prop_patch = svn__apr_hash_index_val(hash_index);
 
+      if (! strcmp(prop_name, SVN_PROP_SPECIAL))
+        target->is_special = TRUE;
+
       /* We'll store matched hunks in prop_content_info. */
       prop_target = apr_hash_get(target->prop_targets, prop_name, 
                                  APR_HASH_KEY_STRING);
@@ -1816,7 +1797,7 @@ apply_one_patch(patch_target_t **patch_t
           if (hi->already_applied)
             continue;
           else if (hi->rejected)
-            SVN_ERR(reject_hunk(target, prop_target->content_info, hi,
+            SVN_ERR(reject_hunk(target, prop_target->content_info, hi->hunk,
                                 prop_target->name,
                                 iterpool));
           else
@@ -1843,7 +1824,31 @@ apply_one_patch(patch_target_t **patch_t
 
   svn_pool_destroy(iterpool);
 
-  SVN_ERR(close_target_streams(target, scratch_pool));
+  /* Now close some streams that we don't need any longer to get
+   * file buffers flushed to disk. First, close the props streams... */
+  for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
+       hash_index;
+       hash_index = apr_hash_next(hash_index))
+    {
+      prop_patch_target_t *prop_target;
+      prop_target = svn__apr_hash_index_val(hash_index);
+      prop_content_info = prop_target->content_info;
+
+      /* ### If the prop did not exist pre-patching we'll not have a
+       * ### stream to read from. Find a better way to store info on
+       * ### the existence of the target prop. */
+      if (prop_content_info->stream)
+        SVN_ERR(svn_stream_close(prop_content_info->stream));
+
+      SVN_ERR(svn_stream_close(prop_content_info->patched));
+    }
+
+  /* .. and then streams associated with the file.
+   * But we're not closing the reject stream -- it still needed and
+   * will be closed later in write_out_rejected_hunks(). */
+  if (target->kind_on_disk == svn_node_file)
+    SVN_ERR(svn_stream_close(target->content_info->stream));
+  SVN_ERR(svn_stream_close(target->content_info->patched));
 
   if (! target->skipped)
     {
@@ -2030,14 +2035,13 @@ create_missing_parents(patch_target_t *t
                * to version control. Allow cancellation since we
                * have not modified the working copy yet for this
                * target. */
-              SVN_ERR(svn_wc_add4(ctx->wc_ctx, local_abspath,
-                                  svn_depth_infinity,
-                                  NULL, SVN_INVALID_REVNUM,
-                                  ctx->cancel_func,
-                                  ctx->cancel_baton,
-                                  ctx->notify_func2,
-                                  ctx->notify_baton2,
-                                  iterpool));
+
+              if (ctx->cancel_func)
+                SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+              SVN_ERR(svn_wc_add_from_disk(ctx->wc_ctx, local_abspath,
+                                           ctx->notify_func2, ctx->notify_baton2,
+                                           iterpool));
             }
         }
     }
@@ -2107,20 +2111,42 @@ install_patched_target(patch_target_t *t
 
       if (! dry_run && ! target->skipped)
         {
-          /* Copy the patched file on top of the target file. */
-          SVN_ERR(svn_io_copy_file(target->patched_path,
-                                   target->local_abspath, FALSE, pool));
+          if (target->is_special)
+            {
+              svn_stream_t *stream;
+              svn_stream_t *patched_stream;
+              apr_file_t *file;
+
+              SVN_ERR(svn_io_file_open(&file, target->patched_path,
+                                       APR_READ | APR_BINARY, APR_OS_DEFAULT,
+                                       pool));
+
+              patched_stream = svn_stream_from_aprfile2(file, FALSE /* disown */,
+                                                pool);
+              SVN_ERR(svn_subst_create_specialfile(&stream, 
+                                                   target->local_abspath,
+                                                   pool, pool));
+              SVN_ERR(svn_stream_copy3(patched_stream, stream, 
+                                       NULL, /* cancel_func */ 
+                                       NULL, /* cancel_baton */
+                                       pool));
+            }
+          else
+            {
+              /* Copy the patched file on top of the target file. */
+              SVN_ERR(svn_io_copy_file(target->patched_path,
+                                       target->local_abspath, FALSE, pool));
+            }
+
           if (target->added || target->replaced)
             {
               /* The target file didn't exist previously,
                * so add it to version control.
                * Suppress notification, we'll do that later (and also
-               * during dry-run). Also suppress cancellation because
+               * during dry-run). Don't allow cancellation because
                * we'd rather notify about what we did before aborting. */
-              SVN_ERR(svn_wc_add4(ctx->wc_ctx, target->local_abspath,
-                                  svn_depth_infinity,
-                                  NULL, SVN_INVALID_REVNUM,
-                                  NULL, NULL, NULL, NULL, pool));
+              SVN_ERR(svn_wc_add_from_disk(ctx->wc_ctx, target->local_abspath,
+                                           NULL, NULL, pool));
             }
 
           /* Restore the target's executable bit if necessary. */
@@ -2142,6 +2168,8 @@ write_out_rejected_hunks(patch_target_t 
                          svn_boolean_t dry_run,
                          apr_pool_t *pool)
 {
+  SVN_ERR(svn_stream_close(target->content_info->reject));
+
   if (! dry_run && (target->had_rejects || target->had_prop_rejects))
     {
       /* Write out rejected hunks, if any. */
@@ -2165,14 +2193,6 @@ install_patched_prop_targets(patch_targe
   apr_hash_index_t *hi;
   apr_pool_t *iterpool;
 
-  if (dry_run)
-    {
-      if (! target->has_text_changes && target->kind_on_disk == svn_node_none)
-        target->added = TRUE;
-
-      return SVN_NO_ERROR;
-    }
-
   iterpool = svn_pool_create(scratch_pool);
 
   for (hi = apr_hash_first(scratch_pool, target->prop_targets);
@@ -2184,19 +2204,22 @@ install_patched_prop_targets(patch_targe
       svn_stream_t *patched_stream;
       svn_stringbuf_t *line;
       svn_stringbuf_t *prop_content;
+      const svn_string_t *prop_val;
       const char *eol_str;
       svn_boolean_t eof;
+      svn_error_t *err;
 
       svn_pool_clear(iterpool);
 
       /* For a deleted prop we only set the value to NULL. */
       if (prop_target->operation == svn_diff_op_deleted)
         {
-          SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
-                                   prop_target->name, NULL, 
-                                   TRUE /* skip_checks */,
-                                   NULL, NULL, /* suppress notification */
-                                   iterpool));
+          if (! dry_run)
+            SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
+                                     prop_target->name, NULL, 
+                                     TRUE /* skip_checks */,
+                                     NULL, NULL, /* suppress notification */
+                                     iterpool));
           continue;
         }
 
@@ -2240,31 +2263,73 @@ install_patched_prop_targets(patch_targe
           && target->kind_on_disk == svn_node_none
           && ! target->added)
         {
-          SVN_ERR(svn_io_file_create(target->local_abspath, "", scratch_pool));
-          SVN_ERR(svn_wc_add4(ctx->wc_ctx, target->local_abspath,
-                              svn_depth_infinity,
-                              NULL, SVN_INVALID_REVNUM,
-                              ctx->cancel_func,
-                              ctx->cancel_baton,
-                              NULL, NULL, /* suppress notification */
-                              iterpool));
+          if (! dry_run)
+            {
+              SVN_ERR(svn_io_file_create(target->local_abspath, "",
+                                         scratch_pool));
+              if (ctx->cancel_func)
+                SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+              SVN_ERR(svn_wc_add_from_disk(ctx->wc_ctx, target->local_abspath,
+                                           /* suppress notification */
+                                           NULL, NULL,
+                                           iterpool));
+            }
           target->added = TRUE;
         }
 
-      /* ### How should we handle SVN_ERR_ILLEGAL_TARGET and
-       * ### SVN_ERR_BAD_MIME_TYPE?
-       *
-       * ### stsp: I'd say reject the property hunk.
-       * ###       We should verify all modified prop hunk texts using
-       * ###       svn_wc_canonicalize_svn_prop() before starting the
-       * ###       patching process, to reject them as early as possible. */
-      SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
-                               prop_target->name,
-                               svn_string_create_from_buf(prop_content, 
-                                                          iterpool),
-                               TRUE /* skip_checks */,
-                               NULL, NULL,
-                               iterpool));
+      /* Attempt to set the property, and reject all hunks if this fails. */
+      prop_val = svn_string_create_from_buf(prop_content, iterpool);
+      if (dry_run)
+        {
+          const svn_string_t *canon_propval;
+
+          err = svn_wc_canonicalize_svn_prop(&canon_propval,
+                                             prop_target->name,
+                                             prop_val, target->local_abspath,
+                                             target->db_kind,
+                                             TRUE, /* ### Skipping checks */
+                                             NULL, NULL,
+                                             iterpool);
+        }
+      else
+        {
+          err = (svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
+                                  prop_target->name, prop_val,
+                                  TRUE, /* ### Skipping checks */
+                                  NULL, NULL,
+                                  iterpool));
+        }
+
+      if (err)
+        {
+          /* ### The errors which svn_wc_canonicalize_svn_prop() will
+           * ### return aren't documented. */
+          if (err->apr_err == SVN_ERR_ILLEGAL_TARGET ||
+              err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND ||
+              err->apr_err == SVN_ERR_IO_UNKNOWN_EOL ||
+              err->apr_err == SVN_ERR_BAD_MIME_TYPE ||
+              err->apr_err == SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION)
+            {
+              int i;
+
+              svn_error_clear(err);
+
+              for (i = 0; i < prop_target->content_info->hunks->nelts; i++)
+                {
+                  hunk_info_t *hunk_info;
+
+                  hunk_info = APR_ARRAY_IDX(prop_target->content_info->hunks,
+                                            i, hunk_info_t *);
+                  hunk_info->rejected = TRUE;
+                  SVN_ERR(reject_hunk(target, prop_target->content_info,
+                                      hunk_info->hunk, prop_target->name,
+                                      iterpool));
+                }
+            }
+          else
+            return svn_error_return(err);
+        }
+
     }
 
   svn_pool_destroy(iterpool);
@@ -2551,9 +2616,6 @@ typedef struct {
   /* Number of leading components to strip from patch target paths. */
   int strip_count;
 
-  /* Whether to use the old path from the patch file instead of the new one. */
-  svn_boolean_t old_patch_target_names;
-
   /* Whether to apply the patch in reverse. */
   svn_boolean_t reverse;
 
@@ -2619,7 +2681,6 @@ apply_patches(void *baton,
 
           SVN_ERR(apply_one_patch(&target, patch, btn->abs_wc_path,
                                   btn->ctx->wc_ctx, btn->strip_count,
-                                  btn->old_patch_target_names,
                                   btn->ignore_whitespace,
                                   btn->remove_tempfiles,
                                   btn->patch_func, btn->patch_baton,
@@ -2639,7 +2700,9 @@ apply_patches(void *baton,
 
               if (! target->skipped)
                 {
-                  if (target->has_text_changes || target->added)
+                  if (target->has_text_changes 
+                      || target->added 
+                      || target->deleted)
                     SVN_ERR(install_patched_target(target, btn->abs_wc_path,
                                                    btn->ctx, btn->dry_run,
                                                    iterpool));
@@ -2675,7 +2738,6 @@ svn_client_patch(const char *patch_abspa
                  const char *local_abspath,
                  svn_boolean_t dry_run,
                  int strip_count,
-                 svn_boolean_t old_patch_target_names,
                  svn_boolean_t reverse,
                  svn_boolean_t ignore_whitespace,
                  svn_boolean_t remove_tempfiles,
@@ -2691,12 +2753,15 @@ svn_client_patch(const char *patch_abspa
     return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
                             _("strip count must be positive"));
 
+  if (svn_path_is_url(local_abspath))
+    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+                             _("'%s' is not a local path"), local_abspath);
+
   baton.patch_abspath = patch_abspath;
   baton.abs_wc_path = local_abspath;
   baton.dry_run = dry_run;
   baton.ctx = ctx;
   baton.strip_count = strip_count;
-  baton.old_patch_target_names = old_patch_target_names;
   baton.reverse = reverse;
   baton.ignore_whitespace = ignore_whitespace;
   baton.remove_tempfiles = remove_tempfiles;