Index: subversion/include/svn_client.h =================================================================== --- subversion/include/svn_client.h (revision 29840) +++ subversion/include/svn_client.h (working copy) @@ -889,6 +889,44 @@ typedef struct svn_client_ctx_t #define SVN_CLIENT_AUTH_PASSWORD "password" /** @} group end: Authentication information file names */ +/** Client argument processing + * + * @defgroup clnt_cmdline Client command-line processing + * + * @{ + */ + +/** + * Pull remaining target argument from @a os into @a *targets_p, + * converting them to UTF-8, followed by targets from @a known_targets + * (which might come from, for example, the "--targets" command line option). + * + * On each URL target, do some IRI-to-URI encoding and some auto-escaping. + * On each local path, canonicalize case and path separators. + * + * Allocate @a *targets_p and its elements in @a pool. + * + * @ctx is required for possible repository authentication. + * + * If a path has the same name as a Subversion working copy + * administrative directory, return SVN_ERR_RESERVED_FILENAME_SPECIFIED; + * if multiple reserved paths are encountered, return a chain of + * errors, all of which are SVN_ERR_RESERVED_FILENAME_SPECIFIED. Do + * not return this type of error in a chain with any other type of + * error, and if this is the only type of error encountered, complete + * the operation before returning the error(s). + * + * @since New in 1.6 + */ +svn_error_t * +svn_client_args_to_target_array(apr_array_header_t **targets_p, + apr_getopt_t *os, + apr_array_header_t *known_targets, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + +/** @} group end: Client command-line processing */ + /** @} */ /** Index: subversion/include/private/svn_opt_private.h =================================================================== --- subversion/include/private/svn_opt_private.h (revision 0) +++ subversion/include/private/svn_opt_private.h (revision 0) @@ -0,0 +1,65 @@ +/** + * @copyright + * ==================================================================== + * Copyright (c) 2008 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + * @endcopyright + * + * @file svn_opt_private.h + * @brief Subversion-internal option parsing APIs. + */ + +#ifndef SVN_OPT_PRIVATE_H +#define SVN_OPT_PRIVATE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Attempt to transform URL_IN, which is a URL-like user input, into a + * valid URL: + * - escape IRI characters and some other non-URI characters + * - check that only valid URI characters remain + * - check that no back-path ("..") components are present + * - canonicalize the separator ("/") characters + * URL_IN is in UTF-8 encoding and has no peg revision specifier. + * Set *URL_OUT to the result, allocated from POOL. + */ +svn_error_t * +svn_opt__arg_canonicalize_url(const char **url_out, + const char *url_in, + apr_pool_t *pool); + +/* + * Attempt to transform PATH_IN, which is a local path-like user input, into a + * valid local path: + * - Attempt to get the correct capitialization by trying to actually find + * the path specified. + * - If the path does not exist (which is valid) the given capitialization + * is used. + * - canonicalize the separator ("/") characters + * PATH_IN is in UTF-8 encoding and has no peg revision specifier. + * Set *PATH_OUT to the result, allocated from POOL. + */ +svn_error_t * +svn_opt__arg_canonicalize_path(const char **path_out, + const char *path_in, + apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_OPT_PRIVATE_H */ Index: subversion/include/svn_opt.h =================================================================== --- subversion/include/svn_opt.h (revision 29840) +++ subversion/include/svn_opt.h (working copy) @@ -489,7 +489,7 @@ svn_opt_resolve_revisions(svn_opt_revision_t *peg_ * error, and if this is the only type of error encountered, complete * the operation before returning the error(s). * - * @since New in 1.5. + * @deprecated Provided for backward compatibility with the 1.5 API. */ svn_error_t * svn_opt_args_to_target_array3(apr_array_header_t **targets_p, Index: subversion/libsvn_subr/opt.c =================================================================== --- subversion/libsvn_subr/opt.c (revision 29840) +++ subversion/libsvn_subr/opt.c (working copy) @@ -37,6 +37,8 @@ #include "svn_utf.h" #include "svn_time.h" +#include "private/svn_opt_private.h" + #include "svn_private_config.h" @@ -965,59 +967,14 @@ svn_opt_args_to_target_array3(apr_array_header_t * /* URLs and wc-paths get treated differently. */ if (svn_path_is_url(utf8_target)) { - /* No need to canonicalize a URL's case or path separators. */ - - /* Convert to URI. */ - target = svn_path_uri_from_iri(utf8_target, pool); - /* Auto-escape some ASCII characters. */ - target = svn_path_uri_autoescape(target, pool); - - /* The above doesn't guarantee a valid URI. */ - if (! svn_path_is_uri_safe(target)) - return svn_error_createf(SVN_ERR_BAD_URL, 0, - _("URL '%s' is not properly URI-encoded"), - utf8_target); - - /* Verify that no backpaths are present in the URL. */ - if (svn_path_is_backpath_present(target)) - return svn_error_createf(SVN_ERR_BAD_URL, 0, - _("URL '%s' contains a '..' element"), - utf8_target); - - /* strip any trailing '/' */ - target = svn_path_canonicalize(target, pool); + SVN_ERR(svn_opt__arg_canonicalize_url(&target, utf8_target, pool)); } else /* not a url, so treat as a path */ { - const char *apr_target; const char *base_name; - char *truenamed_target; /* APR-encoded */ - apr_status_t apr_err; - /* canonicalize case, and change all separators to '/'. */ - SVN_ERR(svn_path_cstring_from_utf8(&apr_target, utf8_target, - pool)); - apr_err = apr_filepath_merge(&truenamed_target, "", apr_target, - APR_FILEPATH_TRUENAME, pool); + SVN_ERR(svn_opt__arg_canonicalize_path(&target, utf8_target, pool)); - if (!apr_err) - /* We have a canonicalized APR-encoded target now. */ - apr_target = truenamed_target; - else if (APR_STATUS_IS_ENOENT(apr_err)) - /* It's okay for the file to not exist, that just means we - have to accept the case given to the client. We'll use - the original APR-encoded target. */ - ; - else - return svn_error_createf(apr_err, NULL, - _("Error resolving case of '%s'"), - svn_path_local_style(utf8_target, - pool)); - - /* convert back to UTF-8. */ - SVN_ERR(svn_path_cstring_to_utf8(&target, apr_target, pool)); - target = svn_path_canonicalize(target, pool); - /* If the target has the same name as a Subversion working copy administrative dir, skip it. */ base_name = svn_path_basename(target, pool); @@ -1141,7 +1098,69 @@ svn_opt_parse_revprop(apr_hash_t **revprop_table_p return SVN_NO_ERROR; } +svn_error_t * +svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in, + apr_pool_t *pool) +{ + const char *target; + /* Convert to URI. */ + target = svn_path_uri_from_iri(url_in, pool); + /* Auto-escape some ASCII characters. */ + target = svn_path_uri_autoescape(target, pool); + + /* The above doesn't guarantee a valid URI. */ + if (! svn_path_is_uri_safe(target)) + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("URL '%s' is not properly URI-encoded"), + target); + + /* Verify that no backpaths are present in the URL. */ + if (svn_path_is_backpath_present(target)) + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("URL '%s' contains a '..' element"), + target); + + /* strip any trailing '/' and collapse other redundant elements */ + target = svn_path_canonicalize(target, pool); + + *url_out = target; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in, + apr_pool_t *pool) +{ + const char *apr_target; + char *truenamed_target; /* APR-encoded */ + apr_status_t apr_err; + + /* canonicalize case, and change all separators to '/'. */ + SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool)); + apr_err = apr_filepath_merge(&truenamed_target, "", apr_target, + APR_FILEPATH_TRUENAME, pool); + + if (!apr_err) + /* We have a canonicalized APR-encoded target now. */ + apr_target = truenamed_target; + else if (APR_STATUS_IS_ENOENT(apr_err)) + /* It's okay for the file to not exist, that just means we + have to accept the case given to the client. We'll use + the original APR-encoded target. */ + ; + else + return svn_error_createf(apr_err, NULL, + _("Error resolving case of '%s'"), + svn_path_local_style(path_in, pool)); + + /* convert back to UTF-8. */ + SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool)); + *path_out = svn_path_canonicalize(*path_out, pool); + + return SVN_NO_ERROR; +} + /* Print version info for PGM_NAME. If QUIET is true, print in * brief. Else if QUIET is not true, print the version more * verbosely, and if FOOTER is non-null, print it following the Index: subversion/libsvn_client/cmdline.c =================================================================== --- subversion/libsvn_client/cmdline.c (revision 0) +++ subversion/libsvn_client/cmdline.c (revision 0) @@ -0,0 +1,307 @@ +/* + * cmdline.c: command-line processing + * + * ==================================================================== + * Copyright (c) 2000-2007 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +/* ==================================================================== */ + + +/*** Includes. ***/ +#include "svn_client.h" +#include "svn_error.h" +#include "svn_path.h" +#include "svn_opt.h" +#include "svn_utf.h" + +#include "client.h" + +#include "private/svn_opt_private.h" + + +/*** Code. ***/ + +#define DEFAULT_ARRAY_SIZE 5 + +/* Return true iff ARG is a repository-relative URL: specifically that + * it starts with the characters "^/". + * ARG is in UTF-8 encoding. + * Do not check whether ARG is properly URI-encoded, canonical, or valid + * in any other way. */ +static svn_boolean_t +arg_is_repos_relative_url(const char *arg) +{ + return (0 == strncmp("^/", arg, 2)); +} + +/* Set *ABSOLUTE_URL to the absolute URL represented by RELATIVE_URL + * relative to REPOS_ROOT_URL. + * *ABSOLUTE_URL will end with a peg revision specifier if RELATIVE_URL did. + * RELATIVE_URL is in repository-relative syntax: + * "^/[REL-URL][@PEG]", + * REPOS_ROOT_URL is the absolute URL of the repository root. + * All strings are in UTF-8 encoding. + * Allocate *ABSOLUTE_URL in POOL. + */ +static svn_error_t * +resolve_repos_relative_url(const char **absolute_url, + const char *relative_url, + const char *repos_root_url, + apr_pool_t *pool) +{ + if (! arg_is_repos_relative_url(relative_url)) + return svn_error_createf(SVN_ERR_BAD_URL, NULL, + "Improper relative URL '%s'", + relative_url); + + *absolute_url = svn_path_join(repos_root_url, relative_url + 2, pool); + + return SVN_NO_ERROR; +} + + +/* Attempt to find the root url for TARGET possibly using CTX for + * authentication. If one is found and *ROOT_URL is + * not NULL, then the root url for TARGET must match the value given in + * *ROOT_URL. If *ROOT_URL is NULL then set it with the root url allocated + * from POOL as determined from TARGET. + * + * TARGET is a UTF-8 encoded string that is fully canonicalized and escaped. + * + * If a root url is not found for TARGET because it does not exist in the + * repository, then return with no error. + */ +static svn_error_t * +check_root_url_of_target(const char **root_url, + const char *target, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + svn_error_t *error; + const char *tmp_root_url; + const char *truepath; + svn_opt_revision_t opt_rev; + + SVN_ERR(svn_opt_parse_path(&opt_rev, &truepath, target, pool)); + + if ((error = svn_client__get_repos_root(&tmp_root_url, + truepath, + &opt_rev, + NULL, ctx, pool))) + { + /* It is OK if the given target does not exist, it just means + * we will not be able to determine the root url from this particular + * argument. + */ + if ( (error->apr_err == SVN_ERR_ENTRY_NOT_FOUND) + || (error->apr_err == SVN_ERR_WC_NOT_DIRECTORY)) + { + svn_error_clear(error); + return SVN_NO_ERROR; + } + else + return error; + } + else if ( (*root_url != NULL) + && (strcmp(*root_url, tmp_root_url) != 0)) + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + "All non-relative targets must have the same root url."); + else + *root_url = tmp_root_url; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client_args_to_target_array(apr_array_header_t **targets_p, + apr_getopt_t *os, + apr_array_header_t *known_targets, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + int i; + svn_boolean_t rel_url_found = FALSE; + const char *root_url = NULL; + svn_error_t *err = SVN_NO_ERROR; + apr_array_header_t *input_targets = + apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); + apr_array_header_t *output_targets = + apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); + + /* Step 1: create a master array of targets that are in UTF-8 + encoding, and come from concatenating the targets left by apr_getopt, + plus any extra targets (e.g., from the --targets switch.) + If any of the targets are relative urls, then set the rel_url_found + flag.*/ + + for (; os->ind < os->argc; os->ind++) + { + /* The apr_getopt targets are still in native encoding. */ + const char *raw_target = os->argv[os->ind]; + const char *utf8_target; + + SVN_ERR(svn_utf_cstring_to_utf8(&utf8_target, + raw_target, pool)); + + if (arg_is_repos_relative_url(utf8_target)) + rel_url_found = TRUE; + + APR_ARRAY_PUSH(input_targets, const char *) = utf8_target; + } + + if (known_targets) + { + for (i = 0; i < known_targets->nelts; i++) + { + /* The --targets array have already been converted to UTF-8, + because we needed to split up the list with svn_cstring_split. */ + const char *utf8_target = APR_ARRAY_IDX(known_targets, + i, const char *); + + if (arg_is_repos_relative_url(utf8_target)) + rel_url_found = TRUE; + + APR_ARRAY_PUSH(input_targets, const char *) = utf8_target; + } + } + + /* Step 2: process each target. */ + + for (i = 0; i < input_targets->nelts; i++) + { + const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *); + const char *peg_start = NULL; /* pointer to the peg revision, if any */ + const char *target; + int j; + + /* Remove a peg revision, if any, in the target so that it can + be properly canonicalized, otherwise the canonicalization + does not treat a ".@BASE" as a "." with a BASE peg revision, + and it is not canonicalized to "@BASE". If any peg revision + exists, it is appended to the final canonicalized path or + URL. Do not use svn_opt_parse_path() because the resulting + peg revision is a structure that would have to be converted + back into a string. Converting from a string date to the + apr_time_t field in the svn_opt_revision_value_t and back to + a string would not necessarily preserve the exact bytes of + the input date, so its easier just to keep it in string + form. */ + for (j = (strlen(utf8_target) - 1); j >= 0; --j) + { + /* If we hit a path separator, stop looking. This is OK + only because our revision specifiers can't contain + '/'. */ + if (utf8_target[j] == '/') + break; + if (utf8_target[j] == '@') + { + peg_start = utf8_target + j; + break; + } + } + if (peg_start) + utf8_target = apr_pstrmemdup(pool, + utf8_target, + peg_start - utf8_target); + + /* Relative urls will be canonicallized when they are resolved later in + * the function + */ + if (arg_is_repos_relative_url(utf8_target)) + { + APR_ARRAY_PUSH(output_targets, const char *) = utf8_target; + } + else + { + /* URLs and wc-paths get treated differently. */ + if (svn_path_is_url(utf8_target)) + { + SVN_ERR(svn_opt__arg_canonicalize_url(&target, + utf8_target, pool)); + } + else /* not a url, so treat as a path */ + { + const char *base_name; + + SVN_ERR(svn_opt__arg_canonicalize_path(&target, + utf8_target, pool)); + + /* If the target has the same name as a Subversion + working copy administrative dir, skip it. */ + base_name = svn_path_basename(target, pool); + + if (svn_wc_is_adm_dir(base_name, pool)) + { + err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, + err, + "'%s' ends in a reserved name", + target); + continue; + } + } + + /* Append the peg revision back to the canonicalized target if + there was a peg revision. */ + if (peg_start) + target = apr_pstrcat(pool, target, peg_start, NULL); + + if (rel_url_found) + { + SVN_ERR(check_root_url_of_target(&root_url, target, + ctx, pool)); + } + + APR_ARRAY_PUSH(output_targets, const char *) = target; + } + } + + /* Only resolve relative urls if there were some actually found earlier. */ + if (rel_url_found) + { + /* + * Use the current directory's root url if one wasn't found using the + * arguments. + */ + if (root_url == NULL) + SVN_ERR(svn_client_root_url_from_path(&root_url, + svn_path_canonicalize(".", pool), + ctx, pool)); + + *targets_p = apr_array_make(pool, output_targets->nelts, sizeof(const char *)); + + for (i = 0; i < output_targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(output_targets, i, + const char *); + + if (arg_is_repos_relative_url(target)) + { + const char *abs_target; + + SVN_ERR(resolve_repos_relative_url(&abs_target, target, + root_url, pool)); + + SVN_ERR(svn_opt__arg_canonicalize_url(&target, abs_target, + pool)); + } + + APR_ARRAY_PUSH(*targets_p, const char *) = target; + } + } + else + *targets_p = output_targets; + + return err; +} Index: subversion/tests/libsvn_client/client-test.c =================================================================== --- subversion/tests/libsvn_client/client-test.c (revision 29840) +++ subversion/tests/libsvn_client/client-test.c (working copy) @@ -20,6 +20,7 @@ #include "svn_mergeinfo.h" #include "../../libsvn_client/mergeinfo.h" #include "svn_pools.h" +#include "svn_client.h" #include "../svn_test.h" @@ -98,11 +99,117 @@ test_elide_mergeinfo_catalog(const char **msg, return SVN_NO_ERROR; } +static svn_error_t * +test_args_to_target_array(const char **msg, + svn_boolean_t msg_only, + svn_test_opts_t *opts, + apr_pool_t *pool) +{ + apr_size_t i; + apr_pool_t *iterpool; + svn_client_ctx_t *ctx; + static struct { + const char *input; + const char *output; /* NULL means an error is expected. */ + } const tests[] = { + { ".", "" }, + { ".@BASE", "@BASE" }, + { "foo///bar", "foo/bar" }, + { "foo///bar@13", "foo/bar@13" }, + { "foo///bar@HEAD", "foo/bar@HEAD" }, + { "foo///bar@{1999-12-31}", "foo/bar@{1999-12-31}" }, + { "http://a//b////", "http://a/b" }, + { "http://a///b@27", "http://a/b@27" }, + { "http://a/b//@COMMITTED", "http://a/b@COMMITTED" }, + { "foo///bar@1:2", "foo/bar@1:2" }, + { "foo///bar@baz", "foo/bar@baz" }, + { "foo///bar@", "foo/bar@" }, + { "foo///bar///@13", "foo/bar@13" }, + { "foo///bar@@13", "foo/bar@@13" }, + { "foo///@bar@HEAD", "foo/@bar@HEAD" }, + { "foo@///bar", "foo@/bar" }, + { "foo@HEAD///bar", "foo@HEAD/bar" }, + }; + + *msg = "test svn_client_args_to_target_array"; + if (msg_only) + return SVN_NO_ERROR; + + SVN_ERR(svn_client_create_context(&ctx, pool)); + + iterpool = svn_pool_create(pool); + + for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) + { + const char *input = tests[i].input; + const char *expected_output = tests[i].output; + apr_array_header_t *targets; + apr_getopt_t *os; + const int argc = 2; + const char *argv[] = { "opt-test", input, NULL }; + apr_status_t apr_err; + svn_error_t *err; + + apr_err = apr_getopt_init(&os, iterpool, argc, argv); + if (apr_err) + return svn_error_wrap_apr(apr_err, + "Error initializing command line arguments"); + + err = svn_client_args_to_target_array(&targets, os, NULL, ctx, iterpool); + + if (expected_output) + { + const char *actual_output; + + if (err) + return err; + if (argc - 1 != targets->nelts) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "Passed %d target(s) to " + "svn_client_args_to_target_array() but " + "got %d back.", + argc - 1, + targets->nelts); + + actual_output = APR_ARRAY_IDX(targets, 0, const char *); + + if (! svn_path_is_canonical(actual_output, iterpool)) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "Input '%s' to " + "svn_client_args_to_target_array() should " + "have returned a canonical path but " + "'%s' is not.", + input, + actual_output); + + if (strcmp(expected_output, actual_output) != 0) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "Input '%s' to " + "svn_client_args_to_target_array() should " + "have returned '%s' but returned '%s'.", + input, + expected_output, + actual_output); + } + else + { + if (! err) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "Unexpected success in passing '%s' " + "to svn_client_args_to_target_array().", + input); + } + } + + return SVN_NO_ERROR; +} + /* ========================================================================== */ struct svn_test_descriptor_t test_funcs[] = { SVN_TEST_NULL, SVN_TEST_PASS(test_elide_mergeinfo_catalog), + SVN_TEST_PASS(test_args_to_target_array), SVN_TEST_NULL }; Index: subversion/tests/cmdline/special_tests.py =================================================================== --- subversion/tests/cmdline/special_tests.py (revision 29840) +++ subversion/tests/cmdline/special_tests.py (working copy) @@ -613,18 +613,18 @@ def warn_on_reserved_name(sbox): sbox.build() wc_dir = sbox.wc_dir if os.path.exists(os.path.join(wc_dir, ".svn")): + reserved_path = os.path.join(wc_dir, ".svn") + elif os.path.exists(os.path.join(wc_dir, "_svn")): reserved_path = os.path.join(wc_dir, "_svn") - elif os.path.exists(os.path.join(wc_dir, "_svn")): - reserved_path = os.path.join(wc_dir, ".svn") else: # We don't know how to test this, but have no reason to believe # it would fail. (TODO: any way to return 'Skip', though?) return - svntest.main.file_append(reserved_path, 'expecting rejection') svntest.actions.run_and_verify_svn( - "Adding a file with a reserved name failed to result in an error", - None, ".*Skipping argument: '.+' ends in a reserved name.*", - 'add', reserved_path) + "Locking a file with a reserved name failed to result in an error", + None, + ".*Skipping argument: '.+' ends in a reserved name.*", + 'lock', reserved_path) ######################################################################## Index: subversion/tests/cmdline/basic_tests.py =================================================================== --- subversion/tests/cmdline/basic_tests.py (revision 29840) +++ subversion/tests/cmdline/basic_tests.py (working copy) @@ -2162,7 +2162,93 @@ def info_nonexisting_file(sbox): #---------------------------------------------------------------------- +# Relative urls +# +# Use blame to test three specific cases for relative url support. +def basic_relative_url_using_current_dir(sbox): + "basic relative url target using current dir" + # We'll use blame to test relative url parsing + 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) + + exit_code, output, error = svntest.actions.run_and_verify_svn(None, + expected_output, [], + 'blame', '^/iota') + + os.chdir(orig_dir) + +def basic_relative_url_using_other_targets(sbox): + "basic relative url target using other targets" + + 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) + + # Now, make a new revision of A/mu . + mu = os.path.join(sbox.wc_dir, 'A', 'mu') + mu_url = sbox.repo_url + '/A/mu' + + svntest.main.file_append(mu, "New contents for mu\n") + svntest.main.run_svn(None, 'ci', + '-m', '', mu) + + + expected_output = [ + " 1 jrandom This is the file 'iota'.\n", + " 2 jrandom New contents for iota\n", + " 1 jrandom This is the file 'mu'.\n", + " 3 jrandom New contents for mu\n", + ] + + exit_code, output, error = svntest.actions.run_and_verify_svn(None, + expected_output, [], 'blame', + '^/iota', mu_url) + +def basic_relative_url_multi_repo(sbox): + "basic relative url target with multiple repos" + + sbox.build() + repo_url1 = sbox.repo_url + repo_dir1 = sbox.repo_dir + wc_dir1 = sbox.wc_dir + + repo_dir2, repo_url2 = sbox.add_repo_path("other") + svntest.main.copy_repos(repo_dir1, repo_dir2, 1, 1) + wc_dir2 = sbox.add_wc_path("other") + svntest.actions.run_and_verify_svn("Unexpected error during co", + svntest.verify.AnyOutput, [], "co", + repo_url2, + wc_dir2) + + # Don't bother with making new revisions, the command should not work. + iota_url_repo1 = repo_url1 + '/iota' + iota_url_repo2 = repo_url2 + '/iota' + + exit_code, output, error = svntest.actions.run_and_verify_svn(None, [], + svntest.verify.AnyOutput, 'blame', + '^/A/mu', iota_url_repo1, iota_url_repo2) + + +#---------------------------------------------------------------------- + ######################################################################## # Run the tests @@ -2208,6 +2294,9 @@ test_list = [ None, XFail(basic_rm_urls_multi_repos), automatic_conflict_resolution, info_nonexisting_file, + basic_relative_url_using_current_dir, + basic_relative_url_using_other_targets, + basic_relative_url_multi_repo, ] if __name__ == '__main__': Index: subversion/svn/merge-cmd.c =================================================================== --- subversion/svn/merge-cmd.c (revision 29840) +++ subversion/svn/merge-cmd.c (working copy) @@ -52,7 +52,7 @@ svn_cl__merge(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Parse at least one, and possible two, sources. */ if (targets->nelts >= 1) Index: subversion/svn/cl.h =================================================================== --- subversion/svn/cl.h (revision 29840) +++ subversion/svn/cl.h (working copy) @@ -581,6 +581,7 @@ svn_error_t * svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets_p, apr_getopt_t *os, apr_array_header_t *known_targets, + svn_client_ctx_t *ctx, apr_pool_t *pool); #ifdef __cplusplus Index: subversion/svn/propdel-cmd.c =================================================================== --- subversion/svn/propdel-cmd.c (revision 29840) +++ subversion/svn/propdel-cmd.c (working copy) @@ -58,7 +58,7 @@ svn_cl__propdel(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Add "." if user passed 0 file arguments */ Index: subversion/svn/checkout-cmd.c =================================================================== --- subversion/svn/checkout-cmd.c (revision 29840) +++ subversion/svn/checkout-cmd.c (working copy) @@ -72,7 +72,7 @@ svn_cl__checkout(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); if (! targets->nelts) return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); Index: subversion/svn/move-cmd.c =================================================================== --- subversion/svn/move-cmd.c (revision 29840) +++ subversion/svn/move-cmd.c (working copy) @@ -48,7 +48,7 @@ svn_cl__move(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); if (targets->nelts < 2) return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); Index: subversion/svn/mkdir-cmd.c =================================================================== --- subversion/svn/mkdir-cmd.c (revision 29840) +++ subversion/svn/mkdir-cmd.c (working copy) @@ -47,7 +47,7 @@ svn_cl__mkdir(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); if (! targets->nelts) return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); Index: subversion/svn/cat-cmd.c =================================================================== --- subversion/svn/cat-cmd.c (revision 29840) +++ subversion/svn/cat-cmd.c (working copy) @@ -45,7 +45,7 @@ svn_cl__cat(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Cat cannot operate on an implicit '.' so a filename is required */ if (! targets->nelts) Index: subversion/svn/revert-cmd.c =================================================================== --- subversion/svn/revert-cmd.c (revision 29840) +++ subversion/svn/revert-cmd.c (working copy) @@ -45,7 +45,7 @@ svn_cl__revert(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Revert has no implicit dot-target `.', so don't you put that code here! */ if (! targets->nelts) Index: subversion/svn/diff-cmd.c =================================================================== --- subversion/svn/diff-cmd.c (revision 29840) +++ subversion/svn/diff-cmd.c (working copy) @@ -147,6 +147,7 @@ svn_cl__diff(apr_getopt_t *os, apr_pool_t *pool) { svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; + svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; apr_array_header_t *options; apr_array_header_t *targets; apr_file_t *outfile, *errfile; @@ -190,7 +191,7 @@ svn_cl__diff(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); if (! opt_state->old_target && ! opt_state->new_target && (targets->nelts == 2) @@ -230,7 +231,7 @@ svn_cl__diff(apr_getopt_t *os, const char *)); SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp, - pool)); + ctx, pool)); SVN_ERR(svn_opt_parse_path(&old_rev, &old_target, APR_ARRAY_IDX(tmp2, 0, const char *), pool)); @@ -328,8 +329,7 @@ svn_cl__diff(apr_getopt_t *os, opt_state->changelists, summarize_func, (void *) target1, - ((svn_cl__cmd_baton_t *)baton)->ctx, - iterpool)); + ctx, iterpool)); else SVN_ERR(svn_client_diff4 (options, @@ -346,8 +346,7 @@ svn_cl__diff(apr_getopt_t *os, outfile, errfile, opt_state->changelists, - ((svn_cl__cmd_baton_t *)baton)->ctx, - iterpool)); + ctx, iterpool)); } else { @@ -374,8 +373,7 @@ svn_cl__diff(apr_getopt_t *os, opt_state->changelists, summarize_func, (void *) truepath, - ((svn_cl__cmd_baton_t *)baton)->ctx, - iterpool)); + ctx, iterpool)); else SVN_ERR(svn_client_diff_peg4 (options, @@ -392,8 +390,7 @@ svn_cl__diff(apr_getopt_t *os, outfile, errfile, opt_state->changelists, - ((svn_cl__cmd_baton_t *)baton)->ctx, - iterpool)); + ctx, iterpool)); } } Index: subversion/svn/copy-cmd.c =================================================================== --- subversion/svn/copy-cmd.c (revision 29840) +++ subversion/svn/copy-cmd.c (working copy) @@ -49,7 +49,7 @@ svn_cl__copy(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); if (targets->nelts < 2) return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); Index: subversion/svn/mergeinfo-cmd.c =================================================================== --- subversion/svn/mergeinfo-cmd.c (revision 29840) +++ subversion/svn/mergeinfo-cmd.c (working copy) @@ -133,7 +133,7 @@ svn_cl__mergeinfo(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Add "." if user passed 0 arguments. */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/list-cmd.c =================================================================== --- subversion/svn/list-cmd.c (revision 29840) +++ subversion/svn/list-cmd.c (working copy) @@ -220,7 +220,7 @@ svn_cl__list(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Add "." if user passed 0 arguments */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/util.c =================================================================== --- subversion/svn/util.c (revision 29840) +++ subversion/svn/util.c (working copy) @@ -1014,10 +1014,12 @@ svn_error_t * svn_cl__args_to_target_array_print_reserved(apr_array_header_t **targets, apr_getopt_t *os, apr_array_header_t *known_targets, + svn_client_ctx_t *ctx, apr_pool_t *pool) { - svn_error_t *error = svn_opt_args_to_target_array3(targets, os, - known_targets, pool); + svn_error_t *error = svn_client_args_to_target_array(targets, os, + known_targets, + ctx, pool); if (error) { if (error->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED) Index: subversion/svn/blame-cmd.c =================================================================== --- subversion/svn/blame-cmd.c (revision 29840) +++ subversion/svn/blame-cmd.c (working copy) @@ -204,7 +204,7 @@ svn_cl__blame(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Blame needs a file on which to operate. */ if (! targets->nelts) Index: subversion/svn/propget-cmd.c =================================================================== --- subversion/svn/propget-cmd.c (revision 29840) +++ subversion/svn/propget-cmd.c (working copy) @@ -182,7 +182,7 @@ svn_cl__propget(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Add "." if user passed 0 file arguments */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/changelist-cmd.c =================================================================== --- subversion/svn/changelist-cmd.c (revision 29840) +++ subversion/svn/changelist-cmd.c (working copy) @@ -60,7 +60,7 @@ svn_cl__changelist(apr_getopt_t *os, /* Parse the remaining arguments as paths. */ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Changelist has no implicit dot-target `.', so don't you put that code here! */ Index: subversion/svn/log-cmd.c =================================================================== --- subversion/svn/log-cmd.c (revision 29840) +++ subversion/svn/log-cmd.c (working copy) @@ -468,7 +468,7 @@ svn_cl__log(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Add "." if user passed 0 arguments */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/update-cmd.c =================================================================== --- subversion/svn/update-cmd.c (revision 29840) +++ subversion/svn/update-cmd.c (working copy) @@ -46,8 +46,9 @@ svn_cl__update(apr_getopt_t *os, svn_depth_t depth; svn_boolean_t depth_is_sticky; - SVN_ERR(svn_opt_args_to_target_array3(&targets, os, - opt_state->targets, pool)); + SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, + opt_state->targets, + ctx, pool)); /* Add "." if user passed 0 arguments */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/resolved-cmd.c =================================================================== --- subversion/svn/resolved-cmd.c (revision 29840) +++ subversion/svn/resolved-cmd.c (working copy) @@ -76,7 +76,7 @@ svn_cl__resolved(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); if (! targets->nelts) return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); Index: subversion/svn/cleanup-cmd.c =================================================================== --- subversion/svn/cleanup-cmd.c (revision 29840) +++ subversion/svn/cleanup-cmd.c (working copy) @@ -45,7 +45,7 @@ svn_cl__cleanup(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Add "." if user passed 0 arguments */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/commit-cmd.c =================================================================== --- subversion/svn/commit-cmd.c (revision 29840) +++ subversion/svn/commit-cmd.c (working copy) @@ -53,7 +53,7 @@ svn_cl__commit(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Add "." if user passed 0 arguments. */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/add-cmd.c =================================================================== --- subversion/svn/add-cmd.c (revision 29840) +++ subversion/svn/add-cmd.c (working copy) @@ -47,7 +47,7 @@ svn_cl__add(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); if (! targets->nelts) return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); Index: subversion/svn/propset-cmd.c =================================================================== --- subversion/svn/propset-cmd.c (revision 29840) +++ subversion/svn/propset-cmd.c (working copy) @@ -93,7 +93,7 @@ svn_cl__propset(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Implicit "." is okay for revision properties; it just helps us find the right repository. */ Index: subversion/svn/switch-cmd.c =================================================================== --- subversion/svn/switch-cmd.c (revision 29840) +++ subversion/svn/switch-cmd.c (working copy) @@ -101,7 +101,7 @@ svn_cl__switch(apr_getopt_t *os, switch to ("switch_url"). */ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* handle only-rewrite case specially */ if (opt_state->relocate) Index: subversion/svn/delete-cmd.c =================================================================== --- subversion/svn/delete-cmd.c (revision 29840) +++ subversion/svn/delete-cmd.c (working copy) @@ -46,7 +46,7 @@ svn_cl__delete(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); if (! targets->nelts) return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); Index: subversion/svn/import-cmd.c =================================================================== --- subversion/svn/import-cmd.c (revision 29840) +++ subversion/svn/import-cmd.c (working copy) @@ -75,7 +75,7 @@ svn_cl__import(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); if (targets->nelts < 1) return svn_error_create Index: subversion/svn/proplist-cmd.c =================================================================== --- subversion/svn/proplist-cmd.c (revision 29840) +++ subversion/svn/proplist-cmd.c (working copy) @@ -114,7 +114,7 @@ svn_cl__proplist(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Add "." if user passed 0 file arguments */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/export-cmd.c =================================================================== --- subversion/svn/export-cmd.c (revision 29840) +++ subversion/svn/export-cmd.c (working copy) @@ -48,7 +48,7 @@ svn_cl__export(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* We want exactly 1 or 2 targets for this subcommand. */ if (targets->nelts < 1) Index: subversion/svn/status-cmd.c =================================================================== --- subversion/svn/status-cmd.c (revision 29840) +++ subversion/svn/status-cmd.c (working copy) @@ -218,7 +218,7 @@ svn_cl__status(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Add "." if user passed 0 arguments */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/propedit-cmd.c =================================================================== --- subversion/svn/propedit-cmd.c (revision 29840) +++ subversion/svn/propedit-cmd.c (working copy) @@ -68,7 +68,7 @@ svn_cl__propedit(apr_getopt_t *os, /* Suck up all the remaining arguments into a targets array */ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); if (opt_state->revprop) /* operate on a revprop */ { Index: subversion/svn/lock-cmd.c =================================================================== --- subversion/svn/lock-cmd.c (revision 29840) +++ subversion/svn/lock-cmd.c (working copy) @@ -87,7 +87,7 @@ svn_cl__lock(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* We only support locking files, so '.' is not valid. */ if (! targets->nelts) Index: subversion/svn/info-cmd.c =================================================================== --- subversion/svn/info-cmd.c (revision 29840) +++ subversion/svn/info-cmd.c (working copy) @@ -452,7 +452,7 @@ svn_cl__info(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* Add "." if user passed 0 arguments. */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/unlock-cmd.c =================================================================== --- subversion/svn/unlock-cmd.c (revision 29840) +++ subversion/svn/unlock-cmd.c (working copy) @@ -46,7 +46,7 @@ svn_cl__unlock(apr_getopt_t *os, SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, - pool)); + ctx, pool)); /* We don't support unlock on directories, so "." is not relevant. */ if (! targets->nelts)