Alien-SVN

 view release on metacpan or  search on metacpan

src/subversion/subversion/libsvn_client/merge.c  view on Meta::CPAN

     are absolute.  Is NULL if MERGE_B->MERGE_SOURCE->ancestral and
     MERGE_B->REINTEGRATE_MERGE are both false. */
  apr_hash_t *skipped_abspaths;

  /* The list of absolute merged paths.  Unused if MERGE_B->MERGE_SOURCE->ancestral
     and MERGE_B->REINTEGRATE_MERGE are both false. */
  apr_hash_t *merged_abspaths;

  /* A hash of (const char *) absolute WC paths mapped to the same which
     represent the roots of subtrees added by the merge. */
  apr_hash_t *added_abspaths;

  /* A list of tree conflict victim absolute paths which may be NULL. */
  apr_hash_t *tree_conflicted_abspaths;

  /* The diff3_cmd in ctx->config, if any, else null.  We could just
     extract this as needed, but since more than one caller uses it,
     we just set it up when this baton is created. */
  const char *diff3_cmd;
  const apr_array_header_t *merge_options;

  /* Array of file extension patterns to preserve as extensions in
     generated conflict files. */
  const apr_array_header_t *ext_patterns;

  /* RA sessions used throughout a merge operation.  Opened/re-parented
     as needed.

     NOTE: During the actual merge editor drive, RA_SESSION1 is used
     for the primary editing and RA_SESSION2 for fetching additional
     information -- as necessary -- from the repository.  So during
     this phase of the merge, you *must not* reparent RA_SESSION1; use
     (temporarily reparenting if you must) RA_SESSION2 instead.  */
  svn_ra_session_t *ra_session1;
  svn_ra_session_t *ra_session2;

  /* During the merge, *USE_SLEEP is set to TRUE if a sleep will be required
     afterwards to ensure timestamp integrity, or unchanged if not. */
  svn_boolean_t *use_sleep;

  /* Pool which has a lifetime limited to one iteration over a given
     merge source, i.e. it is cleared on every call to do_directory_merge()
     or do_file_merge() in do_merge(). */
  apr_pool_t *pool;


  /* State for notify_merge_begin() */
  struct notify_begin_state_t
  {
    /* Cache of which abspath was last notified. */
    const char *last_abspath;

    /* Reference to the one-and-only CHILDREN_WITH_MERGEINFO (see global
       comment) or a similar list for single-file-merges */
    const apr_array_header_t *nodes_with_mergeinfo;
  } notify_begin;

} merge_cmd_baton_t;


/* Return TRUE iff we should be taking account of mergeinfo in deciding what
   changes to merge, for the merge described by MERGE_B.  Specifically, that
   is if the merge source server is capable of merge tracking, the left-side
   merge source is an ancestor of the right-side (or vice-versa), the merge
   source is in the same repository as the merge target, and we are not
   ignoring mergeinfo. */
#define HONOR_MERGEINFO(merge_b) ((merge_b)->mergeinfo_capable      \
                                  && (merge_b)->merge_source.ancestral  \
                                  && (merge_b)->same_repos          \
                                  && (! (merge_b)->ignore_mergeinfo))


/* Return TRUE iff we should be recording mergeinfo for the merge described
   by MERGE_B.  Specifically, that is if we are honoring mergeinfo and the
   merge is not a dry run.  */
#define RECORD_MERGEINFO(merge_b) (HONOR_MERGEINFO(merge_b) \
                                   && !(merge_b)->dry_run)


/*-----------------------------------------------------------------------*/

/*** Utilities ***/

/* Return TRUE iff the session URL of RA_SESSION is equal to URL.  Useful in
 * asserting preconditions. */
static svn_boolean_t
session_url_is(svn_ra_session_t *ra_session,
               const char *url,
               apr_pool_t *scratch_pool)
{
  const char *session_url;
  svn_error_t *err
    = svn_ra_get_session_url(ra_session, &session_url, scratch_pool);

  SVN_ERR_ASSERT_NO_RETURN(! err);
  return strcmp(url, session_url) == 0;
}

/* Return a new merge_source_t structure, allocated in RESULT_POOL,
 * initialized with deep copies of LOC1 and LOC2 and ANCESTRAL. */
static merge_source_t *
merge_source_create(const svn_client__pathrev_t *loc1,
                    const svn_client__pathrev_t *loc2,
                    svn_boolean_t ancestral,
                    apr_pool_t *result_pool)
{
  merge_source_t *s
    = apr_palloc(result_pool, sizeof(*s));

  s->loc1 = svn_client__pathrev_dup(loc1, result_pool);
  s->loc2 = svn_client__pathrev_dup(loc2, result_pool);
  s->ancestral = ancestral;
  return s;
}

/* Return a deep copy of SOURCE, allocated in RESULT_POOL. */
static merge_source_t *
merge_source_dup(const merge_source_t *source,
                 apr_pool_t *result_pool)
{
  merge_source_t *s = apr_palloc(result_pool, sizeof(*s));

src/subversion/subversion/libsvn_client/merge.c  view on Meta::CPAN

      for (hi = apr_hash_first(scratch_pool, db->pending_deletes);
           hi;
           hi = apr_hash_next(hi))
        {
          const char *del_abspath = svn__apr_hash_index_key(hi);
          svn_wc_notify_t *notify;

          notify = svn_wc_create_notify(del_abspath,
                                        svn_wc_notify_update_delete,
                                        scratch_pool);
          notify->kind = svn_node_kind_from_word(
                                    svn__apr_hash_index_val(hi));

          (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2,
                                        notify, scratch_pool);
        }

      db->pending_deletes = NULL;
    }
  return SVN_NO_ERROR;
}

/* Helper function for the merge_dir_*() and merge_file_*() functions.

   Installs and notifies pre-recorded tree conflicts and skips for
   ancestors of operational merges
 */
static svn_error_t *
mark_dir_edited(merge_cmd_baton_t *merge_b,
                struct merge_dir_baton_t *db,
                const char *local_abspath,
                apr_pool_t *scratch_pool)
{
  /* ### Too much common code with mark_file_edited */
  if (db->edited)
    return SVN_NO_ERROR;

  if (db->parent_baton && !db->parent_baton->edited)
    {
      const char *dir_abspath = svn_dirent_dirname(local_abspath,
                                                   scratch_pool);

      SVN_ERR(mark_dir_edited(merge_b, db->parent_baton, dir_abspath,
                              scratch_pool));
    }

  db->edited = TRUE;

  if (! db->shadowed)
    return SVN_NO_ERROR; /* Easy out */

  if (db->parent_baton
      && db->parent_baton->delete_state
      && db->tree_conflict_reason != CONFLICT_REASON_NONE)
    {
      db->parent_baton->delete_state->found_edit = TRUE;
    }
  else if (db->tree_conflict_reason == CONFLICT_REASON_SKIP
           || db->tree_conflict_reason == CONFLICT_REASON_SKIP_WC)
    {
      /* open_directory() decided not to flag a tree conflict, but
         for clarity we produce a skip for this node that
         most likely isn't touched by the merge itself */

      if (merge_b->ctx->notify_func2)
        {
          svn_wc_notify_t *notify;

          SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE,
                                     scratch_pool));

          notify = svn_wc_create_notify(
                            local_abspath,
                            (db->tree_conflict_reason == CONFLICT_REASON_SKIP)
                                ? svn_wc_notify_skip
                                : svn_wc_notify_update_skip_obstruction,
                            scratch_pool);
          notify->kind = svn_node_dir;
          notify->content_state = notify->prop_state = db->skip_reason;

          (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2,
                                        notify,
                                        scratch_pool);
        }

      if (merge_b->merge_source.ancestral
          || merge_b->reintegrate_merge)
        {
          store_path(merge_b->skipped_abspaths, local_abspath);
        }
    }
  else if (db->tree_conflict_reason != CONFLICT_REASON_NONE)
    {
      /* open_directory() decided that a tree conflict should be raised */

      SVN_ERR(record_tree_conflict(merge_b, local_abspath, db->parent_baton,
                                   svn_node_dir, db->tree_conflict_action,
                                   db->tree_conflict_reason,
                                   NULL, TRUE,
                                   scratch_pool));
    }

  return SVN_NO_ERROR;
}

/* Helper function for the merge_file_*() functions.

   Installs and notifies pre-recorded tree conflicts and skips for
   ancestors of operational merges
 */
static svn_error_t *
mark_file_edited(merge_cmd_baton_t *merge_b,
                 struct merge_file_baton_t *fb,
                 const char *local_abspath,
                 apr_pool_t *scratch_pool)
{
  /* ### Too much common code with mark_dir_edited */
  if (fb->edited)
    return SVN_NO_ERROR;

  if (fb->parent_baton && !fb->parent_baton->edited)
    {
      const char *dir_abspath = svn_dirent_dirname(local_abspath,
                                                   scratch_pool);

      SVN_ERR(mark_dir_edited(merge_b, fb->parent_baton, dir_abspath,
                              scratch_pool));
    }

  fb->edited = TRUE;

  if (! fb->shadowed)
    return SVN_NO_ERROR; /* Easy out */

  if (fb->parent_baton
      && fb->parent_baton->delete_state
      && fb->tree_conflict_reason != CONFLICT_REASON_NONE)
    {
      fb->parent_baton->delete_state->found_edit = TRUE;
    }
  else if (fb->tree_conflict_reason == CONFLICT_REASON_SKIP
           || fb->tree_conflict_reason == CONFLICT_REASON_SKIP_WC)
    {
      /* open_directory() decided not to flag a tree conflict, but
         for clarity we produce a skip for this node that
         most likely isn't touched by the merge itself */

      if (merge_b->ctx->notify_func2)
        {
          svn_wc_notify_t *notify;

          SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE,
                                     scratch_pool));

          notify = svn_wc_create_notify(local_abspath, svn_wc_notify_skip,
                                        scratch_pool);
          notify->kind = svn_node_file;
          notify->content_state = notify->prop_state = fb->skip_reason;

          (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2,
                                        notify,
                                        scratch_pool);
        }

      if (merge_b->merge_source.ancestral
          || merge_b->reintegrate_merge)
        {
          store_path(merge_b->skipped_abspaths, local_abspath);
        }
    }
  else if (fb->tree_conflict_reason != CONFLICT_REASON_NONE)
    {
      /* open_file() decided that a tree conflict should be raised */

      SVN_ERR(record_tree_conflict(merge_b, local_abspath, fb->parent_baton,
                                   svn_node_file, fb->tree_conflict_action,
                                   fb->tree_conflict_reason,
                                   NULL, TRUE,
                                   scratch_pool));
    }

  return SVN_NO_ERROR;
}

/* An svn_diff_tree_processor_t function.

   Called before either merge_file_changed(), merge_file_added(),
   merge_file_deleted() or merge_file_closed(), unless it sets *SKIP to TRUE.

   When *SKIP is TRUE, the diff driver avoids work on getting the details
   for the closing callbacks.
 */
static svn_error_t *
merge_file_opened(void **new_file_baton,
                  svn_boolean_t *skip,
                  const char *relpath,
                  const svn_diff_source_t *left_source,
                  const svn_diff_source_t *right_source,
                  const svn_diff_source_t *copyfrom_source,
                  void *dir_baton,
                  const struct svn_diff_tree_processor_t *processor,
                  apr_pool_t *result_pool,
                  apr_pool_t *scratch_pool)
{
  merge_cmd_baton_t *merge_b = processor->baton;
  struct merge_dir_baton_t *pdb = dir_baton;
  struct merge_file_baton_t *fb;
  const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
                                              relpath, scratch_pool);

  fb = apr_pcalloc(result_pool, sizeof(*fb));
  fb->tree_conflict_reason = CONFLICT_REASON_NONE;
  fb->tree_conflict_action = svn_wc_conflict_action_edit;
  fb->skip_reason = svn_wc_notify_state_unknown;

  *new_file_baton = fb;

  if (pdb)
    {
      fb->parent_baton = pdb;
      fb->shadowed = pdb->shadowed;
      fb->skip_reason = pdb->skip_reason;
    }

  if (fb->shadowed)
    {
      /* An ancestor is tree conflicted. Nothing to do here. */
    }
  else if (left_source != NULL)
    {
      /* Node is expected to be a file, which will be changed or deleted. */
      svn_node_kind_t kind;
      svn_boolean_t is_deleted;

src/subversion/subversion/libsvn_client/merge.c  view on Meta::CPAN

  svn_revnum_t child_base_revision;

  /* Since this function should only be called when honoring mergeinfo and
   * SOURCE adheres to the requirements noted in 'MERGEINFO MERGE SOURCE
   * NORMALIZATION', SOURCE must be 'ancestral'. */
  SVN_ERR_ASSERT(source->ancestral);

  /* Determine which of the requested ranges to consider merging... */

  /* Set TARGET_RANGELIST to the portion of TARGET_MERGEINFO that refers
     to SOURCE (excluding any gap in SOURCE): first get all ranges from
     TARGET_MERGEINFO that refer to the path of SOURCE, and then prune
     any ranges that lie in the gap in SOURCE.

     ### [JAF] In fact, that may still leave some ranges that lie entirely
     outside the range of SOURCE; it seems we don't care about that.  */
  if (target_mergeinfo)
    target_rangelist = svn_hash_gets(target_mergeinfo, mergeinfo_path);
  else
    target_rangelist = NULL;
  if (implicit_src_gap && target_rangelist)
    {
      /* Remove any mergeinfo referring to the 'gap' in SOURCE, as that
         mergeinfo doesn't really refer to SOURCE at all but instead
         refers to locations that are non-existent or on a different
         line of history.  (Issue #3242.) */
      SVN_ERR(svn_rangelist_remove(&target_rangelist,
                                   implicit_src_gap, target_rangelist,
                                   FALSE, result_pool));
    }

  /* Initialize CHILD->REMAINING_RANGES and filter out revisions already
     merged (or, in the case of reverse merges, ranges not yet merged). */
  SVN_ERR(filter_merged_revisions(parent, child, mergeinfo_path,
                                  target_rangelist,
                                  source->loc1->rev, source->loc2->rev,
                                  child_inherits_implicit,
                                  ra_session, ctx, result_pool,
                                  scratch_pool));

  /* Issue #2973 -- from the continuing series of "Why, since the advent of
     merge tracking, allowing merges into mixed rev and locally modified
     working copies isn't simple and could be considered downright evil".

     If reverse merging a range to the WC path represented by CHILD, from
     that path's own history, where the path inherits no locally modified
     mergeinfo from its WC parents (i.e. there is no uncommitted merge to
     the WC), and the path's base revision is older than the range, then
     the merge will always be a no-op.  This is because we only allow reverse
     merges of ranges in the path's explicit or natural mergeinfo and a
     reverse merge from the path's future history obviously isn't going to be
     in either, hence the no-op.

     The problem is two-fold.  First, in a mixed rev WC, the change we
     want to revert might actually be to some child of the target path
     which is at a younger base revision.  Sure, we can merge directly
     to that child or update the WC or even use --ignore-ancestry and then
     successfully run the reverse merge, but that gets to the second
     problem: Those courses of action are not very obvious.  Before 1.5 if
     a user committed a change that didn't touch the commit target, then
     immediately decided to revert that change via a reverse merge it would
     just DTRT.  But with the advent of merge tracking the user gets a no-op.

     So in the name of user friendliness, return an error suggesting a helpful
     course of action.
  */
  SVN_ERR(svn_wc__node_get_base(NULL, &child_base_revision,
                                NULL, NULL, NULL, NULL,
                                ctx->wc_ctx, child->abspath,
                                TRUE /* ignore_enoent */,
                                FALSE /* show_hidden */,
                                scratch_pool, scratch_pool));
  /* If CHILD has no base revision then it hasn't been committed yet, so it
     can't have any "future" history. */
  if (SVN_IS_VALID_REVNUM(child_base_revision)
      && ((child->remaining_ranges)->nelts == 0) /* Inoperative merge */
      && (source->loc2->rev < source->loc1->rev)     /* Reverse merge */
      && (child_base_revision <= source->loc2->rev))  /* From CHILD's future */
    {
      /* Hmmm, an inoperative reverse merge from the "future".  If it is
         from our own future return a helpful error. */
      svn_error_t *err;
      svn_client__pathrev_t *start_loc;

      err = svn_client__repos_location(&start_loc,
                                       ra_session,
                                       source->loc1,
                                       child_base_revision,
                                       ctx, scratch_pool, scratch_pool);
      if (err)
        {
          if (err->apr_err == SVN_ERR_FS_NOT_FOUND
              || err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES)
            svn_error_clear(err);
          else
            return svn_error_trace(err);
        }
      else
        {
          const char *url;

          SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, child->abspath,
                                       scratch_pool, scratch_pool));
          if (strcmp(start_loc->url, url) == 0)
            return svn_error_create(SVN_ERR_CLIENT_MERGE_UPDATE_REQUIRED, NULL,
                                    _("Cannot reverse-merge a range from a "
                                      "path's own future history; try "
                                      "updating first"));
        }
    }

  return SVN_NO_ERROR;
}

/* Helper for populate_remaining_ranges().

   SOURCE is cascaded from the arguments of the same name in
   populate_remaining_ranges().

   Note: The following comments assume a forward merge, i.e.
   SOURCE->loc1->rev < SOURCE->loc2->rev.  If this is a reverse merge then

src/subversion/subversion/libsvn_client/merge.c  view on Meta::CPAN

 * Any of these output pointers may be NULL if not wanted.
 *
 * See svn_client_find_reintegrate_merge() for other details.
 */
static svn_error_t *
find_reintegrate_merge(merge_source_t **source_p,
                       svn_client__pathrev_t **yc_ancestor_p,
                       svn_ra_session_t *source_ra_session,
                       const svn_client__pathrev_t *source_loc,
                       svn_ra_session_t *target_ra_session,
                       const merge_target_t *target,
                       svn_client_ctx_t *ctx,
                       apr_pool_t *result_pool,
                       apr_pool_t *scratch_pool)
{
  svn_client__pathrev_t *yc_ancestor;
  svn_client__pathrev_t *loc1;
  merge_source_t source;
  svn_mergeinfo_catalog_t unmerged_to_source_mergeinfo_catalog;
  svn_mergeinfo_catalog_t merged_to_source_mergeinfo_catalog;
  svn_error_t *err;
  apr_hash_t *subtrees_with_mergeinfo;

  assert(session_url_is(source_ra_session, source_loc->url, scratch_pool));
  assert(session_url_is(target_ra_session, target->loc.url, scratch_pool));

  /* As the WC tree is "pure", use its last-updated-to revision as
     the default revision for the left side of our merge, since that's
     what the repository sub-tree is required to be up to date with
     (with regard to the WC). */
  /* ### Bogus/obsolete comment? */

  /* Can't reintegrate to or from the root of the repository. */
  if (strcmp(source_loc->url, source_loc->repos_root_url) == 0
      || strcmp(target->loc.url, target->loc.repos_root_url) == 0)
    return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
                             _("Neither the reintegrate source nor target "
                               "can be the root of the repository"));

  /* Find all the subtrees in TARGET_WCPATH that have explicit mergeinfo. */
  err = get_wc_explicit_mergeinfo_catalog(&subtrees_with_mergeinfo,
                                          target->abspath, svn_depth_infinity,
                                          ctx, scratch_pool, scratch_pool);
  /* Issue #3896: If invalid mergeinfo in the reintegrate target
     prevents us from proceeding, then raise the best error possible. */
  if (err && err->apr_err == SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING)
    err = svn_error_quick_wrap(err, _("Reintegrate merge not possible"));
  SVN_ERR(err);

  SVN_ERR(calculate_left_hand_side(&loc1,
                                   &merged_to_source_mergeinfo_catalog,
                                   &unmerged_to_source_mergeinfo_catalog,
                                   target,
                                   subtrees_with_mergeinfo,
                                   source_loc,
                                   source_ra_session,
                                   target_ra_session,
                                   ctx,
                                   scratch_pool, scratch_pool));

  /* Did calculate_left_hand_side() decide that there was no merge to
     be performed here?  */
  if (! loc1)
    {
      if (source_p)
        *source_p = NULL;
      if (yc_ancestor_p)
        *yc_ancestor_p = NULL;
      return SVN_NO_ERROR;
    }

  source.loc1 = loc1;
  source.loc2 = source_loc;

  /* If the target was moved after the source was branched from it,
     it is possible that the left URL differs from the target's current
     URL.  If so, then adjust TARGET_RA_SESSION to point to the old URL. */
  if (strcmp(source.loc1->url, target->loc.url))
    SVN_ERR(svn_ra_reparent(target_ra_session, source.loc1->url, scratch_pool));

  SVN_ERR(svn_client__get_youngest_common_ancestor(
            &yc_ancestor, source.loc2, source.loc1, target_ra_session,
            ctx, scratch_pool, scratch_pool));

  if (! yc_ancestor)
    return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
                             _("'%s@%ld' must be ancestrally related to "
                               "'%s@%ld'"),
                             source.loc1->url, source.loc1->rev,
                             source.loc2->url, source.loc2->rev);

  /* The source side of a reintegrate merge is not 'ancestral', except in
   * the degenerate case where source == YCA. */
  source.ancestral = (loc1->rev == yc_ancestor->rev);

  if (source.loc1->rev > yc_ancestor->rev)
    {
      /* Have we actually merged anything to the source from the
         target?  If so, make sure we've merged a contiguous
         prefix. */
      svn_mergeinfo_catalog_t final_unmerged_catalog = apr_hash_make(scratch_pool);

      SVN_ERR(find_unsynced_ranges(source_loc, &target->loc,
                                   unmerged_to_source_mergeinfo_catalog,
                                   merged_to_source_mergeinfo_catalog,
                                   final_unmerged_catalog,
                                   target_ra_session, scratch_pool,
                                   scratch_pool));

      if (apr_hash_count(final_unmerged_catalog))
        {
          svn_string_t *source_mergeinfo_cat_string;

          SVN_ERR(svn_mergeinfo__catalog_to_formatted_string(
            &source_mergeinfo_cat_string,
            final_unmerged_catalog,
            "  ", "    Missing ranges: ", scratch_pool));
          return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE,
                                   NULL,
                                   _("Reintegrate can only be used if "
                                     "revisions %ld through %ld were "

src/subversion/subversion/libsvn_client/merge.c  view on Meta::CPAN


  return SVN_NO_ERROR;
}

/** Find out what kind of automatic merge would be needed, when the target
 * is only known as a repository location rather than a WC.
 *
 * Like find_automatic_merge() except that the target is
 * specified by @a target_path_or_url at @a target_revision, which must
 * refer to a repository location, instead of by a WC path argument.
 */
static svn_error_t *
find_automatic_merge_no_wc(automatic_merge_t **merge_p,
                           const char *source_path_or_url,
                           const svn_opt_revision_t *source_revision,
                           const char *target_path_or_url,
                           const svn_opt_revision_t *target_revision,
                           svn_client_ctx_t *ctx,
                           apr_pool_t *result_pool,
                           apr_pool_t *scratch_pool)
{
  source_and_target_t *s_t = apr_palloc(scratch_pool, sizeof(*s_t));
  svn_client__pathrev_t *target_loc;
  automatic_merge_t *merge = apr_palloc(result_pool, sizeof(*merge));

  /* Source */
  SVN_ERR(svn_client__ra_session_from_path2(
            &s_t->source_ra_session, &s_t->source,
            source_path_or_url, NULL, source_revision, source_revision,
            ctx, result_pool));

  /* Target */
  SVN_ERR(svn_client__ra_session_from_path2(
            &s_t->target_ra_session, &target_loc,
            target_path_or_url, NULL, target_revision, target_revision,
            ctx, result_pool));
  s_t->target = apr_palloc(scratch_pool, sizeof(*s_t->target));
  s_t->target->abspath = NULL;  /* indicate the target is not a WC */
  s_t->target->loc = *target_loc;

  SVN_ERR(find_automatic_merge(&merge->base, &merge->is_reintegrate_like, s_t,
                               ctx, result_pool, scratch_pool));

  merge->right = s_t->source;
  merge->target = &s_t->target->loc;
  merge->yca = s_t->yca;
  *merge_p = merge;

  return SVN_NO_ERROR;
}

/* Find the information needed to merge all unmerged changes from a source
 * branch into a target branch.
 *
 * Set @a *merge_p to the information needed to merge all unmerged changes
 * (up to @a source_revision) from the source branch @a source_path_or_url
 * at @a source_revision into the target WC at @a target_abspath.
 *
 * The flags @a allow_mixed_rev, @a allow_local_mods and
 * @a allow_switched_subtrees enable merging into a WC that is in any or all
 * of the states described by their names, but only if this function decides
 * that the merge will be in the same direction as the last automatic merge.
 * If, on the other hand, the last automatic merge was in the opposite
 * direction, then such states of the WC are not allowed regardless
 * of these flags.  This function merely records these flags in the
 * @a *merge_p structure; do_automatic_merge_locked() checks the WC
 * state for compliance.
 *
 * Allocate the @a *merge_p structure in @a result_pool.
 */
static svn_error_t *
client_find_automatic_merge(automatic_merge_t **merge_p,
                            const char *source_path_or_url,
                            const svn_opt_revision_t *source_revision,
                            const char *target_abspath,
                            svn_boolean_t allow_mixed_rev,
                            svn_boolean_t allow_local_mods,
                            svn_boolean_t allow_switched_subtrees,
                            svn_client_ctx_t *ctx,
                            apr_pool_t *result_pool,
                            apr_pool_t *scratch_pool)
{
  source_and_target_t *s_t = apr_palloc(result_pool, sizeof(*s_t));
  automatic_merge_t *merge = apr_palloc(result_pool, sizeof(*merge));

  /* "Open" the target WC.  Check the target WC for mixed-rev, local mods and
   * switched subtrees yet to faster exit and notify user before contacting
   * with server.  After we find out what kind of merge is required, then if a
   * reintegrate-like merge is required we'll do the stricter checks, in
   * do_automatic_merge_locked(). */
  SVN_ERR(open_target_wc(&s_t->target, target_abspath,
                         allow_mixed_rev,
                         allow_local_mods,
                         allow_switched_subtrees,
                         ctx, result_pool, scratch_pool));

  /* Open RA sessions to the source and target trees. */
  SVN_ERR(svn_client_open_ra_session2(&s_t->target_ra_session,
                                      s_t->target->loc.url,
                                      s_t->target->abspath,
                                      ctx, result_pool, scratch_pool));
  /* ### check for null URL (i.e. added path) here, like in reintegrate? */
  SVN_ERR(svn_client__ra_session_from_path2(
            &s_t->source_ra_session, &s_t->source,
            source_path_or_url, NULL, source_revision, source_revision,
            ctx, result_pool));

  /* Check source is in same repos as target. */
  SVN_ERR(check_same_repos(s_t->source, source_path_or_url,
                           &s_t->target->loc, target_abspath,
                           TRUE /* strict_urls */, scratch_pool));

  SVN_ERR(find_automatic_merge(&merge->base, &merge->is_reintegrate_like, s_t,
                               ctx, result_pool, scratch_pool));
  merge->yca = s_t->yca;
  merge->right = s_t->source;
  merge->allow_mixed_rev = allow_mixed_rev;
  merge->allow_local_mods = allow_local_mods;
  merge->allow_switched_subtrees = allow_switched_subtrees;

  *merge_p = merge;



( run in 0.512 second using v1.01-cache-2.11-cpan-d7f47b0818f )