add.c revision 251886
1/* 2 * add.c: wrappers around wc add/mkdir functionality. 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include <string.h> 31#include <apr_lib.h> 32#include <apr_fnmatch.h> 33#include "svn_wc.h" 34#include "svn_client.h" 35#include "svn_string.h" 36#include "svn_pools.h" 37#include "svn_error.h" 38#include "svn_dirent_uri.h" 39#include "svn_path.h" 40#include "svn_io.h" 41#include "svn_config.h" 42#include "svn_props.h" 43#include "svn_hash.h" 44#include "svn_sorts.h" 45#include "client.h" 46#include "svn_ctype.h" 47 48#include "private/svn_client_private.h" 49#include "private/svn_wc_private.h" 50#include "private/svn_ra_private.h" 51#include "private/svn_magic.h" 52 53#include "svn_private_config.h" 54 55 56 57/*** Code. ***/ 58 59/* Remove leading and trailing white space from a C string, in place. */ 60static void 61trim_string(char **pstr) 62{ 63 char *str = *pstr; 64 size_t i; 65 66 while (svn_ctype_isspace(*str)) 67 str++; 68 *pstr = str; 69 i = strlen(str); 70 while ((i > 0) && svn_ctype_isspace(str[i-1])) 71 i--; 72 str[i] = '\0'; 73} 74 75/* Remove leading and trailing single- or double quotes from a C string, 76 * in place. */ 77static void 78unquote_string(char **pstr) 79{ 80 char *str = *pstr; 81 size_t i = strlen(str); 82 83 if (i > 0 && ((*str == '"' && str[i - 1] == '"') || 84 (*str == '\'' && str[i - 1] == '\''))) 85 { 86 str[i - 1] = '\0'; 87 str++; 88 } 89 *pstr = str; 90} 91 92/* Split PROPERTY and store each individual value in PROPS. 93 Allocates from POOL. */ 94static void 95split_props(apr_array_header_t **props, 96 const char *property, 97 apr_pool_t *pool) 98{ 99 apr_array_header_t *temp_props; 100 char *new_prop; 101 int i = 0; 102 int j = 0; 103 104 temp_props = apr_array_make(pool, 4, sizeof(char *)); 105 new_prop = apr_palloc(pool, strlen(property)+1); 106 107 for (i = 0; property[i] != '\0'; i++) 108 { 109 if (property[i] != ';') 110 { 111 new_prop[j] = property[i]; 112 j++; 113 } 114 else if (property[i] == ';') 115 { 116 /* ";;" becomes ";" */ 117 if (property[i+1] == ';') 118 { 119 new_prop[j] = ';'; 120 j++; 121 i++; 122 } 123 else 124 { 125 new_prop[j] = '\0'; 126 APR_ARRAY_PUSH(temp_props, char *) = new_prop; 127 new_prop += j + 1; 128 j = 0; 129 } 130 } 131 } 132 new_prop[j] = '\0'; 133 APR_ARRAY_PUSH(temp_props, char *) = new_prop; 134 *props = temp_props; 135} 136 137/* PROPVALS is a hash mapping char * property names to const char * property 138 values. PROPERTIES can be empty but not NULL. 139 140 If FILENAME doesn't match the filename pattern PATTERN case insensitively, 141 the do nothing. Otherwise for each 'name':'value' pair in PROPVALS, add 142 a new entry mappying 'name' to a svn_string_t * wrapping the 'value' in 143 PROPERTIES. The svn_string_t is allocated in the pool used to allocate 144 PROPERTIES, but the char *'s from PROPVALS are re-used in PROPERTIES. 145 If PROPVALS contains a 'svn:mime-type' mapping, then set *MIMETYPE to 146 the mapped value. Likewise if PROPVALS contains a mapping for 147 svn:executable, then set *HAVE_EXECUTABLE to TRUE. 148 149 Use SCRATCH_POOL for temporary allocations. 150*/ 151static void 152get_auto_props_for_pattern(apr_hash_t *properties, 153 const char **mimetype, 154 svn_boolean_t *have_executable, 155 const char *filename, 156 const char *pattern, 157 apr_hash_t *propvals, 158 apr_pool_t *scratch_pool) 159{ 160 apr_hash_index_t *hi; 161 162 /* check if filename matches and return if it doesn't */ 163 if (apr_fnmatch(pattern, filename, 164 APR_FNM_CASE_BLIND) == APR_FNM_NOMATCH) 165 return; 166 167 for (hi = apr_hash_first(scratch_pool, propvals); 168 hi != NULL; 169 hi = apr_hash_next(hi)) 170 { 171 const char *propname = svn__apr_hash_index_key(hi); 172 const char *propval = svn__apr_hash_index_val(hi); 173 svn_string_t *propval_str = 174 svn_string_create_empty(apr_hash_pool_get(properties)); 175 176 propval_str->data = propval; 177 propval_str->len = strlen(propval); 178 179 svn_hash_sets(properties, propname, propval_str); 180 if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0) 181 *mimetype = propval; 182 else if (strcmp(propname, SVN_PROP_EXECUTABLE) == 0) 183 *have_executable = TRUE; 184 } 185} 186 187svn_error_t * 188svn_client__get_paths_auto_props(apr_hash_t **properties, 189 const char **mimetype, 190 const char *path, 191 svn_magic__cookie_t *magic_cookie, 192 apr_hash_t *autoprops, 193 svn_client_ctx_t *ctx, 194 apr_pool_t *result_pool, 195 apr_pool_t *scratch_pool) 196{ 197 apr_hash_index_t *hi; 198 svn_boolean_t have_executable = FALSE; 199 200 *properties = apr_hash_make(result_pool); 201 *mimetype = NULL; 202 203 if (autoprops) 204 { 205 for (hi = apr_hash_first(scratch_pool, autoprops); 206 hi != NULL; 207 hi = apr_hash_next(hi)) 208 { 209 const char *pattern = svn__apr_hash_index_key(hi); 210 apr_hash_t *propvals = svn__apr_hash_index_val(hi); 211 212 get_auto_props_for_pattern(*properties, mimetype, &have_executable, 213 svn_dirent_basename(path, scratch_pool), 214 pattern, propvals, scratch_pool); 215 } 216 } 217 218 /* if mimetype has not been set check the file */ 219 if (! *mimetype) 220 { 221 SVN_ERR(svn_io_detect_mimetype2(mimetype, path, ctx->mimetypes_map, 222 result_pool)); 223 224 /* If we got no mime-type, or if it is "application/octet-stream", 225 * try to get the mime-type from libmagic. */ 226 if (magic_cookie && 227 (!*mimetype || 228 strcmp(*mimetype, "application/octet-stream") == 0)) 229 { 230 const char *magic_mimetype; 231 232 /* Since libmagic usually treats UTF-16 files as "text/plain", 233 * svn_magic__detect_binary_mimetype() will return NULL for such 234 * files. This is fine for now since we currently don't support 235 * UTF-16-encoded text files (issue #2194). 236 * Once we do support UTF-16 this code path will fail to detect 237 * them as text unless the svn_io_detect_mimetype2() call above 238 * returns "text/plain" for them. */ 239 SVN_ERR(svn_magic__detect_binary_mimetype(&magic_mimetype, 240 path, magic_cookie, 241 result_pool, 242 scratch_pool)); 243 if (magic_mimetype) 244 *mimetype = magic_mimetype; 245 } 246 247 if (*mimetype) 248 apr_hash_set(*properties, SVN_PROP_MIME_TYPE, 249 strlen(SVN_PROP_MIME_TYPE), 250 svn_string_create(*mimetype, result_pool)); 251 } 252 253 /* if executable has not been set check the file */ 254 if (! have_executable) 255 { 256 svn_boolean_t executable = FALSE; 257 SVN_ERR(svn_io_is_file_executable(&executable, path, scratch_pool)); 258 if (executable) 259 apr_hash_set(*properties, SVN_PROP_EXECUTABLE, 260 strlen(SVN_PROP_EXECUTABLE), 261 svn_string_create_empty(result_pool)); 262 } 263 264 return SVN_NO_ERROR; 265} 266 267/* Only call this if the on-disk node kind is a file. */ 268static svn_error_t * 269add_file(const char *local_abspath, 270 svn_magic__cookie_t *magic_cookie, 271 apr_hash_t *autoprops, 272 svn_boolean_t no_autoprops, 273 svn_client_ctx_t *ctx, 274 apr_pool_t *pool) 275{ 276 apr_hash_t *properties; 277 const char *mimetype; 278 svn_node_kind_t kind; 279 svn_boolean_t is_special; 280 281 /* Check to see if this is a special file. */ 282 SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special, pool)); 283 284 /* Determine the properties that the file should have */ 285 if (is_special) 286 { 287 mimetype = NULL; 288 properties = apr_hash_make(pool); 289 svn_hash_sets(properties, SVN_PROP_SPECIAL, 290 svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool)); 291 } 292 else 293 { 294 apr_hash_t *file_autoprops = NULL; 295 296 /* Get automatic properties */ 297 /* If we are setting autoprops grab the inherited svn:auto-props and 298 config file auto-props for this file if we haven't already got them 299 when iterating over the file's unversioned parents. */ 300 if (!no_autoprops) 301 { 302 if (autoprops == NULL) 303 SVN_ERR(svn_client__get_all_auto_props( 304 &file_autoprops, svn_dirent_dirname(local_abspath,pool), 305 ctx, pool, pool)); 306 else 307 file_autoprops = autoprops; 308 } 309 310 /* This may fail on write-only files: 311 we open them to estimate file type. */ 312 SVN_ERR(svn_client__get_paths_auto_props(&properties, &mimetype, 313 local_abspath, magic_cookie, 314 file_autoprops, ctx, pool, 315 pool)); 316 } 317 318 /* Add the file */ 319 SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, local_abspath, properties, 320 ctx->notify_func2, ctx->notify_baton2, pool)); 321 322 return SVN_NO_ERROR; 323} 324 325/* Schedule directory DIR_ABSPATH, and some of the tree under it, for 326 * addition. DEPTH is the depth at this point in the descent (it may 327 * be changed for recursive calls). 328 * 329 * If DIR_ABSPATH (or any item below DIR_ABSPATH) is already scheduled for 330 * addition, add will fail and return an error unless FORCE is TRUE. 331 * 332 * Use MAGIC_COOKIE (which may be NULL) to detect the mime-type of files 333 * if necessary. 334 * 335 * If not NULL, CONFIG_AUTOPROPS is a hash representing the config file and 336 * svn:auto-props autoprops which apply to DIR_ABSPATH. It maps 337 * const char * file patterns to another hash which maps const char * 338 * property names to const char *property values. If CONFIG_AUTOPROPS is 339 * NULL and the config file and svn:auto-props autoprops are required by this 340 * function, then such will be obtained. 341 * 342 * If IGNORES is not NULL, then it is an array of const char * ignore patterns 343 * that apply to any children of DIR_ABSPATH. If REFRESH_IGNORES is TRUE, then 344 * the passed in value of IGNORES (if any) is itself ignored and this function 345 * will gather all ignore patterns applicable to DIR_ABSPATH itself (allocated in 346 * RESULT_POOL). Any recursive calls to this function get the refreshed ignore 347 * patterns. If IGNORES is NULL and REFRESH_IGNORES is FALSE, then all children of DIR_ABSPATH 348 * are unconditionally added. 349 * 350 * If CTX->CANCEL_FUNC is non-null, call it with CTX->CANCEL_BATON to allow 351 * the user to cancel the operation. 352 * 353 * Use SCRATCH_POOL for temporary allocations. 354 */ 355static svn_error_t * 356add_dir_recursive(const char *dir_abspath, 357 svn_depth_t depth, 358 svn_boolean_t force, 359 svn_boolean_t no_autoprops, 360 svn_magic__cookie_t *magic_cookie, 361 apr_hash_t *config_autoprops, 362 svn_boolean_t refresh_ignores, 363 apr_array_header_t *ignores, 364 svn_client_ctx_t *ctx, 365 apr_pool_t *result_pool, 366 apr_pool_t *scratch_pool) 367{ 368 svn_error_t *err; 369 apr_pool_t *iterpool; 370 apr_hash_t *dirents; 371 apr_hash_index_t *hi; 372 svn_boolean_t entry_exists = FALSE; 373 374 /* Check cancellation; note that this catches recursive calls too. */ 375 if (ctx->cancel_func) 376 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 377 378 iterpool = svn_pool_create(scratch_pool); 379 380 /* Add this directory to revision control. */ 381 err = svn_wc_add_from_disk2(ctx->wc_ctx, dir_abspath, NULL /*props*/, 382 ctx->notify_func2, ctx->notify_baton2, 383 iterpool); 384 if (err) 385 { 386 if (err->apr_err == SVN_ERR_ENTRY_EXISTS && force) 387 { 388 svn_error_clear(err); 389 entry_exists = TRUE; 390 } 391 else if (err) 392 { 393 return svn_error_trace(err); 394 } 395 } 396 397 /* Fetch ignores after adding to handle ignores on the directory itself 398 and ancestors via the single db optimization in libsvn_wc */ 399 if (refresh_ignores) 400 SVN_ERR(svn_wc_get_ignores2(&ignores, ctx->wc_ctx, dir_abspath, 401 ctx->config, result_pool, iterpool)); 402 403 /* If DIR_ABSPATH is the root of an unversioned subtree then get the 404 following "autoprops": 405 406 1) Explicit and inherited svn:auto-props properties on 407 DIR_ABSPATH 408 2) auto-props from the CTX->CONFIG hash 409 410 Since this set of autoprops applies to all unversioned children of 411 DIR_ABSPATH, we will pass these along to any recursive calls to 412 add_dir_recursive() and calls to add_file() below. Thus sparing 413 these callees from looking up the same information. */ 414 if (!entry_exists && config_autoprops == NULL) 415 { 416 SVN_ERR(svn_client__get_all_auto_props(&config_autoprops, dir_abspath, 417 ctx, scratch_pool, iterpool)); 418 } 419 420 SVN_ERR(svn_io_get_dirents3(&dirents, dir_abspath, TRUE, scratch_pool, 421 iterpool)); 422 423 /* Read the directory entries one by one and add those things to 424 version control. */ 425 for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi)) 426 { 427 const char *name = svn__apr_hash_index_key(hi); 428 svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi); 429 const char *abspath; 430 431 svn_pool_clear(iterpool); 432 433 /* Check cancellation so you can cancel during an 434 * add of a directory with lots of files. */ 435 if (ctx->cancel_func) 436 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 437 438 /* Skip over SVN admin directories. */ 439 if (svn_wc_is_adm_dir(name, iterpool)) 440 continue; 441 442 if (ignores 443 && svn_wc_match_ignore_list(name, ignores, iterpool)) 444 continue; 445 446 /* Construct the full path of the entry. */ 447 abspath = svn_dirent_join(dir_abspath, name, iterpool); 448 449 /* Recurse on directories; add files; ignore the rest. */ 450 if (dirent->kind == svn_node_dir && depth >= svn_depth_immediates) 451 { 452 svn_depth_t depth_below_here = depth; 453 if (depth == svn_depth_immediates) 454 depth_below_here = svn_depth_empty; 455 456 /* When DIR_ABSPATH is the root of an unversioned subtree then 457 it and all of its children have the same set of ignores. So 458 save any recursive calls the extra work of finding the same 459 set of ignores. */ 460 if (refresh_ignores && !entry_exists) 461 refresh_ignores = FALSE; 462 463 SVN_ERR(add_dir_recursive(abspath, depth_below_here, 464 force, no_autoprops, 465 magic_cookie, config_autoprops, 466 refresh_ignores, ignores, ctx, 467 result_pool, iterpool)); 468 } 469 else if ((dirent->kind == svn_node_file || dirent->special) 470 && depth >= svn_depth_files) 471 { 472 err = add_file(abspath, magic_cookie, config_autoprops, 473 no_autoprops, ctx, iterpool); 474 if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force) 475 svn_error_clear(err); 476 else 477 SVN_ERR(err); 478 } 479 } 480 481 /* Destroy the per-iteration pool. */ 482 svn_pool_destroy(iterpool); 483 484 return SVN_NO_ERROR; 485} 486 487/* This structure is used as baton for collecting the config entries 488 in the auto-props section and any inherited svn:auto-props 489 properties. 490*/ 491typedef struct collect_auto_props_baton_t 492{ 493 /* the hash table for storing the property name/value pairs */ 494 apr_hash_t *autoprops; 495 496 /* a pool used for allocating memory */ 497 apr_pool_t *result_pool; 498} collect_auto_props_baton_t; 499 500/* Implements svn_config_enumerator2_t callback. 501 502 For one auto-props config entry (NAME, VALUE), stash a copy of 503 NAME and VALUE, allocated in BATON->POOL, in BATON->AUTOPROP. 504 BATON must point to an collect_auto_props_baton_t. 505*/ 506static svn_boolean_t 507all_auto_props_collector(const char *name, 508 const char *value, 509 void *baton, 510 apr_pool_t *pool) 511{ 512 collect_auto_props_baton_t *autoprops_baton = baton; 513 apr_array_header_t *autoprops; 514 int i; 515 516 /* nothing to do here without a value */ 517 if (*value == 0) 518 return TRUE; 519 520 split_props(&autoprops, value, pool); 521 522 for (i = 0; i < autoprops->nelts; i ++) 523 { 524 size_t len; 525 const char *this_value; 526 char *property = APR_ARRAY_IDX(autoprops, i, char *); 527 char *equal_sign = strchr(property, '='); 528 529 if (equal_sign) 530 { 531 *equal_sign = '\0'; 532 equal_sign++; 533 trim_string(&equal_sign); 534 unquote_string(&equal_sign); 535 this_value = equal_sign; 536 } 537 else 538 { 539 this_value = ""; 540 } 541 trim_string(&property); 542 len = strlen(property); 543 544 if (len > 0) 545 { 546 apr_hash_t *pattern_hash = svn_hash_gets(autoprops_baton->autoprops, 547 name); 548 svn_string_t *propval; 549 550 /* Force reserved boolean property values to '*'. */ 551 if (svn_prop_is_boolean(property)) 552 { 553 /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */ 554 propval = svn_string_create("*", autoprops_baton->result_pool); 555 } 556 else 557 { 558 propval = svn_string_create(this_value, 559 autoprops_baton->result_pool); 560 } 561 562 if (!pattern_hash) 563 { 564 pattern_hash = apr_hash_make(autoprops_baton->result_pool); 565 svn_hash_sets(autoprops_baton->autoprops, 566 apr_pstrdup(autoprops_baton->result_pool, name), 567 pattern_hash); 568 } 569 svn_hash_sets(pattern_hash, 570 apr_pstrdup(autoprops_baton->result_pool, property), 571 propval->data); 572 } 573 } 574 return TRUE; 575} 576 577/* Go up the directory tree from LOCAL_ABSPATH, looking for a versioned 578 * directory. If found, return its path in *EXISTING_PARENT_ABSPATH. 579 * Otherwise, return SVN_ERR_CLIENT_NO_VERSIONED_PARENT. */ 580static svn_error_t * 581find_existing_parent(const char **existing_parent_abspath, 582 svn_client_ctx_t *ctx, 583 const char *local_abspath, 584 apr_pool_t *result_pool, 585 apr_pool_t *scratch_pool) 586{ 587 svn_node_kind_t kind; 588 const char *parent_abspath; 589 svn_wc_context_t *wc_ctx = ctx->wc_ctx; 590 591 SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, local_abspath, 592 FALSE, FALSE, scratch_pool)); 593 594 if (kind == svn_node_dir) 595 { 596 *existing_parent_abspath = apr_pstrdup(result_pool, local_abspath); 597 return SVN_NO_ERROR; 598 } 599 600 if (svn_dirent_is_root(local_abspath, strlen(local_abspath))) 601 return svn_error_create(SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL, NULL); 602 603 if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, scratch_pool), 604 scratch_pool)) 605 return svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, NULL, 606 _("'%s' ends in a reserved name"), 607 svn_dirent_local_style(local_abspath, 608 scratch_pool)); 609 610 parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 611 612 if (ctx->cancel_func) 613 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 614 615 SVN_ERR(find_existing_parent(existing_parent_abspath, ctx, parent_abspath, 616 result_pool, scratch_pool)); 617 618 return SVN_NO_ERROR; 619} 620 621svn_error_t * 622svn_client__get_all_auto_props(apr_hash_t **autoprops, 623 const char *path_or_url, 624 svn_client_ctx_t *ctx, 625 apr_pool_t *result_pool, 626 apr_pool_t *scratch_pool) 627{ 628 int i; 629 apr_array_header_t *inherited_config_auto_props; 630 apr_hash_t *props; 631 svn_opt_revision_t rev; 632 svn_string_t *config_auto_prop; 633 svn_boolean_t use_autoprops; 634 collect_auto_props_baton_t autoprops_baton; 635 svn_error_t *err = NULL; 636 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 637 svn_boolean_t target_is_url = svn_path_is_url(path_or_url); 638 svn_config_t *cfg = ctx->config ? svn_hash_gets(ctx->config, 639 SVN_CONFIG_CATEGORY_CONFIG) 640 : NULL; 641 *autoprops = apr_hash_make(result_pool); 642 autoprops_baton.result_pool = result_pool; 643 autoprops_baton.autoprops = *autoprops; 644 645 646 /* Are "traditional" auto-props enabled? If so grab them from the 647 config. This is our starting set auto-props, which may be overriden 648 by svn:auto-props. */ 649 SVN_ERR(svn_config_get_bool(cfg, &use_autoprops, 650 SVN_CONFIG_SECTION_MISCELLANY, 651 SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS, FALSE)); 652 if (use_autoprops) 653 svn_config_enumerate2(cfg, SVN_CONFIG_SECTION_AUTO_PROPS, 654 all_auto_props_collector, &autoprops_baton, 655 scratch_pool); 656 657 /* Convert the config file setting (if any) into a hash mapping file 658 patterns to as hash of prop-->val mappings. */ 659 if (svn_path_is_url(path_or_url)) 660 rev.kind = svn_opt_revision_head; 661 else 662 rev.kind = svn_opt_revision_working; 663 664 /* If PATH_OR_URL is a WC path, then it might be unversioned, in which case 665 we find it's nearest versioned parent. */ 666 do 667 { 668 err = svn_client_propget5(&props, &inherited_config_auto_props, 669 SVN_PROP_INHERITABLE_AUTO_PROPS, path_or_url, 670 &rev, &rev, NULL, svn_depth_empty, NULL, ctx, 671 scratch_pool, iterpool); 672 if (err) 673 { 674 if (target_is_url || err->apr_err != SVN_ERR_UNVERSIONED_RESOURCE) 675 return svn_error_trace(err); 676 677 svn_error_clear(err); 678 err = NULL; 679 SVN_ERR(find_existing_parent(&path_or_url, ctx, path_or_url, 680 scratch_pool, iterpool)); 681 } 682 else 683 { 684 break; 685 } 686 } 687 while (err == NULL); 688 689 /* Stash any explicit PROPS for PARENT_PATH into the inherited props array, 690 since these are actually inherited props for LOCAL_ABSPATH. */ 691 config_auto_prop = svn_hash_gets(props, path_or_url); 692 693 if (config_auto_prop) 694 { 695 svn_prop_inherited_item_t *new_iprop = 696 apr_palloc(scratch_pool, sizeof(*new_iprop)); 697 new_iprop->path_or_url = path_or_url; 698 new_iprop->prop_hash = apr_hash_make(scratch_pool); 699 svn_hash_sets(new_iprop->prop_hash, SVN_PROP_INHERITABLE_AUTO_PROPS, 700 config_auto_prop); 701 APR_ARRAY_PUSH(inherited_config_auto_props, 702 svn_prop_inherited_item_t *) = new_iprop; 703 } 704 705 for (i = 0; i < inherited_config_auto_props->nelts; i++) 706 { 707 apr_hash_index_t *hi; 708 svn_prop_inherited_item_t *elt = APR_ARRAY_IDX( 709 inherited_config_auto_props, i, svn_prop_inherited_item_t *); 710 711 for (hi = apr_hash_first(scratch_pool, elt->prop_hash); 712 hi; 713 hi = apr_hash_next(hi)) 714 { 715 const svn_string_t *propval = svn__apr_hash_index_val(hi); 716 const char *ch = propval->data; 717 svn_stringbuf_t *config_auto_prop_pattern; 718 svn_stringbuf_t *config_auto_prop_val; 719 720 svn_pool_clear(iterpool); 721 722 config_auto_prop_pattern = svn_stringbuf_create_empty(iterpool); 723 config_auto_prop_val = svn_stringbuf_create_empty(iterpool); 724 725 /* Parse svn:auto-props value. */ 726 while (*ch != '\0') 727 { 728 svn_stringbuf_setempty(config_auto_prop_pattern); 729 svn_stringbuf_setempty(config_auto_prop_val); 730 731 /* Parse the file pattern. */ 732 while (*ch != '\0' && *ch != '=' && *ch != '\n') 733 { 734 svn_stringbuf_appendbyte(config_auto_prop_pattern, *ch); 735 ch++; 736 } 737 738 svn_stringbuf_strip_whitespace(config_auto_prop_pattern); 739 740 /* Parse the auto-prop group. */ 741 while (*ch != '\0' && *ch != '\n') 742 { 743 svn_stringbuf_appendbyte(config_auto_prop_val, *ch); 744 ch++; 745 } 746 747 /* Strip leading '=' and whitespace from auto-prop group. */ 748 if (config_auto_prop_val->data[0] == '=') 749 svn_stringbuf_remove(config_auto_prop_val, 0, 1); 750 svn_stringbuf_strip_whitespace(config_auto_prop_val); 751 752 all_auto_props_collector(config_auto_prop_pattern->data, 753 config_auto_prop_val->data, 754 &autoprops_baton, 755 scratch_pool); 756 757 /* Skip to next line if any. */ 758 while (*ch != '\0' && *ch != '\n') 759 ch++; 760 if (*ch == '\n') 761 ch++; 762 } 763 } 764 } 765 766 svn_pool_destroy(iterpool); 767 768 return SVN_NO_ERROR; 769} 770 771svn_error_t *svn_client__get_inherited_ignores(apr_array_header_t **ignores, 772 const char *path_or_url, 773 svn_client_ctx_t *ctx, 774 apr_pool_t *result_pool, 775 apr_pool_t *scratch_pool) 776{ 777 svn_opt_revision_t rev; 778 apr_hash_t *explicit_ignores; 779 apr_array_header_t *inherited_ignores; 780 svn_boolean_t target_is_url = svn_path_is_url(path_or_url); 781 svn_string_t *explicit_prop; 782 int i; 783 784 if (target_is_url) 785 rev.kind = svn_opt_revision_head; 786 else 787 rev.kind = svn_opt_revision_working; 788 789 SVN_ERR(svn_client_propget5(&explicit_ignores, &inherited_ignores, 790 SVN_PROP_INHERITABLE_IGNORES, path_or_url, 791 &rev, &rev, NULL, svn_depth_empty, NULL, ctx, 792 scratch_pool, scratch_pool)); 793 794 explicit_prop = svn_hash_gets(explicit_ignores, path_or_url); 795 796 if (explicit_prop) 797 { 798 svn_prop_inherited_item_t *new_iprop = 799 apr_palloc(scratch_pool, sizeof(*new_iprop)); 800 new_iprop->path_or_url = path_or_url; 801 new_iprop->prop_hash = apr_hash_make(scratch_pool); 802 svn_hash_sets(new_iprop->prop_hash, SVN_PROP_INHERITABLE_IGNORES, 803 explicit_prop); 804 APR_ARRAY_PUSH(inherited_ignores, 805 svn_prop_inherited_item_t *) = new_iprop; 806 } 807 808 *ignores = apr_array_make(result_pool, 16, sizeof(const char *)); 809 810 for (i = 0; i < inherited_ignores->nelts; i++) 811 { 812 svn_prop_inherited_item_t *elt = APR_ARRAY_IDX( 813 inherited_ignores, i, svn_prop_inherited_item_t *); 814 svn_string_t *ignore_val = svn_hash_gets(elt->prop_hash, 815 SVN_PROP_INHERITABLE_IGNORES); 816 if (ignore_val) 817 svn_cstring_split_append(*ignores, ignore_val->data, "\n\r\t\v ", 818 FALSE, result_pool); 819 } 820 821 return SVN_NO_ERROR; 822} 823 824/* The main logic of the public svn_client_add5. 825 * 826 * EXISTING_PARENT_ABSPATH is the absolute path to the first existing 827 * parent directory of local_abspath. If not NULL, all missing parents 828 * of LOCAL_ABSPATH must be created before LOCAL_ABSPATH can be added. */ 829static svn_error_t * 830add(const char *local_abspath, 831 svn_depth_t depth, 832 svn_boolean_t force, 833 svn_boolean_t no_ignore, 834 svn_boolean_t no_autoprops, 835 const char *existing_parent_abspath, 836 svn_client_ctx_t *ctx, 837 apr_pool_t *scratch_pool) 838{ 839 svn_node_kind_t kind; 840 svn_error_t *err; 841 svn_magic__cookie_t *magic_cookie; 842 apr_array_header_t *ignores = NULL; 843 844 svn_magic__init(&magic_cookie, scratch_pool); 845 846 if (existing_parent_abspath) 847 { 848 const char *parent_abspath; 849 const char *child_relpath; 850 apr_array_header_t *components; 851 int i; 852 apr_pool_t *iterpool; 853 854 parent_abspath = existing_parent_abspath; 855 child_relpath = svn_dirent_is_child(existing_parent_abspath, 856 local_abspath, NULL); 857 components = svn_path_decompose(child_relpath, scratch_pool); 858 iterpool = svn_pool_create(scratch_pool); 859 for (i = 0; i < components->nelts - 1; i++) 860 { 861 const char *component; 862 svn_node_kind_t disk_kind; 863 864 svn_pool_clear(iterpool); 865 866 if (ctx->cancel_func) 867 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 868 869 component = APR_ARRAY_IDX(components, i, const char *); 870 parent_abspath = svn_dirent_join(parent_abspath, component, 871 scratch_pool); 872 SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, iterpool)); 873 if (disk_kind != svn_node_none && disk_kind != svn_node_dir) 874 return svn_error_createf(SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL, 875 _("'%s' prevents creating parent of '%s'"), 876 parent_abspath, local_abspath); 877 878 SVN_ERR(svn_io_make_dir_recursively(parent_abspath, scratch_pool)); 879 SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, parent_abspath, 880 NULL /*props*/, 881 ctx->notify_func2, ctx->notify_baton2, 882 scratch_pool)); 883 } 884 svn_pool_destroy(iterpool); 885 } 886 887 SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool)); 888 if (kind == svn_node_dir) 889 { 890 /* We use add_dir_recursive for all directory targets 891 and pass depth along no matter what it is, so that the 892 target's depth will be set correctly. */ 893 err = add_dir_recursive(local_abspath, depth, force, 894 no_autoprops, magic_cookie, NULL, 895 !no_ignore, ignores, ctx, 896 scratch_pool, scratch_pool); 897 } 898 else if (kind == svn_node_file) 899 err = add_file(local_abspath, magic_cookie, NULL, 900 no_autoprops, ctx, scratch_pool); 901 else if (kind == svn_node_none) 902 { 903 svn_boolean_t tree_conflicted; 904 905 /* Provide a meaningful error message if the node does not exist 906 * on disk but is a tree conflict victim. */ 907 err = svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted, 908 ctx->wc_ctx, local_abspath, 909 scratch_pool); 910 if (err) 911 svn_error_clear(err); 912 else if (tree_conflicted) 913 return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL, 914 _("'%s' is an existing item in conflict; " 915 "please mark the conflict as resolved " 916 "before adding a new item here"), 917 svn_dirent_local_style(local_abspath, 918 scratch_pool)); 919 920 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 921 _("'%s' not found"), 922 svn_dirent_local_style(local_abspath, 923 scratch_pool)); 924 } 925 else 926 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 927 _("Unsupported node kind for path '%s'"), 928 svn_dirent_local_style(local_abspath, 929 scratch_pool)); 930 931 /* Ignore SVN_ERR_ENTRY_EXISTS when FORCE is set. */ 932 if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force) 933 { 934 svn_error_clear(err); 935 err = SVN_NO_ERROR; 936 } 937 return svn_error_trace(err); 938} 939 940 941 942svn_error_t * 943svn_client_add5(const char *path, 944 svn_depth_t depth, 945 svn_boolean_t force, 946 svn_boolean_t no_ignore, 947 svn_boolean_t no_autoprops, 948 svn_boolean_t add_parents, 949 svn_client_ctx_t *ctx, 950 apr_pool_t *scratch_pool) 951{ 952 const char *parent_abspath; 953 const char *local_abspath; 954 const char *existing_parent_abspath; 955 svn_boolean_t is_wc_root; 956 svn_error_t *err; 957 958 if (svn_path_is_url(path)) 959 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 960 _("'%s' is not a local path"), path); 961 962 SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool)); 963 964 /* See if we're being asked to add a wc-root. That's typically not 965 okay, unless we're in "force" mode. svn_wc__is_wcroot() 966 will return TRUE even if LOCAL_ABSPATH is a *symlink* to a working 967 copy root, which is a scenario we want to treat differently. */ 968 err = svn_wc__is_wcroot(&is_wc_root, ctx->wc_ctx, local_abspath, 969 scratch_pool); 970 if (err) 971 { 972 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND 973 && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) 974 { 975 return svn_error_trace(err); 976 } 977 978 svn_error_clear(err); 979 err = NULL; /* SVN_NO_ERROR */ 980 is_wc_root = FALSE; 981 } 982 if (is_wc_root) 983 { 984#ifdef HAVE_SYMLINK 985 svn_node_kind_t disk_kind; 986 svn_boolean_t is_special; 987 988 SVN_ERR(svn_io_check_special_path(local_abspath, &disk_kind, &is_special, 989 scratch_pool)); 990 991 /* A symlink can be an unversioned target and a wcroot. Lets try to add 992 the symlink, which can't be a wcroot. */ 993 if (is_special) 994 is_wc_root = FALSE; 995 else 996#endif 997 { 998 if (! force) 999 return svn_error_createf( 1000 SVN_ERR_ENTRY_EXISTS, NULL, 1001 _("'%s' is already under version control"), 1002 svn_dirent_local_style(local_abspath, 1003 scratch_pool)); 1004 } 1005 } 1006 1007 if (is_wc_root) 1008 parent_abspath = local_abspath; /* We will only add children */ 1009 else 1010 parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); 1011 1012 existing_parent_abspath = NULL; 1013 if (add_parents && !is_wc_root) 1014 { 1015 apr_pool_t *subpool; 1016 const char *existing_parent_abspath2; 1017 1018 subpool = svn_pool_create(scratch_pool); 1019 SVN_ERR(find_existing_parent(&existing_parent_abspath2, ctx, 1020 parent_abspath, scratch_pool, subpool)); 1021 if (strcmp(existing_parent_abspath2, parent_abspath) != 0) 1022 existing_parent_abspath = existing_parent_abspath2; 1023 svn_pool_destroy(subpool); 1024 } 1025 1026 SVN_WC__CALL_WITH_WRITE_LOCK( 1027 add(local_abspath, depth, force, no_ignore, no_autoprops, 1028 existing_parent_abspath, ctx, scratch_pool), 1029 ctx->wc_ctx, (existing_parent_abspath ? existing_parent_abspath 1030 : parent_abspath), 1031 FALSE /* lock_anchor */, scratch_pool); 1032 return SVN_NO_ERROR; 1033} 1034 1035 1036static svn_error_t * 1037path_driver_cb_func(void **dir_baton, 1038 void *parent_baton, 1039 void *callback_baton, 1040 const char *path, 1041 apr_pool_t *pool) 1042{ 1043 const svn_delta_editor_t *editor = callback_baton; 1044 SVN_ERR(svn_path_check_valid(path, pool)); 1045 return editor->add_directory(path, parent_baton, NULL, 1046 SVN_INVALID_REVNUM, pool, dir_baton); 1047} 1048 1049/* Append URL, and all it's non-existent parent directories, to TARGETS. 1050 Use TEMPPOOL for temporary allocations and POOL for any additions to 1051 TARGETS. */ 1052static svn_error_t * 1053add_url_parents(svn_ra_session_t *ra_session, 1054 const char *url, 1055 apr_array_header_t *targets, 1056 apr_pool_t *temppool, 1057 apr_pool_t *pool) 1058{ 1059 svn_node_kind_t kind; 1060 const char *parent_url = svn_uri_dirname(url, pool); 1061 1062 SVN_ERR(svn_ra_reparent(ra_session, parent_url, temppool)); 1063 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind, 1064 temppool)); 1065 1066 if (kind == svn_node_none) 1067 SVN_ERR(add_url_parents(ra_session, parent_url, targets, temppool, pool)); 1068 1069 APR_ARRAY_PUSH(targets, const char *) = url; 1070 1071 return SVN_NO_ERROR; 1072} 1073 1074static svn_error_t * 1075mkdir_urls(const apr_array_header_t *urls, 1076 svn_boolean_t make_parents, 1077 const apr_hash_t *revprop_table, 1078 svn_commit_callback2_t commit_callback, 1079 void *commit_baton, 1080 svn_client_ctx_t *ctx, 1081 apr_pool_t *pool) 1082{ 1083 svn_ra_session_t *ra_session = NULL; 1084 const svn_delta_editor_t *editor; 1085 void *edit_baton; 1086 const char *log_msg; 1087 apr_array_header_t *targets; 1088 apr_hash_t *targets_hash; 1089 apr_hash_t *commit_revprops; 1090 svn_error_t *err; 1091 const char *common; 1092 int i; 1093 1094 /* Find any non-existent parent directories */ 1095 if (make_parents) 1096 { 1097 apr_array_header_t *all_urls = apr_array_make(pool, urls->nelts, 1098 sizeof(const char *)); 1099 const char *first_url = APR_ARRAY_IDX(urls, 0, const char *); 1100 apr_pool_t *iterpool = svn_pool_create(pool); 1101 1102 SVN_ERR(svn_client_open_ra_session2(&ra_session, first_url, NULL, 1103 ctx, pool, iterpool)); 1104 1105 for (i = 0; i < urls->nelts; i++) 1106 { 1107 const char *url = APR_ARRAY_IDX(urls, i, const char *); 1108 1109 svn_pool_clear(iterpool); 1110 SVN_ERR(add_url_parents(ra_session, url, all_urls, iterpool, pool)); 1111 } 1112 1113 svn_pool_destroy(iterpool); 1114 1115 urls = all_urls; 1116 } 1117 1118 /* Condense our list of mkdir targets. */ 1119 SVN_ERR(svn_uri_condense_targets(&common, &targets, urls, FALSE, 1120 pool, pool)); 1121 1122 /*Remove duplicate targets introduced by make_parents with more targets. */ 1123 SVN_ERR(svn_hash_from_cstring_keys(&targets_hash, targets, pool)); 1124 SVN_ERR(svn_hash_keys(&targets, targets_hash, pool)); 1125 1126 if (! targets->nelts) 1127 { 1128 const char *bname; 1129 svn_uri_split(&common, &bname, common, pool); 1130 APR_ARRAY_PUSH(targets, const char *) = bname; 1131 1132 if (*bname == '\0') 1133 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 1134 _("There is no valid URI above '%s'"), 1135 common); 1136 } 1137 else 1138 { 1139 svn_boolean_t resplit = FALSE; 1140 1141 /* We can't "mkdir" the root of an editor drive, so if one of 1142 our targets is the empty string, we need to back everything 1143 up by a path component. */ 1144 for (i = 0; i < targets->nelts; i++) 1145 { 1146 const char *path = APR_ARRAY_IDX(targets, i, const char *); 1147 if (! *path) 1148 { 1149 resplit = TRUE; 1150 break; 1151 } 1152 } 1153 if (resplit) 1154 { 1155 const char *bname; 1156 1157 svn_uri_split(&common, &bname, common, pool); 1158 1159 if (*bname == '\0') 1160 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 1161 _("There is no valid URI above '%s'"), 1162 common); 1163 1164 for (i = 0; i < targets->nelts; i++) 1165 { 1166 const char *path = APR_ARRAY_IDX(targets, i, const char *); 1167 path = svn_relpath_join(bname, path, pool); 1168 APR_ARRAY_IDX(targets, i, const char *) = path; 1169 } 1170 } 1171 } 1172 qsort(targets->elts, targets->nelts, targets->elt_size, 1173 svn_sort_compare_paths); 1174 1175 /* ### This reparent may be problematic in limited-authz-to-common-parent 1176 ### scenarios (compare issue #3242). See also issue #3649. */ 1177 if (ra_session) 1178 SVN_ERR(svn_ra_reparent(ra_session, common, pool)); 1179 1180 /* Create new commit items and add them to the array. */ 1181 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) 1182 { 1183 svn_client_commit_item3_t *item; 1184 const char *tmp_file; 1185 apr_array_header_t *commit_items 1186 = apr_array_make(pool, targets->nelts, sizeof(item)); 1187 1188 for (i = 0; i < targets->nelts; i++) 1189 { 1190 const char *path = APR_ARRAY_IDX(targets, i, const char *); 1191 1192 item = svn_client_commit_item3_create(pool); 1193 item->url = svn_path_url_add_component2(common, path, pool); 1194 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; 1195 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 1196 } 1197 1198 SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items, 1199 ctx, pool)); 1200 1201 if (! log_msg) 1202 return SVN_NO_ERROR; 1203 } 1204 else 1205 log_msg = ""; 1206 1207 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 1208 log_msg, ctx, pool)); 1209 1210 /* Open an RA session for the URL. Note that we don't have a local 1211 directory, nor a place to put temp files. */ 1212 if (!ra_session) 1213 SVN_ERR(svn_client_open_ra_session2(&ra_session, common, NULL, ctx, 1214 pool, pool)); 1215 else 1216 SVN_ERR(svn_ra_reparent(ra_session, common, pool)); 1217 1218 1219 /* Fetch RA commit editor */ 1220 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session, 1221 svn_client__get_shim_callbacks(ctx->wc_ctx, NULL, 1222 pool))); 1223 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton, 1224 commit_revprops, 1225 commit_callback, 1226 commit_baton, 1227 NULL, TRUE, /* No lock tokens */ 1228 pool)); 1229 1230 /* Call the path-based editor driver. */ 1231 err = svn_delta_path_driver2(editor, edit_baton, targets, TRUE, 1232 path_driver_cb_func, (void *)editor, pool); 1233 1234 if (err) 1235 { 1236 /* At least try to abort the edit (and fs txn) before throwing err. */ 1237 return svn_error_compose_create( 1238 err, 1239 editor->abort_edit(edit_baton, pool)); 1240 } 1241 1242 /* Close the edit. */ 1243 return editor->close_edit(edit_baton, pool); 1244} 1245 1246 1247 1248svn_error_t * 1249svn_client__make_local_parents(const char *path, 1250 svn_boolean_t make_parents, 1251 svn_client_ctx_t *ctx, 1252 apr_pool_t *pool) 1253{ 1254 svn_error_t *err; 1255 svn_node_kind_t orig_kind; 1256 SVN_ERR(svn_io_check_path(path, &orig_kind, pool)); 1257 if (make_parents) 1258 SVN_ERR(svn_io_make_dir_recursively(path, pool)); 1259 else 1260 SVN_ERR(svn_io_dir_make(path, APR_OS_DEFAULT, pool)); 1261 1262 /* Should no longer use svn_depth_empty to indicate that only the directory 1263 itself is added, since it not only constraints the operation depth, but 1264 also defines the depth of the target directory now. Moreover, the new 1265 directory will have no children at all.*/ 1266 err = svn_client_add5(path, svn_depth_infinity, FALSE, FALSE, FALSE, 1267 make_parents, ctx, pool); 1268 1269 /* If we created a new directory, but couldn't add it to version 1270 control, then delete it. */ 1271 if (err && (orig_kind == svn_node_none)) 1272 { 1273 /* ### If this returns an error, should we link it onto 1274 err instead, so that the user is warned that we just 1275 created an unversioned directory? */ 1276 1277 svn_error_clear(svn_io_remove_dir2(path, FALSE, NULL, NULL, pool)); 1278 } 1279 1280 return svn_error_trace(err); 1281} 1282 1283 1284svn_error_t * 1285svn_client_mkdir4(const apr_array_header_t *paths, 1286 svn_boolean_t make_parents, 1287 const apr_hash_t *revprop_table, 1288 svn_commit_callback2_t commit_callback, 1289 void *commit_baton, 1290 svn_client_ctx_t *ctx, 1291 apr_pool_t *pool) 1292{ 1293 if (! paths->nelts) 1294 return SVN_NO_ERROR; 1295 1296 SVN_ERR(svn_client__assert_homogeneous_target_type(paths)); 1297 1298 if (svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *))) 1299 { 1300 SVN_ERR(mkdir_urls(paths, make_parents, revprop_table, commit_callback, 1301 commit_baton, ctx, pool)); 1302 } 1303 else 1304 { 1305 /* This is a regular "mkdir" + "svn add" */ 1306 apr_pool_t *subpool = svn_pool_create(pool); 1307 int i; 1308 1309 for (i = 0; i < paths->nelts; i++) 1310 { 1311 const char *path = APR_ARRAY_IDX(paths, i, const char *); 1312 1313 svn_pool_clear(subpool); 1314 1315 /* See if the user wants us to stop. */ 1316 if (ctx->cancel_func) 1317 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 1318 1319 SVN_ERR(svn_client__make_local_parents(path, make_parents, ctx, 1320 subpool)); 1321 } 1322 svn_pool_destroy(subpool); 1323 } 1324 1325 return SVN_NO_ERROR; 1326} 1327