Index: subversion/libsvn_client/merge.c =================================================================== --- subversion/libsvn_client/merge.c (revision 25175) +++ subversion/libsvn_client/merge.c (working copy) @@ -1135,32 +1135,96 @@ /*** Eliding merge info. ***/ -/* Helper for svn_client__elide_mergeinfo and elide_children. +/* Helper for mergeinfo_elides(). - 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. + Find all paths in CHILD_MERGEINFO which map to empty revision ranges + and move these from CHILD_MERGEINFO to *EMPTY_RANGE_MERGEINFO iff + PARENT_MERGEINFO is NULL or does not have merge info for the path in + question. If no merge info is moved, *EMPTY_RANGE_MERGEINFO is set to + an empty hash. */ +static svn_error_t * +get_child_only_empty_revs(apr_hash_t **empty_range_mergeinfo, + apr_hash_t *child_mergeinfo, + apr_hash_t *parent_mergeinfo, + apr_pool_t *pool) +{ + *empty_range_mergeinfo = apr_hash_make(pool); + if (child_mergeinfo) + { + apr_hash_index_t *hi; + void *child_val; + const void *child_key; + + /* 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); + + if (((apr_array_header_t *)child_val)->nelts == 0) + { + /* "Move" paths with empty revision ranges which don't + exist in PARENT_MERGEINFO from CHILD_MERGEINFO to + *EMPTY_RANGE_MERGEINFO. */ + if (parent_mergeinfo == NULL + || !apr_hash_get(parent_mergeinfo, child_key, + APR_HASH_KEY_STRING)) + { + apr_hash_set(*empty_range_mergeinfo, child_key, + APR_HASH_KEY_STRING, child_val); + apr_hash_set(child_mergeinfo, child_key, + APR_HASH_KEY_STRING, NULL); + } + } + } + } + return SVN_NO_ERROR; +} + +/* A tri-state value returned by mergeinfo_elides(). */ +enum elision_type +{ + elision_type_unknown, /* Elision status not determined - never returned by + mergeinfo_elides(), used internally only. */ + 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. */ +}; + +/* Helper for svn_client__elide_mergeinfo() and elide_children(). + + Compare PARENT_MERGEINFO and CHILD_MERGEINFO to see if they are identical. + If CHILD_MERGEINFO is NULL, ELISION_TYPE is set to ELISION_TYPE_NONE. If + PATH_SUFFIX and PARENT_MERGEINFO are 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). */ + identical (TRUE or FALSE). + + If CHILD_MERGEINFO consists only of paths mapped to empty revision ranges, + and these paths do not exist in PARENT_MERGEINFO ????. */ static svn_error_t * -mergeinfo_elides(svn_boolean_t *elides, +mergeinfo_elides(enum elision_type *elision_type, apr_hash_t *parent_mergeinfo, apr_hash_t *child_mergeinfo, const char *path_suffix, apr_pool_t *pool) { - apr_pool_t *subpool = svn_pool_create(pool); - apr_hash_t *mergeinfo; - - if (parent_mergeinfo == NULL || child_mergeinfo == NULL || - apr_hash_count(parent_mergeinfo) != apr_hash_count(child_mergeinfo)) + apr_pool_t *subpool; + apr_hash_t *mergeinfo, *child_empty_mergeinfo; + svn_boolean_t equal_mergeinfo; + + /* Easy out: No child merge info to elide or no parent to elide to. */ + if (parent_mergeinfo == NULL || child_mergeinfo == NULL) { - *elides = FALSE; + *elision_type = elision_type_none; return SVN_NO_ERROR; } + subpool = svn_pool_create(pool); if (path_suffix) { apr_hash_index_t *hi; @@ -1186,9 +1250,50 @@ 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_mergeinfo, + mergeinfo, pool)); + + *elision_type = elision_type_unknown; + /* 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_mergeinfo) == 0 + ? elision_type_full : elision_type_partial; + } + + if (*elision_type == elision_type_partial + || *elision_type == elision_type_unknown) + { + /*If no determination of elision status has been made yet or we know + only that partial elision occurs, compare CHILD_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_MERGEINFO, which no longer contains any + paths unique to it that map to empty revision ranges, is equivalent to + PARENT_MERGEINFO. */ + SVN_ERR(svn_mergeinfo__equals(&equal_mergeinfo, child_mergeinfo, + mergeinfo, subpool)); + if (*elision_type == elision_type_partial) + { + if (equal_mergeinfo) + *elision_type = elision_type_full; + } + else + { + *elision_type = equal_mergeinfo ? elision_type_full + : elision_type_none; + } + } + +/* DELETE BEFORE COMMIT !!!!!!!!!!!! */ assert(*elision_type != elision_type_unknown); /* DELETE BEFORE COMMIT !!!!!!!!!!!! */ + svn_pool_destroy(subpool); return SVN_NO_ERROR; } @@ -1228,7 +1333,8 @@ { apr_hash_t *child_mergeinfo; const char *child_wcpath; - svn_boolean_t elides, switched; + svn_boolean_t switched; + enum elision_type elision_type; const svn_wc_entry_t *child_entry; svn_sort__item_t *item = &APR_ARRAY_IDX(children_with_mergeinfo, i, svn_sort__item_t); @@ -1285,13 +1391,18 @@ path_prefix = svn_path_dirname(path_prefix, iterpool); } - SVN_ERR(mergeinfo_elides(&elides, target_mergeinfo, + SVN_ERR(mergeinfo_elides(&elision_type, target_mergeinfo, child_mergeinfo, path_suffix, iterpool)); - if (elides) + if (elision_type == elision_type_full) SVN_ERR(svn_wc_prop_set2(SVN_PROP_MERGE_INFO, NULL, child_wcpath, adm_access, TRUE, iterpool)); + else if (elision_type == elision_type_partial) + SVN_ERR(svn_client__record_wc_mergeinfo(child_wcpath, + child_mergeinfo, + adm_access, + iterpool)); } } svn_pool_destroy(iterpool); @@ -1313,7 +1424,8 @@ { apr_hash_t *target_mergeinfo; apr_hash_t *mergeinfo = NULL; - svn_boolean_t inherited, elides, switched; + svn_boolean_t inherited, switched; + enum elision_type elision_type; const char *walk_path; /* Check for second easy out: TARGET_WCPATH is switched. */ @@ -1332,7 +1444,7 @@ 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 @@ -1344,11 +1456,15 @@ if (mergeinfo == NULL) return SVN_NO_ERROR; - SVN_ERR(mergeinfo_elides(&elides, mergeinfo, target_mergeinfo, + SVN_ERR(mergeinfo_elides(&elision_type, mergeinfo, target_mergeinfo, NULL, pool)); - if (elides) + if (elision_type == elision_type_full) SVN_ERR(svn_wc_prop_set2(SVN_PROP_MERGE_INFO, NULL, target_wcpath, adm_access, TRUE, pool)); + else if (elision_type == elision_type_partial) + SVN_ERR(svn_client__record_wc_mergeinfo(target_wcpath, + target_mergeinfo, + adm_access, pool)); } } return SVN_NO_ERROR; @@ -1603,8 +1719,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) Index: subversion/libsvn_client/mergeinfo.h =================================================================== --- subversion/libsvn_client/mergeinfo.h (revision 25175) +++ subversion/libsvn_client/mergeinfo.h (working copy) @@ -51,11 +51,19 @@ 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 ancestor with equivalent mergeinfo. + + 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 and only the paths mapped to empty + revision ranges in TARGET_WCPATH's merge info elide. + + 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. */ svn_error_t * svn_client__elide_mergeinfo(const char *target_wcpath, const char *elision_limit_path,