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