1251881Speter/* 2251881Speter * cmdline.c: command-line processing 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* ==================================================================== */ 25251881Speter 26251881Speter 27251881Speter/*** Includes. ***/ 28251881Speter#include "svn_client.h" 29251881Speter#include "svn_error.h" 30251881Speter#include "svn_dirent_uri.h" 31251881Speter#include "svn_path.h" 32251881Speter#include "svn_opt.h" 33251881Speter#include "svn_utf.h" 34251881Speter 35251881Speter#include "client.h" 36251881Speter 37251881Speter#include "private/svn_opt_private.h" 38251881Speter 39251881Speter#include "svn_private_config.h" 40251881Speter 41251881Speter 42251881Speter/*** Code. ***/ 43251881Speter 44251881Speter#define DEFAULT_ARRAY_SIZE 5 45251881Speter 46251881Speter 47251881Speter/* Attempt to find the repository root url for TARGET, possibly using CTX for 48251881Speter * authentication. If one is found and *ROOT_URL is not NULL, then just check 49251881Speter * that the root url for TARGET matches the value given in *ROOT_URL and 50251881Speter * return an error if it does not. If one is found and *ROOT_URL is NULL then 51251881Speter * set *ROOT_URL to the root url for TARGET, allocated from POOL. 52251881Speter * If a root url is not found for TARGET because it does not exist in the 53251881Speter * repository, then return with no error. 54251881Speter * 55251881Speter * TARGET is a UTF-8 encoded string that is fully canonicalized and escaped. 56251881Speter */ 57251881Speterstatic svn_error_t * 58251881Spetercheck_root_url_of_target(const char **root_url, 59251881Speter const char *target, 60251881Speter svn_client_ctx_t *ctx, 61251881Speter apr_pool_t *pool) 62251881Speter{ 63251881Speter svn_error_t *err; 64251881Speter const char *tmp_root_url; 65251881Speter const char *truepath; 66251881Speter svn_opt_revision_t opt_rev; 67251881Speter 68251881Speter SVN_ERR(svn_opt_parse_path(&opt_rev, &truepath, target, pool)); 69251881Speter if (!svn_path_is_url(truepath)) 70251881Speter SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, pool)); 71251881Speter 72251881Speter err = svn_client_get_repos_root(&tmp_root_url, NULL, truepath, 73251881Speter ctx, pool, pool); 74251881Speter 75251881Speter if (err) 76251881Speter { 77251881Speter /* It is OK if the given target does not exist, it just means 78251881Speter * we will not be able to determine the root url from this particular 79251881Speter * argument. 80251881Speter * 81251881Speter * If the target itself is a URL to a repository that does not exist, 82251881Speter * that's fine, too. The callers will deal with this argument in an 83251881Speter * appropriate manner if it does not make any sense. 84251881Speter * 85251881Speter * Also tolerate locally added targets ("bad revision" error). 86251881Speter */ 87251881Speter if ((err->apr_err == SVN_ERR_ENTRY_NOT_FOUND) 88251881Speter || (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 89251881Speter || (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY) 90299742Sdim || (err->apr_err == SVN_ERR_RA_CANNOT_CREATE_SESSION) 91251881Speter || (err->apr_err == SVN_ERR_CLIENT_BAD_REVISION)) 92251881Speter { 93251881Speter svn_error_clear(err); 94251881Speter return SVN_NO_ERROR; 95251881Speter } 96251881Speter else 97251881Speter return svn_error_trace(err); 98251881Speter } 99251881Speter 100251881Speter if (*root_url && tmp_root_url) 101251881Speter { 102251881Speter if (strcmp(*root_url, tmp_root_url) != 0) 103251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 104251881Speter _("All non-relative targets must have " 105251881Speter "the same root URL")); 106251881Speter } 107251881Speter else 108251881Speter *root_url = tmp_root_url; 109251881Speter 110251881Speter return SVN_NO_ERROR; 111251881Speter} 112251881Speter 113251881Speter/* Note: This is substantially copied from svn_opt__args_to_target_array() in 114251881Speter * order to move to libsvn_client while maintaining backward compatibility. */ 115251881Spetersvn_error_t * 116251881Spetersvn_client_args_to_target_array2(apr_array_header_t **targets_p, 117251881Speter apr_getopt_t *os, 118251881Speter const apr_array_header_t *known_targets, 119251881Speter svn_client_ctx_t *ctx, 120251881Speter svn_boolean_t keep_last_origpath_on_truepath_collision, 121251881Speter apr_pool_t *pool) 122251881Speter{ 123251881Speter int i; 124251881Speter svn_boolean_t rel_url_found = FALSE; 125251881Speter const char *root_url = NULL; 126251881Speter apr_array_header_t *input_targets = 127251881Speter apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); 128251881Speter apr_array_header_t *output_targets = 129251881Speter apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); 130251881Speter apr_array_header_t *reserved_names = NULL; 131251881Speter 132251881Speter /* Step 1: create a master array of targets that are in UTF-8 133251881Speter encoding, and come from concatenating the targets left by apr_getopt, 134251881Speter plus any extra targets (e.g., from the --targets switch.) 135251881Speter If any of the targets are relative urls, then set the rel_url_found 136251881Speter flag.*/ 137251881Speter 138251881Speter for (; os->ind < os->argc; os->ind++) 139251881Speter { 140251881Speter /* The apr_getopt targets are still in native encoding. */ 141251881Speter const char *raw_target = os->argv[os->ind]; 142251881Speter const char *utf8_target; 143251881Speter 144251881Speter SVN_ERR(svn_utf_cstring_to_utf8(&utf8_target, 145251881Speter raw_target, pool)); 146251881Speter 147251881Speter if (svn_path_is_repos_relative_url(utf8_target)) 148251881Speter rel_url_found = TRUE; 149251881Speter 150251881Speter APR_ARRAY_PUSH(input_targets, const char *) = utf8_target; 151251881Speter } 152251881Speter 153251881Speter if (known_targets) 154251881Speter { 155251881Speter for (i = 0; i < known_targets->nelts; i++) 156251881Speter { 157251881Speter /* The --targets array have already been converted to UTF-8, 158251881Speter because we needed to split up the list with svn_cstring_split. */ 159251881Speter const char *utf8_target = APR_ARRAY_IDX(known_targets, 160251881Speter i, const char *); 161251881Speter 162251881Speter if (svn_path_is_repos_relative_url(utf8_target)) 163251881Speter rel_url_found = TRUE; 164251881Speter 165251881Speter APR_ARRAY_PUSH(input_targets, const char *) = utf8_target; 166251881Speter } 167251881Speter } 168251881Speter 169251881Speter /* Step 2: process each target. */ 170251881Speter 171251881Speter for (i = 0; i < input_targets->nelts; i++) 172251881Speter { 173251881Speter const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *); 174251881Speter 175251881Speter /* Relative urls will be canonicalized when they are resolved later in 176251881Speter * the function 177251881Speter */ 178251881Speter if (svn_path_is_repos_relative_url(utf8_target)) 179251881Speter { 180251881Speter APR_ARRAY_PUSH(output_targets, const char *) = utf8_target; 181251881Speter } 182251881Speter else 183251881Speter { 184251881Speter const char *true_target; 185251881Speter const char *peg_rev; 186251881Speter const char *target; 187251881Speter 188251881Speter /* 189251881Speter * This is needed so that the target can be properly canonicalized, 190251881Speter * otherwise the canonicalization does not treat a ".@BASE" as a "." 191251881Speter * with a BASE peg revision, and it is not canonicalized to "@BASE". 192251881Speter * If any peg revision exists, it is appended to the final 193251881Speter * canonicalized path or URL. Do not use svn_opt_parse_path() 194251881Speter * because the resulting peg revision is a structure that would have 195251881Speter * to be converted back into a string. Converting from a string date 196251881Speter * to the apr_time_t field in the svn_opt_revision_value_t and back to 197251881Speter * a string would not necessarily preserve the exact bytes of the 198251881Speter * input date, so its easier just to keep it in string form. 199251881Speter */ 200251881Speter SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev, 201251881Speter utf8_target, pool)); 202251881Speter 203299742Sdim /* Reject the form "@abc", a peg specifier with no path. */ 204299742Sdim if (true_target[0] == '\0' && peg_rev[0] != '\0') 205299742Sdim { 206299742Sdim return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, 207299742Sdim _("'%s' is just a peg revision. " 208299742Sdim "Maybe try '%s@' instead?"), 209299742Sdim utf8_target, utf8_target); 210299742Sdim } 211299742Sdim 212251881Speter /* URLs and wc-paths get treated differently. */ 213251881Speter if (svn_path_is_url(true_target)) 214251881Speter { 215251881Speter SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, 216251881Speter true_target, pool)); 217251881Speter } 218251881Speter else /* not a url, so treat as a path */ 219251881Speter { 220251881Speter const char *base_name; 221251881Speter const char *original_target; 222251881Speter 223251881Speter original_target = svn_dirent_internal_style(true_target, pool); 224251881Speter SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, 225251881Speter true_target, pool)); 226251881Speter 227251881Speter /* There are two situations in which a 'truepath-conversion' 228251881Speter (case-canonicalization to on-disk path on case-insensitive 229251881Speter filesystem) needs to be undone: 230251881Speter 231251881Speter 1. If KEEP_LAST_ORIGPATH_ON_TRUEPATH_COLLISION is TRUE, and 232251881Speter this is the last target of a 2-element target list, and 233251881Speter both targets have the same truepath. */ 234251881Speter if (keep_last_origpath_on_truepath_collision 235251881Speter && input_targets->nelts == 2 && i == 1 236251881Speter && strcmp(original_target, true_target) != 0) 237251881Speter { 238251881Speter const char *src_truepath = APR_ARRAY_IDX(output_targets, 239251881Speter 0, 240251881Speter const char *); 241251881Speter if (strcmp(src_truepath, true_target) == 0) 242251881Speter true_target = original_target; 243251881Speter } 244251881Speter 245251881Speter /* 2. If there is an exact match in the wc-db without a 246251881Speter corresponding on-disk path (e.g. a scheduled-for-delete 247251881Speter file only differing in case from an on-disk file). */ 248251881Speter if (strcmp(original_target, true_target) != 0) 249251881Speter { 250251881Speter const char *target_abspath; 251251881Speter svn_node_kind_t kind; 252251881Speter svn_error_t *err2; 253251881Speter 254251881Speter SVN_ERR(svn_dirent_get_absolute(&target_abspath, 255251881Speter original_target, pool)); 256251881Speter err2 = svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath, 257251881Speter TRUE, FALSE, pool); 258251881Speter if (err2 259251881Speter && (err2->apr_err == SVN_ERR_WC_NOT_WORKING_COPY 260251881Speter || err2->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)) 261251881Speter { 262251881Speter svn_error_clear(err2); 263251881Speter } 264251881Speter else 265251881Speter { 266251881Speter SVN_ERR(err2); 267251881Speter /* We successfully did a lookup in the wc-db. Now see 268251881Speter if it's something interesting. */ 269251881Speter if (kind == svn_node_file || kind == svn_node_dir) 270251881Speter true_target = original_target; 271251881Speter } 272251881Speter } 273251881Speter 274251881Speter /* If the target has the same name as a Subversion 275251881Speter working copy administrative dir, skip it. */ 276251881Speter base_name = svn_dirent_basename(true_target, pool); 277251881Speter 278251881Speter if (svn_wc_is_adm_dir(base_name, pool)) 279251881Speter { 280251881Speter if (!reserved_names) 281251881Speter reserved_names = apr_array_make(pool, DEFAULT_ARRAY_SIZE, 282251881Speter sizeof(const char *)); 283251881Speter 284251881Speter APR_ARRAY_PUSH(reserved_names, const char *) = utf8_target; 285251881Speter 286251881Speter continue; 287251881Speter } 288251881Speter } 289251881Speter 290299742Sdim target = apr_pstrcat(pool, true_target, peg_rev, SVN_VA_NULL); 291251881Speter 292251881Speter if (rel_url_found) 293251881Speter { 294251881Speter /* Later targets have priority over earlier target, I 295251881Speter don't know why, see basic_relative_url_multi_repo. */ 296251881Speter SVN_ERR(check_root_url_of_target(&root_url, target, 297251881Speter ctx, pool)); 298251881Speter } 299251881Speter 300251881Speter APR_ARRAY_PUSH(output_targets, const char *) = target; 301251881Speter } 302251881Speter } 303251881Speter 304251881Speter /* Only resolve relative urls if there were some actually found earlier. */ 305251881Speter if (rel_url_found) 306251881Speter { 307251881Speter /* 308251881Speter * Use the current directory's root url if one wasn't found using the 309251881Speter * arguments. 310251881Speter */ 311251881Speter if (root_url == NULL) 312251881Speter { 313251881Speter const char *current_abspath; 314251881Speter svn_error_t *err; 315251881Speter 316251881Speter SVN_ERR(svn_dirent_get_absolute(¤t_abspath, "", pool)); 317251881Speter err = svn_client_get_repos_root(&root_url, NULL /* uuid */, 318251881Speter current_abspath, ctx, pool, pool); 319251881Speter if (err || root_url == NULL) 320251881Speter return svn_error_create(SVN_ERR_WC_NOT_WORKING_COPY, err, 321251881Speter _("Resolving '^/': no repository root " 322251881Speter "found in the target arguments or " 323251881Speter "in the current directory")); 324251881Speter } 325251881Speter 326251881Speter *targets_p = apr_array_make(pool, output_targets->nelts, 327251881Speter sizeof(const char *)); 328251881Speter 329251881Speter for (i = 0; i < output_targets->nelts; i++) 330251881Speter { 331251881Speter const char *target = APR_ARRAY_IDX(output_targets, i, 332251881Speter const char *); 333251881Speter 334251881Speter if (svn_path_is_repos_relative_url(target)) 335251881Speter { 336251881Speter const char *abs_target; 337251881Speter const char *true_target; 338251881Speter const char *peg_rev; 339251881Speter 340251881Speter SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev, 341251881Speter target, pool)); 342251881Speter 343251881Speter SVN_ERR(svn_path_resolve_repos_relative_url(&abs_target, 344251881Speter true_target, 345251881Speter root_url, pool)); 346251881Speter 347251881Speter SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, abs_target, 348251881Speter pool)); 349251881Speter 350299742Sdim target = apr_pstrcat(pool, true_target, peg_rev, SVN_VA_NULL); 351251881Speter } 352251881Speter 353251881Speter APR_ARRAY_PUSH(*targets_p, const char *) = target; 354251881Speter } 355251881Speter } 356251881Speter else 357251881Speter *targets_p = output_targets; 358251881Speter 359251881Speter if (reserved_names) 360251881Speter { 361251881Speter svn_error_t *err = SVN_NO_ERROR; 362251881Speter 363251881Speter for (i = 0; i < reserved_names->nelts; ++i) 364251881Speter err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, err, 365251881Speter _("'%s' ends in a reserved name"), 366251881Speter APR_ARRAY_IDX(reserved_names, i, 367251881Speter const char *)); 368251881Speter return svn_error_trace(err); 369251881Speter } 370251881Speter 371251881Speter return SVN_NO_ERROR; 372251881Speter} 373