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) 90251881Speter || (err->apr_err == SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED) 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 203251881Speter /* URLs and wc-paths get treated differently. */ 204251881Speter if (svn_path_is_url(true_target)) 205251881Speter { 206251881Speter SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, 207251881Speter true_target, pool)); 208251881Speter } 209251881Speter else /* not a url, so treat as a path */ 210251881Speter { 211251881Speter const char *base_name; 212251881Speter const char *original_target; 213251881Speter 214251881Speter original_target = svn_dirent_internal_style(true_target, pool); 215251881Speter SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, 216251881Speter true_target, pool)); 217251881Speter 218251881Speter /* There are two situations in which a 'truepath-conversion' 219251881Speter (case-canonicalization to on-disk path on case-insensitive 220251881Speter filesystem) needs to be undone: 221251881Speter 222251881Speter 1. If KEEP_LAST_ORIGPATH_ON_TRUEPATH_COLLISION is TRUE, and 223251881Speter this is the last target of a 2-element target list, and 224251881Speter both targets have the same truepath. */ 225251881Speter if (keep_last_origpath_on_truepath_collision 226251881Speter && input_targets->nelts == 2 && i == 1 227251881Speter && strcmp(original_target, true_target) != 0) 228251881Speter { 229251881Speter const char *src_truepath = APR_ARRAY_IDX(output_targets, 230251881Speter 0, 231251881Speter const char *); 232251881Speter if (strcmp(src_truepath, true_target) == 0) 233251881Speter true_target = original_target; 234251881Speter } 235251881Speter 236251881Speter /* 2. If there is an exact match in the wc-db without a 237251881Speter corresponding on-disk path (e.g. a scheduled-for-delete 238251881Speter file only differing in case from an on-disk file). */ 239251881Speter if (strcmp(original_target, true_target) != 0) 240251881Speter { 241251881Speter const char *target_abspath; 242251881Speter svn_node_kind_t kind; 243251881Speter svn_error_t *err2; 244251881Speter 245251881Speter SVN_ERR(svn_dirent_get_absolute(&target_abspath, 246251881Speter original_target, pool)); 247251881Speter err2 = svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath, 248251881Speter TRUE, FALSE, pool); 249251881Speter if (err2 250251881Speter && (err2->apr_err == SVN_ERR_WC_NOT_WORKING_COPY 251251881Speter || err2->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)) 252251881Speter { 253251881Speter svn_error_clear(err2); 254251881Speter } 255251881Speter else 256251881Speter { 257251881Speter SVN_ERR(err2); 258251881Speter /* We successfully did a lookup in the wc-db. Now see 259251881Speter if it's something interesting. */ 260251881Speter if (kind == svn_node_file || kind == svn_node_dir) 261251881Speter true_target = original_target; 262251881Speter } 263251881Speter } 264251881Speter 265251881Speter /* If the target has the same name as a Subversion 266251881Speter working copy administrative dir, skip it. */ 267251881Speter base_name = svn_dirent_basename(true_target, pool); 268251881Speter 269251881Speter if (svn_wc_is_adm_dir(base_name, pool)) 270251881Speter { 271251881Speter if (!reserved_names) 272251881Speter reserved_names = apr_array_make(pool, DEFAULT_ARRAY_SIZE, 273251881Speter sizeof(const char *)); 274251881Speter 275251881Speter APR_ARRAY_PUSH(reserved_names, const char *) = utf8_target; 276251881Speter 277251881Speter continue; 278251881Speter } 279251881Speter } 280251881Speter 281251881Speter target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL); 282251881Speter 283251881Speter if (rel_url_found) 284251881Speter { 285251881Speter /* Later targets have priority over earlier target, I 286251881Speter don't know why, see basic_relative_url_multi_repo. */ 287251881Speter SVN_ERR(check_root_url_of_target(&root_url, target, 288251881Speter ctx, pool)); 289251881Speter } 290251881Speter 291251881Speter APR_ARRAY_PUSH(output_targets, const char *) = target; 292251881Speter } 293251881Speter } 294251881Speter 295251881Speter /* Only resolve relative urls if there were some actually found earlier. */ 296251881Speter if (rel_url_found) 297251881Speter { 298251881Speter /* 299251881Speter * Use the current directory's root url if one wasn't found using the 300251881Speter * arguments. 301251881Speter */ 302251881Speter if (root_url == NULL) 303251881Speter { 304251881Speter const char *current_abspath; 305251881Speter svn_error_t *err; 306251881Speter 307251881Speter SVN_ERR(svn_dirent_get_absolute(¤t_abspath, "", pool)); 308251881Speter err = svn_client_get_repos_root(&root_url, NULL /* uuid */, 309251881Speter current_abspath, ctx, pool, pool); 310251881Speter if (err || root_url == NULL) 311251881Speter return svn_error_create(SVN_ERR_WC_NOT_WORKING_COPY, err, 312251881Speter _("Resolving '^/': no repository root " 313251881Speter "found in the target arguments or " 314251881Speter "in the current directory")); 315251881Speter } 316251881Speter 317251881Speter *targets_p = apr_array_make(pool, output_targets->nelts, 318251881Speter sizeof(const char *)); 319251881Speter 320251881Speter for (i = 0; i < output_targets->nelts; i++) 321251881Speter { 322251881Speter const char *target = APR_ARRAY_IDX(output_targets, i, 323251881Speter const char *); 324251881Speter 325251881Speter if (svn_path_is_repos_relative_url(target)) 326251881Speter { 327251881Speter const char *abs_target; 328251881Speter const char *true_target; 329251881Speter const char *peg_rev; 330251881Speter 331251881Speter SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev, 332251881Speter target, pool)); 333251881Speter 334251881Speter SVN_ERR(svn_path_resolve_repos_relative_url(&abs_target, 335251881Speter true_target, 336251881Speter root_url, pool)); 337251881Speter 338251881Speter SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, abs_target, 339251881Speter pool)); 340251881Speter 341251881Speter target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL); 342251881Speter } 343251881Speter 344251881Speter APR_ARRAY_PUSH(*targets_p, const char *) = target; 345251881Speter } 346251881Speter } 347251881Speter else 348251881Speter *targets_p = output_targets; 349251881Speter 350251881Speter if (reserved_names) 351251881Speter { 352251881Speter svn_error_t *err = SVN_NO_ERROR; 353251881Speter 354251881Speter for (i = 0; i < reserved_names->nelts; ++i) 355251881Speter err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, err, 356251881Speter _("'%s' ends in a reserved name"), 357251881Speter APR_ARRAY_IDX(reserved_names, i, 358251881Speter const char *)); 359251881Speter return svn_error_trace(err); 360251881Speter } 361251881Speter 362251881Speter return SVN_NO_ERROR; 363251881Speter} 364