Index: subversion/include/private/svn_opt_private.h =================================================================== --- subversion/include/private/svn_opt_private.h (revision 30798) +++ subversion/include/private/svn_opt_private.h (working copy) @@ -34,7 +34,7 @@ * - 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. + * URL_IN is in UTF-8 encoding and can have a peg revision specifier. * Set *URL_OUT to the result, allocated from POOL. */ svn_error_t * @@ -50,7 +50,7 @@ * - 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. + * PATH_IN is in UTF-8 encoding and can have a peg revision specifier. * Set *PATH_OUT to the result, allocated from POOL. */ svn_error_t * Index: subversion/libsvn_subr/opt.c =================================================================== --- subversion/libsvn_subr/opt.c (revision 30798) +++ subversion/libsvn_subr/opt.c (working copy) @@ -1114,14 +1083,75 @@ return SVN_NO_ERROR; } +/* Extract the peg revision, if any, from UTF8_TARGET. Return the + * peg revision in *PEG_REVISION and the true target portion + * in *TRUE_TARGET. + * + * This is needed so that UTF8_TARGET 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. + * + * All allocations are done in POOL. + */ +static svn_error_t * +split_arg_at_peg_revision(const char **true_target, + const char **peg_revision, + const char *utf8_target, + apr_pool_t *pool) +{ + const char *peg_start = NULL; /* pointer to the peg revision, if any */ + int j; + + 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) + { + *true_target = apr_pstrmemdup(pool, + utf8_target, + peg_start - utf8_target); + + *peg_revision = apr_pstrdup(pool, peg_start); + } + else + { + *true_target = utf8_target; + + *peg_revision = NULL; + } + + 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; + const char *peg_rev; + SVN_ERR(split_arg_at_peg_revision(&target, &peg_rev, url_in, pool)); + /* Convert to URI. */ - target = svn_path_uri_from_iri(url_in, pool); + target = svn_path_uri_from_iri(target, pool); /* Auto-escape some ASCII characters. */ target = svn_path_uri_autoescape(target, pool); @@ -1140,6 +1170,11 @@ /* strip any trailing '/' and collapse other redundant elements */ target = svn_path_canonicalize(target, pool); + /* Append the peg revision back to the canonicalized target if + there was a peg revision. */ + if (peg_rev) + target = apr_pstrcat(pool, target, peg_rev, NULL); + *url_out = target; return SVN_NO_ERROR; } @@ -1151,9 +1186,12 @@ const char *apr_target; char *truenamed_target; /* APR-encoded */ apr_status_t apr_err; + const char *peg_rev; + SVN_ERR(split_arg_at_peg_revision(&apr_target, &peg_rev, path_in, pool)); + /* canonicalize case, and change all separators to '/'. */ - SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool)); + SVN_ERR(svn_path_cstring_from_utf8(&apr_target, apr_target, pool)); apr_err = apr_filepath_merge(&truenamed_target, "", apr_target, APR_FILEPATH_TRUENAME, pool); @@ -1174,6 +1212,11 @@ SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool)); *path_out = svn_path_canonicalize(*path_out, pool); + /* Append the peg revision back to the canonicalized target if + there was a peg revision. */ + if (peg_rev) + *path_out = apr_pstrcat(pool, *path_out, peg_rev, NULL); + return SVN_NO_ERROR; }