Alien-SVN

 view release on metacpan or  search on metacpan

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

                                scratch_pool);

  if (SVN_IS_VALID_REVNUM(n_range.start))
    {
      /* If the merge source has a gap, then don't mention
         those gap revisions in the notification. */
      remove_source_gap(&n_range, merge_b->implicit_src_gap);
      notify->merge_range = &n_range;
    }
  else
    {
      notify->merge_range = NULL;
    }

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

  return SVN_NO_ERROR;
}

/* Set *OUT_RANGELIST to the intersection of IN_RANGELIST with the simple
 * (inheritable) revision range REV1:REV2, according to CONSIDER_INHERITANCE.
 * If REV1 is equal to REV2, the result is an empty rangelist, otherwise
 * REV1 must be less than REV2.
 *
 * Note: If CONSIDER_INHERITANCE is FALSE, the effect is to treat any non-
 * inheritable input ranges as if they were inheritable.  If it is TRUE, the
 * effect is to discard any non-inheritable input ranges.  Therefore the
 * ranges in *OUT_RANGELIST will always be inheritable. */
static svn_error_t *
rangelist_intersect_range(svn_rangelist_t **out_rangelist,
                          const svn_rangelist_t *in_rangelist,
                          svn_revnum_t rev1,
                          svn_revnum_t rev2,
                          svn_boolean_t consider_inheritance,
                          apr_pool_t *result_pool,
                          apr_pool_t *scratch_pool)
{
  SVN_ERR_ASSERT(rev1 <= rev2);

  if (rev1 < rev2)
    {
      svn_rangelist_t *simple_rangelist =
        svn_rangelist__initialize(rev1, rev2, TRUE, scratch_pool);

      SVN_ERR(svn_rangelist_intersect(out_rangelist,
                                      simple_rangelist, in_rangelist,
                                      consider_inheritance, result_pool));
    }
  else
    {
      *out_rangelist = apr_array_make(result_pool, 0,
                                      sizeof(svn_merge_range_t *));
    }
  return SVN_NO_ERROR;
}

/* Helper for fix_deleted_subtree_ranges().  Like fix_deleted_subtree_ranges()
   this function should only be called when honoring mergeinfo.

   CHILD, PARENT, REVISION1, REVISION2, and RA_SESSION are all cascaded from
   fix_deleted_subtree_ranges() -- see that function for more information on
   each.

   If PARENT is not the merge target then PARENT must have already have been
   processed by this function as a child.  Specifically, this means that
   PARENT->REMAINING_RANGES must already be populated -- it can be an empty
   rangelist but cannot be NULL.

   PRIMARY_URL is the merge source url of CHILD at the younger of REVISION1
   and REVISION2.

   Since this function is only invoked for subtrees of the merge target, the
   guarantees afforded by normalize_merge_sources() don't apply - see the
   'MERGEINFO MERGE SOURCE NORMALIZATION' comment at the top of this file.
   Therefore it is possible that PRIMARY_URL@REVISION1 and
   PRIMARY_URL@REVISION2 don't describe the endpoints of an unbroken line of
   history.  The purpose of this helper is to identify these cases of broken
   history and adjust CHILD->REMAINING_RANGES in such a way we don't later try
   to describe nonexistent path/revisions to the merge report editor -- see
   drive_merge_report_editor().

   If PRIMARY_URL@REVISION1 and PRIMARY_URL@REVISION2 describe an unbroken
   line of history then do nothing and leave CHILD->REMAINING_RANGES as-is.

   If neither PRIMARY_URL@REVISION1 nor PRIMARY_URL@REVISION2 exist then
   there is nothing to merge to CHILD->ABSPATH so set CHILD->REMAINING_RANGES
   equal to PARENT->REMAINING_RANGES.  This will cause the subtree to
   effectively ignore CHILD -- see 'Note: If the first svn_merge_range_t...'
   in drive_merge_report_editor()'s doc string.

   If PRIMARY_URL@REVISION1 *xor* PRIMARY_URL@REVISION2 exist then we take the
   subset of REVISION1:REVISION2 in CHILD->REMAINING_RANGES at which
   PRIMARY_URL doesn't exist and set that subset equal to
   PARENT->REMAINING_RANGES' intersection with that non-existent range.  Why?
   Because this causes CHILD->REMAINING_RANGES to be identical to
   PARENT->REMAINING_RANGES for revisions between REVISION1 and REVISION2 at
   which PRIMARY_URL doesn't exist.  As mentioned above this means that
   drive_merge_report_editor() won't attempt to describe these non-existent
   subtree path/ranges to the reporter (which would break the merge).

   If the preceding paragraph wasn't terribly clear then what follows spells
   out this function's behavior a bit more explicitly:

   For forward merges (REVISION1 < REVISION2)

     If PRIMARY_URL@REVISION1 exists but PRIMARY_URL@REVISION2 doesn't, then
     find the revision 'N' in which PRIMARY_URL@REVISION1 was deleted.  Leave
     the subset of CHILD->REMAINING_RANGES that intersects with
     REVISION1:(N - 1) as-is and set the subset of CHILD->REMAINING_RANGES
     that intersects with (N - 1):REVISION2 equal to PARENT->REMAINING_RANGES'
     intersection with (N - 1):REVISION2.

     If PRIMARY_URL@REVISION1 doesn't exist but PRIMARY_URL@REVISION2 does,
     then find the revision 'M' in which PRIMARY_URL@REVISION2 came into
     existence.  Leave the subset of CHILD->REMAINING_RANGES that intersects with
     (M - 1):REVISION2 as-is and set the subset of CHILD->REMAINING_RANGES
     that intersects with REVISION1:(M - 1) equal to PARENT->REMAINING_RANGES'
     intersection with REVISION1:(M - 1).

   For reverse merges (REVISION1 > REVISION2)

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

         svn_client__repos_location_segments() succeeded.  If there is only
         one segment that starts at oldest_rev then we know that
         PRIMARY_URL@oldest_rev:PRIMARY_URL@peg_rev describes an unbroken
         line of history, so there is nothing more to adjust in
         CHILD->REMAINING_RANGES. */
      if (segment->range_start == older_rev)
        {
          return SVN_NO_ERROR;
        }

      /* If this is a reverse merge reorder CHILD->REMAINING_RANGES and
         PARENT->REMAINING_RANGES so both will work with the
         svn_rangelist_* APIs below. */
      if (is_rollback)
        {
          SVN_ERR(svn_rangelist_reverse(child->remaining_ranges,
                                        scratch_pool));
          SVN_ERR(svn_rangelist_reverse(parent->remaining_ranges,
                                        scratch_pool));
        }

      /* Intersect CHILD->REMAINING_RANGES with the range where PRIMARY_URL
         exists.  Since segment doesn't span older_rev:peg_rev we know
         PRIMARY_URL@peg_rev didn't come into existence until
         segment->range_start + 1. */
      SVN_ERR(rangelist_intersect_range(&child->remaining_ranges,
                                        child->remaining_ranges,
                                        segment->range_start, peg_rev,
                                        FALSE, scratch_pool, scratch_pool));

      /* Merge into CHILD->REMAINING_RANGES the intersection of
         PARENT->REMAINING_RANGES with the range before PRIMARY_URL@peg_rev
         came into existence. */
      SVN_ERR(rangelist_intersect_range(&non_existent_rangelist,
                                        parent->remaining_ranges,
                                        older_rev, segment->range_start,
                                        FALSE, scratch_pool, scratch_pool));
      SVN_ERR(svn_rangelist_merge2(child->remaining_ranges,
                                   non_existent_rangelist, scratch_pool,
                                   scratch_pool));

      /* Return CHILD->REMAINING_RANGES and PARENT->REMAINING_RANGES
         to reverse order if necessary. */
      if (is_rollback)
        {
          SVN_ERR(svn_rangelist_reverse(child->remaining_ranges,
                                        scratch_pool));
          SVN_ERR(svn_rangelist_reverse(parent->remaining_ranges,
                                        scratch_pool));
        }
    }

  /* Make a lasting copy of CHILD->REMAINING_RANGES using POOL. */
  child->remaining_ranges = svn_rangelist_dup(child->remaining_ranges,
                                              result_pool);
  return SVN_NO_ERROR;
}

/* Helper for do_directory_merge().

   SOURCE is cascaded from the argument of the same name in
   do_directory_merge().  TARGET is the merge target.  RA_SESSION is the
   session for the younger of SOURCE->loc1 and SOURCE->loc2.

   Adjust the subtrees in CHILDREN_WITH_MERGEINFO so that we don't
   later try to describe invalid paths in drive_merge_report_editor().
   This function is just a thin wrapper around
   adjust_deleted_subtree_ranges(), which see for further details.

   SCRATCH_POOL is used for all temporary allocations.  Changes to
   CHILDREN_WITH_MERGEINFO are allocated in RESULT_POOL.
*/
static svn_error_t *
fix_deleted_subtree_ranges(const merge_source_t *source,
                           const merge_target_t *target,
                           svn_ra_session_t *ra_session,
                           apr_array_header_t *children_with_mergeinfo,
                           svn_client_ctx_t *ctx,
                           apr_pool_t *result_pool,
                           apr_pool_t *scratch_pool)
{
  int i;
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
  svn_boolean_t is_rollback = source->loc2->rev < source->loc1->rev;

  assert(session_url_is(ra_session,
                        (is_rollback ? source->loc1 : source->loc2)->url,
                        scratch_pool));

  /* CHILDREN_WITH_MERGEINFO is sorted in depth-first order, so
     start at index 1 to examine only subtrees. */
  for (i = 1; i < children_with_mergeinfo->nelts; i++)
    {
      svn_client__merge_path_t *child =
        APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
      svn_client__merge_path_t *parent;
      svn_rangelist_t *deleted_rangelist, *added_rangelist;

      SVN_ERR_ASSERT(child);
      if (child->absent)
        continue;

      svn_pool_clear(iterpool);

      /* Find CHILD's parent. */
      parent = find_nearest_ancestor(children_with_mergeinfo,
                                     FALSE, child->abspath);

      /* Since CHILD is a subtree then its parent must be in
         CHILDREN_WITH_MERGEINFO, see the global comment
         'THE CHILDREN_WITH_MERGEINFO ARRAY'. */
      SVN_ERR_ASSERT(parent);

      /* If this is a reverse merge reorder CHILD->REMAINING_RANGES
         so it will work with the svn_rangelist_diff API. */
      if (is_rollback)
        {
          SVN_ERR(svn_rangelist_reverse(child->remaining_ranges, iterpool));
          SVN_ERR(svn_rangelist_reverse(parent->remaining_ranges, iterpool));
        }

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

                                                    inherited,
                                                    NULL /* from_repos */,
                                                    FALSE,
                                                    inherit, ra_session,
                                                    target_abspath,
                                                    ctx, result_pool));
    }

  if (implicit_mergeinfo)
    {
      svn_client__pathrev_t *target;

      /* Assert that we have sane input. */
      SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start) && SVN_IS_VALID_REVNUM(end)
                     && (start > end));

      /* Retrieve the origin (original_*) of the node, or just the
         url if the node was not copied. */
      SVN_ERR(svn_client__wc_node_get_origin(&target, target_abspath, ctx,
                                             scratch_pool, scratch_pool));

      if (! target)
        {
          /* We've been asked to operate on a locally added target, so its
           * implicit mergeinfo is empty. */
          *implicit_mergeinfo = apr_hash_make(result_pool);
        }
      else if (target->rev <= end)
        {
          /* 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);
        }
      else
        {
          /* Fetch so-called "implicit mergeinfo" (that is, natural
             history). */

          /* 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. */
          SVN_ERR(svn_client__get_history_as_mergeinfo(implicit_mergeinfo,
                                                       NULL,
                                                       target, start, end,
                                                       ra_session, ctx,
                                                       result_pool));
        }
    } /*if (implicit_mergeinfo) */

  return SVN_NO_ERROR;
}

/* Helper for ensure_implicit_mergeinfo().

   PARENT, CHILD, REVISION1, REVISION2 and CTX
   are all cascaded from the arguments of the same names in
   ensure_implicit_mergeinfo().  PARENT and CHILD must both exist, i.e.
   this function should never be called where CHILD is the merge target.

   If PARENT->IMPLICIT_MERGEINFO is NULL, obtain it from the server.

   Set CHILD->IMPLICIT_MERGEINFO to the mergeinfo inherited from
   PARENT->IMPLICIT_MERGEINFO.  CHILD->IMPLICIT_MERGEINFO is allocated
   in RESULT_POOL.

   RA_SESSION is an RA session open to the repository that contains CHILD.
   It may be temporarily reparented by this function.
   */
static svn_error_t *
inherit_implicit_mergeinfo_from_parent(svn_client__merge_path_t *parent,
                                       svn_client__merge_path_t *child,
                                       svn_revnum_t revision1,
                                       svn_revnum_t revision2,
                                       svn_ra_session_t *ra_session,
                                       svn_client_ctx_t *ctx,
                                       apr_pool_t *result_pool,
                                       apr_pool_t *scratch_pool)
{
  const char *path_diff;

  /* This only works on subtrees! */
  SVN_ERR_ASSERT(parent);
  SVN_ERR_ASSERT(child);

  /* While PARENT must exist, it is possible we've deferred
     getting its implicit mergeinfo.  If so get it now. */
  if (!parent->implicit_mergeinfo)
    SVN_ERR(get_full_mergeinfo(NULL, &(parent->implicit_mergeinfo),
                               NULL, svn_mergeinfo_inherited,
                               ra_session, child->abspath,
                               MAX(revision1, revision2),
                               MIN(revision1, revision2),
                               ctx, result_pool, scratch_pool));

  /* Let CHILD inherit PARENT's implicit mergeinfo. */

  path_diff = svn_dirent_is_child(parent->abspath, child->abspath,
                                  scratch_pool);
  /* PARENT->PATH better be an ancestor of CHILD->ABSPATH! */
  SVN_ERR_ASSERT(path_diff);

  SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
            &child->implicit_mergeinfo, parent->implicit_mergeinfo,
            path_diff, result_pool, scratch_pool));
  child->implicit_mergeinfo = svn_mergeinfo_dup(child->implicit_mergeinfo,
                                                result_pool);
  return SVN_NO_ERROR;
}

/* Helper of filter_merged_revisions().

   If we have deferred obtaining CHILD->IMPLICIT_MERGEINFO, then get
   it now, allocating it in RESULT_POOL.  If CHILD_INHERITS_PARENT is true
   then set CHILD->IMPLICIT_MERGEINFO to the mergeinfo inherited from
   PARENT->IMPLICIT_MERGEINFO, otherwise contact the repository.  Use
   SCRATCH_POOL for all temporary allocations.

   RA_SESSION is an RA session open to the repository that contains CHILD.
   It may be temporarily reparented by this function.

   PARENT, CHILD, REVISION1, REVISION2 and
   CTX are all cascaded from the arguments of the same name in
   filter_merged_revisions() and the same conditions for that function
   hold here. */
static svn_error_t *
ensure_implicit_mergeinfo(svn_client__merge_path_t *parent,
                          svn_client__merge_path_t *child,
                          svn_boolean_t child_inherits_parent,
                          svn_revnum_t revision1,
                          svn_revnum_t revision2,
                          svn_ra_session_t *ra_session,
                          svn_client_ctx_t *ctx,
                          apr_pool_t *result_pool,
                          apr_pool_t *scratch_pool)
{
  /* If we haven't already found CHILD->IMPLICIT_MERGEINFO then
     contact the server to get it. */

  if (child->implicit_mergeinfo)
    return SVN_NO_ERROR;

  if (child_inherits_parent)
    SVN_ERR(inherit_implicit_mergeinfo_from_parent(parent,
                                                   child,
                                                   revision1,
                                                   revision2,
                                                   ra_session,
                                                   ctx,
                                                   result_pool,
                                                   scratch_pool));
  else
    SVN_ERR(get_full_mergeinfo(NULL,
                               &(child->implicit_mergeinfo),
                               NULL, svn_mergeinfo_inherited,
                               ra_session, child->abspath,
                               MAX(revision1, revision2),
                               MIN(revision1, revision2),
                               ctx, result_pool, scratch_pool));

  return SVN_NO_ERROR;
}

/* Helper for calculate_remaining_ranges().

   Initialize CHILD->REMAINING_RANGES to a rangelist representing the
   requested merge of REVISION1:REVISION2 from MERGEINFO_PATH to
   CHILD->ABSPATH.

   For forward merges remove any ranges from CHILD->REMAINING_RANGES that
   have already been merged to CHILD->ABSPATH per TARGET_MERGEINFO or
   CHILD->IMPLICIT_MERGEINFO.  For reverse merges remove any ranges from
   CHILD->REMAINING_RANGES that have not already been merged to CHILD->ABSPATH
   per TARGET_MERGEINFO or CHILD->IMPLICIT_MERGEINFO.  If we have deferred
   obtaining CHILD->IMPLICIT_MERGEINFO and it is necessary to use it for
   these calculations, then get it from the server, allocating it in
   RESULT_POOL.

   CHILD represents a working copy path which is the merge target or one of
   the target's subtrees.  If not NULL, PARENT is CHILD's nearest path-wise
   ancestor - see 'THE CHILDREN_WITH_MERGEINFO ARRAY'.

   If the function needs to consider CHILD->IMPLICIT_MERGEINFO and

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

   ### branch to the trunk while automatically ignoring the revisions
   ### common to both.  That's bad.
   ###
   ### If we allow only forward-merges not found in either recorded
   ### mergeinfo or implicit mergeinfo (natural history), then the
   ### previous scenario works great, but we can't reverse-merge a
   ### previous change made to our line of history and then remake it
   ### (because the reverse-merge will leave no mergeinfo trace, and
   ### the remake-it attempt will still find the original change in
   ### natural mergeinfo.  But you know, that we happen to use 'merge'
   ### for revision undoing is somewhat unnatural anyway, so I'm
   ### finding myself having little interest in caring too much about
   ### this.  That said, if we had a way of storing reverse merge
   ### ranges, we'd be in good shape either way.
*/
#ifdef SVN_MERGE__ALLOW_ALL_FORWARD_MERGES_FROM_SELF
        {
          /* ### Don't consider implicit mergeinfo. */
          child->remaining_ranges = svn_rangelist_dup(explicit_rangelist,
                                                      pool);
        }
#else
        {
          /* Based on CHILD's TARGET_MERGEINFO there are ranges to merge.
             Check CHILD's implicit mergeinfo to see if these remaining
             ranges are represented there. */
          SVN_ERR(ensure_implicit_mergeinfo(parent,
                                            child,
                                            child_inherits_implicit,
                                            revision1,
                                            revision2,
                                            ra_session,
                                            ctx,
                                            result_pool,
                                            scratch_pool));

          target_implicit_rangelist = svn_hash_gets(child->implicit_mergeinfo,
                                                    mergeinfo_path);
          if (target_implicit_rangelist)
            SVN_ERR(svn_rangelist_remove(&(child->remaining_ranges),
                                         target_implicit_rangelist,
                                         explicit_rangelist,
                                         FALSE, result_pool));
          else
            child->remaining_ranges = svn_rangelist_dup(explicit_rangelist,
                                                        result_pool);
        }
#endif
    }

  return SVN_NO_ERROR;
}

/* Helper for do_file_merge and do_directory_merge (by way of
   populate_remaining_ranges() for the latter).

   Determine what portions of SOURCE have already
   been merged to CHILD->ABSPATH and populate CHILD->REMAINING_RANGES with
   the ranges that still need merging.

   SOURCE and CTX are all cascaded from the caller's arguments of the same
   names.  Note that this means SOURCE adheres to the requirements noted in
   `MERGEINFO MERGE SOURCE NORMALIZATION'.

   CHILD represents a working copy path which is the merge target or one of
   the target's subtrees.  If not NULL, PARENT is CHILD's nearest path-wise
   ancestor - see 'THE CHILDREN_WITH_MERGEINFO ARRAY'.  TARGET_MERGEINFO is
   the working mergeinfo on CHILD.

   RA_SESSION is the session for the younger of SOURCE->loc1 and
   SOURCE->loc2.

   If the function needs to consider CHILD->IMPLICIT_MERGEINFO and
   CHILD_INHERITS_IMPLICIT is true, then set CHILD->IMPLICIT_MERGEINFO to the
   mergeinfo inherited from PARENT->IMPLICIT_MERGEINFO.  Otherwise contact
   the repository for CHILD->IMPLICIT_MERGEINFO.

   If not null, IMPLICIT_SRC_GAP is the gap, if any, in the natural history
   of SOURCE, see merge_cmd_baton_t.implicit_src_gap.

   SCRATCH_POOL is used for all temporary allocations.  Changes to CHILD and
   PARENT are made in RESULT_POOL.

   NOTE: This should only be called when honoring mergeinfo.

   NOTE: If PARENT is present then this function must have previously been
   called for PARENT, i.e. if populate_remaining_ranges() is calling this
   function for a set of svn_client__merge_path_t* the calls must be made
   in depth-first order.

   NOTE: When performing reverse merges, return
   SVN_ERR_CLIENT_NOT_READY_TO_MERGE if both locations in SOURCE and
   CHILD->ABSPATH are all on the same line of history but CHILD->ABSPATH's
   base revision is older than the SOURCE->rev1:rev2 range, see comment re
   issue #2973 below.
*/
static svn_error_t *
calculate_remaining_ranges(svn_client__merge_path_t *parent,
                           svn_client__merge_path_t *child,
                           const merge_source_t *source,
                           svn_mergeinfo_t target_mergeinfo,
                           const apr_array_header_t *implicit_src_gap,
                           svn_boolean_t child_inherits_implicit,
                           svn_ra_session_t *ra_session,
                           svn_client_ctx_t *ctx,
                           apr_pool_t *result_pool,
                           apr_pool_t *scratch_pool)
{
  const svn_client__pathrev_t *primary_src
    = (source->loc1->rev < source->loc2->rev) ? source->loc2 : source->loc1;
  const char *mergeinfo_path = svn_client__pathrev_fspath(primary_src,
                                                          scratch_pool);
  /* Intersection of TARGET_MERGEINFO and the merge history
     described by SOURCE. */
  svn_rangelist_t *target_rangelist;
  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);

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

     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
   all the following comments still apply, but with SOURCE->loc1 switched
   with SOURCE->loc2.

   Like populate_remaining_ranges(), SOURCE must adhere to the restrictions
   documented in 'MERGEINFO MERGE SOURCE NORMALIZATION'.  These restrictions
   allow for a *single* gap in SOURCE, GAP_REV1:GAP_REV2 exclusive:inclusive
   (where SOURCE->loc1->rev == GAP_REV1 <= GAP_REV2 < SOURCE->loc2->rev),
   if SOURCE->loc2->url@(GAP_REV2+1) was copied from SOURCE->loc1.  If such
   a gap exists, set *GAP_START and *GAP_END to the starting and ending
   revisions of the gap.  Otherwise set both to SVN_INVALID_REVNUM.

   For example, if the natural history of URL@2:URL@9 is 'trunk/:2,7-9' this
   would indicate that trunk@7 was copied from trunk@2.  This function would
   return GAP_START:GAP_END of 2:6 in this case.  Note that a path 'trunk'
   might exist at r3-6, but it would not be on the same line of history as
   trunk@9.

   ### GAP_START is basically redundant, as (if there is a gap at all) it is
   necessarily the older revision of SOURCE.

   RA_SESSION is an open RA session to the repository in which SOURCE lives.
*/
static svn_error_t *
find_gaps_in_merge_source_history(svn_revnum_t *gap_start,
                                  svn_revnum_t *gap_end,
                                  const merge_source_t *source,
                                  svn_ra_session_t *ra_session,
                                  svn_client_ctx_t *ctx,
                                  apr_pool_t *scratch_pool)
{
  svn_mergeinfo_t implicit_src_mergeinfo;
  svn_revnum_t old_rev = MIN(source->loc1->rev, source->loc2->rev);
  const svn_client__pathrev_t *primary_src
    = (source->loc1->rev < source->loc2->rev) ? source->loc2 : source->loc1;
  const char *merge_src_fspath = svn_client__pathrev_fspath(primary_src,
                                                            scratch_pool);
  svn_rangelist_t *rangelist;

  SVN_ERR_ASSERT(source->ancestral);

  /* Start by assuming there is no gap. */
  *gap_start = *gap_end = SVN_INVALID_REVNUM;

  /* Easy out: There can't be a gap between adjacent revisions. */
  if (abs(source->loc1->rev - source->loc2->rev) == 1)
    return SVN_NO_ERROR;

  /* Get SOURCE as mergeinfo. */
  SVN_ERR(svn_client__get_history_as_mergeinfo(&implicit_src_mergeinfo, NULL,
                                               primary_src,
                                               primary_src->rev, old_rev,
                                               ra_session,
                                               ctx, scratch_pool));

  rangelist = svn_hash_gets(implicit_src_mergeinfo, merge_src_fspath);

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

        {
          SVN_ERR(svn_rangelist_merge2(rangelist, ranges, iterpool, iterpool));
        }
      /* Update the mergeinfo by adjusting the path's rangelist. */
      svn_hash_sets(mergeinfo, fspath, rangelist);

      if (is_rollback && apr_hash_count(mergeinfo) == 0)
        mergeinfo = NULL;

      svn_mergeinfo__remove_empty_rangelists(mergeinfo, scratch_pool);

      if (result_catalog)
        {
          svn_mergeinfo_t existing_mergeinfo =
            svn_hash_gets(result_catalog, local_abspath);
          apr_pool_t *result_catalog_pool = apr_hash_pool_get(result_catalog);

          if (existing_mergeinfo)
            SVN_ERR(svn_mergeinfo_merge2(mergeinfo, existing_mergeinfo,
                                         result_catalog_pool, scratch_pool));
          svn_hash_sets(result_catalog,
                        apr_pstrdup(result_catalog_pool, local_abspath),
                        svn_mergeinfo_dup(mergeinfo, result_catalog_pool));
        }
      else
        {
          err = svn_client__record_wc_mergeinfo(local_abspath, mergeinfo,
                                                TRUE, ctx, iterpool);

          if (err && err->apr_err == SVN_ERR_ENTRY_NOT_FOUND)
            {
              /* PATH isn't just missing, it's not even versioned as far
                 as this working copy knows.  But it was included in
                 MERGES, which means that the server knows about it.
                 Likely we don't have access to the source due to authz
                 restrictions.  For now just clear the error and
                 continue...

                 ### TODO:  Set non-inheritable mergeinfo on PATH's immediate
                 ### parent and normal mergeinfo on PATH's siblings which we
                 ### do have access to. */
              svn_error_clear(err);
            }
          else
            SVN_ERR(err);
        }
    }

  svn_pool_destroy(iterpool);
  return SVN_NO_ERROR;
}

/* Helper for record_mergeinfo_for_dir_merge().

   Record override mergeinfo on any paths skipped during a merge.

   Set empty mergeinfo on each path in MERGE_B->SKIPPED_ABSPATHS so the path
   does not incorrectly inherit mergeinfo that will later be describing
   the merge.

   MERGEINFO_PATH and MERGE_B are cascaded from
   arguments of the same name in the caller.

   IS_ROLLBACK is true if the caller is recording a reverse merge and false
   otherwise.  RANGELIST is the set of revisions being merged from
   MERGEINFO_PATH to MERGE_B->target. */
static svn_error_t *
record_skips_in_mergeinfo(const char *mergeinfo_path,
                          const svn_rangelist_t *rangelist,
                          svn_boolean_t is_rollback,
                          merge_cmd_baton_t *merge_b,
                          apr_pool_t *scratch_pool)
{
  apr_hash_index_t *hi;
  apr_hash_t *merges;
  apr_size_t nbr_skips = apr_hash_count(merge_b->skipped_abspaths);
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);

  if (nbr_skips == 0)
    return SVN_NO_ERROR;

  merges = apr_hash_make(scratch_pool);

  /* Override the mergeinfo for child paths which weren't actually merged. */
  for (hi = apr_hash_first(scratch_pool, merge_b->skipped_abspaths); hi;
       hi = apr_hash_next(hi))
    {
      const char *skipped_abspath = svn__apr_hash_index_key(hi);
      svn_wc_notify_state_t obstruction_state;

      svn_pool_clear(iterpool);

      /* Before we override, make sure this is a versioned path, it might
         be an external or missing from disk due to authz restrictions. */
      SVN_ERR(perform_obstruction_check(&obstruction_state, NULL, NULL,
                                        NULL, NULL,
                                        merge_b, skipped_abspath,
                                        iterpool));
      if (obstruction_state == svn_wc_notify_state_obstructed
          || obstruction_state == svn_wc_notify_state_missing)
        continue;

      /* Add an empty range list for this path.

         ### TODO: This works fine for a file path skipped because it is
         ### missing as long as the file's parent directory is present.
         ### But missing directory paths skipped are not handled yet,
         ### see issue #2915.

         ### TODO: An empty range is fine if the skipped path doesn't
         ### inherit any mergeinfo from a parent, but if it does
         ### we need to account for that.  See issue #3440
         ### http://subversion.tigris.org/issues/show_bug.cgi?id=3440. */
      svn_hash_sets(merges, skipped_abspath,
                    apr_array_make(scratch_pool, 0,
                                   sizeof(svn_merge_range_t *)));

      /* if (nbr_skips < notify_b->nbr_notifications)
           ### Use RANGELIST as the mergeinfo for all children of
           ### this path which were not also explicitly
           ### skipped? */

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

  new->target_abspath = apr_pstrdup(result_pool, report->target_abspath);
  new->conflicted_range = merge_source_dup(report->conflicted_range,
                                           result_pool);
  return new;
}

/* Create and return an error structure appropriate for the unmerged
   revisions range(s). */
static APR_INLINE svn_error_t *
make_merge_conflict_error(conflict_report_t *report,
                          apr_pool_t *scratch_pool)
{
  assert(!report || svn_dirent_is_absolute(report->target_abspath));

  if (report && ! report->was_last_range)
    {
      svn_error_t *err = svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
       _("One or more conflicts were produced while merging r%ld:%ld into\n"
         "'%s' --\n"
         "resolve all conflicts and rerun the merge to apply the remaining\n"
         "unmerged revisions"),
       report->conflicted_range->loc1->rev, report->conflicted_range->loc2->rev,
       svn_dirent_local_style(report->target_abspath, scratch_pool));
      assert(report->conflicted_range->loc1->rev != report->conflicted_range->loc2->rev); /* ### is a valid case in a 2-URL merge */
      return err;
    }
  return SVN_NO_ERROR;
}

/* Helper for do_directory_merge().

   TARGET_WCPATH is a directory and CHILDREN_WITH_MERGEINFO is filled
   with paths (svn_client__merge_path_t *) arranged in depth first order,
   which have mergeinfo set on them or meet one of the other criteria
   defined in get_mergeinfo_paths().  Remove any paths absent from disk
   or scheduled for deletion from CHILDREN_WITH_MERGEINFO which are equal to
   or are descendants of TARGET_WCPATH by setting those children to NULL. */
static void
remove_absent_children(const char *target_wcpath,
                       apr_array_header_t *children_with_mergeinfo)
{
  /* Before we try to override mergeinfo for skipped paths, make sure
     the path isn't absent due to authz restrictions, because there's
     nothing we can do about those. */
  int i;
  for (i = 0; i < children_with_mergeinfo->nelts; i++)
    {
      svn_client__merge_path_t *child =
        APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
      if ((child->absent || child->scheduled_for_deletion)
          && svn_dirent_is_ancestor(target_wcpath, child->abspath))
        {
          svn_sort__array_delete(children_with_mergeinfo, i--, 1);
        }
    }
}

/* Helper for do_directory_merge() to handle the case where a merge editor
   drive removes explicit mergeinfo from a subtree of the merge target.

   MERGE_B is cascaded from the argument of the same name in
   do_directory_merge().  For each path (if any) in
   MERGE_B->PATHS_WITH_DELETED_MERGEINFO remove that path from
   CHILDREN_WITH_MERGEINFO.

   The one exception is for the merge target itself,
   MERGE_B->target->abspath, this must always be present in
   CHILDREN_WITH_MERGEINFO so this is never removed by this
   function. */
static void
remove_children_with_deleted_mergeinfo(merge_cmd_baton_t *merge_b,
                                       apr_array_header_t *children_with_mergeinfo)
{
  int i;

  if (!merge_b->paths_with_deleted_mergeinfo)
    return;

  /* CHILDREN_WITH_MERGEINFO[0] is the always the merge target
     so start at the first child. */
  for (i = 1; i < children_with_mergeinfo->nelts; i++)
    {
      svn_client__merge_path_t *child =
        APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);

      if (svn_hash_gets(merge_b->paths_with_deleted_mergeinfo, child->abspath))
        {
          svn_sort__array_delete(children_with_mergeinfo, i--, 1);
        }
    }
}

/* Helper for do_directory_merge().

   Set up the diff editor report to merge the SOURCE diff
   into TARGET_ABSPATH and drive it.

   If mergeinfo is not being honored (based on MERGE_B -- see the doc
   string for HONOR_MERGEINFO() for how this is determined), then ignore
   CHILDREN_WITH_MERGEINFO and merge the SOURCE diff to TARGET_ABSPATH.

   If mergeinfo is being honored then perform a history-aware merge,
   describing TARGET_ABSPATH and its subtrees to the reporter in such as way
   as to avoid repeating merges already performed per the mergeinfo and
   natural history of TARGET_ABSPATH and its subtrees.

   The ranges that still need to be merged to the TARGET_ABSPATH and its
   subtrees are described in CHILDREN_WITH_MERGEINFO, an array of
   svn_client__merge_path_t * -- see 'THE CHILDREN_WITH_MERGEINFO ARRAY'
   comment at the top of this file for more info.  Note that it is possible
   TARGET_ABSPATH and/or some of its subtrees need only a subset, or no part,
   of SOURCE to be merged.  Though there is little point to
   calling this function if TARGET_ABSPATH and all its subtrees have already
   had SOURCE merged, this will work but is a no-op.

   SOURCE->rev1 and SOURCE->rev2 must be bound by the set of remaining_ranges
   fields in CHILDREN_WITH_MERGEINFO's elements, specifically:

   For forward merges (SOURCE->rev1 < SOURCE->rev2):

     1) The first svn_merge_range_t * element of each child's remaining_ranges
        array must meet one of the following conditions:

        a) The range's start field is greater than or equal to SOURCE->rev2.

        b) The range's end field is SOURCE->rev2.

     2) Among all the ranges that meet condition 'b' the oldest start
        revision must equal SOURCE->rev1.

   For reverse merges (SOURCE->rev1 > SOURCE->rev2):

     1) The first svn_merge_range_t * element of each child's remaining_ranges
        array must meet one of the following conditions:

        a) The range's start field is less than or equal to SOURCE->rev2.

        b) The range's end field is SOURCE->rev2.

     2) Among all the ranges that meet condition 'b' the youngest start
        revision must equal SOURCE->rev1.

   Note: If the first svn_merge_range_t * element of some subtree child's
   remaining_ranges array is the same as the first range of that child's
   nearest path-wise ancestor, then the subtree child *will not* be described
   to the reporter.

   DEPTH, NOTIFY_B, and MERGE_B are cascaded from do_directory_merge(), see
   that function for more info.

   MERGE_B->ra_session1 and MERGE_B->ra_session2 are RA sessions open to any
   URL in the repository of SOURCE; they may be temporarily reparented within
   this function.

   If SOURCE->ancestral is set, then SOURCE->loc1 must be a
   historical ancestor of SOURCE->loc2, or vice-versa (see
   `MERGEINFO MERGE SOURCE NORMALIZATION' for more requirements around
   SOURCE in this case).
*/
static svn_error_t *
drive_merge_report_editor(const char *target_abspath,
                          const merge_source_t *source,
                          const apr_array_header_t *children_with_mergeinfo,
                          const svn_diff_tree_processor_t *processor,
                          svn_depth_t depth,
                          merge_cmd_baton_t *merge_b,
                          apr_pool_t *scratch_pool)
{
  const svn_ra_reporter3_t *reporter;
  const svn_delta_editor_t *diff_editor;
  void *diff_edit_baton;
  void *report_baton;
  svn_revnum_t target_start;
  svn_boolean_t honor_mergeinfo = HONOR_MERGEINFO(merge_b);
  const char *old_sess1_url, *old_sess2_url;
  svn_boolean_t is_rollback = source->loc1->rev > source->loc2->rev;

  /* Start with a safe default starting revision for the editor and the
     merge target. */
  target_start = source->loc1->rev;

  /* If we are honoring mergeinfo the starting revision for the merge target
     might not be SOURCE->rev1, in fact the merge target might not need *any*
     part of SOURCE merged -- Instead some subtree of the target
     needs SOURCE -- So get the right starting revision for the
     target. */
  if (honor_mergeinfo)
    {
      svn_client__merge_path_t *child;

      /* CHILDREN_WITH_MERGEINFO must always exist if we are honoring
         mergeinfo and must have at least one element (describing the
         merge target). */
      SVN_ERR_ASSERT(children_with_mergeinfo);
      SVN_ERR_ASSERT(children_with_mergeinfo->nelts);

      /* Get the merge target's svn_client__merge_path_t, which is always
         the first in the array due to depth first sorting requirement,
         see 'THE CHILDREN_WITH_MERGEINFO ARRAY'. */
      child = APR_ARRAY_IDX(children_with_mergeinfo, 0,
                            svn_client__merge_path_t *);
      SVN_ERR_ASSERT(child);
      if (child->remaining_ranges->nelts == 0)
        {
          /* The merge target doesn't need anything merged. */
          target_start = source->loc2->rev;
        }
      else

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

compare_merge_path_t_as_paths(const void *a,
                              const void *b)
{
  const svn_client__merge_path_t *child1
    = *((const svn_client__merge_path_t * const *) a);
  const svn_client__merge_path_t *child2
    = *((const svn_client__merge_path_t * const *) b);

  return svn_path_compare_paths(child1->abspath, child2->abspath);
}

/* Return a pointer to the element of CHILDREN_WITH_MERGEINFO whose path
 * is PATH, or return NULL if there is no such element. */
static svn_client__merge_path_t *
get_child_with_mergeinfo(const apr_array_header_t *children_with_mergeinfo,
                         const char *abspath)
{
  svn_client__merge_path_t merge_path;
  svn_client__merge_path_t *key;
  svn_client__merge_path_t **pchild;

  merge_path.abspath = abspath;
  key = &merge_path;
  pchild = bsearch(&key, children_with_mergeinfo->elts,
                   children_with_mergeinfo->nelts,
                   children_with_mergeinfo->elt_size,
                   compare_merge_path_t_as_paths);
  return pchild ? *pchild : NULL;
}

/* Insert a deep copy of INSERT_ELEMENT into the CHILDREN_WITH_MERGEINFO
   array at its correct position.  Allocate the new storage in POOL.
   CHILDREN_WITH_MERGEINFO is a depth first sorted array of
   (svn_client__merge_path_t *).

   ### Most callers don't need this to deep-copy the new element.
   ### It may be more efficient for some callers to insert a bunch of items
       out of order and then sort afterwards. (One caller is doing a qsort
       after calling this anyway.)
 */
static void
insert_child_to_merge(apr_array_header_t *children_with_mergeinfo,
                      const svn_client__merge_path_t *insert_element,
                      apr_pool_t *pool)
{
  int insert_index;
  const svn_client__merge_path_t *new_element;

  /* Find where to insert the new element */
  insert_index =
    svn_sort__bsearch_lower_bound(&insert_element, children_with_mergeinfo,
                                  compare_merge_path_t_as_paths);

  new_element = svn_client__merge_path_dup(insert_element, pool);
  svn_sort__array_insert(&new_element, children_with_mergeinfo, insert_index);
}

/* Helper for get_mergeinfo_paths().

   CHILDREN_WITH_MERGEINFO, DEPTH, and POOL are
   all cascaded from the arguments of the same name to get_mergeinfo_paths().

   TARGET is the merge target.

   *CHILD is the element in in CHILDREN_WITH_MERGEINFO that
   get_mergeinfo_paths() is iterating over and *CURR_INDEX is index for
   *CHILD.

   If CHILD->ABSPATH is equal to MERGE_CMD_BATON->target->abspath do nothing.
   Else if CHILD->ABSPATH is switched or absent then make sure its immediate
   (as opposed to nearest) parent in CHILDREN_WITH_MERGEINFO is marked as
   missing a child.  If the immediate parent does not exist in
   CHILDREN_WITH_MERGEINFO then create it (and increment *CURR_INDEX so that
   caller doesn't process the inserted element).  Also ensure that
   CHILD->ABSPATH's siblings which are not already present in
   CHILDREN_WITH_MERGEINFO are also added to the array, limited by DEPTH
   (e.g. don't add directory siblings of a switched file).
   Use POOL for temporary allocations only, any new CHILDREN_WITH_MERGEINFO
   elements are allocated in POOL. */
static svn_error_t *
insert_parent_and_sibs_of_sw_absent_del_subtree(
                                   apr_array_header_t *children_with_mergeinfo,
                                   const merge_target_t *target,
                                   int *curr_index,
                                   svn_client__merge_path_t *child,
                                   svn_depth_t depth,
                                   svn_client_ctx_t *ctx,
                                   apr_pool_t *pool)
{
  svn_client__merge_path_t *parent;
  const char *parent_abspath;
  apr_pool_t *iterpool;
  const apr_array_header_t *children;
  int i;

  if (!(child->absent
          || (child->switched
              && strcmp(target->abspath,
                        child->abspath) != 0)))
    return SVN_NO_ERROR;

  parent_abspath = svn_dirent_dirname(child->abspath, pool);
  parent = get_child_with_mergeinfo(children_with_mergeinfo, parent_abspath);
  if (parent)
    {
      parent->missing_child = child->absent;
      parent->switched_child = child->switched;
    }
  else
    {
      /* Create a new element to insert into CHILDREN_WITH_MERGEINFO. */
      parent = svn_client__merge_path_create(parent_abspath, pool);
      parent->missing_child = child->absent;
      parent->switched_child = child->switched;
      /* Insert PARENT into CHILDREN_WITH_MERGEINFO. */
      insert_child_to_merge(children_with_mergeinfo, parent, pool);
      /* Increment for loop index so we don't process the inserted element. */
      (*curr_index)++;
    } /*(parent == NULL) */

  /* Add all of PARENT's non-missing children that are not already present.*/

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

          if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
            {
              err = svn_error_createf(
                SVN_ERR_CLIENT_INVALID_MERGEINFO_NO_MERGETRACKING, err,
                _("Invalid mergeinfo detected on '%s', "
                  "merge tracking not possible"),
                svn_dirent_local_style(wc_path, scratch_pool));
            }
          return svn_error_trace(err);
        }
      svn_hash_sets(*subtrees_with_mergeinfo, wc_path, mergeinfo);
    }
  svn_pool_destroy(iterpool);

  return SVN_NO_ERROR;
}

/* Helper for do_directory_merge() when performing merge-tracking aware
   merges.

   Walk of the working copy tree rooted at TARGET->abspath to
   depth DEPTH.  Create an svn_client__merge_path_t * for any path which meets
   one or more of the following criteria:

     1) Path has working svn:mergeinfo.
     2) Path is switched.
     3) Path is a subtree of the merge target (i.e. is not equal to
        TARGET->abspath) and has no mergeinfo of its own but
        its immediate parent has mergeinfo with non-inheritable ranges.  If
        this isn't a dry-run and the merge is between differences in the same
        repository, then this function will set working mergeinfo on the path
        equal to the mergeinfo inheritable from its parent.
     4) Path has an immediate child (or children) missing from the WC because
        the child is switched or absent from the WC, or due to a sparse
        checkout.
     5) Path has a sibling (or siblings) missing from the WC because the
        sibling is switched, absent, scheduled for deletion, or missing due to
        a sparse checkout.
     6) Path is absent from disk due to an authz restriction.
     7) Path is equal to TARGET->abspath.
     8) Path is an immediate *directory* child of
        TARGET->abspath and DEPTH is svn_depth_immediates.
     9) Path is an immediate *file* child of TARGET->abspath
        and DEPTH is svn_depth_files.
     10) Path is at a depth of 'empty' or 'files'.
     11) Path is missing from disk (e.g. due to an OS-level deletion).

   If subtrees within the requested DEPTH are unexpectedly missing disk,
   then raise SVN_ERR_CLIENT_NOT_READY_TO_MERGE.

   Store the svn_client__merge_path_t *'s in *CHILDREN_WITH_MERGEINFO in
   depth-first order based on the svn_client__merge_path_t *s path member as
   sorted by svn_path_compare_paths().  Set the remaining_ranges field of each
   element to NULL.

   Note: Since the walk is rooted at TARGET->abspath, the
   latter is guaranteed to be in *CHILDREN_WITH_MERGEINFO and due to the
   depth-first ordering it is guaranteed to be the first element in
   *CHILDREN_WITH_MERGEINFO.

   MERGE_CMD_BATON is cascaded from the argument of the same name in
   do_directory_merge().
*/
static svn_error_t *
get_mergeinfo_paths(apr_array_header_t *children_with_mergeinfo,
                    const merge_target_t *target,
                    svn_depth_t depth,
                    svn_boolean_t dry_run,
                    svn_boolean_t same_repos,
                    svn_client_ctx_t *ctx,
                    apr_pool_t *result_pool,
                    apr_pool_t *scratch_pool)
{
  int i;
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
  apr_hash_t *subtrees_with_mergeinfo;
  apr_hash_t *excluded_subtrees;
  apr_hash_t *switched_subtrees;
  apr_hash_t *shallow_subtrees;
  apr_hash_t *missing_subtrees;
  struct pre_merge_status_baton_t pre_merge_status_baton;

  /* Case 1: Subtrees with explicit mergeinfo. */
  SVN_ERR(get_wc_explicit_mergeinfo_catalog(&subtrees_with_mergeinfo,
                                            target->abspath,
                                            depth, ctx,
                                            result_pool, scratch_pool));
  if (subtrees_with_mergeinfo)
    {
      apr_hash_index_t *hi;

      for (hi = apr_hash_first(scratch_pool, subtrees_with_mergeinfo);
           hi;
           hi = apr_hash_next(hi))
        {
          const char *wc_path = svn__apr_hash_index_key(hi);
          svn_mergeinfo_t mergeinfo = svn__apr_hash_index_val(hi);
          svn_client__merge_path_t *mergeinfo_child =
            svn_client__merge_path_create(wc_path, result_pool);

          svn_pool_clear(iterpool);

          /* Stash this child's pre-existing mergeinfo. */
          mergeinfo_child->pre_merge_mergeinfo = mergeinfo;

          /* Note if this child has non-inheritable mergeinfo */
          mergeinfo_child->has_noninheritable
            = svn_mergeinfo__is_noninheritable(
                mergeinfo_child->pre_merge_mergeinfo, iterpool);

          /* Append it.  We'll sort below. */
          APR_ARRAY_PUSH(children_with_mergeinfo, svn_client__merge_path_t *)
            = svn_client__merge_path_dup(mergeinfo_child, result_pool);
        }

      /* Sort CHILDREN_WITH_MERGEINFO by each child's path (i.e. as per
         compare_merge_path_t_as_paths).  Any subsequent insertions of new
         children with insert_child_to_merge() require this ordering. */
      qsort(children_with_mergeinfo->elts,
            children_with_mergeinfo->nelts,
            children_with_mergeinfo->elt_size,

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

                                                              scratch_pool);
      svn_rangelist_t *filtered_rangelist;

      /* Filter any ranges from TARGET_WCPATH's own history, there is no
         need to record this explicitly in mergeinfo, it is already part
         of TARGET_WCPATH's natural history (implicit mergeinfo). */
      SVN_ERR(filter_natural_history_from_mergeinfo(
        &filtered_rangelist,
        mergeinfo_path,
        merge_target->implicit_mergeinfo,
        &range,
        iterpool));

      /* Only record mergeinfo if there is something other than
         self-referential mergeinfo, but don't record mergeinfo if
         TARGET_WCPATH was skipped. */
      if (filtered_rangelist->nelts
          && (apr_hash_count(merge_b->skipped_abspaths) == 0))
        {
          apr_hash_t *merges = apr_hash_make(iterpool);

          /* If merge target has inherited mergeinfo set it before
             recording the first merge range. */
          if (inherited)
            SVN_ERR(svn_client__record_wc_mergeinfo(target_abspath,
                                                    target_mergeinfo,
                                                    FALSE, ctx,
                                                    iterpool));

          svn_hash_sets(merges, target_abspath, filtered_rangelist);

          if (!squelch_mergeinfo_notifications)
            {
              /* Notify that we are recording mergeinfo describing a merge. */
              svn_merge_range_t n_range;

              SVN_ERR(svn_mergeinfo__get_range_endpoints(
                        &n_range.end, &n_range.start, merges, iterpool));
              n_range.inheritable = TRUE;
              notify_mergeinfo_recording(target_abspath, &n_range,
                                         merge_b->ctx, iterpool);
            }

          SVN_ERR(update_wc_mergeinfo(result_catalog, target_abspath,
                                      mergeinfo_path, merges, is_rollback,
                                      ctx, iterpool));
        }
    }

  merge_b->notify_begin.nodes_with_mergeinfo = NULL;

  svn_pool_destroy(iterpool);

  return SVN_NO_ERROR;
}

/* Helper for do_directory_merge() to handle the case where a merge editor
   drive adds explicit mergeinfo to a path which didn't have any explicit
   mergeinfo previously.

   MERGE_B is cascaded from the argument of the same
   name in do_directory_merge().  Should be called only after
   do_directory_merge() has called populate_remaining_ranges() and populated
   the remaining_ranges field of each child in
   CHILDREN_WITH_MERGEINFO (i.e. the remaining_ranges fields can be
   empty but never NULL).

   If MERGE_B->DRY_RUN is true do nothing, if it is false then
   for each path (if any) in MERGE_B->PATHS_WITH_NEW_MERGEINFO merge that
   path's inherited mergeinfo (if any) with its working explicit mergeinfo
   and set that as the path's new explicit mergeinfo.  Then add an
   svn_client__merge_path_t * element representing the path to
   CHILDREN_WITH_MERGEINFO if it isn't already present.  All fields
   in any elements added to CHILDREN_WITH_MERGEINFO are initialized
   to FALSE/NULL with the exception of 'path' and 'remaining_ranges'.  The
   latter is set to a rangelist equal to the remaining_ranges of the path's
   nearest path-wise ancestor in CHILDREN_WITH_MERGEINFO.

   Any elements added to CHILDREN_WITH_MERGEINFO are allocated
   in POOL. */
static svn_error_t *
process_children_with_new_mergeinfo(merge_cmd_baton_t *merge_b,
                                    apr_array_header_t *children_with_mergeinfo,
                                    apr_pool_t *pool)
{
  apr_pool_t *iterpool;
  apr_hash_index_t *hi;

  if (!merge_b->paths_with_new_mergeinfo || merge_b->dry_run)
    return SVN_NO_ERROR;

  /* Iterate over each path with explicit mergeinfo added by the merge. */
  iterpool = svn_pool_create(pool);
  for (hi = apr_hash_first(pool, merge_b->paths_with_new_mergeinfo);
       hi;
       hi = apr_hash_next(hi))
    {
      const char *abspath_with_new_mergeinfo = svn__apr_hash_index_key(hi);
      svn_mergeinfo_t path_inherited_mergeinfo;
      svn_mergeinfo_t path_explicit_mergeinfo;
      svn_client__merge_path_t *new_child;

      svn_pool_clear(iterpool);

      /* Note: We could skip recording inherited mergeinfo here if this path
         was added (with preexisting mergeinfo) by the merge.  That's actually
         more correct, since the inherited mergeinfo likely describes
         non-existent or unrelated merge history, but it's not quite so simple
         as that, see http://subversion.tigris.org/issues/show_bug.cgi?id=4309
         */

      /* Get the path's new explicit mergeinfo... */
      SVN_ERR(svn_client__get_wc_mergeinfo(&path_explicit_mergeinfo, NULL,
                                           svn_mergeinfo_explicit,
                                           abspath_with_new_mergeinfo,
                                           NULL, NULL, FALSE,
                                           merge_b->ctx,
                                           iterpool, iterpool));
      /* ...there *should* always be explicit mergeinfo at this point
         but you can't be too careful. */
      if (path_explicit_mergeinfo)

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

              /* Set the path's remaining_ranges equal to its parent's. */
              new_child->remaining_ranges = svn_rangelist_dup(
                 parent->remaining_ranges, pool);
              insert_child_to_merge(children_with_mergeinfo, new_child, pool);
            }
        }
    }
  svn_pool_destroy(iterpool);

  return SVN_NO_ERROR;
}

/* Return true if any path in SUBTREES is equal to, or is a subtree of,
   LOCAL_ABSPATH.  Return false otherwise.  The keys of SUBTREES are
   (const char *) absolute paths and its values are irrelevant.
   If SUBTREES is NULL return false. */
static svn_boolean_t
path_is_subtree(const char *local_abspath,
                apr_hash_t *subtrees,
                apr_pool_t *pool)
{
  if (subtrees)
    {
      apr_hash_index_t *hi;

      for (hi = apr_hash_first(pool, subtrees);
           hi; hi = apr_hash_next(hi))
        {
          const char *path_touched_by_merge = svn__apr_hash_index_key(hi);
          if (svn_dirent_is_ancestor(local_abspath, path_touched_by_merge))
            return TRUE;
        }
    }
  return FALSE;
}

/* Return true if any merged, skipped, added or tree-conflicted path
   recorded in MERGE_B is equal to, or is a subtree of LOCAL_ABSPATH.  Return
   false otherwise.

   ### Why not text- or prop-conflicted paths? Are such paths guaranteed
       to be recorded as 'merged' or 'skipped' or 'added', perhaps?
*/
static svn_boolean_t
subtree_touched_by_merge(const char *local_abspath,
                         merge_cmd_baton_t *merge_b,
                         apr_pool_t *pool)
{
  return (path_is_subtree(local_abspath, merge_b->merged_abspaths, pool)
          || path_is_subtree(local_abspath, merge_b->skipped_abspaths, pool)
          || path_is_subtree(local_abspath, merge_b->added_abspaths, pool)
          || path_is_subtree(local_abspath, merge_b->tree_conflicted_abspaths,
                             pool));
}

/* Helper for do_directory_merge() when performing mergeinfo unaware merges.

   Merge the SOURCE diff into TARGET_DIR_WCPATH.

   SOURCE, DEPTH, NOTIFY_B, and MERGE_B
   are all cascaded from do_directory_merge's arguments of the same names.

   CONFLICT_REPORT is as documented for do_directory_merge().

   NOTE: This is a very thin wrapper around drive_merge_report_editor() and
   exists only to populate CHILDREN_WITH_MERGEINFO with the single element
   expected during mergeinfo unaware merges.
*/
static svn_error_t *
do_mergeinfo_unaware_dir_merge(single_range_conflict_report_t **conflict_report,
                               const merge_source_t *source,
                               const char *target_dir_wcpath,
                               apr_array_header_t *children_with_mergeinfo,
                               const svn_diff_tree_processor_t *processor,
                               svn_depth_t depth,
                               merge_cmd_baton_t *merge_b,
                               apr_pool_t *result_pool,
                               apr_pool_t *scratch_pool)
{
  /* Initialize CHILDREN_WITH_MERGEINFO and populate it with
     one element describing the merge of SOURCE->rev1:rev2 to
     TARGET_DIR_WCPATH. */
  svn_client__merge_path_t *item
    = svn_client__merge_path_create(target_dir_wcpath, scratch_pool);

  *conflict_report = NULL;
  item->remaining_ranges = svn_rangelist__initialize(source->loc1->rev,
                                                     source->loc2->rev,
                                                     TRUE, scratch_pool);
  APR_ARRAY_PUSH(children_with_mergeinfo,
                 svn_client__merge_path_t *) = item;
  SVN_ERR(drive_merge_report_editor(target_dir_wcpath,
                                    source,
                                    NULL, processor, depth,
                                    merge_b, scratch_pool));
  if (is_path_conflicted_by_merge(merge_b))
    {
      *conflict_report = single_range_conflict_report_create(
                           source, NULL, result_pool);
    }
  return SVN_NO_ERROR;
}

/* A svn_log_entry_receiver_t baton for log_find_operative_subtree_revs(). */
typedef struct log_find_operative_subtree_baton_t
{
  /* Mapping of const char * absolute working copy paths to those
     path's const char * repos absolute paths. */
  apr_hash_t *operative_children;

  /* As per the arguments of the same name to
     get_operative_immediate_children(). */
  const char *merge_source_fspath;
  const char *merge_target_abspath;
  svn_depth_t depth;
  svn_wc_context_t *wc_ctx;

  /* A pool to allocate additions to the hashes in. */
  apr_pool_t *result_pool;
} log_find_operative_subtree_baton_t;

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

   -r(OLDEST_REV - 1):YOUNGEST_REV were merged to MERGE_TARGET_ABSPATH at
   svn_depth_infinity rather than DEPTH.

   RESULT_POOL is used to allocate the contents of *OPERATIVE_CHILDREN.
   SCRATCH_POOL is used for temporary allocations. */
static svn_error_t *
get_operative_immediate_children(apr_hash_t **operative_children,
                                 const char *merge_source_fspath,
                                 svn_revnum_t oldest_rev,
                                 svn_revnum_t youngest_rev,
                                 const char *merge_target_abspath,
                                 svn_depth_t depth,
                                 svn_wc_context_t *wc_ctx,
                                 svn_ra_session_t *ra_session,
                                 apr_pool_t *result_pool,
                                 apr_pool_t *scratch_pool)
{
  log_find_operative_subtree_baton_t log_baton;

  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(oldest_rev));
  SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev));
  SVN_ERR_ASSERT(oldest_rev <= youngest_rev);

  *operative_children = apr_hash_make(result_pool);

  if (depth == svn_depth_infinity)
    return SVN_NO_ERROR;

  /* Now remove any paths from *OPERATIVE_CHILDREN that are inoperative when
     merging MERGE_SOURCE_REPOS_PATH -r(OLDEST_REV - 1):YOUNGEST_REV to
     MERGE_TARGET_ABSPATH at --depth infinity. */
  log_baton.operative_children = *operative_children;
  log_baton.merge_source_fspath = merge_source_fspath;
  log_baton.merge_target_abspath = merge_target_abspath;
  log_baton.depth = depth;
  log_baton.wc_ctx = wc_ctx;
  log_baton.result_pool = result_pool;

  SVN_ERR(get_log(ra_session, "", youngest_rev, oldest_rev,
                  TRUE, /* discover_changed_paths */
                  log_find_operative_subtree_revs,
                  &log_baton, scratch_pool));

  return SVN_NO_ERROR;
}

/* Helper for record_mergeinfo_for_dir_merge(): Identify which elements of
   CHILDREN_WITH_MERGEINFO need new mergeinfo set to accurately
   describe a merge, what inheritance type such new mergeinfo should have,
   and what subtrees can be ignored altogether.

   For each svn_client__merge_path_t CHILD in CHILDREN_WITH_MERGEINFO,
   set CHILD->RECORD_MERGEINFO and CHILD->RECORD_NONINHERITABLE to true
   if the subtree needs mergeinfo to describe the merge and if that
   mergeinfo should be non-inheritable respectively.

   If OPERATIVE_MERGE is true, then the merge being described is operative
   as per subtree_touched_by_merge().  OPERATIVE_MERGE is false otherwise.

   MERGED_RANGE, MERGEINFO_FSPATH, DEPTH, NOTIFY_B, and MERGE_B are all
   cascaded from record_mergeinfo_for_dir_merge's arguments of the same
   names.

   SCRATCH_POOL is used for temporary allocations.
*/
static svn_error_t *
flag_subtrees_needing_mergeinfo(svn_boolean_t operative_merge,
                                const svn_merge_range_t *merged_range,
                                apr_array_header_t *children_with_mergeinfo,
                                const char *mergeinfo_fspath,
                                svn_depth_t depth,
                                merge_cmd_baton_t *merge_b,
                                apr_pool_t *scratch_pool)
{
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
  int i;
  apr_hash_t *operative_immediate_children = NULL;

  assert(! merge_b->dry_run);

  if (!merge_b->record_only
      && merged_range->start <= merged_range->end
      && (depth < svn_depth_infinity))
    SVN_ERR(get_operative_immediate_children(
      &operative_immediate_children,
      mergeinfo_fspath, merged_range->start + 1, merged_range->end,
      merge_b->target->abspath, depth, merge_b->ctx->wc_ctx,
      merge_b->ra_session1, scratch_pool, iterpool));

  /* Issue #4056: Walk NOTIFY_B->CHILDREN_WITH_MERGEINFO reverse depth-first
     order.  This way each child knows if it has operative missing/switched
     children which necessitates non-inheritable mergeinfo. */
  for (i = children_with_mergeinfo->nelts - 1; i >= 0; i--)
    {
      svn_client__merge_path_t *child =
                     APR_ARRAY_IDX(children_with_mergeinfo, i,
                                   svn_client__merge_path_t *);

      /* Can't record mergeinfo on something that isn't here. */
      if (child->absent)
        continue;

      /* Verify that remove_children_with_deleted_mergeinfo() did its job */
      assert((i == 0)
             ||! merge_b->paths_with_deleted_mergeinfo
             || !svn_hash_gets(merge_b->paths_with_deleted_mergeinfo,
                               child->abspath));

      /* Don't record mergeinfo on skipped paths. */
      if (svn_hash_gets(merge_b->skipped_abspaths, child->abspath))
        continue;

      /* ### ptb: Yes, we could combine the following into a single
         ### conditional, but clarity would suffer (even more than
         ### it does now). */
      if (i == 0)
        {
          /* Always record mergeinfo on the merge target. */
          child->record_mergeinfo = TRUE;
        }
      else if (merge_b->record_only && !merge_b->reintegrate_merge)

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


                     ### What about svn_depth_immediates?  In that case
                     ### the merge target needs only normal inheritable
                     ### mergeinfo and the target's immediate children will
                     ### get non-inheritable mergeinfo, assuming they
                     ### need even that. */
                  if (depth < svn_depth_immediates
                      && operative_immediate_children
                      && apr_hash_count(operative_immediate_children))
                    child->record_noninheritable = TRUE;
                }
              else if (depth == svn_depth_immediates)
                {
                  /* An immediate directory child of the merge target, which
                      was affected by a --depth=immediates merge, needs
                      non-inheritable mergeinfo. */
                  if (svn_hash_gets(operative_immediate_children,
                                    child->abspath))
                    child->record_noninheritable = TRUE;
                }
            }
        }
      else /* child->record_mergeinfo */
        {
          /* If CHILD is in NOTIFY_B->CHILDREN_WITH_MERGEINFO simply
             because it had no explicit mergeinfo of its own at the
             start of the merge but is the child of of some path with
             non-inheritable mergeinfo, then the explicit mergeinfo it
             has *now* was set by get_mergeinfo_paths() -- see criteria
             3 in that function's doc string.  So since CHILD->ABSPATH
             was not touched by the merge we can remove the
             mergeinfo. */
          if (child->child_of_noninheritable)
            SVN_ERR(svn_client__record_wc_mergeinfo(child->abspath,
                                                    NULL, FALSE,
                                                    merge_b->ctx,
                                                    iterpool));
        }
    }

  svn_pool_destroy(iterpool);
  return SVN_NO_ERROR;
}

/* Helper for do_directory_merge().

   If RESULT_CATALOG is NULL then record mergeinfo describing a merge of
   MERGED_RANGE->START:MERGED_RANGE->END from the repository relative path
   MERGEINFO_FSPATH to the merge target (and possibly its subtrees) described
   by NOTIFY_B->CHILDREN_WITH_MERGEINFO -- see the global comment
   'THE CHILDREN_WITH_MERGEINFO ARRAY'.  Obviously this should only
   be called if recording mergeinfo -- see doc string for RECORD_MERGEINFO().

   If RESULT_CATALOG is not NULL, then don't record the new mergeinfo on the
   WC, but instead record it in RESULT_CATALOG, where the keys are absolute
   working copy paths and the values are the new mergeinfos for each.
   Allocate additions to RESULT_CATALOG in pool which RESULT_CATALOG was
   created in.

   DEPTH, NOTIFY_B, MERGE_B, and SQUELCH_MERGEINFO_NOTIFICATIONS are all
   cascaded from do_directory_merge's arguments of the same names.

   SCRATCH_POOL is used for temporary allocations.
*/
static svn_error_t *
record_mergeinfo_for_dir_merge(svn_mergeinfo_catalog_t result_catalog,
                               const svn_merge_range_t *merged_range,
                               const char *mergeinfo_fspath,
                               apr_array_header_t *children_with_mergeinfo,
                               svn_depth_t depth,
                               svn_boolean_t squelch_mergeinfo_notifications,
                               merge_cmd_baton_t *merge_b,
                               apr_pool_t *scratch_pool)
{
  int i;
  svn_boolean_t is_rollback = (merged_range->start > merged_range->end);
  svn_boolean_t operative_merge;

  /* Update the WC mergeinfo here to account for our new
     merges, minus any unresolved conflicts and skips. */

  /* We need a scratch pool for iterations below. */
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);

  svn_merge_range_t range = *merged_range;

  assert(! merge_b->dry_run);

  /* Regardless of what subtrees in MERGE_B->target->abspath might be missing
     could this merge have been operative? */
  operative_merge = subtree_touched_by_merge(merge_b->target->abspath,
                                             merge_b, iterpool);

  /* If this couldn't be an operative merge then don't bother with
     the added complexity (and user confusion) of non-inheritable ranges.
     There is no harm in subtrees inheriting inoperative mergeinfo. */
  if (!operative_merge)
    range.inheritable = TRUE;

  /* Remove absent children at or under MERGE_B->target->abspath from
     NOTIFY_B->CHILDREN_WITH_MERGEINFO
     before we calculate the merges performed. */
  remove_absent_children(merge_b->target->abspath,
                         children_with_mergeinfo);

  /* Determine which subtrees of interest need mergeinfo recorded... */
  SVN_ERR(flag_subtrees_needing_mergeinfo(operative_merge, &range,
                                          children_with_mergeinfo,
                                          mergeinfo_fspath, depth,
                                          merge_b, iterpool));

  /* ...and then record it. */
  for (i = 0; i < children_with_mergeinfo->nelts; i++)
    {
      const char *child_repos_path;
      const char *child_merge_src_fspath;
      svn_rangelist_t *child_merge_rangelist;
      apr_hash_t *child_merges;
      svn_client__merge_path_t *child =
                     APR_ARRAY_IDX(children_with_mergeinfo, i,
                                   svn_client__merge_path_t *);

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

                                      merge_b->ctx, iterpool));

          /* Once is enough: We don't need to record mergeinfo describing
             the merge a second.  If CHILD->ABSPATH is in
             MERGE_B->ADDED_ABSPATHS, we'll do just that, so remove the
             former from the latter. */
          svn_hash_sets(merge_b->added_abspaths, child->abspath, NULL);
        }

      /* Elide explicit subtree mergeinfo whether or not we updated it. */
      if (i > 0)
        {
          svn_boolean_t in_switched_subtree = FALSE;

          if (child->switched)
            in_switched_subtree = TRUE;
          else if (i > 1)
            {
              /* Check if CHILD is part of a switched subtree */
              svn_client__merge_path_t *parent;
              int j = i - 1;
              for (; j > 0; j--)
                {
                  parent = APR_ARRAY_IDX(children_with_mergeinfo,
                                         j, svn_client__merge_path_t *);
                  if (parent
                      && parent->switched
                      && svn_dirent_is_ancestor(parent->abspath,
                                                child->abspath))
                    {
                      in_switched_subtree = TRUE;
                      break;
                    }
                }
            }

          /* Allow mergeinfo on switched subtrees to elide to the
             repository. Otherwise limit elision to the merge target
             for now.  do_directory_merge() will eventually try to
             elide that when the merge is complete. */
          SVN_ERR(svn_client__elide_mergeinfo(
            child->abspath,
            in_switched_subtree ? NULL : merge_b->target->abspath,
            merge_b->ctx, iterpool));
        }
    } /* (i = 0; i < notify_b->children_with_mergeinfo->nelts; i++) */

  svn_pool_destroy(iterpool);
  return SVN_NO_ERROR;
}

/* Helper for do_directory_merge().

   Record mergeinfo describing a merge of
   MERGED_RANGE->START:MERGED_RANGE->END from the repository relative path
   MERGEINFO_FSPATH to each path in ADDED_ABSPATHS which has explicit
   mergeinfo or is the immediate child of a parent with explicit
   non-inheritable mergeinfo.

   DEPTH, MERGE_B, and SQUELCH_MERGEINFO_NOTIFICATIONS, are
   cascaded from do_directory_merge's arguments of the same names.

   Note: This is intended to support forward merges only, i.e.
   MERGED_RANGE->START must be older than MERGED_RANGE->END.
*/
static svn_error_t *
record_mergeinfo_for_added_subtrees(
  svn_merge_range_t *merged_range,
  const char *mergeinfo_fspath,
  svn_depth_t depth,
  svn_boolean_t squelch_mergeinfo_notifications,
  apr_hash_t *added_abspaths,
  merge_cmd_baton_t *merge_b,
  apr_pool_t *pool)
{
  apr_pool_t *iterpool;
  apr_hash_index_t *hi;

  /* If no paths were added by the merge then we have nothing to do. */
  if (!added_abspaths)
    return SVN_NO_ERROR;

  SVN_ERR_ASSERT(merged_range->start < merged_range->end);

  iterpool = svn_pool_create(pool);
  for (hi = apr_hash_first(pool, added_abspaths); hi; hi = apr_hash_next(hi))
    {
      const char *added_abspath = svn__apr_hash_index_key(hi);
      const char *dir_abspath;
      svn_mergeinfo_t parent_mergeinfo;
      svn_mergeinfo_t added_path_mergeinfo;

      svn_pool_clear(iterpool);
      dir_abspath = svn_dirent_dirname(added_abspath, iterpool);

      /* Grab the added path's explicit mergeinfo. */
      SVN_ERR(svn_client__get_wc_mergeinfo(&added_path_mergeinfo, NULL,
                                           svn_mergeinfo_explicit,
                                           added_abspath, NULL, NULL, FALSE,
                                           merge_b->ctx, iterpool, iterpool));

      /* If the added path doesn't have explicit mergeinfo, does its immediate
         parent have non-inheritable mergeinfo? */
      if (!added_path_mergeinfo)
        SVN_ERR(svn_client__get_wc_mergeinfo(&parent_mergeinfo, NULL,
                                             svn_mergeinfo_explicit,
                                             dir_abspath, NULL, NULL, FALSE,
                                             merge_b->ctx,
                                             iterpool, iterpool));

      if (added_path_mergeinfo
          || svn_mergeinfo__is_noninheritable(parent_mergeinfo, iterpool))
        {
          svn_node_kind_t added_path_kind;
          svn_mergeinfo_t merge_mergeinfo;
          svn_mergeinfo_t adds_history_as_mergeinfo;
          svn_rangelist_t *rangelist;
          const char *rel_added_path;
          const char *added_path_mergeinfo_fspath;
          svn_client__pathrev_t *added_path_pathrev;

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


          if (child && child->pre_merge_mergeinfo)
            {
              /* Found some explicit mergeinfo, grab any ranges
                 for PATH. */
              paths_explicit_rangelist =
                            svn_hash_gets(child->pre_merge_mergeinfo, fspath);
              break;
            }

          if (cwmi_abspath[0] == '\0'
              || svn_dirent_is_root(cwmi_abspath, strlen(cwmi_abspath))
              || strcmp(log_gap_baton->target->abspath, cwmi_abspath) == 0)
            {
              /* Can't crawl any higher. */
              break;
            }

          /* Didn't find anything so crawl up to the parent. */
          cwmi_abspath = svn_dirent_dirname(cwmi_abspath, scratch_pool);
          fspath = svn_fspath__dirname(fspath, scratch_pool);

          /* At this point *if* we find mergeinfo it will be inherited. */
          mergeinfo_inherited = TRUE;
        }

      if (paths_explicit_rangelist)
        {
          svn_rangelist_t *intersecting_range;
          svn_rangelist_t *rangelist;

          rangelist = svn_rangelist__initialize(revision - 1, revision, TRUE,
                                                scratch_pool);

          /* If PATH inherited mergeinfo we must consider inheritance in the
             event the inherited mergeinfo is actually non-inheritable. */
          SVN_ERR(svn_rangelist_intersect(&intersecting_range,
                                          paths_explicit_rangelist,
                                          rangelist,
                                          mergeinfo_inherited, scratch_pool));

          if (intersecting_range->nelts == 0)
            log_entry_rev_required = TRUE;
        }
      else
        {
          log_entry_rev_required = TRUE;
        }
    }

  if (!log_entry_rev_required)
    SVN_ERR(rangelist_merge_revision(log_gap_baton->merged_ranges,
                                     revision,
                                     log_gap_baton->pool));

  return SVN_NO_ERROR;
}

/* Helper for do_directory_merge().

   SOURCE is cascaded from the argument of the same name in
   do_directory_merge().  TARGET is the merge target.  RA_SESSION is the
   session for SOURCE->loc2.

   Find all the ranges required by subtrees in
   CHILDREN_WITH_MERGEINFO that are *not* required by
   TARGET->abspath (i.e. CHILDREN_WITH_MERGEINFO[0]).  If such
   ranges exist, then find any subset of ranges which, if merged, would be
   inoperative.  Finally, if any inoperative ranges are found then remove
   these ranges from all of the subtree's REMAINING_RANGES.

   This function should only be called when honoring mergeinfo during
   forward merges (i.e. SOURCE->rev1 < SOURCE->rev2).
*/
static svn_error_t *
remove_noop_subtree_ranges(const merge_source_t *source,
                           const merge_target_t *target,
                           svn_ra_session_t *ra_session,
                           apr_array_header_t *children_with_mergeinfo,
                           apr_pool_t *result_pool,
                           apr_pool_t *scratch_pool)
{
  /* ### Do we need to check that we are at a uniform working revision? */
  int i;
  svn_client__merge_path_t *root_child =
    APR_ARRAY_IDX(children_with_mergeinfo, 0, svn_client__merge_path_t *);
  svn_rangelist_t *requested_ranges;
  svn_rangelist_t *subtree_gap_ranges;
  svn_rangelist_t *subtree_remaining_ranges;
  log_noop_baton_t log_gap_baton;
  svn_merge_range_t *oldest_gap_rev;
  svn_merge_range_t *youngest_gap_rev;
  svn_rangelist_t *inoperative_ranges;
  apr_pool_t *iterpool;
  const char *longest_common_subtree_ancestor = NULL;
  svn_error_t *err;

  assert(session_url_is(ra_session, source->loc2->url, scratch_pool));

  /* This function is only intended to work with forward merges. */
  if (source->loc1->rev > source->loc2->rev)
    return SVN_NO_ERROR;

  /* Another easy out: There are no subtrees. */
  if (children_with_mergeinfo->nelts < 2)
    return SVN_NO_ERROR;

  subtree_remaining_ranges = apr_array_make(scratch_pool, 1,
                                            sizeof(svn_merge_range_t *));

  /* Given the requested merge of SOURCE->rev1:rev2 might there be any
     part of this range required for subtrees but not for the target? */
  requested_ranges = svn_rangelist__initialize(MIN(source->loc1->rev,
                                                   source->loc2->rev),
                                               MAX(source->loc1->rev,
                                                   source->loc2->rev),
                                               TRUE, scratch_pool);
  SVN_ERR(svn_rangelist_remove(&subtree_gap_ranges,
                               root_child->remaining_ranges,
                               requested_ranges, FALSE, scratch_pool));



( run in 0.401 second using v1.01-cache-2.11-cpan-39bf76dae61 )