Index: subversion/libsvn_client/client.h =================================================================== --- subversion/libsvn_client/client.h (revision 24575) +++ subversion/libsvn_client/client.h (working copy) @@ -239,6 +239,20 @@ svn_wc_adm_access_t *adm_access, apr_pool_t *pool); +/* Elide any svn:mergeinfo set on TARGET_CHILD_PATH to its nearest working + copy ancestor with equivalent mergeinfo. If ELIDE_TARGET is TRUE check + up to the root of the working copy for elidable mergeinfo. If + ELIDE_TARGET is false check up to and including the immedediate children + of TARGET_PATH. */ +svn_error_t * +svn_client__elide_mergeinfo(const char *target_child_wcpath, + const char *target_path, + svn_boolean_t elide_target, + const svn_wc_entry_t *entry, + svn_wc_adm_access_t *adm_access, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + /* ---------------------------------------------------------------- */ /*** RA callbacks ***/ Index: subversion/libsvn_client/merge.c =================================================================== --- subversion/libsvn_client/merge.c (revision 24575) +++ subversion/libsvn_client/merge.c (working copy) @@ -850,13 +850,25 @@ /*** Retrieving merge info. ***/ /* Find explicit or inherited WC merge info for WCPATH, and return it - in *MERGEINFO. Set *INHERITED to whether the merge info was - inherited (TRUE or FALSE). */ + in *MERGEINFO. Set *INHERITED to whether the merge info was inherited + (TRUE or FALSE). + + If NEAREST_ANCESTOR is TRUE look only for inherited merge info and ignore + explicit merge info on WCPATH. + + If LIMIT_PATH is not NULL don't look for inherited merge info any higher + than LIMIT_PATH. + + If WALKED_PATH is not NULL set *WALKED_PATH to the path climbed from + WCPATH to find inherited mergeinfo or "" if none was found. */ static svn_error_t * get_wc_merge_info(apr_hash_t **mergeinfo, svn_boolean_t *inherited, + svn_boolean_t nearest_ancestor, const svn_wc_entry_t *entry, const char *wcpath, + const char *limit_path, + const char **walked_path, svn_wc_adm_access_t *adm_access, svn_client_ctx_t *ctx, apr_pool_t *pool) @@ -866,30 +878,44 @@ while (TRUE) { - /* Look for merge info on WCPATH. If there isn't any, walk - towards the root of the WC until we encounter either (a) an - unversioned directory, or (b) merge info. If we encounter (b), - use that inherited merge info as our baseline. */ - SVN_ERR(svn_client__parse_merge_info(&wc_mergeinfo, entry, wcpath, - adm_access, ctx, pool)); + /* Don't look for explicit mergeinfo on WCPATH if we are only + interested in inherited mergeinfo. */ + if (nearest_ancestor) + { + wc_mergeinfo = apr_hash_make(pool); + nearest_ancestor = FALSE; + } + else + { + /* Look for merge info on WCPATH. If there isn't any, walk + towards the root of the WC until we encounter either (a) an + unversioned directory, or (b) merge info. If we encounter (b), + use that inherited merge info as our baseline. */ + SVN_ERR(svn_client__parse_merge_info(&wc_mergeinfo, entry, wcpath, + adm_access, ctx, pool)); + } /* Subsequent svn_wc_adm_access_t need to be opened with an absolute path so we can walk up and out of the WC - if necessary. */ + if necessary. If we are using LIMIT_PATH it needs to + be absolute too. */ #if defined(WIN32) || defined(__CYGWIN__) /* On Windows a path is also absolute when it starts with 'H:/' where 'H' is any upper or lower case letter. */ - if ((strlen(wcpath) > 0 && wcpath[0] != '/') - || !(strlen(wcpath) > 2 - && (wcpath[1] == ':') - && (wcpath[2] == '/') - && ((wcpath[0] >= 'A' && wcpath[0] <= 'Z') - || (wcpath[0] >= 'a' && wcpath[0] <= 'z')))) + if ((strlen(wcpath) == 0) + || ((strlen(wcpath) > 0 && wcpath[0] != '/') + && !(strlen(wcpath) > 2 + && (wcpath[1] == ':') + && (wcpath[2] == '/') + && ((wcpath[0] >= 'A' && wcpath[0] <= 'Z') + || (wcpath[0] >= 'a' && wcpath[0] <= 'z'))))) #else if (!(strlen(wcpath) > 0 && wcpath[0] == '/')) #endif /* WIN32 or Cygwin */ { SVN_ERR(svn_path_get_absolute(&wcpath, wcpath, pool)); + if (limit_path) + SVN_ERR(svn_path_get_absolute(&limit_path, limit_path, pool)); } if (apr_hash_count(wc_mergeinfo) == 0 && @@ -897,6 +923,10 @@ { svn_error_t *err; + /* Don't look any higher than the limit path. */ + if (limit_path && strcmp(limit_path, wcpath) == 0) + break; + /* No explicit merge info on this path. Look higher up the directory tree while keeping track of what we've walked. */ walk_path = svn_path_join(svn_path_basename(wcpath, pool), @@ -956,6 +986,9 @@ } } + if (walked_path) + *walked_path = walk_path; + return SVN_NO_ERROR; } @@ -1017,8 +1050,9 @@ ### differs from that of TARGET_WCPATH, and if those paths are ### directories, their children as well. */ - SVN_ERR(get_wc_merge_info(target_mergeinfo, inherited, *entry, - target_wcpath, adm_access, ctx, pool)); + SVN_ERR(get_wc_merge_info(target_mergeinfo, inherited, FALSE, *entry, + target_wcpath, NULL, NULL, adm_access, ctx, + pool)); /* If there in no WC merge info check the repository. */ if (!apr_hash_count(*target_mergeinfo)) @@ -1041,6 +1075,213 @@ /*-----------------------------------------------------------------------*/ +/*** Eliding merge info. ***/ + +/* Helper for svn_client__elide_mergeinfo and elide_children. + + Compare PARENT_MERGEINFO and CHILD_MERGEINFO to see if they are + identical. 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). */ +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) +{ + apr_pool_t *subpool = svn_pool_create(pool); + apr_hash_t *mergeinfo, *deleted, *added; + + if (path_suffix) + { + apr_hash_index_t *hi; + void *val; + const void *key; + const char *path; + apr_array_header_t *rangelist; + + mergeinfo = apr_hash_make(subpool); + + for (hi = apr_hash_first(subpool, parent_mergeinfo); hi; + hi = apr_hash_next(hi)) + { + apr_hash_this(hi, &key, NULL, &val); + path = svn_path_join((const char *) key, path_suffix, pool); + rangelist = val; + + apr_hash_set(mergeinfo, path, APR_HASH_KEY_STRING, rangelist); + } + } + else + { + mergeinfo = parent_mergeinfo; + } + + SVN_ERR(svn_mergeinfo_diff(&deleted, &added, mergeinfo, child_mergeinfo, + subpool)); + *elides = (apr_hash_count(deleted) || apr_hash_count(added)) + ? FALSE : TRUE; + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + +/* Helper for svn_client_merge3 and svn_client_merge_peg3 + + CHILDREN_WITH_MERGEINFO is filled with child paths of TARGET_WCPATH which + have svn:mergeinfo set on them, arranged in depth first order (see + discover_and_merge_children). + + For each path in CHILDREN_WITH_MERGEINFO which is an immediate child of + TARGET_WCPATH, check if that path's mergeinfo elides to TARGET_WCPATH. + If it does elide, clear all mergeinfo from the path. */ +static svn_error_t * +elide_children(apr_array_header_t *children_with_mergeinfo, + const char *target_wcpath, + const svn_wc_entry_t *entry, + svn_wc_adm_access_t *adm_access, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + if (children_with_mergeinfo && children_with_mergeinfo->nelts) + { + int i; + const char *last_immediate_child; + apr_hash_t *target_mergeinfo; + apr_pool_t *subpool = svn_pool_create(pool); + + /* Get mergeinfo for the target of the merge. */ + SVN_ERR(svn_client__parse_merge_info(&target_mergeinfo, entry, + target_wcpath, adm_access, + ctx, pool)); + + /* For each immediate child of the merge target check if + its merginfo elides to the target. */ + for (i = 0; i < children_with_mergeinfo->nelts; i++) + { + apr_hash_t *child_mergeinfo; + const char *child_wcpath; + const char *path_suffix, *path_prefix; + svn_boolean_t elides; + svn_sort__item_t *item = &APR_ARRAY_IDX(children_with_mergeinfo, + i, + svn_sort__item_t); + svn_pool_clear(subpool); + child_wcpath = item->key; + + if (i == 0) + { + /* children_with_mergeinfo is sorted depth + first so first path might be the target of + the merge if the target had mergeinfo prior + to the start of the merge. */ + if (strcmp(target_wcpath, child_wcpath) == 0) + { + last_immediate_child = NULL; + continue; + } + last_immediate_child = child_wcpath; + } + else if (last_immediate_child + && svn_path_is_ancestor(last_immediate_child, child_wcpath)) + { + /* Not an immediate child. */ + continue; + } + else + { + /* Found the first (last_immediate_child == NULL) + or another immediate child. */ + last_immediate_child = child_wcpath; + } + + SVN_ERR(svn_client__parse_merge_info(&child_mergeinfo, entry, + child_wcpath, adm_access, + ctx, subpool)); + path_prefix = svn_path_dirname(child_wcpath, subpool); + path_suffix = svn_path_basename(child_wcpath, subpool); + + while (strcmp(path_prefix, target_wcpath)) + { + path_suffix = svn_path_join(svn_path_basename(path_prefix, + subpool), + path_suffix, subpool); + path_prefix = svn_path_dirname(path_prefix, subpool); + } + + SVN_ERR(mergeinfo_elides(&elides, target_mergeinfo, + child_mergeinfo, path_suffix, subpool)); + + if (elides) + SVN_ERR(svn_wc_prop_set2(SVN_PROP_MERGE_INFO, NULL, + child_wcpath, adm_access, TRUE, subpool)); + } + svn_pool_destroy(subpool); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__elide_mergeinfo(const char *target_child_wcpath, + const char *target_path, + svn_boolean_t elide_target, + const svn_wc_entry_t *entry, + svn_wc_adm_access_t *adm_access, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + if (strcmp(target_child_wcpath, target_path) || elide_target) + { + apr_hash_t *target_mergeinfo; + apr_hash_t *mergeinfo = NULL; + svn_boolean_t inherited; + const char *walk_path; + + /* Get the target's explicit or inherited mergeinfo. */ + SVN_ERR(get_wc_merge_info(&target_mergeinfo, &inherited, FALSE, entry, + target_child_wcpath, target_path, + &walk_path, adm_access, ctx, pool)); + + /* No ancestor exists with mergeinfo we are done. */ + if (apr_hash_count(target_mergeinfo) == 0) + return SVN_NO_ERROR; + + /* If the target had explicit mergeinfo also get the mergeinfo + inherited from it's nearest ancestor. This might be the + originating merge's target or some intermediate dir. */ + if (!inherited) + { + SVN_ERR(get_wc_merge_info(&mergeinfo, &inherited, TRUE, entry, + target_child_wcpath, + elide_target ? NULL : target_path, + &walk_path, adm_access, ctx, pool)); + } + + if (strcmp(svn_path_join(target_path, walk_path, pool), + target_child_wcpath) || elide_target) + { + svn_boolean_t elides; + + /* Nearest ancestor is intermediate node already visited in depth + first traversal *or* we are eliding a merge target. */ + SVN_ERR(mergeinfo_elides(&elides, mergeinfo, target_mergeinfo, + NULL, pool)); + + if (elides) + SVN_ERR(svn_wc_prop_set2(SVN_PROP_MERGE_INFO, NULL, + target_child_wcpath, adm_access, TRUE, + pool)); + } + } + return SVN_NO_ERROR; +} + +/*-----------------------------------------------------------------------*/ + /*** Doing the actual merging. ***/ /* Calculate a rangelist of svn_merge_range_t *'s -- for use by @@ -1645,6 +1886,9 @@ } } + SVN_ERR(svn_client__elide_mergeinfo(target_wcpath, merge_b->target, FALSE, + entry, adm_access, ctx, pool)); + /* Sleep to ensure timestamp integrity. */ svn_sleep_for_timestamps(); @@ -1920,6 +2164,9 @@ } } + SVN_ERR(svn_client__elide_mergeinfo(target_wcpath, merge_b->target, FALSE, + entry, adm_access, ctx, pool)); + /* Sleep to ensure timestamp integrity. */ svn_sleep_for_timestamps(); @@ -2163,8 +2410,17 @@ &merge_cmd_baton, children_with_mergeinfo, pool)); + + /* The merge of the actual target is complete, see if the target's + immediate children's mergeinfo elides to the target. */ + SVN_ERR(elide_children(children_with_mergeinfo, target_wcpath, + entry, adm_access, ctx, pool)); } + /* The final mergeinfo on TARGET_WCPATH may itself elide. */ + SVN_ERR(svn_client__elide_mergeinfo(target_wcpath, merge_cmd_baton.target, + TRUE, entry, adm_access, ctx, pool)); + SVN_ERR(svn_wc_adm_close(adm_access)); return SVN_NO_ERROR; @@ -2332,8 +2588,17 @@ &merge_cmd_baton, children_with_mergeinfo, pool)); + + /* The merge of the actual target is complete, see if the target's + immediate children's mergeinfo elides to the target. */ + SVN_ERR(elide_children(children_with_mergeinfo, target_wcpath, + entry, adm_access, ctx, pool)); } + /* The final mergeinfo on TARGET_WCPATH may itself elide. */ + SVN_ERR(svn_client__elide_mergeinfo(target_wcpath, merge_cmd_baton.target, + TRUE, entry, adm_access, ctx, pool)); + SVN_ERR(svn_wc_adm_close(adm_access)); return SVN_NO_ERROR; Index: subversion/libsvn_client/switch.c =================================================================== --- subversion/libsvn_client/switch.c (revision 24575) +++ subversion/libsvn_client/switch.c (working copy) @@ -29,6 +29,7 @@ #include "svn_time.h" #include "svn_path.h" #include "svn_config.h" +#include "svn_sorts.h" #include "client.h" #include "svn_private_config.h" @@ -168,6 +169,49 @@ err = svn_client__handle_externals(traversal_info, FALSE, use_sleep, ctx, pool); + if (!err) + { + /* Check if any mergeinfo on PATH or any its children elides as a + result of the switch. */ + apr_hash_t *children_with_mergeinfo_hash = apr_hash_make(pool); + svn_wc_adm_access_t *path_adm_access; + SVN_ERR(svn_wc_adm_probe_retrieve(&path_adm_access, adm_access, path, + pool)); + err = svn_client__get_prop_from_wc(children_with_mergeinfo_hash, + SVN_PROP_MERGE_INFO, path, FALSE, + entry, path_adm_access, TRUE, ctx, + pool); + if (err) + { + if (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE) + svn_error_clear(err); + } + else + { + int i; + apr_array_header_t *children_with_mergeinfo = + svn_sort__hash(children_with_mergeinfo_hash, + svn_sort_compare_items_as_paths, pool); + + /* children_with_mergeinfo is sorted in depth first order. + To minimize svn_client__elide_mergeinfo()'s crawls up the + working copy from each child, run through the array backwards, + effectively doing a right-left post-order traversal. */ + for (i = children_with_mergeinfo->nelts -1; i >= 0; i--) + { + const char *child_wcpath; + svn_sort__item_t *item = + &APR_ARRAY_IDX(children_with_mergeinfo, i, + svn_sort__item_t); + child_wcpath = item->key; + /* Elide to target yes, but also elide target? */ + SVN_ERR(svn_client__elide_mergeinfo(child_wcpath, path, TRUE, + entry, adm_access, ctx, + pool)); + } + } + } + /* Sleep to ensure timestamp integrity (we do this regardless of errors in the actual switch operation(s)). */ if (sleep_here) Index: subversion/libsvn_client/update.c =================================================================== --- subversion/libsvn_client/update.c (revision 24575) +++ subversion/libsvn_client/update.c (working copy) @@ -32,6 +32,7 @@ #include "svn_path.h" #include "svn_pools.h" #include "svn_io.h" +#include "svn_sorts.h" #include "client.h" #include "svn_private_config.h" @@ -69,6 +70,9 @@ const char *diff3_cmd; svn_ra_session_t *ra_session; svn_wc_adm_access_t *dir_access; + svn_wc_adm_access_t *path_adm_access; + apr_array_header_t *children_with_mergeinfo; + apr_hash_t *children_with_mergeinfo_hash; svn_config_t *cfg = ctx->config ? apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG, APR_HASH_KEY_STRING) : NULL; @@ -234,8 +238,63 @@ if (sleep_here) svn_sleep_for_timestamps(); - SVN_ERR(svn_wc_adm_close(adm_access)); + if (adm_open_depth) + { + SVN_ERR(svn_wc_adm_probe_retrieve(&path_adm_access, adm_access, path, + pool)); + } + else + { + /* A depth other than infinity means we need to open a new + access to lock PATH's children for possible elision. */ + SVN_ERR(svn_wc_adm_close(adm_access)); + SVN_ERR(svn_wc_adm_open3(&path_adm_access, NULL, path, TRUE, -1, + ctx->cancel_func, ctx->cancel_baton, + pool)); + } + /* Check if any mergeinfo on PATH or any its children elides as a + result of the update. */ + children_with_mergeinfo_hash = apr_hash_make(pool); + + err = svn_client__get_prop_from_wc(children_with_mergeinfo_hash, + SVN_PROP_MERGE_INFO, path, FALSE, + entry, path_adm_access, TRUE, ctx, + pool); + if (err) + { + if (err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE) + svn_error_clear(err); + else + return err; + } + else + { + int i; + children_with_mergeinfo = svn_sort__hash(children_with_mergeinfo_hash, + svn_sort_compare_items_as_paths, + pool); + + /* children_with_mergeinfo is sorted in depth first order. + To minimize svn_client__elide_mergeinfo()'s crawls up the + working copy from each child, run through the array backwards, + effectively doing a right-left post-order traversal. */ + for (i = children_with_mergeinfo->nelts -1; i >= 0; i--) + { + const char *child_wcpath; + svn_sort__item_t *item = &APR_ARRAY_IDX(children_with_mergeinfo, + i, + svn_sort__item_t); + child_wcpath = item->key; + /* Elide to target yes, but also elide target? */ + SVN_ERR(svn_client__elide_mergeinfo(child_wcpath, path, TRUE, + entry, adm_access, ctx, + pool)); + } + } + + SVN_ERR(svn_wc_adm_close(adm_open_depth ? path_adm_access : adm_access)); + /* Let everyone know we're finished here. */ if (ctx->notify_func2) { @@ -252,7 +311,7 @@ /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = revnum; - + return SVN_NO_ERROR; } Index: subversion/tests/cmdline/merge_tests.py =================================================================== --- subversion/tests/cmdline/merge_tests.py (revision 24575) +++ subversion/tests/cmdline/merge_tests.py (working copy) @@ -4373,7 +4373,7 @@ }) expected_disk = wc.State('', { '' : Item(props={SVN_PROP_MERGE_INFO : '/A/B:5-8'}), - 'F/E' : Item(props={SVN_PROP_MERGE_INFO : '/A/B/F/E:5-8'}), + 'F/E' : Item(), 'F/E/alpha' : Item(new_content_for_alpha), 'F/E/beta' : Item("This is the file 'beta'.\n"), 'F' : Item(), @@ -4531,17 +4531,19 @@ 'umlaut' : Item(status='A '), }) + # All the local svn:mergeinfo under A/copy-of-D elides + # to A/copy-of-D. expected_status = wc.State(short_copy_of_A_D_path, { '' : Item(status=' M', wc_rev=copy_of_A_D_wc_rev), 'G' : Item(status=' ', wc_rev=copy_of_A_D_wc_rev), - 'G/pi' : Item(status='MM', wc_rev=copy_of_A_D_wc_rev), - 'G/rho' : Item(status='MM', wc_rev=copy_of_A_D_wc_rev), - 'G/tau' : Item(status='MM', wc_rev=copy_of_A_D_wc_rev), + 'G/pi' : Item(status='M ', wc_rev=copy_of_A_D_wc_rev), + 'G/rho' : Item(status='M ', wc_rev=copy_of_A_D_wc_rev), + 'G/tau' : Item(status='M ', wc_rev=copy_of_A_D_wc_rev), 'H' : Item(status=' ', wc_rev=copy_of_A_D_wc_rev), - 'H/chi' : Item(status='MM', wc_rev=copy_of_A_D_wc_rev), - 'H/omega' : Item(status='MM', wc_rev=copy_of_A_D_wc_rev), - 'H/psi' : Item(status='MM', wc_rev=copy_of_A_D_wc_rev), - 'gamma' : Item(status='MM', wc_rev=copy_of_A_D_wc_rev), + 'H/chi' : Item(status='M ', wc_rev=copy_of_A_D_wc_rev), + 'H/omega' : Item(status='M ', wc_rev=copy_of_A_D_wc_rev), + 'H/psi' : Item(status='M ', wc_rev=copy_of_A_D_wc_rev), + 'gamma' : Item(status='M ', wc_rev=copy_of_A_D_wc_rev), 'umlaut' : Item(status='A ', copied='+', wc_rev='-'), }) @@ -4551,21 +4553,14 @@ expected_disk = wc.State('', { '' : Item(props={SVN_PROP_MERGE_INFO : '/A/D:' + merged_rangelist}), 'G' : Item(), - 'G/pi' : Item(new_content_for_pi, - props={SVN_PROP_MERGE_INFO : '/A/D/G/pi:' + merged_rangelist}), - 'G/rho' : Item(new_content_for_rho, - props={SVN_PROP_MERGE_INFO : '/A/D/G/rho:' + merged_rangelist}), - 'G/tau' : Item(new_content_for_tau, - props={SVN_PROP_MERGE_INFO : '/A/D/G/tau:' + merged_rangelist}), + 'G/pi' : Item(new_content_for_pi), + 'G/rho' : Item(new_content_for_rho), + 'G/tau' : Item(new_content_for_tau), 'H' : Item(), - 'H/chi' : Item(new_content_for_chi, - props={SVN_PROP_MERGE_INFO : '/A/D/H/chi:' + merged_rangelist}), - 'H/omega' : Item(new_content_for_omega, - props={SVN_PROP_MERGE_INFO : '/A/D/H/omega:' + merged_rangelist}), - 'H/psi' : Item(new_content_for_psi, - props={SVN_PROP_MERGE_INFO : '/A/D/H/psi:' + merged_rangelist}), - 'gamma' : Item(new_content_for_gamma, - props={SVN_PROP_MERGE_INFO : '/A/D/gamma:' + merged_rangelist}), + 'H/chi' : Item(new_content_for_chi,), + 'H/omega' : Item(new_content_for_omega,), + 'H/psi' : Item(new_content_for_psi,), + 'gamma' : Item(new_content_for_gamma,), 'umlaut' : Item(), }) expected_skip = wc.State(short_copy_of_A_D_path, { }) @@ -5458,7 +5453,7 @@ avoid_repeated_merge_on_subtree_with_merge_info, obey_reporter_api_semantics_while_doing_subtree_merges, mergeinfo_inheritance, - XFail(mergeinfo_elision), + mergeinfo_elision, mergeinfo_inheritance_and_discontinuous_ranges, XFail(merge_to_target_with_copied_children), ] Index: subversion/tests/cmdline/switch_tests.py =================================================================== --- subversion/tests/cmdline/switch_tests.py (revision 24575) +++ subversion/tests/cmdline/switch_tests.py (working copy) @@ -1676,7 +1676,7 @@ forced_switch, forced_switch_failures, switch_scheduled_add, - XFail(mergeinfo_switch_elision), + mergeinfo_switch_elision, ] if __name__ == '__main__': Index: subversion/tests/cmdline/update_tests.py =================================================================== --- subversion/tests/cmdline/update_tests.py (revision 24575) +++ subversion/tests/cmdline/update_tests.py (working copy) @@ -3159,7 +3159,7 @@ update_wc_with_replaced_file, update_with_obstructing_additions, update_conflicted, - XFail(mergeinfo_update_elision), + mergeinfo_update_elision, ] if __name__ == '__main__':