diff options
Diffstat (limited to 'subversion/libsvn_client/cmdline.c')
-rw-r--r-- | subversion/libsvn_client/cmdline.c | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/subversion/libsvn_client/cmdline.c b/subversion/libsvn_client/cmdline.c new file mode 100644 index 0000000..a17f8c4 --- /dev/null +++ b/subversion/libsvn_client/cmdline.c @@ -0,0 +1,363 @@ +/* + * cmdline.c: command-line processing + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + +/*** Includes. ***/ +#include "svn_client.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_opt.h" +#include "svn_utf.h" + +#include "client.h" + +#include "private/svn_opt_private.h" + +#include "svn_private_config.h" + + +/*** Code. ***/ + +#define DEFAULT_ARRAY_SIZE 5 + + +/* Attempt to find the repository root url for TARGET, possibly using CTX for + * authentication. If one is found and *ROOT_URL is not NULL, then just check + * that the root url for TARGET matches the value given in *ROOT_URL and + * return an error if it does not. If one is found and *ROOT_URL is NULL then + * set *ROOT_URL to the root url for TARGET, allocated from POOL. + * If a root url is not found for TARGET because it does not exist in the + * repository, then return with no error. + * + * TARGET is a UTF-8 encoded string that is fully canonicalized and escaped. + */ +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 *err; + 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 (!svn_path_is_url(truepath)) + SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, pool)); + + err = svn_client_get_repos_root(&tmp_root_url, NULL, truepath, + ctx, pool, pool); + + if (err) + { + /* 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 the target itself is a URL to a repository that does not exist, + * that's fine, too. The callers will deal with this argument in an + * appropriate manner if it does not make any sense. + * + * Also tolerate locally added targets ("bad revision" error). + */ + if ((err->apr_err == SVN_ERR_ENTRY_NOT_FOUND) + || (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + || (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY) + || (err->apr_err == SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED) + || (err->apr_err == SVN_ERR_CLIENT_BAD_REVISION)) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + return svn_error_trace(err); + } + + if (*root_url && tmp_root_url) + { + if (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; +} + +/* Note: This is substantially copied from svn_opt__args_to_target_array() in + * order to move to libsvn_client while maintaining backward compatibility. */ +svn_error_t * +svn_client_args_to_target_array2(apr_array_header_t **targets_p, + apr_getopt_t *os, + const apr_array_header_t *known_targets, + svn_client_ctx_t *ctx, + svn_boolean_t keep_last_origpath_on_truepath_collision, + apr_pool_t *pool) +{ + int i; + svn_boolean_t rel_url_found = FALSE; + const char *root_url = NULL; + 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 *)); + apr_array_header_t *reserved_names = NULL; + + /* 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 (svn_path_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 (svn_path_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 *); + + /* Relative urls will be canonicalized when they are resolved later in + * the function + */ + if (svn_path_is_repos_relative_url(utf8_target)) + { + APR_ARRAY_PUSH(output_targets, const char *) = utf8_target; + } + else + { + const char *true_target; + const char *peg_rev; + const char *target; + + /* + * This is needed so that the 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. + */ + SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev, + utf8_target, pool)); + + /* URLs and wc-paths get treated differently. */ + if (svn_path_is_url(true_target)) + { + SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, + true_target, pool)); + } + else /* not a url, so treat as a path */ + { + const char *base_name; + const char *original_target; + + original_target = svn_dirent_internal_style(true_target, pool); + SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, + true_target, pool)); + + /* There are two situations in which a 'truepath-conversion' + (case-canonicalization to on-disk path on case-insensitive + filesystem) needs to be undone: + + 1. If KEEP_LAST_ORIGPATH_ON_TRUEPATH_COLLISION is TRUE, and + this is the last target of a 2-element target list, and + both targets have the same truepath. */ + if (keep_last_origpath_on_truepath_collision + && input_targets->nelts == 2 && i == 1 + && strcmp(original_target, true_target) != 0) + { + const char *src_truepath = APR_ARRAY_IDX(output_targets, + 0, + const char *); + if (strcmp(src_truepath, true_target) == 0) + true_target = original_target; + } + + /* 2. If there is an exact match in the wc-db without a + corresponding on-disk path (e.g. a scheduled-for-delete + file only differing in case from an on-disk file). */ + if (strcmp(original_target, true_target) != 0) + { + const char *target_abspath; + svn_node_kind_t kind; + svn_error_t *err2; + + SVN_ERR(svn_dirent_get_absolute(&target_abspath, + original_target, pool)); + err2 = svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath, + TRUE, FALSE, pool); + if (err2 + && (err2->apr_err == SVN_ERR_WC_NOT_WORKING_COPY + || err2->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)) + { + svn_error_clear(err2); + } + else + { + SVN_ERR(err2); + /* We successfully did a lookup in the wc-db. Now see + if it's something interesting. */ + if (kind == svn_node_file || kind == svn_node_dir) + true_target = original_target; + } + } + + /* If the target has the same name as a Subversion + working copy administrative dir, skip it. */ + base_name = svn_dirent_basename(true_target, pool); + + if (svn_wc_is_adm_dir(base_name, pool)) + { + if (!reserved_names) + reserved_names = apr_array_make(pool, DEFAULT_ARRAY_SIZE, + sizeof(const char *)); + + APR_ARRAY_PUSH(reserved_names, const char *) = utf8_target; + + continue; + } + } + + target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL); + + if (rel_url_found) + { + /* Later targets have priority over earlier target, I + don't know why, see basic_relative_url_multi_repo. */ + 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) + { + const char *current_abspath; + svn_error_t *err; + + SVN_ERR(svn_dirent_get_absolute(¤t_abspath, "", pool)); + err = svn_client_get_repos_root(&root_url, NULL /* uuid */, + current_abspath, ctx, pool, pool); + if (err || root_url == NULL) + return svn_error_create(SVN_ERR_WC_NOT_WORKING_COPY, err, + _("Resolving '^/': no repository root " + "found in the target arguments or " + "in the current directory")); + } + + *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 (svn_path_is_repos_relative_url(target)) + { + const char *abs_target; + const char *true_target; + const char *peg_rev; + + SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev, + target, pool)); + + SVN_ERR(svn_path_resolve_repos_relative_url(&abs_target, + true_target, + root_url, pool)); + + SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, abs_target, + pool)); + + target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL); + } + + APR_ARRAY_PUSH(*targets_p, const char *) = target; + } + } + else + *targets_p = output_targets; + + if (reserved_names) + { + svn_error_t *err = SVN_NO_ERROR; + + for (i = 0; i < reserved_names->nelts; ++i) + err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, err, + _("'%s' ends in a reserved name"), + APR_ARRAY_IDX(reserved_names, i, + const char *)); + return svn_error_trace(err); + } + + return SVN_NO_ERROR; +} |