Index: subversion/include/svn_path.h =================================================================== --- subversion/include/svn_path.h (revision 27753) +++ subversion/include/svn_path.h (working copy) @@ -456,9 +456,52 @@ /** Return TRUE iff @a path looks like a valid absolute URL. */ svn_boolean_t svn_path_is_url(const char *path); +/** + * Return true iff @a url is a relative URL. Specifically that it starts + * with the characters "^/". + */ +svn_boolean_t svn_path_is_relative_url(const char *url); + +/** + * Return true iff there is at least one relative URL in @a paths. + */ +svn_boolean_t svn_path_array_has_relative_url(const apr_array_header_t *paths); + /** Return @c TRUE iff @a path is URI-safe, @c FALSE otherwise. */ svn_boolean_t svn_path_is_uri_safe(const char *path); +/** + * Convert the possibly relative url in @a relative_url to an absolute + * url using @a repos_root_url and stick it in @a absolute_url. If the + * @a relative_url starts with the characters '^/', they are replaced by + * the repository root url, otherwise the @a relative_url is returned. + * Backpaths ("../") within the URL are also supported. + * + * If @a relative_url is NULL, then the current value of the dereferenced + * @a absolute_url is used as the relative URL. + */ +svn_error_t * +svn_path_resolve_relative_url(const char **absolute_url, + const char *relative_url, + const char *repos_root_url, + apr_pool_t *pool); + +/** + * Takes an array containing possibly relative URLs in @a relative_urls and + * converts them into an array containing absolute URLS in @a absolute_urls. + * This simplifies working with arrays of URLs and is basically an interative + * wrapper around svn_path_resolve_relative_url. + * + * If @a relative_urls is NULL, then the current value of the dereferenced + * @a absolute_urls is used as the relative URL array. + */ +svn_error_t * +svn_path_resolve_relative_url_array(apr_array_header_t **absolute_urls, + apr_array_header_t *relative_urls, + const char *repos_root_url, + apr_pool_t *pool); + + /** Return a URI-encoded copy of @a path, allocated in @a pool. */ const char *svn_path_uri_encode(const char *path, apr_pool_t *pool); Index: subversion/include/svn_wc.h =================================================================== --- subversion/include/svn_wc.h (revision 27753) +++ subversion/include/svn_wc.h (working copy) @@ -4574,6 +4574,47 @@ /** @} */ + +/** + * Converts the possibly relative URL given in @a relative_url to an absolute + * URL that is allocated in @a pool and returned in @a absolute_url. The + * repository root URL is determined from the working copy path given in + * @ wc_path. + * + * This function is designed to make getting the repository root URL easier + * and then passes the conversion off to the svn_path_resolve_relative_url() + * function to perform the actual URL manipulation, so see it for conversion + * details. + * + * Note that @a relative_url may be NULL, in which case the dereferenced value + * of @a absolute_url will be used as the relative URL. + */ +svn_error_t * +svn_wc_get_absolute_url(const char **absolute_url, + const char *relative_url, + const char *wc_path, + apr_pool_t *pool); + +/** + * Converts the possibly relative URLs given in @a relative_urls to absolute + * URLs that are allocated in @a pool and returned in @a absolute_urls. The + * repository root URL is determined from the working copy path given in + * @ wc_path. + * + * This function is designed to make getting the repository root URL easier + * and then passes the conversion off to the svn_path_resolve_relative_url_array() + * function to perform the actual URL manipulation, so see it for conversion + * details. + * + * Note that @a relative_urls may be NULL, in which case the dereferenced value + * of @a absolute_urls will be used as the relative URL array. + */ +svn_error_t * +svn_wc_get_absolute_url_array(apr_array_header_t **absolute_urls, + apr_array_header_t *relative_urls, + const char *wc_path, + apr_pool_t *pool); + #ifdef __cplusplus } #endif /* __cplusplus */ Index: subversion/libsvn_wc/util.c =================================================================== --- subversion/libsvn_wc/util.c (revision 27753) +++ subversion/libsvn_wc/util.c (working copy) @@ -289,3 +289,112 @@ return SVN_NO_ERROR; } + +svn_error_t * +svn_wc_get_absolute_url(const char **absolute_url, + const char *relative_url, + const char *wc_path, + apr_pool_t *pool) +{ + const char *repos_root; + const svn_wc_entry_t *entry; + svn_wc_adm_access_t *adm_access = NULL; + svn_error_t *err = SVN_NO_ERROR; + const char *canon_wc_path; + + if (relative_url == NULL) + relative_url = *absolute_url; + + if (svn_path_is_relative_url(relative_url)) + { + canon_wc_path = svn_path_canonicalize(wc_path, pool); + + SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, canon_wc_path, + FALSE, 0, NULL, NULL, pool)); + + if ((err = svn_wc__entry_versioned(&entry, canon_wc_path, adm_access, + FALSE, pool))) + goto cleanup; + + repos_root = entry->repos; + + if ((err = svn_path_resolve_relative_url(absolute_url, relative_url, + repos_root, pool))) + goto cleanup; + } + /* If the caller gave us an actual relative_url value, then they expect + * there to be a new string in *absolute_url with the "resolved" URL. + */ + else if (relative_url != *absolute_url) + { + *absolute_url = apr_pstrdup(pool, relative_url); + } + /* Else just leave the *absolute_url value alone. */ + +cleanup: + if (adm_access) + { + svn_error_t *err2 = svn_wc_adm_close(adm_access); + if (! err) + err = err2; + else + svn_error_clear(err2); + } + + return err; +} + +svn_error_t * +svn_wc_get_absolute_url_array(apr_array_header_t **absolute_urls, + apr_array_header_t *relative_urls, + const char *wc_path, + apr_pool_t *pool) +{ + const char *repos_root; + const svn_wc_entry_t *entry; + svn_wc_adm_access_t *adm_access = NULL; + svn_error_t *err = SVN_NO_ERROR; + const char *canon_wc_path; + + if (relative_urls == NULL) + relative_urls = *absolute_urls; + + if (svn_path_array_has_relative_url(relative_urls)) + { + canon_wc_path = svn_path_canonicalize(wc_path, pool); + + SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, canon_wc_path, + FALSE, 0, NULL, NULL, pool)); + + if ((err = svn_wc__entry_versioned(&entry, canon_wc_path, adm_access, + FALSE, pool))) + goto cleanup; + + repos_root = entry->repos; + + if ((err = svn_path_resolve_relative_url_array(absolute_urls, relative_urls, + repos_root, pool))) + goto cleanup; + } + /* If the caller gave us an actual relative_urls value, then they expect + * there to be a new array in *absolute_urls with all the "resolved" URLS. + */ + else if (relative_urls != *absolute_urls) + { + *absolute_urls = apr_array_copy(pool, relative_urls); + } + /* Else just leave the *absolute_urls value alone. */ + +cleanup: + if (adm_access) + { + svn_error_t *err2 = svn_wc_adm_close(adm_access); + if(! err) + err = err2; + else + svn_error_clear(err2); + } + + return err; +} + Index: subversion/libsvn_subr/path.c =================================================================== --- subversion/libsvn_subr/path.c (revision 27753) +++ subversion/libsvn_subr/path.c (working copy) @@ -23,6 +23,7 @@ #include #include +#include #include "svn_string.h" #include "svn_path.h" @@ -871,8 +872,26 @@ return skip_uri_scheme(path) ? TRUE : FALSE; } +svn_boolean_t +svn_path_is_relative_url(const char *url) +{ + return(0 == strncmp("^/", url, 2) ? TRUE : FALSE); +} +svn_boolean_t +svn_path_array_has_relative_url(const apr_array_header_t *paths) +{ + int i; + for (i = 0; i < paths->nelts; i++) + { + if (svn_path_is_relative_url(APR_ARRAY_IDX(paths, i, const char *))) + return(TRUE); + } + + return(FALSE); +} + /* Here is the BNF for path components in a URI. "pchar" is a character in a path component. @@ -1174,8 +1193,127 @@ return SVN_NO_ERROR; } +svn_error_t * +svn_path_resolve_relative_url(const char **absolute_url, + const char *relative_url, + const char *repos_root_url, + apr_pool_t *pool) +{ + apr_status_t status; + apr_array_header_t *base_components; + apr_array_header_t *relative_components; + apr_uri_t parsed_uri; + int i; + if (relative_url == NULL) + relative_url = *absolute_url; + + if (svn_path_is_url(relative_url)) + { + if (*absolute_url != relative_url) + { + *absolute_url = apr_pstrdup(pool, relative_url); + } + /* Else *absolute_url is already = to relative_url */ + + return SVN_NO_ERROR; + } + + /* + * This is a partial re-implementation of the relative external support + * found in: + * libsvn_client/externals.c: resolve_relative_external_url() + * + * Updates to this code MAY be applicable to the other code + * (and vice versa). + */ + + if (0 == strncmp("^/", relative_url, 2)) + { + /* Is there something beyond '^/'? If not just use what we have + * (the repository root URL) + */ + if (strlen(relative_url) == 2) + { + *absolute_url = apr_pstrdup(pool, repos_root_url); + } + else + { + status = apr_uri_parse(pool, repos_root_url, &parsed_uri); + if (status) + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("Illegal repository root URL '%s'"), + repos_root_url); + + base_components = svn_path_decompose(parsed_uri.path, + pool); + + relative_components = svn_path_decompose(relative_url + 2, + pool); + + for (i = 0; i < relative_components->nelts; ++i) + { + const char *component = APR_ARRAY_IDX(relative_components, + i, + const char *); + if (0 == strcmp("..", component)) + { + /* Constructing the final absolute URL together with + * apr_uri_unparse() requires that the path be absolute, + * so only pop a component if the component being popped + * is not the component for the root directory. */ + if (base_components->nelts > 1) + apr_array_pop(base_components); + } + else + APR_ARRAY_PUSH(base_components, const char *) = component; + } + + parsed_uri.path = (char *)svn_path_compose(base_components, + pool); + parsed_uri.query = NULL; + parsed_uri.fragment = NULL; + + *absolute_url = apr_uri_unparse(pool, &parsed_uri, 0); + } + } + else + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("Improper relative URL '%s'"), + relative_url); + + return SVN_NO_ERROR; +} + svn_error_t * +svn_path_resolve_relative_url_array(apr_array_header_t **absolute_urls, + apr_array_header_t *relative_urls, + const char *repos_root_url, + apr_pool_t *pool) +{ + int i; + + if (relative_urls == NULL) + relative_urls = *absolute_urls; + + apr_array_header_t *urls = apr_array_make(pool, relative_urls->nelts, sizeof(const char *)); + + for (i=0; i < relative_urls->nelts; i++) + { + const char *url = APR_ARRAY_IDX(relative_urls, i, const char *); + + if (svn_path_is_relative_url(url)) + SVN_ERR(svn_path_resolve_relative_url(&url, NULL, repos_root_url, pool)); + + APR_ARRAY_PUSH(urls, const char *) = url; + } + + *absolute_urls = urls; + + return SVN_NO_ERROR; +} + +svn_error_t * svn_path_split_if_file(const char *path, const char **pdirectory, const char **pfile, Index: subversion/libsvn_client/externals.c =================================================================== --- subversion/libsvn_client/externals.c (revision 27753) +++ subversion/libsvn_client/externals.c (working copy) @@ -368,7 +368,12 @@ repository root. The backpaths may only remove path elements, not the hostname. This allows an external to refer to another repository in the same server relative to the location of this - repository, say using SVNParentPath. */ + repository, say using SVNParentPath. + + Note: There is a partial re-implementation of this code in + libsvn_subr/path.c: svn_path_resolve_relative_url(). + Updates to this code MAY be applicable to the other code + (and vice versa). */ if ((0 == strncmp("../", uncanonicalized_url, 3)) || (0 == strncmp("^/", uncanonicalized_url, 2))) { Index: subversion/tests/libsvn_subr/path-test.c =================================================================== --- subversion/tests/libsvn_subr/path-test.c (revision 27753) +++ subversion/tests/libsvn_subr/path-test.c (working copy) @@ -205,7 +205,6 @@ return SVN_NO_ERROR; } - static svn_error_t * test_is_uri_safe(const char **msg, svn_boolean_t msg_only, @@ -1238,6 +1237,317 @@ return SVN_NO_ERROR; } +static svn_error_t * +test_is_relative_url(const char **msg, + svn_boolean_t msg_only, + svn_test_opts_t *opts, + apr_pool_t *pool) +{ + apr_size_t i; + + /* Paths to test and their expected results. */ + struct { + const char *path; + svn_boolean_t result; + } tests[] = { + { "", FALSE }, + { "/blah/blah", FALSE }, + { "//blah/blah", FALSE }, + { "://blah/blah", FALSE }, + { "http://svn.collab.net/repos/svn", FALSE }, + { "scheme/with", FALSE }, + { "^/trunk", TRUE }, + { "^/branches/../trunk", TRUE }, + { "^trunk", FALSE }, + { "./^/trunk", FALSE }, + { "scheme/with:", FALSE }, + { "scheme/with:/", FALSE }, + { "file:///path/to/repository", FALSE }, + { "file://", FALSE }, + { "file:/", FALSE }, + { "file:", FALSE }, + { "file", FALSE }, + }; + + *msg = "test svn_path_is_relative_url"; + + if (msg_only) + return SVN_NO_ERROR; + + for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) + { + svn_boolean_t retval; + + retval = svn_path_is_relative_url(tests[i].path); + if (tests[i].result != retval) + return svn_error_createf + (SVN_ERR_TEST_FAILED, NULL, + "svn_path_is_relative_url (%s) returned %s instead of %s", + tests[i].path, retval ? "TRUE" : "FALSE", + tests[i].result ? "TRUE" : "FALSE"); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_array_has_relative_url(const char **msg, + svn_boolean_t msg_only, + svn_test_opts_t *opts, + apr_pool_t *pool) +{ + apr_size_t i; + svn_error_t *err = SVN_NO_ERROR; + + apr_pool_t *subpool = svn_pool_create(pool); + apr_array_header_t *test_urls_no_rel; + apr_array_header_t *test_urls_has_rel; + + test_urls_no_rel = apr_array_make(subpool, 0, sizeof(const char *)); + test_urls_has_rel = apr_array_make(subpool, 0, sizeof(const char *)); + + APR_ARRAY_PUSH(test_urls_no_rel, const char *) = "file:///repos/path/trunk"; + APR_ARRAY_PUSH(test_urls_no_rel, const char *) = + "http://svn.collab.net/repos/svn/trunk"; + APR_ARRAY_PUSH(test_urls_no_rel, const char *) = + "http://domain.com/repos/svn/^/test"; + APR_ARRAY_PUSH(test_urls_no_rel, const char *) = "/some/wc/path"; + APR_ARRAY_PUSH(test_urls_no_rel, const char *) = "relative/wc/path"; + APR_ARRAY_PUSH(test_urls_no_rel, const char *) = "../up/and/over"; + + APR_ARRAY_PUSH(test_urls_has_rel, const char *) = "file:///repos/path/trunk"; + APR_ARRAY_PUSH(test_urls_has_rel, const char *) = + "http://svn.collab.net/repos/svn/trunk"; + APR_ARRAY_PUSH(test_urls_has_rel, const char *) = + "http://domain.com/repos/svn/^/test"; + APR_ARRAY_PUSH(test_urls_has_rel, const char *) = "/some/wc/path"; + APR_ARRAY_PUSH(test_urls_has_rel, const char *) = "relative/wc/path"; + APR_ARRAY_PUSH(test_urls_has_rel, const char *) = "../up/and/over"; + APR_ARRAY_PUSH(test_urls_has_rel, const char *) = "^/trunk"; + APR_ARRAY_PUSH(test_urls_has_rel, const char *) = "^/branches/../trunk"; + + svn_pool_clear(subpool); + + *msg = "test svn_path_array_has_relative_url"; + + if (msg_only) + goto exit_func; + + if (TRUE == svn_path_array_has_relative_url(test_urls_no_rel)) + { + err = svn_error_createf + (SVN_ERR_TEST_FAILED, NULL, + "svn_path_array_has_relative_url incorrectly detected a " + "relative URL"); + + goto exit_func; + } + + if (FALSE == svn_path_array_has_relative_url(test_urls_has_rel)) + { + err = svn_error_createf + (SVN_ERR_TEST_FAILED, NULL, + "svn_path_array_has_relative_url failed to detect a relative URL"); + + goto exit_func; + } + +exit_func: + svn_pool_destroy(subpool); + return err; +} + +static svn_error_t * +test_resolve_relative_url(const char **msg, + svn_boolean_t msg_only, + svn_test_opts_t *opts, + apr_pool_t *pool) +{ + apr_size_t i; + + /* Relative urls to test with their root urls and expected results */ + struct { + const char *rel_url; + const char *root_url; + const char *abs_url; + } tests[] = { + { "file:///repos/path/trunk", "file:///repos/path", + "file:///repos/path/trunk" }, + { "http://svn.collab.net/repos/svn/trunk", "http://svn.collab.net/repos/svn", + "http://svn.collab.net/repos/svn/trunk" }, + { "^/trunk", "file:///repos/path", + "file:///repos/path/trunk" }, + { "^/trunk", "http://svn.collab.net/repos/svn", + "http://svn.collab.net/repos/svn/trunk" }, + { "^/branches/../trunk", "file:///repos/path", + "file:///repos/path/trunk" }, + { "^/branches/../trunk", "http://svn.collab.net/repos/svn", + "http://svn.collab.net/repos/svn/trunk" }, + }; + + *msg = "test svn_path_resolve_relative_url"; + + if (msg_only) + return SVN_NO_ERROR; + + for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) + { + const char *rel_url = tests[i].rel_url; + const char *root_url = tests[i].root_url; + const char *abs_url; + + /* + * First test with both arguments being non-null + */ + svn_path_resolve_relative_url(&abs_url, rel_url, root_url, pool); + if ((strcmp(tests[i].abs_url, abs_url))) + return svn_error_createf + (SVN_ERR_TEST_FAILED, NULL, + "svn_path_resolve_relative_url (%s, %s) returned '%s' " + "instead of '%s'", + rel_url, root_url, abs_url, + tests[i].abs_url); + + /* + * Now test with a NULL RELATIVE_URL argument + */ + abs_url = rel_url; + svn_path_resolve_relative_url(&abs_url, NULL, root_url, pool); + if ((strcmp(tests[i].abs_url, abs_url))) + return svn_error_createf + (SVN_ERR_TEST_FAILED, NULL, + "svn_path_resolve_relative_url (%s, %s) returned '%s' " + "instead of '%s'", + tests[i].rel_url, root_url, abs_url, + tests[i].abs_url); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_resolve_relative_url_array(const char **msg, + svn_boolean_t msg_only, + svn_test_opts_t *opts, + apr_pool_t *pool) +{ + apr_size_t i; + const char *root_url = "http://svn.collab.net/repos/svn"; + svn_error_t *err = SVN_NO_ERROR; + + apr_pool_t *subpool = svn_pool_create(pool); + apr_array_header_t *test_urls; + apr_array_header_t *expected_urls; + apr_array_header_t *result_urls; + + svn_pool_clear(subpool); + + test_urls = apr_array_make(subpool, 0, sizeof(const char *)); + expected_urls = apr_array_make(subpool, 0, sizeof(const char *)); + + /* Absolute URLs */ + APR_ARRAY_PUSH(test_urls, const char *) + = "http://svn.collab.net/repos/svn/trunk"; + APR_ARRAY_PUSH(expected_urls, const char *) + = "http://svn.collab.net/repos/svn/trunk"; + + APR_ARRAY_PUSH(test_urls, const char *) = "file:///local/repo/trunk"; + APR_ARRAY_PUSH(expected_urls, const char *) = "file:///local/repo/trunk"; + + /* Relative URLs */ + APR_ARRAY_PUSH(test_urls, const char *) = "^/trunk"; + APR_ARRAY_PUSH(expected_urls, const char *) + = "http://svn.collab.net/repos/svn/trunk"; + + APR_ARRAY_PUSH(test_urls, const char *) = "^/branches/../trunk"; + APR_ARRAY_PUSH(expected_urls, const char *) + = "http://svn.collab.net/repos/svn/trunk"; + + *msg = "test svn_path_resolve_relative_url_array"; + + if (msg_only) + goto exit_func; + + /* + * First test with both array arguments non-NULL + */ + svn_path_resolve_relative_url_array(&result_urls, test_urls, + root_url, subpool); + + if (result_urls->nelts != expected_urls->nelts) + { + err = svn_error_createf + (SVN_ERR_TEST_FAILED, NULL, + "svn_path_resolve_relative_url_array with %d element test array " + "returned a %d element array when a %d element array was expected", + test_urls->nelts, + result_urls->nelts, expected_urls->nelts); + + goto exit_func; + } + + for (i=0; i < test_urls->nelts; i++) + { + const char *result_url = APR_ARRAY_IDX(result_urls, i, const char *); + const char *expected_url = APR_ARRAY_IDX(expected_urls, i, const char *); + const char *test_url = APR_ARRAY_IDX(test_urls, i, const char *); + + if (strcmp(result_url, expected_url)) + + { + err = svn_error_createf + (SVN_ERR_TEST_FAILED, NULL, + "svn_path_resolve_relative_url_array given '%s' but returned " + "'%s' when '%s' was expected in element %d", + test_url, + result_url, expected_url, i); + goto exit_func; + } + } + + /* + * Now test with a NULL RELATIVE_URLS argument + */ + result_urls = apr_array_copy(subpool, test_urls); + + svn_path_resolve_relative_url_array(&result_urls, NULL, root_url, subpool); + + if (result_urls->nelts != expected_urls->nelts) + { + err = svn_error_createf + (SVN_ERR_TEST_FAILED, NULL, + "svn_path_resolve_relative_url_array with %d element test array " + "returned a %d element array when a %d element array was expected", + test_urls->nelts, + result_urls->nelts, expected_urls->nelts); + + goto exit_func; + } + + for (i=0; i < test_urls->nelts; i++) + { + const char *result_url = APR_ARRAY_IDX(result_urls, i, const char *); + const char *expected_url = APR_ARRAY_IDX(expected_urls, i, const char *); + const char *test_url = APR_ARRAY_IDX(test_urls, i, const char *); + + if (strcmp(result_url, expected_url)) + + { + err = svn_error_createf + (SVN_ERR_TEST_FAILED, NULL, + "svn_path_resolve_relative_url_array given '%s' but returned " + "'%s' when '%s' was expected in element %d", + test_url, + result_url, expected_url, i); + goto exit_func; + } + } + +exit_func: + svn_pool_destroy(subpool); + return err; +} + /* local define to support XFail-ing tests on Windows/Cygwin only */ #if defined(WIN32) || defined(__CYGWIN__) #define WINDOWS_OR_CYGWIN TRUE @@ -1273,5 +1583,9 @@ SVN_TEST_PASS(test_get_longest_ancestor), SVN_TEST_PASS(test_splitext), SVN_TEST_PASS(test_compose), + SVN_TEST_PASS(test_is_relative_url), + SVN_TEST_PASS(test_array_has_relative_url), + SVN_TEST_PASS(test_resolve_relative_url), + SVN_TEST_PASS(test_resolve_relative_url_array), SVN_TEST_NULL }; Index: subversion/tests/cmdline/merge_tests.py =================================================================== --- subversion/tests/cmdline/merge_tests.py (revision 27753) +++ subversion/tests/cmdline/merge_tests.py (working copy) @@ -9300,6 +9300,111 @@ expected_status, expected_skip, None, None, None, None, None, 1, 1) +#---------------------------------------------------------------------- + +# Merge with relative urls +def merge_with_relative_urls(sbox): + "merge with relative urls" + + sbox.build() + wc_dir = os.path.join(os.getcwd() , sbox.wc_dir) + + # Rev 2 copy B to B2 + B_path = os.path.join(sbox.wc_dir, 'A', 'B') + lambda_path = os.path.join(sbox.wc_dir, 'A', 'B', 'lambda') + B2_path = os.path.join(sbox.wc_dir, 'A', 'B2') + B_url = sbox.repo_url + '/A/B' + + svntest.actions.run_and_verify_svn(None, None, [], + 'copy', B_path, B2_path) + + expected_output = wc.State(sbox.wc_dir, { + 'A/B2' : Item(verb='Adding'), + }) + expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1) + expected_status.add({ + 'A/B2' : Item(status=' ', wc_rev=2), + 'A/B2/E' : Item(status=' ', wc_rev=2), + 'A/B2/E/alpha' : Item(status=' ', wc_rev=2), + 'A/B2/E/beta' : Item(status=' ', wc_rev=2), + 'A/B2/F' : Item(status=' ', wc_rev=2), + 'A/B2/lambda' : Item(status=' ', wc_rev=2), + }) + svntest.actions.run_and_verify_commit(sbox.wc_dir, + expected_output, + expected_status, + None, + None, None, + None, None, + sbox.wc_dir) + + svntest.actions.run_and_verify_svn(None, None, [], + 'up', sbox.wc_dir) + # Rev 3 modify A/B/lambda + lambda_text = fill_file_with_lines(lambda_path, 2) + + expected_output = wc.State(sbox.wc_dir, { + 'A/B/lambda' : Item(verb='Sending'), + }) + + expected_status.tweak(wc_rev=2) + expected_status.add({ + 'A/B/lambda' : Item(status=' ', wc_rev=3), + }) + + svntest.actions.run_and_verify_commit(sbox.wc_dir, + expected_output, + expected_status, + None, + None, None, + None, None, + sbox.wc_dir) + + svntest.actions.run_and_verify_svn(None, None, [], + 'up', sbox.wc_dir) + # Now do the merge with relative url to A/B where we made + # the modifications in r3 + saved_cwd = os.getcwd() + + os.chdir(B2_path) + + expected_output = wc.State('.', { + 'lambda' : Item(status='U '), + }) + + expected_disk = wc.State('', { + 'E' : Item(), + 'E/alpha' : Item("This is the file 'alpha'.\n"), + 'E/beta' : Item("This is the file 'beta'.\n"), + 'F' : Item(), + 'lambda' : Item("This is the file 'lambda'.\n" + lambda_text), + }) + + expected_status = wc.State('', { + '' : Item(status=' M'), + 'E' : Item(status=' '), + 'E/alpha' : Item(status=' '), + 'E/beta' : Item(status=' '), + 'F' : Item(status=' '), + 'lambda' : Item(status='M '), + }) + + expected_status.tweak(wc_rev=3) + + expected_skip = wc.State('', { }) + + svntest.actions.run_and_verify_merge('.', '2', '3', '^/A/B', + expected_output, + expected_disk, + expected_status, + expected_skip, + None, None, None, + None, None, # no B singleton handler + False, # Don't check props + False) # Not a dry run + os.chdir(saved_cwd) + + ######################################################################## # Run the tests @@ -9384,6 +9489,7 @@ reverse_merge_prop_add_on_child, XFail(merge_target_with_non_inheritable_mergeinfo), self_reverse_merge, + merge_with_relative_urls, ] if __name__ == '__main__': Index: subversion/svn/merge-cmd.c =================================================================== --- subversion/svn/merge-cmd.c (revision 27753) +++ subversion/svn/merge-cmd.c (working copy) @@ -52,6 +52,9 @@ SVN_ERR(svn_opt_args_to_target_array2(&targets, os, opt_state->targets, pool)); + + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + if (targets->nelts >= 1) { SVN_ERR(svn_opt_parse_path(&peg_revision1, &sourcepath1,