Index: subversion/include/svn_path.h =================================================================== --- subversion/include/svn_path.h (revision 27846) +++ subversion/include/svn_path.h (working copy) @@ -456,9 +456,56 @@ /** 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); +/** + * If @a relative_url is a relative url, set @a *absolute_url to the absolute + * URL it represents relative to @a repos_root_url, else set @a *absolute_url + * to @a relative_url. + * + * The relative to absolute URL conversion is accomplished by replacing a + * leading '^/' in the relative URL with the repository root url followed by a + * slash. + * + * 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 27846) +++ 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 27846) +++ 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/opt.c =================================================================== --- subversion/libsvn_subr/opt.c (revision 27846) +++ subversion/libsvn_subr/opt.c (working copy) @@ -942,9 +942,19 @@ utf8_target, peg_start - utf8_target); - /* URLs and wc-paths get treated differently. */ - if (svn_path_is_url(utf8_target)) + /* URLs, relative URLs, and wc-paths each get treated differently. */ + if (svn_path_is_relative_url(utf8_target)) { + /* Convert to URI. */ + target = svn_path_uri_from_iri(utf8_target, pool); + + /* Do not try to canonicalize or auto escape relative urls, they + * will be processed * later and we may very well need the + * trailing slash, i.e. '^/'. + */ + } + else if (svn_path_is_url(utf8_target)) + { /* No need to canonicalize a URL's case or path separators. */ /* Convert to URI. */ Index: subversion/libsvn_subr/path.c =================================================================== --- subversion/libsvn_subr/path.c (revision 27846) +++ 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 27846) +++ 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 27846) +++ 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/cat_tests.py =================================================================== --- subversion/tests/cmdline/cat_tests.py (revision 27846) +++ subversion/tests/cmdline/cat_tests.py (working copy) @@ -139,7 +139,24 @@ expected_err1 + expected_err2, 'cat', rho_path, G_path, new_file_path) +def cat_relative_url(sbox): + "cat a relative url" + sbox.build() + expected_out = ["This is the file 'gamma'.\n"] + + orig_dir = os.getcwd() + os.chdir(sbox.wc_dir) + + svntest.actions.run_and_verify_svn(None, expected_out, [], + 'cat', '^/A/D/gamma') + + os.chdir(orig_dir) + + + + + ######################################################################## # Run the tests @@ -151,6 +168,7 @@ cat_base, cat_nonexistent_file, cat_skip_uncattable, + cat_relative_url, ] if __name__ == '__main__': Index: subversion/tests/cmdline/import_tests.py =================================================================== --- subversion/tests/cmdline/import_tests.py (revision 27846) +++ subversion/tests/cmdline/import_tests.py (working copy) @@ -350,7 +350,71 @@ 'diff', file_path, '--config-dir', config_dir) +#---------------------------------------------------------------------- +def import_to_relative_url(sbox): + 'import to a relative url' + sbox.build() + wc_dir = sbox.wc_dir + + # These paths are relative to wc_dir + dir_path = 'dir' + foo_c_path = os.path.join(dir_path, 'foo.c') + + # import new dir into repository + url = '^/dir' + + orig_dir = os.getcwd() + os.chdir(wc_dir) + + os.mkdir(dir_path, 0755) + open(foo_c_path, 'w') + + output, errput = svntest.actions.run_and_verify_svn( + None, None, [], 'import', + '-m', 'Log message for new import', + dir_path, url) + + lastline = output.pop().strip() + cm = re.compile ("(Committed|Imported) revision [0-9]+.") + match = cm.search (lastline) + if not match: + raise svntest.actions.SVNUnexpectedOutput + + # remove (uncontrolled) local dir + svntest.main.safe_rmtree(dir_path) + + os.chdir(orig_dir) + + # Create expected disk tree for the update (disregarding props) + expected_disk = svntest.main.greek_state.copy() + expected_disk.add({ + 'dir/foo.c' : Item(''), + }) + + # Create expected status tree for the update (disregarding props). + # Newly imported file should be at revision 2. + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + expected_status.add({ + 'dir' : Item(status=' ', wc_rev=2), + 'dir/foo.c' : Item(status=' ', wc_rev=2), + }) + + # Create expected output tree for the update. + expected_output = svntest.wc.State(wc_dir, { + 'dir' : Item(status='A '), + 'dir/foo.c' : Item(status='A '), + }) + + # do update and check three ways + svntest.actions.run_and_verify_update(wc_dir, + expected_output, + expected_disk, + expected_status, + None, None, None, + None, None, 1) + + #---------------------------------------------------------------------- ######################################################################## # Run the tests @@ -363,6 +427,7 @@ import_avoid_empty_revision, import_no_ignores, import_eol_style, + import_to_relative_url, ] if __name__ == '__main__': Index: subversion/tests/cmdline/log_tests.py =================================================================== --- subversion/tests/cmdline/log_tests.py (revision 27846) +++ subversion/tests/cmdline/log_tests.py (working copy) @@ -1186,7 +1186,21 @@ svntest.actions.run_and_verify_log_xml( expected_revprops=(r0_props,), args=[sbox.repo_url]) +#---------------------------------------------------------------------- +def log_with_relative_urls(sbox): + "'svn log', with relative urls" + guarantee_repos_and_wc(sbox) + + os.chdir(sbox.wc_dir) + + output, err = svntest.actions.run_and_verify_svn( + None, None, [], + 'log', '^/A/D', 'G', 'H') + + log_chain = parse_log_output(output) + check_log_chain(log_chain, [8, 6, 5, 3, 1]) + ######################################################################## # Run the tests @@ -1222,6 +1236,7 @@ only_one_wc_path, retrieve_revprops, log_xml_with_bad_data, + log_with_relative_urls, ] if __name__ == '__main__': Index: subversion/tests/cmdline/lock_tests.py =================================================================== --- subversion/tests/cmdline/lock_tests.py (revision 27846) +++ subversion/tests/cmdline/lock_tests.py (working copy) @@ -1420,7 +1420,36 @@ pi_path) svntest.actions.run_and_verify_status(wc_dir, expected_status) +#---------------------------------------------------------------------- +def lock_file_with_relative_url(sbox): + "lock a file using a relative url" + sbox.build() + wc_dir = sbox.wc_dir + + # Make a second copy of the working copy + wc_b = sbox.add_wc_path('_b') + svntest.actions.duplicate_dir(wc_dir, wc_b) + + file_path = os.path.join(sbox.wc_dir, 'iota') + + svntest.main.file_append(file_path, "This represents a binary file\n") + svntest.main.run_svn(None, 'commit', + '-m', '', file_path) + + orig_dir = os.getcwd() + os.chdir(wc_dir) + + # First lock it + svntest.actions.run_and_verify_svn(None, ".*locked by user", [], 'lock', + '-m', '', '^/iota') + + # Now unlock it + svntest.actions.run_and_verify_svn(None, ".* unlocked.", [], 'unlock', + '^/iota') + + os.chdir(orig_dir) + ######################################################################## # Run the tests @@ -1460,6 +1489,7 @@ unlock_wrong_token, examine_lock_encoded_recurse, unlocked_lock_of_other_user, + lock_file_with_relative_url, ] if __name__ == '__main__': Index: subversion/tests/cmdline/switch_tests.py =================================================================== --- subversion/tests/cmdline/switch_tests.py (revision 27846) +++ subversion/tests/cmdline/switch_tests.py (working copy) @@ -2124,6 +2124,19 @@ expected_disk, expected_status) +def switch_with_relative_url(sbox): + "switch with a relative url" + + sbox.build() + + orig_dir = os.getcwd() + os.chdir(sbox.wc_dir) + + # Setup (and verify) some switched things + do_routine_switching(".", '^/', 1) + + os.chdir(orig_dir) + ######################################################################## # Run the tests @@ -2158,6 +2171,7 @@ switch_urls_with_spaces, switch_to_dir_with_peg_rev2, switch_to_root, + switch_with_relative_url, ] if __name__ == '__main__': Index: subversion/tests/cmdline/diff_tests.py =================================================================== --- subversion/tests/cmdline/diff_tests.py (revision 27846) +++ subversion/tests/cmdline/diff_tests.py (working copy) @@ -2951,6 +2951,27 @@ svntest.actions.run_and_verify_diff_summarize_xml( [], sbox.repo_url, paths, items, props, kinds, '-r1:2', sbox.repo_url) +# Test relative url as diff argument +def diff_relative_url(sbox): + "relative url as diff argument" + + sbox.build() + + change_diff_commit_diff(sbox.wc_dir, 1, + update_a_file, + check_update_a_file) + + orig_dir = os.getcwd() + os.chdir(sbox.wc_dir) + + diff_output, err_output = svntest.main.run_svn(None, + 'diff', '-r2:1', + '^/A/B/E/alpha') + if check_diff_output(diff_output, 'alpha', 'M'): + raise svntest.Failure + + os.chdir(orig_dir) + ######################################################################## #Run the tests @@ -3002,6 +3023,7 @@ diff_ignore_eolstyle_empty_lines, diff_backward_repos_wc_copy, diff_summarize_xml, + diff_relative_url, ] if __name__ == '__main__': Index: subversion/tests/cmdline/copy_tests.py =================================================================== --- subversion/tests/cmdline/copy_tests.py (revision 27846) +++ subversion/tests/cmdline/copy_tests.py (working copy) @@ -3781,6 +3781,52 @@ copy_URL_to_WC('A', 'A COPY', 2) copy_URL_to_WC('A COPY', 'A_COPY_2', 3) +#---------------------------------------------------------------------- +# Test copy with relative urls +def copy_with_relative_urls(sbox): + "copy with relative urls" + + sbox.build() + + src_url = sbox.repo_url + '/A/B/E' + dst_url = '^/A/C' + + orig_dir = os.getcwd() + os.chdir(sbox.wc_dir) + + svntest.actions.run_and_verify_svn(None, + ['\n', 'Committed revision 2.\n'], [], + 'cp', + '-m', "Relative urls for all.", + src_url, dst_url) + os.chdir(orig_dir) + + # Do an update to verify the copy worked + expected_output = svntest.wc.State(sbox.wc_dir, { + 'A/C/E' : Item(status='A '), + 'A/C/E/alpha' : Item(status='A '), + 'A/C/E/beta' : Item(status='A '), + }) + + expected_disk = svntest.main.greek_state.copy() + expected_disk.add({ + 'A/C/E' : Item(), + 'A/C/E/alpha' : Item("This is the file 'alpha'.\n"), + 'A/C/E/beta' : Item("This is the file 'beta'.\n"), + }) + + expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 2) + expected_status.add({ + 'A/C/E' : Item(status=' ', wc_rev=2), + 'A/C/E/alpha' : Item(status=' ', wc_rev=2), + 'A/C/E/beta' : Item(status=' ', wc_rev=2), + }) + + svntest.actions.run_and_verify_update(sbox.wc_dir, + expected_output, + expected_disk, + expected_status) + ######################################################################## # Run the tests @@ -3856,6 +3902,7 @@ copy_make_parents_wc_repo, copy_make_parents_repo_repo, URI_encoded_repos_to_wc, + copy_with_relative_urls, ] if __name__ == '__main__': Index: subversion/tests/cmdline/blame_tests.py =================================================================== --- subversion/tests/cmdline/blame_tests.py (revision 27846) +++ subversion/tests/cmdline/blame_tests.py (working copy) @@ -548,7 +548,31 @@ svntest.actions.run_and_verify_svn(None, [], expected_err, 'blame', notexisting_url) +def blame_relative_url(sbox): + "blame relative url target" + sbox.build() + + # First, make a new revision of iota. + iota = os.path.join(sbox.wc_dir, 'iota') + svntest.main.file_append(iota, "New contents for iota\n") + svntest.main.run_svn(None, 'ci', + '-m', '', iota) + + expected_output = [ + " 1 jrandom This is the file 'iota'.\n", + " 2 jrandom New contents for iota\n", + ] + + orig_dir = os.getcwd() + os.chdir(sbox.wc_dir) + + output, error = svntest.actions.run_and_verify_svn(None, expected_output, [], + 'blame', '^/iota') + + os.chdir(orig_dir) + + ######################################################################## # Run the tests @@ -570,6 +594,7 @@ server_has_mergeinfo), blame_peg_rev_file_not_in_head, blame_file_not_in_head, + blame_relative_url, ] if __name__ == '__main__': Index: subversion/tests/cmdline/prop_tests.py =================================================================== --- subversion/tests/cmdline/prop_tests.py (revision 27846) +++ subversion/tests/cmdline/prop_tests.py (working copy) @@ -1439,6 +1439,96 @@ finally: os.chdir(saved_cwd) +#---------------------------------------------------------------------- + +def url_props_relative_url(sbox): + "property operations on a relative URL" + + # Bootstrap + sbox.build() + wc_dir = sbox.wc_dir + + prop1 = 'prop1' + propval1 = 'propval1 is foo' + prop2 = 'prop2' + propval2 = 'propval2' + + iota_path = os.path.join(sbox.wc_dir, 'iota') + iota_url = '^/iota' + A_path = os.path.join(sbox.wc_dir, 'A') + A_url = '^/A' + + # Add a couple of properties + svntest.main.run_svn(None, 'propset', prop1, propval1, iota_path) + svntest.main.run_svn(None, 'propset', prop1, propval1, A_path) + + # Commit + svntest.main.run_svn(None, + 'ci', '-m', 'logmsg', sbox.wc_dir) + + # Add a few more properties + svntest.main.run_svn(None, 'propset', prop2, propval2, iota_path) + svntest.main.run_svn(None, 'propset', prop2, propval2, A_path) + + # Commit again + svntest.main.run_svn(None, + 'ci', '-m', 'logmsg', sbox.wc_dir) + + orig_dir = os.getcwd() + os.chdir(wc_dir) + + # Test propget + svntest.actions.run_and_verify_svn(None, [ propval1 + '\n' ], [], + 'propget', prop1, iota_url) + svntest.actions.run_and_verify_svn(None, [ propval1 + '\n' ], [], + 'propget', prop1, A_url) + + # Test normal proplist + output, errput = svntest.main.run_svn(None, + 'proplist', iota_url) + verify_output([ prop1, prop2, 'Properties on ' ], + output, errput) + + output, errput = svntest.main.run_svn(None, + 'proplist', A_url) + verify_output([ prop1, prop2, 'Properties on ' ], + output, errput) + + # Test verbose proplist + output, errput = svntest.main.run_svn(None, + 'proplist', '-v', iota_url) + verify_output([ prop1 + ' : ' + propval1, prop2 + ' : ' + propval2, + 'Properties on ' ], output, errput) + + output, errput = svntest.main.run_svn(None, + 'proplist', '-v', A_url) + verify_output([ prop1 + ' : ' + propval1, prop2 + ' : ' + propval2, + 'Properties on ' ], output, errput) + + # Test propedit + svntest.main.use_editor('foo_to_bar') + propval1 = propval1.replace('foo', 'bar') + svntest.main.run_svn(None, + 'propedit', prop1, '-m', 'editlog', iota_url) + svntest.main.run_svn(None, + 'propedit', prop1, '-m', 'editlog', A_url) + svntest.actions.run_and_verify_svn(None, [ propval1 + '\n' ], [], + 'propget', prop1, iota_url) + svntest.actions.run_and_verify_svn(None, [ propval1 + '\n' ], [], + 'propget', prop1, A_url) + + # Edit without actually changing the property + svntest.main.use_editor('identity') + svntest.actions.run_and_verify_svn(None, + "No changes to property '%s' on '.*'" + % prop1, + [], + 'propedit', prop1, '-m', 'nocommit', + iota_url) + + os.chdir(orig_dir) + + ######################################################################## # Run the tests @@ -1469,6 +1559,7 @@ depthy_url_proplist, invalid_propnames, SkipUnless(perms_on_symlink, svntest.main.is_posix_os), + url_props_relative_url, ] if __name__ == '__main__': Index: subversion/tests/cmdline/export_tests.py =================================================================== --- subversion/tests/cmdline/export_tests.py (revision 27846) +++ subversion/tests/cmdline/export_tests.py (working copy) @@ -390,6 +390,39 @@ expected_output, svntest.main.greek_state.copy()) +def export_relative_url(sbox): + "export from a relative url" + + svntest.main.safe_rmtree(sbox.wc_dir) + + sbox.build(create_wc = False) + + # Checked out path + co_path = os.path.join(sbox.wc_dir, 'co') + + # Export path, relative to the co_path since that is where we'll be + export_target = os.path.join('..', 'export') + + svntest.main.run_svn(None, 'co', + sbox.repo_url, co_path) + + expected_output = svntest.main.greek_state.subtree('A') + expected_output.wc_dir = export_target + expected_output.desc[''] = Item() + expected_output.tweak(contents=None, status='A ') + + expected_disk = svntest.main.greek_state.subtree('A') + + orig_dir = os.getcwd() + os.chdir(co_path) + + svntest.actions.run_and_verify_export('^/A', + export_target, + expected_output, + expected_disk) + + os.chdir(orig_dir) + ######################################################################## # Run the tests @@ -412,6 +445,7 @@ export_unversioned_file, export_with_state_deleted, export_creates_intermediate_folders, + export_relative_url, ] if __name__ == '__main__': Index: subversion/tests/cmdline/merge_tests.py =================================================================== --- subversion/tests/cmdline/merge_tests.py (revision 27846) +++ subversion/tests/cmdline/merge_tests.py (working copy) @@ -9299,7 +9299,110 @@ expected_output, expected_disk, 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 +9487,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/tests/cmdline/checkout_tests.py =================================================================== --- subversion/tests/cmdline/checkout_tests.py (revision 27846) +++ subversion/tests/cmdline/checkout_tests.py (working copy) @@ -712,7 +712,58 @@ "name is already scheduled for addition with history\n", serr) #---------------------------------------------------------------------- +def co_with_relative_url(sbox): + "co handles a relative url correctly" + sbox.build(create_wc = False) + + wc1_path = os.path.join(sbox.wc_dir, 'H') + wc1_url = sbox.repo_url + '/A/D/H' + + # The second working copy will be checked out relative to the first one + wc2_path = os.path.join('..', 'G') + wc2_url = '^/A/D/G' + + expected_output1 = wc.State(wc1_path, { + 'chi' : Item(status='A '), + 'psi' : Item(status='A '), + 'omega' : Item(status='A '), + }) + + expected_disk1 = wc.State('', { + 'chi' : Item("This is the file 'chi'.\n"), + 'psi' : Item("This is the file 'psi'.\n"), + 'omega' : Item("This is the file 'omega'.\n"), + }) + + svntest.actions.run_and_verify_checkout(wc1_url, wc1_path, + expected_output1, expected_disk1) + + # Now chdir into the first working copy so that svn has a working + # copy to get the repository root url from. Then svn can do a + # checkout using relative urls and stick it in some other directory. + orig_dir = os.getcwd() + os.chdir(wc1_path) + + expected_output2 = wc.State(wc2_path, { + 'pi' : Item(status='A '), + 'rho' : Item(status='A '), + 'tau' : Item(status='A '), + }) + + expected_disk2 = wc.State('', { + 'pi' : Item("This is the file 'pi'.\n"), + 'rho' : Item("This is the file 'rho'.\n"), + 'tau' : Item("This is the file 'tau'.\n"), + }) + + svntest.actions.run_and_verify_checkout(wc2_url, wc2_path, + expected_output2, expected_disk2) + + os.chdir(orig_dir) + +#---------------------------------------------------------------------- + # list all tests here, starting with None: test_list = [ None, checkout_with_obstructions, @@ -728,6 +779,7 @@ checkout_peg_rev, checkout_peg_rev_date, co_with_obstructing_local_adds, + co_with_relative_url, ] if __name__ == "__main__": Index: subversion/svn/merge-cmd.c =================================================================== --- subversion/svn/merge-cmd.c (revision 27846) +++ 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, Index: subversion/svn/propdel-cmd.c =================================================================== --- subversion/svn/propdel-cmd.c (revision 27846) +++ subversion/svn/propdel-cmd.c (working copy) @@ -84,6 +84,9 @@ SVN_ERR(svn_opt_args_to_target_array2(&targets, os, combined_targets, pool)); + if (svn_path_array_has_relative_url(targets)) + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + /* Add "." if user passed 0 file arguments */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/checkout-cmd.c =================================================================== --- subversion/svn/checkout-cmd.c (revision 27846) +++ subversion/svn/checkout-cmd.c (working copy) @@ -76,6 +76,10 @@ if (! targets->nelts) return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + /* Resolve any relative urls found in targets */ + if (svn_path_array_has_relative_url(targets)) + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + /* Add a path if the user only specified URLs */ local_dir = APR_ARRAY_IDX(targets, targets->nelts - 1, const char *); if (svn_path_is_url(local_dir)) Index: subversion/svn/move-cmd.c =================================================================== --- subversion/svn/move-cmd.c (revision 27846) +++ subversion/svn/move-cmd.c (working copy) @@ -64,6 +64,9 @@ svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2, FALSE, FALSE, FALSE, pool); + if (svn_path_array_has_relative_url(targets)) + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + dst_path = APR_ARRAY_IDX(targets, targets->nelts - 1, const char *); apr_array_pop(targets); Index: subversion/svn/mkdir-cmd.c =================================================================== --- subversion/svn/mkdir-cmd.c (revision 27846) +++ subversion/svn/mkdir-cmd.c (working copy) @@ -55,6 +55,9 @@ svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2, FALSE, FALSE, FALSE, pool); + if (svn_path_array_has_relative_url(targets)) + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + if (! svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *))) { ctx->log_msg_func3 = NULL; Index: subversion/svn/cat-cmd.c =================================================================== --- subversion/svn/cat-cmd.c (revision 27846) +++ subversion/svn/cat-cmd.c (working copy) @@ -61,6 +61,10 @@ svn_pool_clear(subpool); SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + /* Resolve relative url (if it is one) */ + if (svn_path_is_relative_url(target)) + SVN_ERR(svn_wc_get_absolute_url(&target, NULL, ".", subpool)); + /* Get peg revisions. */ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool)); Index: subversion/svn/diff-cmd.c =================================================================== --- subversion/svn/diff-cmd.c (revision 27846) +++ subversion/svn/diff-cmd.c (working copy) @@ -216,6 +216,10 @@ SVN_ERR(svn_opt_args_to_target_array2(&targets, os, combined_targets, pool)); + /* Resolve any relative urls found in targets */ + if (svn_path_array_has_relative_url(targets)) + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + if (! opt_state->old_target && ! opt_state->new_target && (targets->nelts == 2) && svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)) Index: subversion/svn/copy-cmd.c =================================================================== --- subversion/svn/copy-cmd.c (revision 27846) +++ subversion/svn/copy-cmd.c (working copy) @@ -52,6 +52,10 @@ if (targets->nelts < 2) return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + /* Resolve any relative urls found in targets */ + if (svn_path_array_has_relative_url(targets)) + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + /* Get the src list and associated peg revs */ sources = apr_array_make(pool, targets->nelts - 1, sizeof(svn_client_copy_source_t *)); Index: subversion/svn/list-cmd.c =================================================================== --- subversion/svn/list-cmd.c (revision 27846) +++ subversion/svn/list-cmd.c (working copy) @@ -268,6 +268,10 @@ SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + /* Resolve relative url (if it is one) */ + if (svn_path_is_relative_url(target)) + SVN_ERR(svn_wc_get_absolute_url(&target, NULL, ".", subpool)); + /* Get peg revisions. */ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool)); Index: subversion/svn/mergeinfo-cmd.c =================================================================== --- subversion/svn/mergeinfo-cmd.c (revision 27846) +++ subversion/svn/mergeinfo-cmd.c (working copy) @@ -91,6 +91,9 @@ svn_pool_clear(subpool); SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + if (svn_path_is_relative_url(target)) + SVN_ERR(svn_wc_get_absolute_url(&target, NULL, ".", subpool)); + /* Parse the path into a path and peg revision. */ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool)); Index: subversion/svn/blame-cmd.c =================================================================== --- subversion/svn/blame-cmd.c (revision 27846) +++ subversion/svn/blame-cmd.c (working copy) @@ -271,6 +271,10 @@ svn_pool_clear(subpool); SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + /* Resolve relative url (if it is one) */ + if (svn_path_is_relative_url(target)) + SVN_ERR(svn_wc_get_absolute_url(&target, NULL, ".", subpool)); + /* Check for a peg revision. */ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool)); @@ -285,6 +289,7 @@ opt_state->end_revision.kind = svn_opt_revision_base; } + if (opt_state->xml) { /* "" */ Index: subversion/svn/propget-cmd.c =================================================================== --- subversion/svn/propget-cmd.c (revision 27846) +++ subversion/svn/propget-cmd.c (working copy) @@ -208,6 +208,9 @@ SVN_ERR(svn_opt_args_to_target_array2(&targets, os, combined_targets, pool)); + if (svn_path_array_has_relative_url(targets)) + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + /* Add "." if user passed 0 file arguments */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/log-cmd.c =================================================================== --- subversion/svn/log-cmd.c (revision 27846) +++ subversion/svn/log-cmd.c (working copy) @@ -494,6 +494,9 @@ SVN_ERR(svn_opt_args_to_target_array2(&targets, os, combined_targets, pool)); + if (svn_path_array_has_relative_url(targets)) + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + /* Add "." if user passed 0 arguments */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/propset-cmd.c =================================================================== --- subversion/svn/propset-cmd.c (revision 27846) +++ subversion/svn/propset-cmd.c (working copy) @@ -123,6 +123,9 @@ svn_revnum_t rev; const char *URL; + if (svn_path_array_has_relative_url(targets)) + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + /* Implicit "." is okay for revision properties; it just helps us find the right repository. */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/switch-cmd.c =================================================================== --- subversion/svn/switch-cmd.c (revision 27846) +++ subversion/svn/switch-cmd.c (working copy) @@ -123,6 +123,9 @@ target = APR_ARRAY_IDX(targets, 1, const char *); } + if (svn_path_is_relative_url(switch_url)) + SVN_ERR(svn_wc_get_absolute_url(&switch_url, NULL, ".", pool)); + /* Strip peg revision if targets contains an URI. */ SVN_ERR(svn_opt_parse_path(&peg_revision, &true_path, switch_url, pool)); APR_ARRAY_IDX(targets, 0, const char *) = true_path; Index: subversion/svn/delete-cmd.c =================================================================== --- subversion/svn/delete-cmd.c (revision 27846) +++ subversion/svn/delete-cmd.c (working copy) @@ -54,6 +54,10 @@ svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2, FALSE, FALSE, FALSE, pool); + /* Resolve any relative urls found in targets */ + if (svn_path_array_has_relative_url(targets)) + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + if (! svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *))) { ctx->log_msg_func3 = NULL; Index: subversion/svn/import-cmd.c =================================================================== --- subversion/svn/import-cmd.c (revision 27846) +++ subversion/svn/import-cmd.c (working copy) @@ -95,6 +95,9 @@ url = APR_ARRAY_IDX(targets, 1, const char *); } + if (svn_path_is_relative_url(url)) + SVN_ERR(svn_wc_get_absolute_url(&url, NULL, ".", pool)); + if (! svn_path_is_url(url)) return svn_error_createf (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, Index: subversion/svn/proplist-cmd.c =================================================================== --- subversion/svn/proplist-cmd.c (revision 27846) +++ subversion/svn/proplist-cmd.c (working copy) @@ -140,6 +140,9 @@ SVN_ERR(svn_opt_args_to_target_array2(&targets, os, combined_targets, pool)); + if (svn_path_array_has_relative_url(targets)) + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + /* Add "." if user passed 0 arguments */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/export-cmd.c =================================================================== --- subversion/svn/export-cmd.c (revision 27846) +++ subversion/svn/export-cmd.c (working copy) @@ -58,6 +58,10 @@ /* The first target is the `from' path. */ from = APR_ARRAY_IDX(targets, 0, const char *); + /* Resolve relative url, if it is one. */ + if (svn_path_is_relative_url(from)) + SVN_ERR(svn_wc_get_absolute_url(&from, NULL, ".", pool)); + /* Get the peg revision if present. */ SVN_ERR(svn_opt_parse_path(&peg_revision, &truefrom, from, pool)); Index: subversion/svn/propedit-cmd.c =================================================================== --- subversion/svn/propedit-cmd.c (revision 27846) +++ subversion/svn/propedit-cmd.c (working copy) @@ -64,6 +64,9 @@ SVN_ERR(svn_opt_args_to_target_array2(&targets, os, opt_state->targets, pool)); + if (svn_path_array_has_relative_url(targets)) + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + if (opt_state->revprop) /* operate on a revprop */ { svn_revnum_t rev; Index: subversion/svn/lock-cmd.c =================================================================== --- subversion/svn/lock-cmd.c (revision 27846) +++ subversion/svn/lock-cmd.c (working copy) @@ -117,6 +117,9 @@ if (! targets->nelts) return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + if (svn_path_array_has_relative_url(targets)) + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + /* Get comment. */ SVN_ERR(get_comment(&comment, ctx, opt_state, pool)); Index: subversion/svn/info-cmd.c =================================================================== --- subversion/svn/info-cmd.c (revision 27846) +++ subversion/svn/info-cmd.c (working copy) @@ -512,6 +512,9 @@ svn_pool_clear(subpool); SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); + if (svn_path_is_relative_url(target)) + SVN_ERR(svn_wc_get_absolute_url(&target, NULL, ".", subpool)); + /* Get peg revisions. */ SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, subpool)); Index: subversion/svn/unlock-cmd.c =================================================================== --- subversion/svn/unlock-cmd.c (revision 27846) +++ subversion/svn/unlock-cmd.c (working copy) @@ -76,6 +76,9 @@ if (! targets->nelts) return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + if (svn_path_array_has_relative_url(targets)) + SVN_ERR(svn_wc_get_absolute_url_array(&targets, NULL, ".", pool)); + svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2, FALSE, FALSE, FALSE, pool);