1251881Speter/* 2251881Speter * externals.c: handle the svn:externals property 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 28251881Speter/*** Includes. ***/ 29251881Speter 30251881Speter#include <apr_uri.h> 31251881Speter#include "svn_hash.h" 32251881Speter#include "svn_wc.h" 33251881Speter#include "svn_pools.h" 34251881Speter#include "svn_client.h" 35251881Speter#include "svn_types.h" 36251881Speter#include "svn_error.h" 37251881Speter#include "svn_dirent_uri.h" 38251881Speter#include "svn_path.h" 39251881Speter#include "svn_props.h" 40251881Speter#include "svn_config.h" 41251881Speter#include "client.h" 42251881Speter 43251881Speter#include "svn_private_config.h" 44251881Speter#include "private/svn_wc_private.h" 45251881Speter 46251881Speter 47251881Speter/* Remove the directory at LOCAL_ABSPATH from revision control, and do the 48251881Speter * same to any revision controlled directories underneath LOCAL_ABSPATH 49251881Speter * (including directories not referred to by parent svn administrative areas); 50251881Speter * then if LOCAL_ABSPATH is empty afterwards, remove it, else rename it to a 51251881Speter * unique name in the same parent directory. 52251881Speter * 53251881Speter * Pass CANCEL_FUNC, CANCEL_BATON to svn_wc_remove_from_revision_control. 54251881Speter * 55251881Speter * Use SCRATCH_POOL for all temporary allocation. 56251881Speter */ 57251881Speterstatic svn_error_t * 58251881Speterrelegate_dir_external(svn_wc_context_t *wc_ctx, 59251881Speter const char *wri_abspath, 60251881Speter const char *local_abspath, 61251881Speter svn_cancel_func_t cancel_func, 62251881Speter void *cancel_baton, 63251881Speter svn_wc_notify_func2_t notify_func, 64251881Speter void *notify_baton, 65251881Speter apr_pool_t *scratch_pool) 66251881Speter{ 67251881Speter svn_error_t *err = SVN_NO_ERROR; 68251881Speter 69251881Speter SVN_ERR(svn_wc__acquire_write_lock(NULL, wc_ctx, local_abspath, 70251881Speter FALSE, scratch_pool, scratch_pool)); 71251881Speter 72251881Speter err = svn_wc__external_remove(wc_ctx, wri_abspath, local_abspath, FALSE, 73251881Speter cancel_func, cancel_baton, scratch_pool); 74251881Speter if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)) 75251881Speter { 76251881Speter const char *parent_dir; 77251881Speter const char *dirname; 78251881Speter const char *new_path; 79251881Speter 80251881Speter svn_error_clear(err); 81251881Speter err = SVN_NO_ERROR; 82251881Speter 83251881Speter svn_dirent_split(&parent_dir, &dirname, local_abspath, scratch_pool); 84251881Speter 85251881Speter /* Reserve the new dir name. */ 86251881Speter SVN_ERR(svn_io_open_uniquely_named(NULL, &new_path, 87251881Speter parent_dir, dirname, ".OLD", 88251881Speter svn_io_file_del_none, 89251881Speter scratch_pool, scratch_pool)); 90251881Speter 91251881Speter /* Sigh... We must fall ever so slightly from grace. 92251881Speter 93251881Speter Ideally, there would be no window, however brief, when we 94251881Speter don't have a reservation on the new name. Unfortunately, 95251881Speter at least in the Unix (Linux?) version of apr_file_rename(), 96251881Speter you can't rename a directory over a file, because it's just 97251881Speter calling stdio rename(), which says: 98251881Speter 99251881Speter ENOTDIR 100251881Speter A component used as a directory in oldpath or newpath 101251881Speter path is not, in fact, a directory. Or, oldpath is 102251881Speter a directory, and newpath exists but is not a directory 103251881Speter 104251881Speter So instead, we get the name, then remove the file (ugh), then 105251881Speter rename the directory, hoping that nobody has gotten that name 106251881Speter in the meantime -- which would never happen in real life, so 107251881Speter no big deal. 108251881Speter */ 109251881Speter /* Do our best, but no biggy if it fails. The rename will fail. */ 110251881Speter svn_error_clear(svn_io_remove_file2(new_path, TRUE, scratch_pool)); 111251881Speter 112251881Speter /* Rename. If this is still a working copy we should use the working 113251881Speter copy rename function (to release open handles) */ 114251881Speter err = svn_wc__rename_wc(wc_ctx, local_abspath, new_path, 115251881Speter scratch_pool); 116251881Speter 117251881Speter if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS) 118251881Speter { 119251881Speter svn_error_clear(err); 120251881Speter 121251881Speter /* And if it is no longer a working copy, we should just rename 122251881Speter it */ 123251881Speter err = svn_io_file_rename(local_abspath, new_path, scratch_pool); 124251881Speter } 125251881Speter 126251881Speter /* ### TODO: We should notify the user about the rename */ 127251881Speter if (notify_func) 128251881Speter { 129251881Speter svn_wc_notify_t *notify; 130251881Speter 131251881Speter notify = svn_wc_create_notify(err ? local_abspath : new_path, 132251881Speter svn_wc_notify_left_local_modifications, 133251881Speter scratch_pool); 134251881Speter notify->kind = svn_node_dir; 135251881Speter notify->err = err; 136251881Speter 137251881Speter notify_func(notify_baton, notify, scratch_pool); 138251881Speter } 139251881Speter } 140251881Speter 141251881Speter return svn_error_trace(err); 142251881Speter} 143251881Speter 144251881Speter/* Try to update a directory external at PATH to URL at REVISION. 145251881Speter Use POOL for temporary allocations, and use the client context CTX. */ 146251881Speterstatic svn_error_t * 147251881Speterswitch_dir_external(const char *local_abspath, 148251881Speter const char *url, 149289166Speter const char *url_from_externals_definition, 150251881Speter const svn_opt_revision_t *peg_revision, 151251881Speter const svn_opt_revision_t *revision, 152251881Speter const char *defining_abspath, 153251881Speter svn_boolean_t *timestamp_sleep, 154299742Sdim svn_ra_session_t *ra_session, 155251881Speter svn_client_ctx_t *ctx, 156251881Speter apr_pool_t *pool) 157251881Speter{ 158251881Speter svn_node_kind_t kind; 159251881Speter svn_error_t *err; 160251881Speter svn_revnum_t external_peg_rev = SVN_INVALID_REVNUM; 161251881Speter svn_revnum_t external_rev = SVN_INVALID_REVNUM; 162251881Speter apr_pool_t *subpool = svn_pool_create(pool); 163251881Speter const char *repos_root_url; 164251881Speter const char *repos_uuid; 165251881Speter 166251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 167251881Speter 168251881Speter if (peg_revision->kind == svn_opt_revision_number) 169251881Speter external_peg_rev = peg_revision->value.number; 170251881Speter 171251881Speter if (revision->kind == svn_opt_revision_number) 172251881Speter external_rev = revision->value.number; 173251881Speter 174299742Sdim /* 175289166Speter * The code below assumes existing versioned paths are *not* part of 176289166Speter * the external's defining working copy. 177289166Speter * The working copy library does not support registering externals 178289166Speter * on top of existing BASE nodes and will error out if we try. 179289166Speter * So if the external target is part of the defining working copy's 180289166Speter * BASE tree, don't attempt to create the external. Doing so would 181289166Speter * leave behind a switched path instead of an external (since the 182289166Speter * switch succeeds but registration of the external in the DB fails). 183289166Speter * The working copy then cannot be updated until the path is switched back. 184289166Speter * See issue #4085. 185289166Speter */ 186289166Speter SVN_ERR(svn_wc__node_get_base(&kind, NULL, NULL, 187289166Speter &repos_root_url, &repos_uuid, 188289166Speter NULL, ctx->wc_ctx, local_abspath, 189289166Speter TRUE, /* ignore_enoent */ 190289166Speter pool, pool)); 191289166Speter if (kind != svn_node_unknown) 192289166Speter { 193289166Speter const char *wcroot_abspath; 194289166Speter const char *defining_wcroot_abspath; 195289166Speter 196289166Speter SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 197289166Speter local_abspath, pool, pool)); 198289166Speter SVN_ERR(svn_wc__get_wcroot(&defining_wcroot_abspath, ctx->wc_ctx, 199289166Speter defining_abspath, pool, pool)); 200289166Speter if (strcmp(wcroot_abspath, defining_wcroot_abspath) == 0) 201289166Speter return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 202289166Speter _("The external '%s' defined in %s at '%s' " 203289166Speter "cannot be checked out because '%s' is " 204289166Speter "already a versioned path."), 205289166Speter url_from_externals_definition, 206289166Speter SVN_PROP_EXTERNALS, 207289166Speter svn_dirent_local_style(defining_abspath, 208289166Speter pool), 209289166Speter svn_dirent_local_style(local_abspath, 210289166Speter pool)); 211289166Speter } 212289166Speter 213251881Speter /* If path is a directory, try to update/switch to the correct URL 214251881Speter and revision. */ 215251881Speter SVN_ERR(svn_io_check_path(local_abspath, &kind, pool)); 216251881Speter if (kind == svn_node_dir) 217251881Speter { 218251881Speter const char *node_url; 219251881Speter 220251881Speter /* Doubles as an "is versioned" check. */ 221251881Speter err = svn_wc__node_get_url(&node_url, ctx->wc_ctx, local_abspath, 222251881Speter pool, subpool); 223251881Speter if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 224251881Speter { 225251881Speter svn_error_clear(err); 226251881Speter err = SVN_NO_ERROR; 227251881Speter goto relegate; 228251881Speter } 229251881Speter else if (err) 230251881Speter return svn_error_trace(err); 231251881Speter 232251881Speter if (node_url) 233251881Speter { 234299742Sdim svn_boolean_t is_wcroot; 235299742Sdim 236299742Sdim SVN_ERR(svn_wc__is_wcroot(&is_wcroot, ctx->wc_ctx, local_abspath, 237299742Sdim pool)); 238299742Sdim 239299742Sdim if (! is_wcroot) 240299742Sdim { 241299742Sdim /* This can't be a directory external! */ 242299742Sdim 243299742Sdim err = svn_wc__external_remove(ctx->wc_ctx, defining_abspath, 244299742Sdim local_abspath, 245299742Sdim TRUE /* declaration_only */, 246299742Sdim ctx->cancel_func, ctx->cancel_baton, 247299742Sdim pool); 248299742Sdim 249299742Sdim if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 250299742Sdim { 251299742Sdim /* New external... No problem that we can't remove it */ 252299742Sdim svn_error_clear(err); 253299742Sdim err = NULL; 254299742Sdim } 255299742Sdim else if (err) 256299742Sdim return svn_error_trace(err); 257299742Sdim 258299742Sdim return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 259299742Sdim _("The external '%s' defined in %s at '%s' " 260299742Sdim "cannot be checked out because '%s' is " 261299742Sdim "already a versioned path."), 262299742Sdim url_from_externals_definition, 263299742Sdim SVN_PROP_EXTERNALS, 264299742Sdim svn_dirent_local_style(defining_abspath, 265299742Sdim pool), 266299742Sdim svn_dirent_local_style(local_abspath, 267299742Sdim pool)); 268299742Sdim } 269299742Sdim 270251881Speter /* If we have what appears to be a version controlled 271251881Speter subdir, and its top-level URL matches that of our 272251881Speter externals definition, perform an update. */ 273251881Speter if (strcmp(node_url, url) == 0) 274251881Speter { 275299742Sdim SVN_ERR(svn_client__update_internal(NULL, timestamp_sleep, 276299742Sdim local_abspath, 277251881Speter revision, svn_depth_unknown, 278251881Speter FALSE, FALSE, FALSE, TRUE, 279251881Speter FALSE, TRUE, 280299742Sdim ra_session, ctx, subpool)); 281289166Speter 282289166Speter /* We just decided that this existing directory is an external, 283289166Speter so update the external registry with this information, like 284289166Speter when checking out an external */ 285289166Speter SVN_ERR(svn_wc__external_register(ctx->wc_ctx, 286289166Speter defining_abspath, 287289166Speter local_abspath, svn_node_dir, 288289166Speter repos_root_url, repos_uuid, 289289166Speter svn_uri_skip_ancestor(repos_root_url, 290289166Speter url, pool), 291289166Speter external_peg_rev, 292289166Speter external_rev, 293289166Speter pool)); 294289166Speter 295251881Speter svn_pool_destroy(subpool); 296251881Speter goto cleanup; 297251881Speter } 298251881Speter 299251881Speter /* We'd really prefer not to have to do a brute-force 300251881Speter relegation -- blowing away the current external working 301251881Speter copy and checking it out anew -- so we'll first see if we 302251881Speter can get away with a generally cheaper relocation (if 303251881Speter required) and switch-style update. 304251881Speter 305251881Speter To do so, we need to know the repository root URL of the 306251881Speter external working copy as it currently sits. */ 307251881Speter err = svn_wc__node_get_repos_info(NULL, NULL, 308251881Speter &repos_root_url, &repos_uuid, 309251881Speter ctx->wc_ctx, local_abspath, 310251881Speter pool, subpool); 311251881Speter if (err) 312251881Speter { 313251881Speter if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND 314251881Speter && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) 315251881Speter return svn_error_trace(err); 316251881Speter 317251881Speter svn_error_clear(err); 318251881Speter repos_root_url = NULL; 319251881Speter repos_uuid = NULL; 320251881Speter } 321251881Speter 322251881Speter if (repos_root_url) 323251881Speter { 324251881Speter /* If the new external target URL is not obviously a 325251881Speter child of the external working copy's current 326251881Speter repository root URL... */ 327251881Speter if (! svn_uri__is_ancestor(repos_root_url, url)) 328251881Speter { 329251881Speter const char *repos_root; 330251881Speter 331251881Speter /* ... then figure out precisely which repository 332251881Speter root URL that target URL *is* a child of ... */ 333251881Speter SVN_ERR(svn_client_get_repos_root(&repos_root, NULL, url, 334251881Speter ctx, subpool, subpool)); 335251881Speter 336251881Speter /* ... and use that to try to relocate the external 337251881Speter working copy to the target location. */ 338251881Speter err = svn_client_relocate2(local_abspath, repos_root_url, 339251881Speter repos_root, FALSE, ctx, subpool); 340251881Speter 341251881Speter /* If the relocation failed because the new URL 342251881Speter points to a totally different repository, we've 343251881Speter no choice but to relegate and check out a new WC. */ 344251881Speter if (err 345251881Speter && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION 346251881Speter || (err->apr_err 347251881Speter == SVN_ERR_CLIENT_INVALID_RELOCATION))) 348251881Speter { 349251881Speter svn_error_clear(err); 350251881Speter goto relegate; 351251881Speter } 352251881Speter else if (err) 353251881Speter return svn_error_trace(err); 354251881Speter 355251881Speter /* If the relocation went without a hitch, we should 356251881Speter have a new repository root URL. */ 357251881Speter repos_root_url = repos_root; 358251881Speter } 359251881Speter 360251881Speter SVN_ERR(svn_client__switch_internal(NULL, local_abspath, url, 361251881Speter peg_revision, revision, 362251881Speter svn_depth_infinity, 363251881Speter TRUE, FALSE, FALSE, 364251881Speter TRUE /* ignore_ancestry */, 365251881Speter timestamp_sleep, 366251881Speter ctx, subpool)); 367251881Speter 368251881Speter SVN_ERR(svn_wc__external_register(ctx->wc_ctx, 369251881Speter defining_abspath, 370251881Speter local_abspath, svn_node_dir, 371251881Speter repos_root_url, repos_uuid, 372251881Speter svn_uri_skip_ancestor( 373251881Speter repos_root_url, 374251881Speter url, subpool), 375251881Speter external_peg_rev, 376251881Speter external_rev, 377251881Speter subpool)); 378251881Speter 379251881Speter svn_pool_destroy(subpool); 380251881Speter goto cleanup; 381251881Speter } 382251881Speter } 383251881Speter } 384251881Speter 385251881Speter relegate: 386251881Speter 387251881Speter /* Fall back on removing the WC and checking out a new one. */ 388251881Speter 389251881Speter /* Ensure that we don't have any RA sessions or WC locks from failed 390251881Speter operations above. */ 391251881Speter svn_pool_destroy(subpool); 392251881Speter 393251881Speter if (kind == svn_node_dir) 394251881Speter { 395251881Speter /* Buh-bye, old and busted ... */ 396251881Speter SVN_ERR(relegate_dir_external(ctx->wc_ctx, defining_abspath, 397251881Speter local_abspath, 398251881Speter ctx->cancel_func, ctx->cancel_baton, 399251881Speter ctx->notify_func2, ctx->notify_baton2, 400251881Speter pool)); 401251881Speter } 402251881Speter else 403251881Speter { 404251881Speter /* The target dir might have multiple components. Guarantee 405251881Speter the path leading down to the last component. */ 406251881Speter const char *parent = svn_dirent_dirname(local_abspath, pool); 407251881Speter SVN_ERR(svn_io_make_dir_recursively(parent, pool)); 408251881Speter } 409251881Speter 410251881Speter /* ... Hello, new hotness. */ 411299742Sdim SVN_ERR(svn_client__checkout_internal(NULL, timestamp_sleep, 412299742Sdim url, local_abspath, peg_revision, 413251881Speter revision, svn_depth_infinity, 414299742Sdim FALSE, FALSE, 415299742Sdim ra_session, 416251881Speter ctx, pool)); 417251881Speter 418251881Speter SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, 419251881Speter &repos_root_url, 420251881Speter &repos_uuid, 421251881Speter ctx->wc_ctx, local_abspath, 422251881Speter pool, pool)); 423251881Speter 424251881Speter SVN_ERR(svn_wc__external_register(ctx->wc_ctx, 425251881Speter defining_abspath, 426251881Speter local_abspath, svn_node_dir, 427251881Speter repos_root_url, repos_uuid, 428251881Speter svn_uri_skip_ancestor(repos_root_url, 429251881Speter url, pool), 430251881Speter external_peg_rev, 431251881Speter external_rev, 432251881Speter pool)); 433251881Speter 434251881Speter cleanup: 435251881Speter /* Issues #4123 and #4130: We don't need to keep the newly checked 436251881Speter out external's DB open. */ 437251881Speter SVN_ERR(svn_wc__close_db(local_abspath, ctx->wc_ctx, pool)); 438251881Speter 439251881Speter return SVN_NO_ERROR; 440251881Speter} 441251881Speter 442299742Sdim/* Try to update a file external at LOCAL_ABSPATH to SWITCH_LOC. This function 443299742Sdim assumes caller has a write lock in CTX. Use SCRATCH_POOL for temporary 444251881Speter allocations, and use the client context CTX. */ 445251881Speterstatic svn_error_t * 446251881Speterswitch_file_external(const char *local_abspath, 447299742Sdim const svn_client__pathrev_t *switch_loc, 448299742Sdim const char *record_url, 449299742Sdim const svn_opt_revision_t *record_peg_revision, 450299742Sdim const svn_opt_revision_t *record_revision, 451251881Speter const char *def_dir_abspath, 452251881Speter svn_ra_session_t *ra_session, 453251881Speter svn_client_ctx_t *ctx, 454251881Speter apr_pool_t *scratch_pool) 455251881Speter{ 456251881Speter svn_config_t *cfg = ctx->config 457251881Speter ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) 458251881Speter : NULL; 459251881Speter svn_boolean_t use_commit_times; 460251881Speter const char *diff3_cmd; 461251881Speter const char *preserved_exts_str; 462251881Speter const apr_array_header_t *preserved_exts; 463251881Speter svn_node_kind_t kind, external_kind; 464251881Speter 465251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 466251881Speter 467251881Speter /* See if the user wants last-commit timestamps instead of current ones. */ 468251881Speter SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, 469251881Speter SVN_CONFIG_SECTION_MISCELLANY, 470251881Speter SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); 471251881Speter 472251881Speter /* Get the external diff3, if any. */ 473251881Speter svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, 474251881Speter SVN_CONFIG_OPTION_DIFF3_CMD, NULL); 475251881Speter 476251881Speter if (diff3_cmd != NULL) 477251881Speter SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool)); 478251881Speter 479251881Speter /* See which files the user wants to preserve the extension of when 480251881Speter conflict files are made. */ 481251881Speter svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, 482251881Speter SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); 483251881Speter preserved_exts = *preserved_exts_str 484251881Speter ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool) 485251881Speter : NULL; 486251881Speter 487251881Speter { 488251881Speter const char *wcroot_abspath; 489251881Speter 490251881Speter SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath, 491251881Speter scratch_pool, scratch_pool)); 492251881Speter 493251881Speter /* File externals can only be installed inside the current working copy. 494251881Speter So verify if the working copy that contains/will contain the target 495251881Speter is the defining abspath, or one of its ancestors */ 496251881Speter 497251881Speter if (!svn_dirent_is_ancestor(wcroot_abspath, def_dir_abspath)) 498251881Speter return svn_error_createf( 499251881Speter SVN_ERR_WC_BAD_PATH, NULL, 500251881Speter _("Cannot insert a file external defined on '%s' " 501251881Speter "into the working copy '%s'."), 502251881Speter svn_dirent_local_style(def_dir_abspath, 503251881Speter scratch_pool), 504251881Speter svn_dirent_local_style(wcroot_abspath, 505251881Speter scratch_pool)); 506251881Speter } 507251881Speter 508251881Speter SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, 509251881Speter TRUE, FALSE, scratch_pool)); 510251881Speter 511251881Speter SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, 512251881Speter ctx->wc_ctx, local_abspath, local_abspath, 513251881Speter TRUE, scratch_pool, scratch_pool)); 514251881Speter 515251881Speter /* If there is a versioned item with this name, ensure it's a file 516251881Speter external before working with it. If there is no entry in the 517251881Speter working copy, then create an empty file and add it to the working 518251881Speter copy. */ 519251881Speter if (kind != svn_node_none && kind != svn_node_unknown) 520251881Speter { 521251881Speter if (external_kind != svn_node_file) 522251881Speter { 523251881Speter return svn_error_createf( 524251881Speter SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0, 525251881Speter _("The file external from '%s' cannot overwrite the existing " 526251881Speter "versioned item at '%s'"), 527299742Sdim switch_loc->url, 528299742Sdim svn_dirent_local_style(local_abspath, scratch_pool)); 529251881Speter } 530251881Speter } 531251881Speter else 532251881Speter { 533251881Speter svn_node_kind_t disk_kind; 534251881Speter 535251881Speter SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); 536251881Speter 537251881Speter if (kind == svn_node_file || kind == svn_node_dir) 538251881Speter return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL, 539251881Speter _("The file external '%s' can not be " 540251881Speter "created because the node exists."), 541251881Speter svn_dirent_local_style(local_abspath, 542251881Speter scratch_pool)); 543251881Speter } 544251881Speter 545251881Speter { 546251881Speter const svn_ra_reporter3_t *reporter; 547251881Speter void *report_baton; 548251881Speter const svn_delta_editor_t *switch_editor; 549251881Speter void *switch_baton; 550251881Speter svn_revnum_t revnum; 551251881Speter apr_array_header_t *inherited_props; 552299742Sdim const char *target = svn_dirent_basename(local_abspath, scratch_pool); 553251881Speter 554251881Speter /* Get the external file's iprops. */ 555251881Speter SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "", 556251881Speter switch_loc->rev, 557251881Speter scratch_pool, scratch_pool)); 558251881Speter 559299742Sdim SVN_ERR(svn_ra_reparent(ra_session, 560299742Sdim svn_uri_dirname(switch_loc->url, scratch_pool), 561251881Speter scratch_pool)); 562251881Speter 563251881Speter SVN_ERR(svn_wc__get_file_external_editor(&switch_editor, &switch_baton, 564251881Speter &revnum, ctx->wc_ctx, 565251881Speter local_abspath, 566251881Speter def_dir_abspath, 567251881Speter switch_loc->url, 568251881Speter switch_loc->repos_root_url, 569251881Speter switch_loc->repos_uuid, 570251881Speter inherited_props, 571251881Speter use_commit_times, 572251881Speter diff3_cmd, preserved_exts, 573251881Speter def_dir_abspath, 574299742Sdim record_url, 575299742Sdim record_peg_revision, 576299742Sdim record_revision, 577251881Speter ctx->cancel_func, 578251881Speter ctx->cancel_baton, 579251881Speter ctx->notify_func2, 580251881Speter ctx->notify_baton2, 581251881Speter scratch_pool, scratch_pool)); 582251881Speter 583251881Speter /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an 584251881Speter invalid revnum, that means RA will use the latest revision. */ 585251881Speter SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton, 586251881Speter switch_loc->rev, 587289166Speter target, svn_depth_unknown, switch_loc->url, 588251881Speter FALSE /* send_copyfrom */, 589251881Speter TRUE /* ignore_ancestry */, 590251881Speter switch_editor, switch_baton, 591251881Speter scratch_pool, scratch_pool)); 592251881Speter 593251881Speter SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath, 594251881Speter reporter, report_baton, 595251881Speter TRUE, use_commit_times, 596251881Speter ctx->cancel_func, ctx->cancel_baton, 597251881Speter ctx->notify_func2, ctx->notify_baton2, 598251881Speter scratch_pool)); 599251881Speter 600251881Speter if (ctx->notify_func2) 601251881Speter { 602251881Speter svn_wc_notify_t *notify 603251881Speter = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed, 604251881Speter scratch_pool); 605251881Speter notify->kind = svn_node_none; 606251881Speter notify->content_state = notify->prop_state 607251881Speter = svn_wc_notify_state_inapplicable; 608251881Speter notify->lock_state = svn_wc_notify_lock_state_inapplicable; 609251881Speter notify->revision = revnum; 610299742Sdim ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 611251881Speter } 612251881Speter } 613251881Speter 614251881Speter return SVN_NO_ERROR; 615251881Speter} 616251881Speter 617251881Speter/* Wrappers around svn_wc__external_remove, obtaining and releasing a lock for 618251881Speter directory externals */ 619251881Speterstatic svn_error_t * 620251881Speterremove_external2(svn_boolean_t *removed, 621251881Speter svn_wc_context_t *wc_ctx, 622251881Speter const char *wri_abspath, 623251881Speter const char *local_abspath, 624251881Speter svn_node_kind_t external_kind, 625251881Speter svn_cancel_func_t cancel_func, 626251881Speter void *cancel_baton, 627251881Speter apr_pool_t *scratch_pool) 628251881Speter{ 629251881Speter SVN_ERR(svn_wc__external_remove(wc_ctx, wri_abspath, 630251881Speter local_abspath, 631251881Speter (external_kind == svn_node_none), 632251881Speter cancel_func, cancel_baton, 633251881Speter scratch_pool)); 634251881Speter 635251881Speter *removed = TRUE; 636251881Speter return SVN_NO_ERROR; 637251881Speter} 638251881Speter 639251881Speter 640251881Speterstatic svn_error_t * 641251881Speterremove_external(svn_boolean_t *removed, 642251881Speter svn_wc_context_t *wc_ctx, 643251881Speter const char *wri_abspath, 644251881Speter const char *local_abspath, 645251881Speter svn_node_kind_t external_kind, 646251881Speter svn_cancel_func_t cancel_func, 647251881Speter void *cancel_baton, 648251881Speter apr_pool_t *scratch_pool) 649251881Speter{ 650251881Speter *removed = FALSE; 651251881Speter switch (external_kind) 652251881Speter { 653251881Speter case svn_node_dir: 654251881Speter SVN_WC__CALL_WITH_WRITE_LOCK( 655251881Speter remove_external2(removed, 656251881Speter wc_ctx, wri_abspath, 657251881Speter local_abspath, external_kind, 658251881Speter cancel_func, cancel_baton, 659251881Speter scratch_pool), 660251881Speter wc_ctx, local_abspath, FALSE, scratch_pool); 661251881Speter break; 662251881Speter case svn_node_file: 663251881Speter default: 664251881Speter SVN_ERR(remove_external2(removed, 665251881Speter wc_ctx, wri_abspath, 666251881Speter local_abspath, external_kind, 667251881Speter cancel_func, cancel_baton, 668251881Speter scratch_pool)); 669251881Speter break; 670251881Speter } 671251881Speter 672251881Speter return SVN_NO_ERROR; 673251881Speter} 674251881Speter 675251881Speter/* Called when an external that is in the EXTERNALS table is no longer 676251881Speter referenced from an svn:externals property */ 677251881Speterstatic svn_error_t * 678251881Speterhandle_external_item_removal(const svn_client_ctx_t *ctx, 679251881Speter const char *defining_abspath, 680251881Speter const char *local_abspath, 681251881Speter apr_pool_t *scratch_pool) 682251881Speter{ 683251881Speter svn_error_t *err; 684251881Speter svn_node_kind_t external_kind; 685251881Speter svn_node_kind_t kind; 686251881Speter svn_boolean_t removed = FALSE; 687251881Speter 688251881Speter /* local_abspath should be a wcroot or a file external */ 689251881Speter SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, 690251881Speter ctx->wc_ctx, defining_abspath, 691251881Speter local_abspath, FALSE, 692251881Speter scratch_pool, scratch_pool)); 693251881Speter 694251881Speter SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE, 695251881Speter scratch_pool)); 696251881Speter 697251881Speter if (external_kind != kind) 698251881Speter external_kind = svn_node_none; /* Only remove the registration */ 699251881Speter 700251881Speter err = remove_external(&removed, 701251881Speter ctx->wc_ctx, defining_abspath, local_abspath, 702251881Speter external_kind, 703251881Speter ctx->cancel_func, ctx->cancel_baton, 704251881Speter scratch_pool); 705251881Speter 706251881Speter if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED && removed) 707251881Speter { 708251881Speter svn_error_clear(err); 709251881Speter err = NULL; /* We removed the working copy, so we can't release the 710251881Speter lock that was stored inside */ 711251881Speter } 712251881Speter 713251881Speter if (ctx->notify_func2) 714251881Speter { 715251881Speter svn_wc_notify_t *notify = 716251881Speter svn_wc_create_notify(local_abspath, 717251881Speter svn_wc_notify_update_external_removed, 718251881Speter scratch_pool); 719251881Speter 720251881Speter notify->kind = kind; 721251881Speter notify->err = err; 722251881Speter 723299742Sdim ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 724251881Speter 725251881Speter if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD) 726251881Speter { 727251881Speter notify = svn_wc_create_notify(local_abspath, 728251881Speter svn_wc_notify_left_local_modifications, 729251881Speter scratch_pool); 730251881Speter notify->kind = svn_node_dir; 731251881Speter notify->err = err; 732251881Speter 733299742Sdim ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 734251881Speter } 735251881Speter } 736251881Speter 737251881Speter if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD) 738251881Speter { 739251881Speter svn_error_clear(err); 740251881Speter err = NULL; 741251881Speter } 742251881Speter 743251881Speter return svn_error_trace(err); 744251881Speter} 745251881Speter 746251881Speterstatic svn_error_t * 747251881Speterhandle_external_item_change(svn_client_ctx_t *ctx, 748251881Speter const char *repos_root_url, 749251881Speter const char *parent_dir_abspath, 750251881Speter const char *parent_dir_url, 751251881Speter const char *local_abspath, 752251881Speter const char *old_defining_abspath, 753251881Speter const svn_wc_external_item2_t *new_item, 754299742Sdim svn_ra_session_t *ra_session, 755251881Speter svn_boolean_t *timestamp_sleep, 756251881Speter apr_pool_t *scratch_pool) 757251881Speter{ 758251881Speter svn_client__pathrev_t *new_loc; 759251881Speter const char *new_url; 760251881Speter svn_node_kind_t ext_kind; 761251881Speter 762251881Speter SVN_ERR_ASSERT(repos_root_url && parent_dir_url); 763251881Speter SVN_ERR_ASSERT(new_item != NULL); 764251881Speter 765251881Speter /* Don't bother to check status, since we'll get that for free by 766251881Speter attempting to retrieve the hash values anyway. */ 767251881Speter 768251881Speter /* When creating the absolute URL, use the pool and not the 769251881Speter iterpool, since the hash table values outlive the iterpool and 770251881Speter any pointers they have should also outlive the iterpool. */ 771251881Speter 772251881Speter SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, 773251881Speter new_item, repos_root_url, 774251881Speter parent_dir_url, 775251881Speter scratch_pool, scratch_pool)); 776251881Speter 777251881Speter /* Determine if the external is a file or directory. */ 778299742Sdim /* Get the RA connection, if needed. */ 779299742Sdim if (ra_session) 780299742Sdim { 781299742Sdim svn_error_t *err = svn_ra_reparent(ra_session, new_url, scratch_pool); 782251881Speter 783299742Sdim if (err) 784299742Sdim { 785299742Sdim if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL) 786299742Sdim { 787299742Sdim svn_error_clear(err); 788299742Sdim ra_session = NULL; 789299742Sdim } 790299742Sdim else 791299742Sdim return svn_error_trace(err); 792299742Sdim } 793299742Sdim else 794299742Sdim { 795299742Sdim SVN_ERR(svn_client__resolve_rev_and_url(&new_loc, 796299742Sdim ra_session, new_url, 797299742Sdim &(new_item->peg_revision), 798299742Sdim &(new_item->revision), ctx, 799299742Sdim scratch_pool)); 800299742Sdim 801299742Sdim SVN_ERR(svn_ra_reparent(ra_session, new_loc->url, scratch_pool)); 802299742Sdim } 803299742Sdim } 804299742Sdim 805299742Sdim if (!ra_session) 806299742Sdim SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc, 807299742Sdim new_url, NULL, 808299742Sdim &(new_item->peg_revision), 809299742Sdim &(new_item->revision), ctx, 810299742Sdim scratch_pool)); 811299742Sdim 812251881Speter SVN_ERR(svn_ra_check_path(ra_session, "", new_loc->rev, &ext_kind, 813251881Speter scratch_pool)); 814251881Speter 815251881Speter if (svn_node_none == ext_kind) 816251881Speter return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 817251881Speter _("URL '%s' at revision %ld doesn't exist"), 818251881Speter new_loc->url, new_loc->rev); 819251881Speter 820251881Speter if (svn_node_dir != ext_kind && svn_node_file != ext_kind) 821251881Speter return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 822251881Speter _("URL '%s' at revision %ld is not a file " 823251881Speter "or a directory"), 824251881Speter new_loc->url, new_loc->rev); 825251881Speter 826251881Speter 827251881Speter /* Not protecting against recursive externals. Detecting them in 828251881Speter the global case is hard, and it should be pretty obvious to a 829251881Speter user when it happens. Worst case: your disk fills up :-). */ 830251881Speter 831251881Speter /* First notify that we're about to handle an external. */ 832251881Speter if (ctx->notify_func2) 833251881Speter { 834299742Sdim ctx->notify_func2( 835251881Speter ctx->notify_baton2, 836251881Speter svn_wc_create_notify(local_abspath, 837251881Speter svn_wc_notify_update_external, 838251881Speter scratch_pool), 839251881Speter scratch_pool); 840251881Speter } 841251881Speter 842251881Speter if (! old_defining_abspath) 843251881Speter { 844251881Speter /* The target dir might have multiple components. Guarantee the path 845251881Speter leading down to the last component. */ 846251881Speter SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath, 847251881Speter scratch_pool), 848251881Speter scratch_pool)); 849251881Speter } 850251881Speter 851251881Speter switch (ext_kind) 852251881Speter { 853251881Speter case svn_node_dir: 854262253Speter SVN_ERR(switch_dir_external(local_abspath, new_loc->url, 855289166Speter new_item->url, 856251881Speter &(new_item->peg_revision), 857251881Speter &(new_item->revision), 858251881Speter parent_dir_abspath, 859299742Sdim timestamp_sleep, ra_session, ctx, 860251881Speter scratch_pool)); 861251881Speter break; 862251881Speter case svn_node_file: 863251881Speter if (strcmp(repos_root_url, new_loc->repos_root_url)) 864251881Speter { 865251881Speter const char *local_repos_root_url; 866251881Speter const char *local_repos_uuid; 867251881Speter const char *ext_repos_relpath; 868251881Speter svn_error_t *err; 869251881Speter 870251881Speter /* 871251881Speter * The working copy library currently requires that all files 872251881Speter * in the working copy have the same repository root URL. 873251881Speter * The URL from the file external's definition differs from the 874251881Speter * one used by the working copy. As a workaround, replace the 875251881Speter * root URL portion of the file external's URL, after making 876251881Speter * sure both URLs point to the same repository. See issue #4087. 877251881Speter */ 878251881Speter 879251881Speter err = svn_wc__node_get_repos_info(NULL, NULL, 880251881Speter &local_repos_root_url, 881251881Speter &local_repos_uuid, 882251881Speter ctx->wc_ctx, parent_dir_abspath, 883251881Speter scratch_pool, scratch_pool); 884251881Speter if (err) 885251881Speter { 886251881Speter if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND 887251881Speter && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) 888251881Speter return svn_error_trace(err); 889251881Speter 890251881Speter svn_error_clear(err); 891251881Speter local_repos_root_url = NULL; 892251881Speter local_repos_uuid = NULL; 893251881Speter } 894251881Speter 895251881Speter ext_repos_relpath = svn_uri_skip_ancestor(new_loc->repos_root_url, 896251881Speter new_url, scratch_pool); 897251881Speter if (local_repos_uuid == NULL || local_repos_root_url == NULL || 898251881Speter ext_repos_relpath == NULL || 899251881Speter strcmp(local_repos_uuid, new_loc->repos_uuid) != 0) 900251881Speter return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 901251881Speter _("Unsupported external: URL of file external '%s' " 902251881Speter "is not in repository '%s'"), 903251881Speter new_url, repos_root_url); 904251881Speter 905251881Speter new_url = svn_path_url_add_component2(local_repos_root_url, 906251881Speter ext_repos_relpath, 907251881Speter scratch_pool); 908251881Speter SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc, 909251881Speter new_url, 910251881Speter NULL, 911251881Speter &(new_item->peg_revision), 912251881Speter &(new_item->revision), 913251881Speter ctx, scratch_pool)); 914251881Speter } 915251881Speter 916251881Speter SVN_ERR(switch_file_external(local_abspath, 917299742Sdim new_loc, 918251881Speter new_url, 919251881Speter &new_item->peg_revision, 920251881Speter &new_item->revision, 921251881Speter parent_dir_abspath, 922251881Speter ra_session, 923251881Speter ctx, 924251881Speter scratch_pool)); 925251881Speter break; 926251881Speter 927251881Speter default: 928251881Speter SVN_ERR_MALFUNCTION(); 929251881Speter break; 930251881Speter } 931251881Speter 932251881Speter return SVN_NO_ERROR; 933251881Speter} 934251881Speter 935251881Speterstatic svn_error_t * 936251881Speterwrap_external_error(const svn_client_ctx_t *ctx, 937251881Speter const char *target_abspath, 938251881Speter svn_error_t *err, 939251881Speter apr_pool_t *scratch_pool) 940251881Speter{ 941251881Speter if (err && err->apr_err != SVN_ERR_CANCELLED) 942251881Speter { 943251881Speter if (ctx->notify_func2) 944251881Speter { 945251881Speter svn_wc_notify_t *notifier = svn_wc_create_notify( 946251881Speter target_abspath, 947251881Speter svn_wc_notify_failed_external, 948251881Speter scratch_pool); 949251881Speter notifier->err = err; 950251881Speter ctx->notify_func2(ctx->notify_baton2, notifier, scratch_pool); 951251881Speter } 952251881Speter svn_error_clear(err); 953251881Speter return SVN_NO_ERROR; 954251881Speter } 955251881Speter 956251881Speter return err; 957251881Speter} 958251881Speter 959251881Speterstatic svn_error_t * 960251881Speterhandle_externals_change(svn_client_ctx_t *ctx, 961251881Speter const char *repos_root_url, 962251881Speter svn_boolean_t *timestamp_sleep, 963251881Speter const char *local_abspath, 964251881Speter const char *new_desc_text, 965251881Speter apr_hash_t *old_externals, 966251881Speter svn_depth_t ambient_depth, 967251881Speter svn_depth_t requested_depth, 968299742Sdim svn_ra_session_t *ra_session, 969251881Speter apr_pool_t *scratch_pool) 970251881Speter{ 971251881Speter apr_array_header_t *new_desc; 972251881Speter int i; 973251881Speter apr_pool_t *iterpool; 974251881Speter const char *url; 975251881Speter 976251881Speter iterpool = svn_pool_create(scratch_pool); 977251881Speter 978251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 979251881Speter 980251881Speter /* Bag out if the depth here is too shallow for externals action. */ 981251881Speter if ((requested_depth < svn_depth_infinity 982251881Speter && requested_depth != svn_depth_unknown) 983251881Speter || (ambient_depth < svn_depth_infinity 984251881Speter && requested_depth < svn_depth_infinity)) 985251881Speter return SVN_NO_ERROR; 986251881Speter 987251881Speter if (new_desc_text) 988251881Speter SVN_ERR(svn_wc_parse_externals_description3(&new_desc, local_abspath, 989251881Speter new_desc_text, 990251881Speter FALSE, scratch_pool)); 991251881Speter else 992251881Speter new_desc = NULL; 993251881Speter 994251881Speter SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, local_abspath, 995251881Speter scratch_pool, iterpool)); 996251881Speter 997251881Speter SVN_ERR_ASSERT(url); 998251881Speter 999251881Speter for (i = 0; new_desc && (i < new_desc->nelts); i++) 1000251881Speter { 1001251881Speter const char *old_defining_abspath; 1002251881Speter svn_wc_external_item2_t *new_item; 1003251881Speter const char *target_abspath; 1004251881Speter svn_boolean_t under_root; 1005251881Speter 1006251881Speter new_item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *); 1007251881Speter 1008251881Speter svn_pool_clear(iterpool); 1009251881Speter 1010251881Speter if (ctx->cancel_func) 1011251881Speter SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1012251881Speter 1013251881Speter SVN_ERR(svn_dirent_is_under_root(&under_root, &target_abspath, 1014251881Speter local_abspath, new_item->target_dir, 1015251881Speter iterpool)); 1016251881Speter 1017251881Speter if (! under_root) 1018251881Speter { 1019251881Speter return svn_error_createf( 1020251881Speter SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, 1021251881Speter _("Path '%s' is not in the working copy"), 1022251881Speter svn_dirent_local_style( 1023251881Speter svn_dirent_join(local_abspath, new_item->target_dir, 1024251881Speter iterpool), 1025251881Speter iterpool)); 1026251881Speter } 1027251881Speter 1028251881Speter old_defining_abspath = svn_hash_gets(old_externals, target_abspath); 1029251881Speter 1030251881Speter SVN_ERR(wrap_external_error( 1031251881Speter ctx, target_abspath, 1032251881Speter handle_external_item_change(ctx, 1033251881Speter repos_root_url, 1034251881Speter local_abspath, url, 1035251881Speter target_abspath, 1036251881Speter old_defining_abspath, 1037299742Sdim new_item, ra_session, 1038251881Speter timestamp_sleep, 1039251881Speter iterpool), 1040251881Speter iterpool)); 1041251881Speter 1042251881Speter /* And remove already processed items from the to-remove hash */ 1043251881Speter if (old_defining_abspath) 1044251881Speter svn_hash_sets(old_externals, target_abspath, NULL); 1045251881Speter } 1046251881Speter 1047251881Speter svn_pool_destroy(iterpool); 1048251881Speter 1049251881Speter return SVN_NO_ERROR; 1050251881Speter} 1051251881Speter 1052251881Speter 1053251881Spetersvn_error_t * 1054251881Spetersvn_client__handle_externals(apr_hash_t *externals_new, 1055251881Speter apr_hash_t *ambient_depths, 1056251881Speter const char *repos_root_url, 1057251881Speter const char *target_abspath, 1058251881Speter svn_depth_t requested_depth, 1059251881Speter svn_boolean_t *timestamp_sleep, 1060299742Sdim svn_ra_session_t *ra_session, 1061251881Speter svn_client_ctx_t *ctx, 1062251881Speter apr_pool_t *scratch_pool) 1063251881Speter{ 1064251881Speter apr_hash_t *old_external_defs; 1065251881Speter apr_hash_index_t *hi; 1066251881Speter apr_pool_t *iterpool; 1067251881Speter 1068251881Speter SVN_ERR_ASSERT(repos_root_url); 1069251881Speter 1070251881Speter iterpool = svn_pool_create(scratch_pool); 1071251881Speter 1072251881Speter SVN_ERR(svn_wc__externals_defined_below(&old_external_defs, 1073251881Speter ctx->wc_ctx, target_abspath, 1074251881Speter scratch_pool, iterpool)); 1075251881Speter 1076251881Speter for (hi = apr_hash_first(scratch_pool, externals_new); 1077251881Speter hi; 1078251881Speter hi = apr_hash_next(hi)) 1079251881Speter { 1080299742Sdim const char *local_abspath = apr_hash_this_key(hi); 1081299742Sdim const char *desc_text = apr_hash_this_val(hi); 1082251881Speter svn_depth_t ambient_depth = svn_depth_infinity; 1083251881Speter 1084251881Speter svn_pool_clear(iterpool); 1085251881Speter 1086251881Speter if (ambient_depths) 1087251881Speter { 1088251881Speter const char *ambient_depth_w; 1089251881Speter 1090251881Speter ambient_depth_w = apr_hash_get(ambient_depths, local_abspath, 1091299742Sdim apr_hash_this_key_len(hi)); 1092251881Speter 1093251881Speter if (ambient_depth_w == NULL) 1094251881Speter { 1095251881Speter return svn_error_createf( 1096251881Speter SVN_ERR_WC_CORRUPT, NULL, 1097251881Speter _("Traversal of '%s' found no ambient depth"), 1098251881Speter svn_dirent_local_style(local_abspath, scratch_pool)); 1099251881Speter } 1100251881Speter else 1101251881Speter { 1102251881Speter ambient_depth = svn_depth_from_word(ambient_depth_w); 1103251881Speter } 1104251881Speter } 1105251881Speter 1106251881Speter SVN_ERR(handle_externals_change(ctx, repos_root_url, timestamp_sleep, 1107251881Speter local_abspath, 1108251881Speter desc_text, old_external_defs, 1109251881Speter ambient_depth, requested_depth, 1110299742Sdim ra_session, iterpool)); 1111251881Speter } 1112251881Speter 1113251881Speter /* Remove the remaining externals */ 1114251881Speter for (hi = apr_hash_first(scratch_pool, old_external_defs); 1115251881Speter hi; 1116251881Speter hi = apr_hash_next(hi)) 1117251881Speter { 1118299742Sdim const char *item_abspath = apr_hash_this_key(hi); 1119299742Sdim const char *defining_abspath = apr_hash_this_val(hi); 1120251881Speter const char *parent_abspath; 1121251881Speter 1122251881Speter svn_pool_clear(iterpool); 1123251881Speter 1124251881Speter SVN_ERR(wrap_external_error( 1125251881Speter ctx, item_abspath, 1126251881Speter handle_external_item_removal(ctx, defining_abspath, 1127251881Speter item_abspath, iterpool), 1128251881Speter iterpool)); 1129251881Speter 1130251881Speter /* Are there any unversioned directories between the removed 1131251881Speter * external and the DEFINING_ABSPATH which we can remove? */ 1132251881Speter parent_abspath = item_abspath; 1133251881Speter do { 1134251881Speter svn_node_kind_t kind; 1135251881Speter 1136251881Speter parent_abspath = svn_dirent_dirname(parent_abspath, iterpool); 1137251881Speter SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, parent_abspath, 1138269847Speter FALSE /* show_deleted*/, 1139269847Speter FALSE /* show_hidden */, 1140269847Speter iterpool)); 1141251881Speter if (kind == svn_node_none) 1142251881Speter { 1143251881Speter svn_error_t *err; 1144251881Speter 1145251881Speter err = svn_io_dir_remove_nonrecursive(parent_abspath, iterpool); 1146269847Speter if (err) 1147251881Speter { 1148269847Speter if (APR_STATUS_IS_ENOTEMPTY(err->apr_err)) 1149269847Speter { 1150269847Speter svn_error_clear(err); 1151269847Speter break; /* No parents to delete */ 1152269847Speter } 1153269847Speter else if (APR_STATUS_IS_ENOENT(err->apr_err) 1154269847Speter || APR_STATUS_IS_ENOTDIR(err->apr_err)) 1155269847Speter { 1156269847Speter svn_error_clear(err); 1157269847Speter /* Fall through; parent dir might be unversioned */ 1158269847Speter } 1159269847Speter else 1160269847Speter return svn_error_trace(err); 1161251881Speter } 1162251881Speter } 1163251881Speter } while (strcmp(parent_abspath, defining_abspath) != 0); 1164251881Speter } 1165251881Speter 1166251881Speter 1167251881Speter svn_pool_destroy(iterpool); 1168251881Speter return SVN_NO_ERROR; 1169251881Speter} 1170251881Speter 1171251881Speter 1172251881Spetersvn_error_t * 1173251881Spetersvn_client__export_externals(apr_hash_t *externals, 1174251881Speter const char *from_url, 1175251881Speter const char *to_abspath, 1176251881Speter const char *repos_root_url, 1177251881Speter svn_depth_t requested_depth, 1178251881Speter const char *native_eol, 1179251881Speter svn_boolean_t ignore_keywords, 1180251881Speter svn_client_ctx_t *ctx, 1181251881Speter apr_pool_t *scratch_pool) 1182251881Speter{ 1183251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1184251881Speter apr_pool_t *sub_iterpool = svn_pool_create(scratch_pool); 1185251881Speter apr_hash_index_t *hi; 1186251881Speter 1187251881Speter SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath)); 1188251881Speter 1189251881Speter for (hi = apr_hash_first(scratch_pool, externals); 1190251881Speter hi; 1191251881Speter hi = apr_hash_next(hi)) 1192251881Speter { 1193299742Sdim const char *local_abspath = apr_hash_this_key(hi); 1194299742Sdim const char *desc_text = apr_hash_this_val(hi); 1195251881Speter const char *local_relpath; 1196251881Speter const char *dir_url; 1197251881Speter apr_array_header_t *items; 1198251881Speter int i; 1199251881Speter 1200251881Speter svn_pool_clear(iterpool); 1201251881Speter 1202251881Speter SVN_ERR(svn_wc_parse_externals_description3(&items, local_abspath, 1203251881Speter desc_text, FALSE, 1204251881Speter iterpool)); 1205251881Speter 1206251881Speter if (! items->nelts) 1207251881Speter continue; 1208251881Speter 1209251881Speter local_relpath = svn_dirent_skip_ancestor(to_abspath, local_abspath); 1210251881Speter 1211251881Speter dir_url = svn_path_url_add_component2(from_url, local_relpath, 1212251881Speter scratch_pool); 1213251881Speter 1214251881Speter for (i = 0; i < items->nelts; i++) 1215251881Speter { 1216251881Speter const char *item_abspath; 1217251881Speter const char *new_url; 1218251881Speter svn_boolean_t under_root; 1219251881Speter svn_wc_external_item2_t *item = APR_ARRAY_IDX(items, i, 1220251881Speter svn_wc_external_item2_t *); 1221251881Speter 1222251881Speter svn_pool_clear(sub_iterpool); 1223251881Speter 1224251881Speter SVN_ERR(svn_dirent_is_under_root(&under_root, &item_abspath, 1225251881Speter local_abspath, item->target_dir, 1226251881Speter sub_iterpool)); 1227251881Speter 1228251881Speter if (! under_root) 1229251881Speter { 1230251881Speter return svn_error_createf( 1231251881Speter SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, 1232251881Speter _("Path '%s' is not in the working copy"), 1233251881Speter svn_dirent_local_style( 1234251881Speter svn_dirent_join(local_abspath, item->target_dir, 1235251881Speter sub_iterpool), 1236251881Speter sub_iterpool)); 1237251881Speter } 1238251881Speter 1239251881Speter SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, item, 1240251881Speter repos_root_url, 1241251881Speter dir_url, sub_iterpool, 1242251881Speter sub_iterpool)); 1243251881Speter 1244251881Speter /* The target dir might have multiple components. Guarantee 1245251881Speter the path leading down to the last component. */ 1246251881Speter SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(item_abspath, 1247251881Speter sub_iterpool), 1248251881Speter sub_iterpool)); 1249251881Speter 1250299742Sdim /* First notify that we're about to handle an external. */ 1251299742Sdim if (ctx->notify_func2) 1252299742Sdim { 1253299742Sdim ctx->notify_func2( 1254299742Sdim ctx->notify_baton2, 1255299742Sdim svn_wc_create_notify(item_abspath, 1256299742Sdim svn_wc_notify_update_external, 1257299742Sdim sub_iterpool), 1258299742Sdim sub_iterpool); 1259299742Sdim } 1260299742Sdim 1261251881Speter SVN_ERR(wrap_external_error( 1262251881Speter ctx, item_abspath, 1263251881Speter svn_client_export5(NULL, new_url, item_abspath, 1264251881Speter &item->peg_revision, 1265251881Speter &item->revision, 1266251881Speter TRUE, FALSE, ignore_keywords, 1267251881Speter svn_depth_infinity, 1268251881Speter native_eol, 1269251881Speter ctx, sub_iterpool), 1270251881Speter sub_iterpool)); 1271251881Speter } 1272251881Speter } 1273251881Speter 1274251881Speter svn_pool_destroy(sub_iterpool); 1275251881Speter svn_pool_destroy(iterpool); 1276251881Speter 1277251881Speter return SVN_NO_ERROR; 1278251881Speter} 1279251881Speter 1280