Index: subversion/include/svn_path.h =================================================================== --- subversion/include/svn_path.h (revision 29310) +++ subversion/include/svn_path.h (working copy) @@ -456,9 +456,28 @@ /** 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 @c TRUE iff @a path is URI-safe, @c FALSE otherwise. */ svn_boolean_t svn_path_is_uri_safe(const char *path); +/** + * Convert the possibly relative url in @a relative_url to an absolute + * url using @a repos_root_url and stick it in @a absolute_url. If the + * @a relative_url starts with the characters '^/', they are replaced by + * the repository root url, otherwise the @a relative_url is returned. + * Backpaths ("../") within the URL are also supported. + */ +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); + /** 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/libsvn_subr/opt.c =================================================================== --- subversion/libsvn_subr/opt.c (revision 29310) +++ subversion/libsvn_subr/opt.c (working copy) @@ -964,9 +964,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 29310) +++ 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,12 @@ 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); +} - /* Here is the BNF for path components in a URI. "pchar" is a character in a path component. @@ -1174,7 +1179,91 @@ 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 (svn_path_is_url(relative_url)) + { + *absolute_url = apr_pstrdup(pool, 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_split_if_file(const char *path, const char **pdirectory, Index: subversion/libsvn_client/externals.c =================================================================== --- subversion/libsvn_client/externals.c (revision 29310) +++ 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 29310) +++ 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,111 @@ 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_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; + + 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); + } + + return SVN_NO_ERROR; +} + /* local define to support XFail-ing tests on Windows/Cygwin only */ #if defined(WIN32) || defined(__CYGWIN__) #define WINDOWS_OR_CYGWIN TRUE @@ -1273,5 +1377,7 @@ 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_resolve_relative_url), SVN_TEST_NULL }; Index: subversion/tests/cmdline/cat_tests.py =================================================================== --- subversion/tests/cmdline/cat_tests.py (revision 29310) +++ subversion/tests/cmdline/cat_tests.py (working copy) @@ -139,7 +139,20 @@ 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 +164,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 29310) +++ 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 29310) +++ subversion/tests/cmdline/log_tests.py (working copy) @@ -1349,7 +1349,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 @@ -1385,6 +1399,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 29310) +++ subversion/tests/cmdline/lock_tests.py (working copy) @@ -1408,7 +1408,39 @@ 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', + '--username', svntest.main.wc_author, + '-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', + '--username', svntest.main.wc_author, + '-m', '', '^/iota') + + # Now unlock it + svntest.actions.run_and_verify_svn(None, ".* unlocked.", [], 'unlock', + '--username', svntest.main.wc_author, + '^/iota') + + os.chdir(orig_dir) + ######################################################################## # Run the tests @@ -1448,6 +1480,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 29310) +++ subversion/tests/cmdline/switch_tests.py (working copy) @@ -2095,7 +2095,6 @@ expected_disk = svntest.main.greek_state.copy() expected_disk.remove('A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau') expected_disk.add_state('A/D/G', svntest.main.greek_state.copy()) - expected_status = svntest.actions.get_virginal_state(wc_dir, 1) expected_status.remove('A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau') expected_status.add_state('A/D/G', @@ -2106,6 +2105,20 @@ 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 @@ -2140,6 +2153,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 29310) +++ subversion/tests/cmdline/diff_tests.py (working copy) @@ -2944,6 +2944,27 @@ if len(out) < 4: raise svntest.Failure +# 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 @@ -2996,6 +3017,7 @@ diff_backward_repos_wc_copy, diff_summarize_xml, diff_file_depth_empty, + diff_relative_url, ] if __name__ == '__main__': Index: subversion/tests/cmdline/copy_tests.py =================================================================== --- subversion/tests/cmdline/copy_tests.py (revision 29310) +++ subversion/tests/cmdline/copy_tests.py (working copy) @@ -3720,7 +3720,53 @@ wc2_dir, copy_to_path) +#---------------------------------------------------------------------- +# 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 @@ -3796,6 +3842,7 @@ copy_make_parents_repo_repo, URI_encoded_repos_to_wc, allow_unversioned_parent_for_copy_src, + copy_with_relative_urls, ] if __name__ == '__main__': Index: subversion/tests/cmdline/prop_tests.py =================================================================== --- subversion/tests/cmdline/prop_tests.py (revision 29310) +++ subversion/tests/cmdline/prop_tests.py (working copy) @@ -1617,6 +1617,94 @@ svntest.actions.run_and_verify_svn(None, plist_expected, [], 'proplist', '-v', peg_path) +#---------------------------------------------------------------------- +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 @@ -1654,6 +1742,7 @@ SkipUnless(perms_on_symlink, svntest.main.is_posix_os), remove_custom_ns_props, props_over_time, + url_props_relative_url, ] if __name__ == '__main__': Index: subversion/tests/cmdline/blame_tests.py =================================================================== --- subversion/tests/cmdline/blame_tests.py (revision 29310) +++ subversion/tests/cmdline/blame_tests.py (working copy) @@ -538,8 +538,33 @@ expected_err = ".*notexisting' (is not a file.*|path not found)" 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 @@ -559,6 +584,7 @@ SkipUnless(blame_merge_out_of_range, 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/export_tests.py =================================================================== --- subversion/tests/cmdline/export_tests.py (revision 29310) +++ subversion/tests/cmdline/export_tests.py (working copy) @@ -389,6 +389,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 @@ -411,6 +444,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 29310) +++ subversion/tests/cmdline/merge_tests.py (working copy) @@ -10462,7 +10462,106 @@ svntest.actions.run_and_verify_svn(None, expected_output, [], 'merge', trunk_url, branch_path) +#---------------------------------------------------------------------- +# 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, + 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, + 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 @@ -10559,6 +10658,7 @@ XFail(reintegrate_fail_on_stale_source), dont_add_mergeinfo_from_own_history, merge_range_predates_history, + merge_with_relative_urls, ] if __name__ == '__main__': Index: subversion/tests/cmdline/checkout_tests.py =================================================================== --- subversion/tests/cmdline/checkout_tests.py (revision 29310) +++ subversion/tests/cmdline/checkout_tests.py (working copy) @@ -715,6 +715,59 @@ test_stderr("svn: Failed to add file '.*omicron': a file of the same " \ "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: @@ -732,6 +785,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 29310) +++ subversion/svn/merge-cmd.c (working copy) @@ -50,9 +50,9 @@ peg_revision2; apr_array_header_t *options, *ranges_to_merge = opt_state->revision_ranges; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + ctx, pool)); /* Parse at least one, and possible two, sources. */ if (targets->nelts >= 1) Index: subversion/svn/cl.h =================================================================== --- subversion/svn/cl.h (revision 29310) +++ subversion/svn/cl.h (working copy) @@ -556,6 +556,20 @@ const char *svn_cl__node_kind_str(svn_node_kind_t kind); +/* Command-line client wrapper function for the svn_opt_args_to_target_array3() + * function which performs a few extra command-line specific operations: + * - Prints a warning if a reserved filename is specified in the arguments. + * - Resolves any relative urls present in the arguments or known + * targets based on the other arguments, or failing that on the current + * directory. + */ +svn_error_t * +svn_cl__args_to_target_array(apr_array_header_t **targets_ret, + apr_getopt_t *os, + apr_array_header_t *known_targets, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + /* If PROPNAME is one of the svn: properties with a boolean value, and * PROPVAL looks like an attempt to turn the property off (i.e., it's * "off", "no", "false", or ""), then print a warning to the user that @@ -576,12 +590,6 @@ svn_client_ctx_t *ctx, apr_pool_t *pool); -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, - apr_pool_t *pool); - #ifdef __cplusplus } #endif /* __cplusplus */ Index: subversion/svn/propdel-cmd.c =================================================================== --- subversion/svn/propdel-cmd.c (revision 29310) +++ subversion/svn/propdel-cmd.c (working copy) @@ -56,11 +56,10 @@ properties, and it may even be useful to allow, in case invalid properties sneaked through somehow. */ - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + ctx, 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 29310) +++ subversion/svn/checkout-cmd.c (working copy) @@ -70,9 +70,9 @@ const char *repos_url; int i; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/move-cmd.c (working copy) @@ -46,9 +46,9 @@ svn_commit_info_t *commit_info = NULL; svn_error_t *err; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/mkdir-cmd.c (working copy) @@ -45,9 +45,9 @@ svn_commit_info_t *commit_info = NULL; svn_error_t *err; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/cat-cmd.c (working copy) @@ -43,9 +43,9 @@ svn_stream_t *out; apr_pool_t *subpool = svn_pool_create(pool); - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/revert-cmd.c (working copy) @@ -43,9 +43,9 @@ apr_array_header_t *targets = NULL; svn_error_t *err; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/diff-cmd.c (working copy) @@ -147,6 +147,7 @@ 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; @@ -188,9 +189,9 @@ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); } - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + ctx, pool)); if (! opt_state->old_target && ! opt_state->new_target && (targets->nelts == 2) @@ -229,8 +230,8 @@ : APR_ARRAY_IDX(tmp, 0, const char *)); - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&tmp2, os, tmp, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&tmp2, os, tmp, ctx, pool)); + SVN_ERR(svn_opt_parse_path(&old_rev, &old_target, APR_ARRAY_IDX(tmp2, 0, const char *), pool)); Index: subversion/svn/copy-cmd.c =================================================================== --- subversion/svn/copy-cmd.c (revision 29310) +++ subversion/svn/copy-cmd.c (working copy) @@ -47,9 +47,10 @@ svn_error_t *err; int i; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/mergeinfo-cmd.c (working copy) @@ -131,9 +131,9 @@ apr_pool_t *subpool = svn_pool_create(pool); int i; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/list-cmd.c (working copy) @@ -218,9 +218,9 @@ apr_uint32_t dirent_fields; struct print_baton pb; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/util.c (working copy) @@ -1009,15 +1009,20 @@ } } - 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, - apr_pool_t *pool) +svn_cl__args_to_target_array(apr_array_header_t **targets_ret, + 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); + int i; + const char *root_url = NULL; + apr_array_header_t *targets; + svn_error_t *error; + + error = svn_opt_args_to_target_array3(&targets, os, known_targets, pool); + if (error) { if (error->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED) @@ -1028,10 +1033,96 @@ else return error; } + + /* + * First determine whether to even bother with root urls, + * at least one relative url for it to matter. + */ + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + + if (svn_path_is_relative_url(target)) + break; + } + + /* Was a relative url found? */ + if (i >= targets->nelts) + { + *targets_ret = targets; + return SVN_NO_ERROR; + } + + /* + * Now determine the common root url to use. + */ + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + const char *tmp_root_url; + + if (svn_path_is_relative_url(target)) + continue; + + if ((error = svn_client_root_url_from_path(&tmp_root_url, target, + ctx, pool))) + { + if ( (error->apr_err == SVN_ERR_ENTRY_NOT_FOUND) + || (error->apr_err == SVN_ERR_WC_NOT_DIRECTORY)) + { + svn_error_clear(error); + continue; + } + 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 paths must have the same root url.\n")); + else + root_url = tmp_root_url; + } + + /* + * Use the current directory's root url if one wasn't found using the + * arguments. + */ + if (root_url == NULL) + { + if ((error = svn_client_root_url_from_path(&root_url, + svn_path_canonicalize(".", pool), + ctx, pool))) + return error; + } + + /* + * Finally, resolve any relative urls found in the targets array. + */ + + *targets_ret = apr_array_make(pool, targets->nelts, sizeof(const char *)); + + for (i = 0; i < targets->nelts; i++) + { + const char *target = APR_ARRAY_IDX(targets, i, const char *); + + if (svn_path_is_relative_url(target)) + { + const char *abs_target; + + SVN_ERR(svn_path_resolve_relative_url(&abs_target, target, + root_url, pool)); + APR_ARRAY_PUSH(*targets_ret, const char *) = abs_target; + } + else + { + APR_ARRAY_PUSH(*targets_ret, const char *) = target; + } + } + return SVN_NO_ERROR; } - /* Helper for svn_cl__get_changelist(); implements svn_changelist_receiver_t. */ static svn_error_t * @@ -1046,7 +1137,6 @@ return SVN_NO_ERROR; } - svn_error_t * svn_cl__changelist_paths(apr_array_header_t **paths, const apr_array_header_t *changelists, @@ -1067,6 +1157,7 @@ } found = apr_array_make(pool, 8, sizeof(const char *)); + for (i = 0; i < targets->nelts; i++) { const char *target = APR_ARRAY_IDX(targets, i, const char *); @@ -1080,5 +1171,3 @@ SVN_ERR(svn_hash_from_cstring_keys(&paths_hash, found, pool)); return svn_hash_keys(paths, paths_hash, pool); } - - Index: subversion/svn/blame-cmd.c =================================================================== --- subversion/svn/blame-cmd.c (revision 29310) +++ subversion/svn/blame-cmd.c (working copy) @@ -202,9 +202,9 @@ svn_boolean_t end_revision_unspecified = FALSE; svn_diff_file_options_t *diff_options = svn_diff_file_options_create(pool); - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/propget-cmd.c (working copy) @@ -180,9 +180,9 @@ _("'%s' is not a valid Subversion property name"), pname_utf8); - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/changelist-cmd.c (working copy) @@ -58,9 +58,9 @@ } /* Parse the remaining arguments as paths. */ - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + ctx, pool)); /* Add "." if user passed 0 arguments */ svn_opt_push_implicit_dot_target(targets, pool); Index: subversion/svn/log-cmd.c =================================================================== --- subversion/svn/log-cmd.c (revision 29310) +++ subversion/svn/log-cmd.c (working copy) @@ -466,9 +466,9 @@ " XML mode")); } - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/update-cmd.c (working copy) @@ -46,8 +46,9 @@ 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(&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 29310) +++ subversion/svn/resolved-cmd.c (working copy) @@ -74,9 +74,10 @@ _("invalid 'accept' ARG")); } - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/cleanup-cmd.c (working copy) @@ -43,9 +43,9 @@ apr_pool_t *subpool; int i; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/commit-cmd.c (working copy) @@ -51,9 +51,9 @@ svn_boolean_t no_unlock = FALSE; svn_commit_info_t *commit_info = NULL; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/add-cmd.c (working copy) @@ -45,9 +45,9 @@ int i; apr_pool_t *subpool; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/propset-cmd.c (working copy) @@ -90,9 +90,9 @@ /* 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)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/switch-cmd.c (working copy) @@ -99,9 +99,9 @@ /* This command should discover (or derive) exactly two cmdline arguments: a local path to update ("target"), and a new url to switch to ("switch_url"). */ - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + ctx, pool)); /* handle only-rewrite case specially */ if (opt_state->relocate) Index: subversion/svn/delete-cmd.c =================================================================== --- subversion/svn/delete-cmd.c (revision 29310) +++ subversion/svn/delete-cmd.c (working copy) @@ -44,9 +44,9 @@ svn_commit_info_t *commit_info = NULL; svn_error_t *err; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/import-cmd.c (working copy) @@ -73,9 +73,9 @@ * ### kff todo: review above behaviors. */ - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + ctx, pool)); if (targets->nelts < 1) return svn_error_create Index: subversion/svn/proplist-cmd.c =================================================================== --- subversion/svn/proplist-cmd.c (revision 29310) +++ subversion/svn/proplist-cmd.c (working copy) @@ -112,10 +112,11 @@ apr_array_header_t *targets; int i; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/export-cmd.c (working copy) @@ -46,9 +46,9 @@ svn_opt_revision_t peg_revision; const char *truefrom; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/status-cmd.c (working copy) @@ -216,9 +216,9 @@ svn_opt_revision_t rev; struct status_baton sb; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/propedit-cmd.c (working copy) @@ -61,9 +61,9 @@ pname_utf8); /* 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)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + ctx, pool)); if (opt_state->revprop) /* operate on a revprop */ { Index: subversion/svn/lock-cmd.c =================================================================== --- subversion/svn/lock-cmd.c (revision 29310) +++ subversion/svn/lock-cmd.c (working copy) @@ -85,9 +85,9 @@ apr_array_header_t *targets; const char *comment; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/info-cmd.c (working copy) @@ -450,9 +450,9 @@ svn_opt_revision_t peg_revision; svn_info_receiver_t receiver; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + 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 29310) +++ subversion/svn/unlock-cmd.c (working copy) @@ -44,9 +44,9 @@ svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; apr_array_header_t *targets; - SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, - opt_state->targets, - pool)); + SVN_ERR(svn_cl__args_to_target_array(&targets, os, + opt_state->targets, + ctx, pool)); /* We don't support unlock on directories, so "." is not relevant. */ if (! targets->nelts)