Index: subversion/include/private/svn_fs_mergeinfo.h =================================================================== --- subversion/include/private/svn_fs_mergeinfo.h (revision 25324) +++ subversion/include/private/svn_fs_mergeinfo.h (working copy) @@ -51,15 +51,18 @@ /* Get the merge info for the set of PATHS (an array of absolute-in-the-fs paths) under ROOT and return it in *MERGEINFO, mapping char * paths to char * strings with mergeinfo, allocated in - POOL. If INCLUDE_PARENTS is TRUE elide for mergeinfo. If a path - has no mergeinfo, just return no info for that path. Return an - error if the mergeinfo store does not exist or doesn't use the - 'mergeinfo' schema. */ + POOL. If INCLUDE_PARENTS is TRUE look for inherited mergeinfo from a + path's nearest ancestor. If PARENTS_ONLY is TRUE ignore mergeinfo on + a path and look only for inherited mergeinfo. If PARENTS_ONLY is TRUE + the value of INCLUDE_PARENTS is ignored. If a path has no mergeinfo, + just return no info for that path. Return an error if the mergeinfo + store does not exist or doesn't use the 'mergeinfo' schema. */ svn_error_t * svn_fs_mergeinfo__get_mergeinfo(apr_hash_t **mergeinfo, svn_fs_root_t *root, const apr_array_header_t *paths, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool); /* Get the combined mergeinfo for the tree under each one of PATHS Index: subversion/include/svn_fs.h =================================================================== --- subversion/include/svn_fs.h (revision 25324) +++ subversion/include/svn_fs.h (working copy) @@ -1205,7 +1205,9 @@ * @a paths indicate the paths you are requesting information for * * When @a include_parents is @c TRUE, include inherited merge info - * from parent directories of @a paths. + * from parent directories of @a paths. When @a parents_only is + * @c TRUE, ignore @a include_parents and include only inherited + * merge info. * * Do any necessary temporary allocation in @a pool. * @@ -1215,6 +1217,7 @@ svn_fs_root_t *root, const apr_array_header_t *paths, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool); /** Retrieve combined mergeinfo for multiple nodes, and their children. Index: subversion/include/svn_ra.h =================================================================== --- subversion/include/svn_ra.h (revision 25324) +++ subversion/include/svn_ra.h (working copy) @@ -791,7 +791,9 @@ * info available. Allocate the returned values in @a pool. * * When @a include_parents is @c TRUE, include inherited merge info - * from parent directories of @a paths. + * from parent directories of @a paths. When @a parents_only is + * @c TRUE, ignore @a include_parents and include only inherited + * merge info. * * If @a revision is @c SVN_INVALID_REVNUM, it defaults to youngest. * @@ -802,6 +804,7 @@ const apr_array_header_t *paths, svn_revnum_t revision, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool); /** Index: subversion/include/svn_repos.h =================================================================== --- subversion/include/svn_repos.h (revision 25324) +++ subversion/include/svn_repos.h (working copy) @@ -1238,7 +1238,9 @@ * merge info visible or available. * * When @a include_parents is @c TRUE, include inherited merge info - * from parent directories of @a paths. + * from parent directories of @a paths. When @a parents_only is + * @c TRUE, ignore @a include_parents and include only inherited + * merge info. * * If @a revision is @c SVN_INVALID_REVNUM, it defaults to youngest. * @@ -1257,6 +1259,7 @@ const apr_array_header_t *paths, svn_revnum_t revision, svn_boolean_t include_parents, + svn_boolean_t parents_only, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); Index: subversion/libsvn_client/copy.c =================================================================== --- subversion/libsvn_client/copy.c (revision 25324) +++ subversion/libsvn_client/copy.c (working copy) @@ -419,7 +419,7 @@ SVN_ERR(get_implied_mergeinfo(ra_session, target_mergeinfo, src_rel_path, src_path, src_revnum, pool)); SVN_ERR(svn_client__get_repos_mergeinfo(ra_session, &src_mergeinfo, - src_path, src_revnum, pool)); + src_path, src_revnum, FALSE, pool)); /* Combine and return all merge info. */ if (src_mergeinfo) Index: subversion/libsvn_client/merge.c =================================================================== --- subversion/libsvn_client/merge.c (revision 25324) +++ subversion/libsvn_client/merge.c (working copy) @@ -1038,18 +1038,25 @@ /* Retrieve the direct merge info for the TARGET_WCPATH from the WC's merge info prop, or that inherited from its nearest ancestor if the - target has no info of its own. If no merge info can be obtained - from the WC get if from repository (opening a new RA session if - RA_SESSION is NULL). Store any merge info obtained for the target - (reflected by *ENTRY, which is also acquired and returned by this - function) in *TARGET_MERGEINFO, if no merge info is found - *TARGET_MERGEINFO is NULL. If TARGET_WCPATH inherited its merge info - from a working copy ancestor or if it was obtained from the repository, - set *INDIRECT to TRUE, set it to FALSE *otherwise. */ + target has no info of its own. + + If no merge info can be obtained from the WC or REPOS_ONLY is TRUE, + get it from the repository (opening a new RA session if RA_SESSION is + NULL). Store any merge info obtained for the target (reflected by + *ENTRY, which is also acquired and returned by this function) in + *TARGET_MERGEINFO, if no merge info is found *TARGET_MERGEINFO is NULL. + + If PARENT_ONLY is TRUE only return inherited merge info. + + If TARGET_WCPATH inherited its merge info from a working copy ancestor + or if it was obtained from the repository, set *INDIRECT to TRUE, set it + to FALSE *otherwise. */ static svn_error_t * get_wc_or_repos_mergeinfo(apr_hash_t **target_mergeinfo, const svn_wc_entry_t **entry, svn_boolean_t *indirect, + svn_boolean_t repos_only, + svn_boolean_t parent_only, svn_ra_session_t *ra_session, const char *target_wcpath, svn_wc_adm_access_t *adm_access, @@ -1103,9 +1110,12 @@ ### differs from that of TARGET_WCPATH, and if those paths are ### directories, their children as well. */ - SVN_ERR(get_wc_mergeinfo(target_mergeinfo, indirect, FALSE, *entry, - target_wcpath, NULL, NULL, adm_access, ctx, - pool)); + if (repos_only) + *target_mergeinfo = NULL; + else + SVN_ERR(get_wc_mergeinfo(target_mergeinfo, indirect, parent_only, *entry, + target_wcpath, NULL, NULL, adm_access, ctx, + pool)); /* If there in no WC merge info check the repository. */ if (*target_mergeinfo == NULL) @@ -1118,7 +1128,7 @@ SVN_ERR(svn_client__get_repos_mergeinfo(ra_session, &repos_mergeinfo, repos_rel_path, target_rev, - pool)); + parent_only, pool)); if (repos_mergeinfo) { @@ -1133,33 +1143,112 @@ /*** Eliding merge info. ***/ -/* Helper for svn_client__elide_mergeinfo and elide_children. +/* Helper for elide_mergeinfo(). - Compare PARENT_MERGEINFO and CHILD_MERGEINFO to see if they are - identical (they may be NULL). If PATH_SUFFIX is not NULL append - PATH_SUFFIX to each path in PARENT_MERGEINFO before performing the - comparison. - - Set *ELIDES to whether PARENT_MERGEINFO and CHILD_MERGEINFO are - identical (TRUE or FALSE). */ + Find all paths in CHILD_MERGEINFO which map to empty revision ranges + and copy these from CHILD_MERGEINFO to *EMPTY_RANGE_MERGEINFO iff + PARENT_MERGEINFO is NULL or does not have merge info for the path in + question. + + All mergeinfo in CHILD_MERGEINFO not copied to *EMPTY_RANGE_MERGEINFO + is copied to *NONEMPTY_RANGE_MERGEINFO. + + *EMPTY_RANGE_MERGEINFO and *NONEMPTY_RANGE_MERGEINFO are set to empty + hashes if nothing is copied into them. + + All copied hashes are deep copies allocated in POOL. */ static svn_error_t * -mergeinfo_elides(svn_boolean_t *elides, - apr_hash_t *parent_mergeinfo, - apr_hash_t *child_mergeinfo, - const char *path_suffix, - apr_pool_t *pool) +get_child_only_empty_revs(apr_hash_t **empty_range_mergeinfo, + apr_hash_t **nonempty_range_mergeinfo, + apr_hash_t *child_mergeinfo, + apr_hash_t *parent_mergeinfo, + apr_pool_t *pool) { - apr_pool_t *subpool = svn_pool_create(pool); - apr_hash_t *mergeinfo; + *empty_range_mergeinfo = apr_hash_make(pool); + *nonempty_range_mergeinfo = apr_hash_make(pool); - if (parent_mergeinfo == NULL || child_mergeinfo == NULL || - apr_hash_count(parent_mergeinfo) != apr_hash_count(child_mergeinfo)) + if (child_mergeinfo) { - *elides = FALSE; - return SVN_NO_ERROR; + apr_hash_index_t *hi; + void *child_val; + const void *child_key; + apr_array_header_t *child_range; + const char *child_path; + + /* Iterate through CHILD_MERGEINFO looking for merge info with empty + revision ranges. */ + for (hi = apr_hash_first(pool, child_mergeinfo); hi; + hi = apr_hash_next(hi)) + { + apr_hash_this(hi, &child_key, NULL, &child_val); + child_path = child_key; + child_range = child_val; + + /* Copy paths with empty revision ranges which don't exist in + PARENT_MERGEINFO from CHILD_MERGEINFO to *EMPTY_RANGE_MERGEINFO. + Copy everything else to *NONEMPTY_RANGE_MERGEINFO. */ + if (child_range->nelts == 0 + && (parent_mergeinfo == NULL + || apr_hash_get(parent_mergeinfo, child_path, + APR_HASH_KEY_STRING) == NULL)) + { + apr_hash_set(*empty_range_mergeinfo, + apr_pstrdup(pool, child_path), + APR_HASH_KEY_STRING, + svn_rangelist_dup(child_range, pool)); + } + else + { + apr_hash_set(*nonempty_range_mergeinfo, + apr_pstrdup(pool, child_path), + APR_HASH_KEY_STRING, + svn_rangelist_dup(child_range, pool)); + } + } } + return SVN_NO_ERROR; +} - if (path_suffix) +/* Helper for svn_client__elide_mergeinfo() and elide_children(). + + Given a working copy PATH, its mergeinfo hash CHILD_MERGEINFO, and the + mergeinfo of PATH's nearest ancestor PARENT_MERGEINFO, compare + CHILD_MERGEINFO to PARENT_MERGEINFO to see if the former elides to + the latter, following the elision rules described in + svn_client__elide_mergeinfo()'s docstring. If elision (full or partial) + does occur, then update PATH's mergeinfo appropriately. If CHILD_MERGEINFO + is NULL, do nothing. + + If PATH_SUFFIX and PARENT_MERGEINFO are not NULL append PATH_SUFFIX to each + path in PARENT_MERGEINFO before performing the comparison. */ +static svn_error_t * +elide_mergeinfo(apr_hash_t *parent_mergeinfo, + apr_hash_t *child_mergeinfo, + const char *path, + const char *path_suffix, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + apr_pool_t *subpool; + apr_hash_t *mergeinfo, *child_empty_mergeinfo, *child_nonempty_mergeinfo; + svn_boolean_t equal_mergeinfo; + + /* A tri-state value describing the various types of elision possible for + svn:mergeinfo set on a WC path. */ + enum wc_elision_type + { + elision_type_none, /* No elision occurs. */ + elision_type_partial, /* Paths that exist only in CHILD_MERGEINFO and + map to empty revision ranges elide. */ + elision_type_full /* All merge info in CHILD_MERGEINFO elides. */ + } elision_type = elision_type_none; + + /* Easy out: No child merge info to elide. */ + if (child_mergeinfo == NULL) + return SVN_NO_ERROR; + + subpool = svn_pool_create(pool); + if (path_suffix && parent_mergeinfo) { apr_hash_index_t *hi; void *val; @@ -1173,9 +1262,8 @@ hi = apr_hash_next(hi)) { apr_hash_this(hi, &key, NULL, &val); - path = svn_path_join((const char *) key, path_suffix, pool); + path = svn_path_join((const char *) key, path_suffix, subpool); rangelist = val; - apr_hash_set(mergeinfo, path, APR_HASH_KEY_STRING, rangelist); } } @@ -1184,9 +1272,76 @@ mergeinfo = parent_mergeinfo; } - SVN_ERR(svn_mergeinfo__equals(elides, child_mergeinfo, mergeinfo, - subpool)); + /* Separate any merge info with empty rev ranges for paths that exist only + in CHILD_MERGEINFO and store these in CHILD_EMPTY_MERGEINFO. */ + SVN_ERR(get_child_only_empty_revs(&child_empty_mergeinfo, + &child_nonempty_mergeinfo, + child_mergeinfo, mergeinfo, subpool)); + + /* If *all* paths in CHILD_MERGEINFO map to empty revision ranges and none + of these paths exist in PARENT_MERGEINFO full elision occurs; if only + *some* of the paths in CHILD_MERGEINFO meet this criteria we know, at a + minimum, partial elision will occur. */ + if (apr_hash_count(child_empty_mergeinfo) > 0) + elision_type = apr_hash_count(child_nonempty_mergeinfo) == 0 + ? elision_type_full : elision_type_partial; + if (elision_type == elision_type_none && mergeinfo) + { + apr_hash_t *parent_empty_mergeinfo, *parent_nonempty_mergeinfo; + + /* Full elision also occurs if MERGEINFO and TARGET_MERGEINFO are + equal except for paths unique to MERGEINFO that map to empty + revision ranges. + + Separate any merge info with empty rev ranges for paths that exist + only in MERGEINFO and store these in PARENT_EMPTY_MERGEINFO and + compare that with CHILD_MERGEINFO. */ + SVN_ERR(get_child_only_empty_revs(&parent_empty_mergeinfo, + &parent_nonempty_mergeinfo, + mergeinfo, child_mergeinfo, + subpool)); + SVN_ERR(svn_mergeinfo__equals(&equal_mergeinfo, + parent_nonempty_mergeinfo, + child_mergeinfo, subpool)); + if (equal_mergeinfo) + elision_type = elision_type_full; + } + + if (elision_type != elision_type_full && mergeinfo) + { + /* If no determination of elision status has been made yet or we know + only that partial elision occurs, compare CHILD_NONEMPTY_MERGEINFO + with the PATH_SUFFIX tweaked version of PARENT_MERGEINFO for equality. + + If we determined that at least partial elision occurs, full elision + may still yet occur if CHILD_NONEMPTY_MERGEINFO, which no longer + contains any paths unique to it that map to empty revision ranges, + is equivalent to PARENT_MERGEINFO. */ + svn_boolean_t equal_mergeinfo; + + SVN_ERR(svn_mergeinfo__equals(&equal_mergeinfo, + child_nonempty_mergeinfo, + mergeinfo, subpool)); + if (equal_mergeinfo) + elision_type = elision_type_full; + } + + switch (elision_type) + { + case elision_type_full: + SVN_ERR(svn_wc_prop_set2(SVN_PROP_MERGE_INFO, NULL, path, adm_access, + TRUE, subpool)); + break; + case elision_type_partial: + SVN_ERR(svn_client__record_wc_mergeinfo(path, + child_nonempty_mergeinfo, + adm_access, subpool)); + break; + default: + break; /* Leave mergeinfo on PATH as-is. */ + } + svn_pool_destroy(subpool); return SVN_NO_ERROR; } @@ -1226,7 +1381,7 @@ { apr_hash_t *child_mergeinfo; const char *child_wcpath; - svn_boolean_t elides, switched; + svn_boolean_t switched; const svn_wc_entry_t *child_entry; svn_sort__item_t *item = &APR_ARRAY_IDX(children_with_mergeinfo, i, svn_sort__item_t); @@ -1283,13 +1438,9 @@ path_prefix = svn_path_dirname(path_prefix, iterpool); } - SVN_ERR(mergeinfo_elides(&elides, target_mergeinfo, - child_mergeinfo, path_suffix, - iterpool)); - if (elides) - SVN_ERR(svn_wc_prop_set2(SVN_PROP_MERGE_INFO, NULL, - child_wcpath, adm_access, TRUE, - iterpool)); + SVN_ERR(elide_mergeinfo(target_mergeinfo, child_mergeinfo, + child_wcpath, path_suffix, adm_access, + iterpool)); } } svn_pool_destroy(iterpool); @@ -1300,18 +1451,19 @@ svn_error_t * svn_client__elide_mergeinfo(const char *target_wcpath, - const char *elision_limit_path, + const char *wc_elision_limit_path, const svn_wc_entry_t *entry, svn_wc_adm_access_t *adm_access, svn_client_ctx_t *ctx, apr_pool_t *pool) { /* Check for first easy out: We are already at the limit path. */ - if (!elision_limit_path || strcmp(target_wcpath, elision_limit_path) != 0) + if (!wc_elision_limit_path + || strcmp(target_wcpath, wc_elision_limit_path) != 0) { apr_hash_t *target_mergeinfo; apr_hash_t *mergeinfo = NULL; - svn_boolean_t inherited, elides, switched; + svn_boolean_t inherited, switched; const char *walk_path; /* Check for second easy out: TARGET_WCPATH is switched. */ @@ -1321,8 +1473,8 @@ /* Get the TARGET_WCPATH's explicit mergeinfo. */ SVN_ERR(get_wc_mergeinfo(&target_mergeinfo, &inherited, FALSE, entry, target_wcpath, - elision_limit_path - ? elision_limit_path : NULL, + wc_elision_limit_path + ? wc_elision_limit_path : NULL, &walk_path, adm_access, ctx, pool)); /* If TARGET_WCPATH has no explicit mergeinfo, there's nothing to @@ -1330,23 +1482,33 @@ if (inherited || target_mergeinfo == NULL) return SVN_NO_ERROR; - /* Get TARGET_WCPATH's inherited mergeinfo. */ + /* Get TARGET_WCPATH's inherited mergeinfo from the WC. */ SVN_ERR(get_wc_mergeinfo(&mergeinfo, &inherited, TRUE, entry, target_wcpath, - elision_limit_path - ? elision_limit_path : NULL, + wc_elision_limit_path + ? wc_elision_limit_path : NULL, &walk_path, adm_access, ctx, pool)); - /* If TARGET_WCPATH has no inherited mergeinfo, there's - nowhere to elide to, we're done. */ - if (mergeinfo == NULL) + /* If TARGET_WCPATH inherited no mergeinfo from the WC and we are + not limiting our search to the working copy then check if it + inherits any from the repos. */ + if (!mergeinfo && !wc_elision_limit_path) + { + const svn_wc_entry_t *tmp_entry; + + SVN_ERR(get_wc_or_repos_mergeinfo(&mergeinfo, &tmp_entry, + &inherited, TRUE, TRUE, + NULL, target_wcpath, + adm_access, ctx, pool)); + } + + /* If there is nowhere to elide TARGET_WCPATH's mergeinfo to and + the elision is limited, then we are done.*/ + if (!mergeinfo && wc_elision_limit_path) return SVN_NO_ERROR; - SVN_ERR(mergeinfo_elides(&elides, mergeinfo, target_mergeinfo, - NULL, pool)); - if (elides) - SVN_ERR(svn_wc_prop_set2(SVN_PROP_MERGE_INFO, NULL, - target_wcpath, adm_access, TRUE, pool)); + SVN_ERR(elide_mergeinfo(mergeinfo, target_mergeinfo, target_wcpath, + NULL, adm_access, pool)); } } return SVN_NO_ERROR; @@ -1601,8 +1763,6 @@ SVN_ERR(svn_rangelist_merge(&rangelist, ranges, subpool)); } /* Update the merge info by adjusting the path's rangelist. */ - if (rangelist->nelts == 0) - rangelist = NULL; apr_hash_set(mergeinfo, rel_path, APR_HASH_KEY_STRING, rangelist); if (is_revert && apr_hash_count(mergeinfo) == 0) @@ -1869,7 +2029,7 @@ if (notify_b.same_urls) { SVN_ERR(get_wc_or_repos_mergeinfo(&target_mergeinfo, &entry, - &indirect, ra_session, + &indirect, FALSE, FALSE, ra_session, target_wcpath, adm_access, ctx, pool)); @@ -2228,7 +2388,7 @@ return SVN_NO_ERROR; SVN_ERR(get_wc_or_repos_mergeinfo(&target_mergeinfo, &entry, - &indirect, ra_session1, + &indirect, FALSE, FALSE, ra_session1, target_wcpath, adm_access, ctx, pool)); @@ -2978,7 +3138,8 @@ SVN_ERR(svn_client__path_relative_to_root(&repos_rel_path, path_or_url, NULL, ra_session, NULL, pool)); SVN_ERR(svn_client__get_repos_mergeinfo(ra_session, mergeinfo, - repos_rel_path, rev, pool)); + repos_rel_path, rev, FALSE, + pool)); } else { @@ -2989,8 +3150,8 @@ SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, path_or_url, FALSE, 0, ctx->cancel_func, ctx->cancel_baton, pool)); - SVN_ERR(get_wc_or_repos_mergeinfo(mergeinfo, &entry, &indirect, - NULL, path_or_url, adm_access, + SVN_ERR(get_wc_or_repos_mergeinfo(mergeinfo, &entry, &indirect, FALSE, + FALSE, NULL, path_or_url, adm_access, ctx, pool)); SVN_ERR(svn_wc_adm_close(adm_access)); } Index: subversion/libsvn_client/mergeinfo.c =================================================================== --- subversion/libsvn_client/mergeinfo.c (revision 25324) +++ subversion/libsvn_client/mergeinfo.c (working copy) @@ -38,13 +38,14 @@ apr_hash_t **target_mergeinfo, const char *rel_path, svn_revnum_t rev, + svn_boolean_t inherited_only, apr_pool_t *pool) { apr_hash_t *repos_mergeinfo; apr_array_header_t *rel_paths = apr_array_make(pool, 1, sizeof(rel_path)); APR_ARRAY_PUSH(rel_paths, const char *) = rel_path; SVN_ERR(svn_ra_get_mergeinfo(ra_session, &repos_mergeinfo, rel_paths, - rev, TRUE, pool)); + rev, TRUE, inherited_only, pool)); /* Grab only the merge info provided for REL_PATH. */ if (repos_mergeinfo) Index: subversion/libsvn_client/mergeinfo.h =================================================================== --- subversion/libsvn_client/mergeinfo.h (revision 25324) +++ subversion/libsvn_client/mergeinfo.h (working copy) @@ -28,6 +28,7 @@ apr_hash_t **target_mergeinfo, const char *rel_path, svn_revnum_t rev, + svn_boolean_t inherited_only, apr_pool_t *pool); /* Parse any merge info from the WCPATH's ENTRY and store it in @@ -51,14 +52,38 @@ apr_pool_t *pool); /* Elide any svn:mergeinfo set on TARGET_PATH to its nearest working - copy ancestor with equivalent mergeinfo. If ELISION_LIMIT_PATH is NULL - check up to the root of the working copy for elidable mergeinfo, - otherwise check as far as ELISION_LIMIT_PATH. TARGET_PATH and - ELISION_LIMIT_PATH, if it exists, must both be absolute or relative to - the working directory. */ + copy (or possibly repository) ancestor with equivalent mergeinfo. + + If WC_ELISION_LIMIT_PATH is NULL check up to the root of the working copy + for an elision destination, if none is found check the repository, + otherwise check as far as WC_ELISION_LIMIT_PATH within the working copy. + TARGET_PATH and WC_ELISION_LIMIT_PATH, if it exists, must both be absolute + or relative to the working directory. + + If TARGET_WCPATH's merge info and its nearest ancestor's merge info + differ by paths existing only in TARGET_PATH's merge info that map to + empty revision ranges, then the merge info between the two is considered + equivalent and elision occurs. If the merge info between the two still + differs then partial elision occurs: only the paths mapped to empty + revision ranges in TARGET_WCPATH's merge info elide. + + If TARGET_WCPATH's merge info and its nearest ancestor's merge info + differ by paths existing only in the ancestor's merge info that map to + empty revision ranges, then the merge info between the two is considered + equivalent and elision occurs. + + If TARGET_WCPATH's merge info consists only of paths mapped to empty + revision ranges and none of these paths exist in TARGET_WCPATH's nearest + ancestor, then elision occurs. + + If TARGET_WCPATH's merge info consists only of paths mapped to empty + revision ranges and TARGET_WCPATH has no working copy or repository + ancestor with merge info (WC_ELISION_LIMIT_PATH must be NULL to ensure the + repository is checked), then elision occurs. + */ svn_error_t * svn_client__elide_mergeinfo(const char *target_wcpath, - const char *elision_limit_path, + const char *wc_elision_limit_path, const svn_wc_entry_t *entry, svn_wc_adm_access_t *adm_access, svn_client_ctx_t *ctx, Index: subversion/libsvn_fs/fs-loader.c =================================================================== --- subversion/libsvn_fs/fs-loader.c (revision 25324) +++ subversion/libsvn_fs/fs-loader.c (working copy) @@ -811,10 +811,11 @@ svn_fs_root_t *root, const apr_array_header_t *paths, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool) { return root->vtable->get_mergeinfo(minfohash, root, paths, include_parents, - pool); + parents_only, pool); } svn_error_t * Index: subversion/libsvn_fs/fs-loader.h =================================================================== --- subversion/libsvn_fs/fs-loader.h (revision 25324) +++ subversion/libsvn_fs/fs-loader.h (working copy) @@ -311,6 +311,7 @@ svn_fs_root_t *root, const apr_array_header_t *paths, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool); svn_error_t *(*get_mergeinfo_for_tree)(apr_hash_t **mergeinfo, svn_fs_root_t *root, Index: subversion/libsvn_fs_util/mergeinfo-sqlite-index.c =================================================================== --- subversion/libsvn_fs_util/mergeinfo-sqlite-index.c (revision 25324) +++ subversion/libsvn_fs_util/mergeinfo-sqlite-index.c (working copy) @@ -199,6 +199,7 @@ apr_hash_t *result, apr_hash_t *cache, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool); /* Represents "no merge info". */ @@ -232,7 +233,7 @@ apr_hash_t *cache = apr_hash_make(pool); remove_mergeinfo = TRUE; SVN_ERR(get_mergeinfo_for_path(db, path, new_rev, mergeinfo, cache, - TRUE, pool)); + TRUE, FALSE, pool)); mergeinfo = apr_hash_get(mergeinfo, path, APR_HASH_KEY_STRING); if (mergeinfo == NULL) /* There was previously no merge info, inherited or explicit, @@ -511,6 +512,7 @@ apr_hash_t *result, apr_hash_t *cache, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool) { apr_hash_t *path_mergeinfo; @@ -518,50 +520,56 @@ int sqlite_result; sqlite_int64 lastmerged_rev; - path_mergeinfo = apr_hash_get(cache, path, APR_HASH_KEY_STRING); - if (path_mergeinfo != NULL) + if (!parents_only) { - if (path_mergeinfo != NEGATIVE_CACHE_RESULT && result) - apr_hash_set(result, path, APR_HASH_KEY_STRING, path_mergeinfo); - return SVN_NO_ERROR; - } + path_mergeinfo = apr_hash_get(cache, path, APR_HASH_KEY_STRING); + if (path_mergeinfo != NULL) + { + if (path_mergeinfo != NEGATIVE_CACHE_RESULT && result) + apr_hash_set(result, path, APR_HASH_KEY_STRING, path_mergeinfo); + return SVN_NO_ERROR; + } - /* See if we have a mergeinfo_changed record for this path. If not, - then it can't have mergeinfo. */ - SQLITE_ERR(sqlite3_prepare(db, - "SELECT MAX(revision) FROM mergeinfo_changed " - "WHERE path = ? AND revision <= ?;", - -1, &stmt, NULL), db); + /* See if we have a mergeinfo_changed record for this path. If not, + then it can't have mergeinfo. */ + SQLITE_ERR(sqlite3_prepare(db, + "SELECT MAX(revision) FROM mergeinfo_changed " + "WHERE path = ? AND revision <= ?;", + -1, &stmt, NULL), db); - SQLITE_ERR(sqlite3_bind_text(stmt, 1, path, -1, SQLITE_TRANSIENT), db); - SQLITE_ERR(sqlite3_bind_int64(stmt, 2, rev), db); - sqlite_result = sqlite3_step(stmt); - if (sqlite_result != SQLITE_ROW) - return svn_error_create(SVN_ERR_FS_SQLITE_ERROR, NULL, - sqlite3_errmsg(db)); + SQLITE_ERR(sqlite3_bind_text(stmt, 1, path, -1, SQLITE_TRANSIENT), db); + SQLITE_ERR(sqlite3_bind_int64(stmt, 2, rev), db); + sqlite_result = sqlite3_step(stmt); + if (sqlite_result != SQLITE_ROW) + return svn_error_create(SVN_ERR_FS_SQLITE_ERROR, NULL, + sqlite3_errmsg(db)); - lastmerged_rev = sqlite3_column_int64(stmt, 0); - SQLITE_ERR(sqlite3_finalize(stmt), db); + lastmerged_rev = sqlite3_column_int64(stmt, 0); + SQLITE_ERR(sqlite3_finalize(stmt), db); - /* If we've got mergeinfo data, transform it from the db into a - mergeinfo hash */ - if (lastmerged_rev > 0) - { - SVN_ERR(parse_mergeinfo_from_db(db, path, lastmerged_rev, - &path_mergeinfo, pool)); - if (path_mergeinfo) + /* If we've got mergeinfo data, transform it from the db into a + mergeinfo hash */ + if (lastmerged_rev > 0) { - if (result) - apr_hash_set(result, path, APR_HASH_KEY_STRING, path_mergeinfo); - apr_hash_set(cache, path, APR_HASH_KEY_STRING, path_mergeinfo); + SVN_ERR(parse_mergeinfo_from_db(db, path, lastmerged_rev, + &path_mergeinfo, pool)); + if (path_mergeinfo) + { + if (result) + apr_hash_set(result, path, APR_HASH_KEY_STRING, + path_mergeinfo); + apr_hash_set(cache, path, APR_HASH_KEY_STRING, path_mergeinfo); + } + else + apr_hash_set(cache, path, APR_HASH_KEY_STRING, + NEGATIVE_CACHE_RESULT); + return SVN_NO_ERROR; } - else - apr_hash_set(cache, path, APR_HASH_KEY_STRING, NEGATIVE_CACHE_RESULT); - return SVN_NO_ERROR; - } + } /* !parents_only */ - /* If this path has no mergeinfo, and we are asked to, check our parent */ - if (lastmerged_rev == 0 && include_parents) + /* If we want only this path's parent's mergeinfo or this path has no + mergeinfo, and we are asked to, check our parent */ + if ((lastmerged_rev == 0 && include_parents) || parents_only) { svn_stringbuf_t *parentpath; @@ -579,7 +587,7 @@ SVN_ERR(get_mergeinfo_for_path(db, parentpath->data, rev, NULL, cache, include_parents, - pool)); + FALSE, pool)); path_mergeinfo = apr_hash_get(cache, parentpath->data, APR_HASH_KEY_STRING); if (path_mergeinfo == NEGATIVE_CACHE_RESULT) @@ -669,6 +677,7 @@ svn_fs_root_t *root, const apr_array_header_t *paths, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool) { apr_hash_t *mergeinfo_cache = apr_hash_make(pool); @@ -686,7 +695,8 @@ { const char *path = APR_ARRAY_IDX(paths, i, const char *); SVN_ERR(get_mergeinfo_for_path(db, path, rev, *mergeinfo, - mergeinfo_cache, include_parents, pool)); + mergeinfo_cache, include_parents, + parents_only, pool)); } return SVN_NO_ERROR; @@ -698,13 +708,15 @@ svn_fs_root_t *root, const apr_array_header_t *paths, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool) { sqlite3 *db; int i; SVN_ERR(open_db(&db, root->fs->path, pool)); - SVN_ERR(get_mergeinfo(db, mergeinfo, root, paths, include_parents, pool)); + SVN_ERR(get_mergeinfo(db, mergeinfo, root, paths, include_parents, + parents_only, pool)); SQLITE_ERR(sqlite3_close(db), db); for (i = 0; i < paths->nelts; i++) @@ -739,7 +751,7 @@ int i; SVN_ERR(open_db(&db, root->fs->path, pool)); - SVN_ERR(get_mergeinfo(db, mergeinfo, root, paths, TRUE, pool)); + SVN_ERR(get_mergeinfo(db, mergeinfo, root, paths, TRUE, FALSE, pool)); rev = REV_ROOT_REV(root); Index: subversion/libsvn_ra/ra_loader.c =================================================================== --- subversion/libsvn_ra/ra_loader.c (revision 25324) +++ subversion/libsvn_ra/ra_loader.c (working copy) @@ -591,10 +591,12 @@ const apr_array_header_t *paths, svn_revnum_t revision, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool) { return session->vtable->get_mergeinfo(session, mergeinfo, paths, - revision, include_parents, pool); + revision, include_parents, + parents_only, pool); } svn_error_t *svn_ra_do_update2(svn_ra_session_t *session, Index: subversion/libsvn_ra/ra_loader.h =================================================================== --- subversion/libsvn_ra/ra_loader.h (revision 25324) +++ subversion/libsvn_ra/ra_loader.h (working copy) @@ -109,6 +109,7 @@ const apr_array_header_t *paths, svn_revnum_t revision, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool); svn_error_t *(*do_update)(svn_ra_session_t *session, const svn_ra_reporter3_t **reporter, Index: subversion/libsvn_ra_dav/mergeinfo.c =================================================================== --- subversion/libsvn_ra_dav/mergeinfo.c (revision 25324) +++ subversion/libsvn_ra_dav/mergeinfo.c (working copy) @@ -156,6 +156,7 @@ const apr_array_header_t *paths, svn_revnum_t revision, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool) { svn_error_t *err; @@ -182,6 +183,13 @@ "")); } + if (parents_only) + { + svn_stringbuf_appendcstr(request_body, + apr_psprintf(pool, + "")); + } + if (paths) { for (i = 0; i < paths->nelts; i++) Index: subversion/libsvn_ra_dav/ra_dav.h =================================================================== --- subversion/libsvn_ra_dav/ra_dav.h (revision 25324) +++ subversion/libsvn_ra_dav/ra_dav.h (working copy) @@ -246,6 +246,7 @@ const apr_array_header_t *paths, svn_revnum_t revision, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool); svn_error_t * svn_ra_dav__do_update(svn_ra_session_t *session, Index: subversion/libsvn_ra_local/ra_plugin.c =================================================================== --- subversion/libsvn_ra_local/ra_plugin.c (revision 25324) +++ subversion/libsvn_ra_local/ra_plugin.c (working copy) @@ -645,13 +645,14 @@ const apr_array_header_t *paths, svn_revnum_t revision, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool) { svn_ra_local__session_baton_t *baton = session->priv; apr_hash_t *tmp_mergeinfo; SVN_ERR(svn_repos_fs_get_mergeinfo(&tmp_mergeinfo, baton->repos, paths, - revision, include_parents, + revision, include_parents, parents_only, NULL, NULL, pool)); if (tmp_mergeinfo != NULL && apr_hash_count(tmp_mergeinfo) > 0) { Index: subversion/libsvn_ra_serf/mergeinfo.c =================================================================== --- subversion/libsvn_ra_serf/mergeinfo.c (revision 25324) +++ subversion/libsvn_ra_serf/mergeinfo.c (working copy) @@ -163,6 +163,7 @@ const apr_array_header_t *paths, svn_revnum_t revision, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool) { svn_error_t *err; @@ -201,6 +202,12 @@ NULL, session->bkt_alloc); } + if (parents_only) + { + svn_ra_serf__add_tag_buckets(buckets, "S:parents-only", + NULL, session->bkt_alloc); + } + if (paths) { for (i = 0; i < paths->nelts; i++) Index: subversion/libsvn_ra_svn/client.c =================================================================== --- subversion/libsvn_ra_svn/client.c (revision 25324) +++ subversion/libsvn_ra_svn/client.c (working copy) @@ -1022,6 +1022,7 @@ const apr_array_header_t *paths, svn_revnum_t revision, svn_boolean_t include_parents, + svn_boolean_t parents_only, apr_pool_t *pool) { svn_ra_svn__session_baton_t *sess_baton = session->priv; @@ -1044,8 +1045,8 @@ path = APR_ARRAY_IDX(paths, i, const char *); SVN_ERR(svn_ra_svn_write_cstring(conn, pool, path)); } - SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)b)", revision, - include_parents)); + SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "!)(?r)bb)", revision, + include_parents, parents_only)); SVN_ERR(handle_auth_request(sess_baton, pool)); SVN_ERR(svn_ra_svn_read_cmd_response(conn, pool, "(?l)", &mergeinfo_tuple)); Index: subversion/libsvn_repos/fs-wrap.c =================================================================== --- subversion/libsvn_repos/fs-wrap.c (revision 25324) +++ subversion/libsvn_repos/fs-wrap.c (working copy) @@ -556,6 +556,7 @@ const apr_array_header_t *paths, svn_revnum_t rev, svn_boolean_t include_parents, + svn_boolean_t parents_only, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool) @@ -606,7 +607,7 @@ the change itself. */ if (readable_paths->nelts > 0) SVN_ERR(svn_fs_get_mergeinfo(mergeinfo, root, readable_paths, - include_parents, pool)); + include_parents, parents_only, pool)); else *mergeinfo = NULL; Index: subversion/mod_dav_svn/reports/mergeinfo.c =================================================================== --- subversion/mod_dav_svn/reports/mergeinfo.c (revision 25324) +++ subversion/mod_dav_svn/reports/mergeinfo.c (working copy) @@ -53,6 +53,7 @@ /* These get determined from the request document. */ svn_revnum_t rev = SVN_INVALID_REVNUM; svn_boolean_t include_parents = FALSE; /* off by default */ + svn_boolean_t parents_only = FALSE; /* off by default */ apr_array_header_t *paths = apr_array_make(resource->pool, 0, sizeof(const char *)); @@ -78,6 +79,8 @@ rev = SVN_STR_TO_REV(dav_xml_get_cdata(child, resource->pool, 1)); else if (strcmp(child->name, "include-parents") == 0) include_parents = 1; + else if (strcmp(child->name, "parents-only") == 0) + parents_only = 1; else if (strcmp(child->name, "path") == 0) { const char *target; @@ -100,7 +103,7 @@ serr = svn_repos_fs_get_mergeinfo(&mergeinfo, repos->repos, paths, rev, - include_parents, + include_parents, parents_only, dav_svn__authz_read_func(&arb), &arb, resource->pool); if (serr) Index: subversion/svnserve/serve.c =================================================================== --- subversion/svnserve/serve.c (revision 25324) +++ subversion/svnserve/serve.c (working copy) @@ -1457,10 +1457,10 @@ int i; apr_hash_index_t *hi; const char *path, *info; - svn_boolean_t include_parents; + svn_boolean_t include_parents, parents_only; - SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)b", &paths, &rev, - &include_parents)); + SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "l(?r)bb", &paths, &rev, + &include_parents, &parents_only)); /* Canonicalize the paths which merge info has been requested for. */ canonical_paths = apr_array_make(pool, paths->nelts, sizeof(const char *)); @@ -1478,7 +1478,7 @@ SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repos, canonical_paths, rev, - include_parents, + include_parents, parents_only, authz_check_access_cb_func(b), b, pool)); if (mergeinfo != NULL && apr_hash_count(mergeinfo) > 0) Index: subversion/tests/cmdline/merge_tests.py =================================================================== --- subversion/tests/cmdline/merge_tests.py (revision 25324) +++ subversion/tests/cmdline/merge_tests.py (working copy) @@ -6833,7 +6833,7 @@ merge_to_switched_path, XFail(merge_to_path_with_switched_children), merge_with_implicit_target_file, - XFail(empty_rev_range_mergeinfo), + empty_rev_range_mergeinfo, XFail(detect_copy_src_for_target_with_multiple_ancestors), prop_add_to_child_with_mergeinfo, diff_repos_does_not_update_mergeinfo, Index: subversion/tests/libsvn_fs/fs-test.c =================================================================== --- subversion/tests/libsvn_fs/fs-test.c (revision 25324) +++ subversion/tests/libsvn_fs/fs-test.c (working copy) @@ -4489,10 +4489,12 @@ paths = apr_array_make(pool, 1, sizeof (const char *)); APR_ARRAY_PUSH(paths, const char *) = "/A/E"; - SVN_ERR(svn_fs_get_mergeinfo(&result, revision_root, paths, TRUE, pool)); + SVN_ERR(svn_fs_get_mergeinfo(&result, revision_root, paths, TRUE, FALSE, + pool)); paths = apr_array_make(pool, 1, sizeof (const char *)); APR_ARRAY_PUSH(paths, const char *) = "/A/B/E"; - SVN_ERR(svn_fs_get_mergeinfo(&result, revision_root, paths, TRUE, pool)); + SVN_ERR(svn_fs_get_mergeinfo(&result, revision_root, paths, TRUE, FALSE, + pool)); return SVN_NO_ERROR; }