1 /*
2 * import.c: wrappers around import 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_strings.h>
32 #include <apr_hash.h>
33 #include <apr_md5.h>
34
35 #include "svn_hash.h"
36 #include "svn_ra.h"
37 #include "svn_delta.h"
38 #include "svn_subst.h"
39 #include "svn_client.h"
40 #include "svn_string.h"
41 #include "svn_pools.h"
42 #include "svn_error_codes.h"
43 #include "svn_dirent_uri.h"
44 #include "svn_path.h"
45 #include "svn_io.h"
46 #include "svn_sorts.h"
47 #include "svn_props.h"
48
49 #include "client.h"
50 #include "private/svn_ra_private.h"
51 #include "private/svn_sorts_private.h"
52 #include "private/svn_subr_private.h"
53 #include "private/svn_magic.h"
54
55 #include "svn_private_config.h"
56
57 /* Import context baton. */
58
59 typedef struct import_ctx_t
60 {
61 /* Whether any changes were made to the repository */
62 svn_boolean_t repos_changed;
63
64 /* A magic cookie for mime-type detection. */
65 svn_magic__cookie_t *magic_cookie;
66
67 /* Collection of all possible configuration file dictated auto-props and
68 svn:auto-props. A hash mapping const char * file patterns to a
69 second hash which maps const char * property names to const char *
70 property values. Properties which don't have a value, e.g.
71 svn:executable, simply map the property name to an empty string.
72 May be NULL if autoprops are disabled. */
73 apr_hash_t *autoprops;
74 } import_ctx_t;
75
76 typedef struct open_txdelta_stream_baton_t
77 {
78 svn_boolean_t need_reset;
79 svn_stream_t *stream;
80 } open_txdelta_stream_baton_t;
81
82 /* Implements svn_txdelta_stream_open_func_t */
83 static svn_error_t *
open_txdelta_stream(svn_txdelta_stream_t ** txdelta_stream_p,void * baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)84 open_txdelta_stream(svn_txdelta_stream_t **txdelta_stream_p,
85 void *baton,
86 apr_pool_t *result_pool,
87 apr_pool_t *scratch_pool)
88 {
89 open_txdelta_stream_baton_t *b = baton;
90
91 if (b->need_reset)
92 {
93 /* Under rare circumstances, we can be restarted and would need to
94 * supply the delta stream again. In this case, reset the base
95 * stream. */
96 SVN_ERR(svn_stream_reset(b->stream));
97 }
98
99 /* Get the delta stream (delta against the empty string). */
100 svn_txdelta2(txdelta_stream_p, svn_stream_empty(result_pool),
101 b->stream, FALSE, result_pool);
102 b->need_reset = TRUE;
103 return SVN_NO_ERROR;
104 }
105
106 /* Apply LOCAL_ABSPATH's contents (as a delta against the empty string) to
107 FILE_BATON in EDITOR. Use POOL for any temporary allocation.
108 PROPERTIES is the set of node properties set on this file.
109
110 Return the resulting checksum in *RESULT_MD5_CHECKSUM_P. */
111
112 /* ### how does this compare against svn_wc_transmit_text_deltas2() ??? */
113
114 static svn_error_t *
send_file_contents(svn_checksum_t ** result_md5_checksum_p,const char * local_abspath,void * file_baton,const svn_delta_editor_t * editor,apr_hash_t * properties,apr_pool_t * pool)115 send_file_contents(svn_checksum_t **result_md5_checksum_p,
116 const char *local_abspath,
117 void *file_baton,
118 const svn_delta_editor_t *editor,
119 apr_hash_t *properties,
120 apr_pool_t *pool)
121 {
122 svn_stream_t *contents;
123 const svn_string_t *eol_style_val = NULL, *keywords_val = NULL;
124 svn_boolean_t special = FALSE;
125 svn_subst_eol_style_t eol_style;
126 const char *eol;
127 apr_hash_t *keywords;
128 open_txdelta_stream_baton_t baton = { 0 };
129
130 /* If there are properties, look for EOL-style and keywords ones. */
131 if (properties)
132 {
133 eol_style_val = apr_hash_get(properties, SVN_PROP_EOL_STYLE,
134 sizeof(SVN_PROP_EOL_STYLE) - 1);
135 keywords_val = apr_hash_get(properties, SVN_PROP_KEYWORDS,
136 sizeof(SVN_PROP_KEYWORDS) - 1);
137 if (svn_hash_gets(properties, SVN_PROP_SPECIAL))
138 special = TRUE;
139 }
140
141 if (eol_style_val)
142 svn_subst_eol_style_from_value(&eol_style, &eol, eol_style_val->data);
143 else
144 {
145 eol = NULL;
146 eol_style = svn_subst_eol_style_none;
147 }
148
149 if (keywords_val)
150 SVN_ERR(svn_subst_build_keywords3(&keywords, keywords_val->data,
151 APR_STRINGIFY(SVN_INVALID_REVNUM),
152 "", "", 0, "", pool));
153 else
154 keywords = NULL;
155
156 if (special)
157 {
158 SVN_ERR(svn_subst_read_specialfile(&contents, local_abspath,
159 pool, pool));
160 }
161 else
162 {
163 /* Open the working copy file. */
164 SVN_ERR(svn_stream_open_readonly(&contents, local_abspath, pool, pool));
165
166 /* If we have EOL styles or keywords, then detranslate the file. */
167 if (svn_subst_translation_required(eol_style, eol, keywords,
168 FALSE, TRUE))
169 {
170 if (eol_style == svn_subst_eol_style_unknown)
171 return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
172 _("%s property on '%s' contains "
173 "unrecognized EOL-style '%s'"),
174 SVN_PROP_EOL_STYLE,
175 svn_dirent_local_style(local_abspath,
176 pool),
177 eol_style_val->data);
178
179 /* We're importing, so translate files with 'native' eol-style to
180 * repository-normal form, not to this platform's native EOL. */
181 if (eol_style == svn_subst_eol_style_native)
182 eol = SVN_SUBST_NATIVE_EOL_STR;
183
184 /* Wrap the working copy stream with a filter to detranslate it. */
185 contents = svn_subst_stream_translated(contents,
186 eol,
187 TRUE /* repair */,
188 keywords,
189 FALSE /* expand */,
190 pool);
191 }
192 }
193
194 /* Arrange the stream to calculate the resulting MD5. */
195 contents = svn_stream_checksummed2(contents, result_md5_checksum_p, NULL,
196 svn_checksum_md5, TRUE, pool);
197 /* Send the contents. */
198 baton.need_reset = FALSE;
199 baton.stream = svn_stream_disown(contents, pool);
200 SVN_ERR(editor->apply_textdelta_stream(editor, file_baton, NULL,
201 open_txdelta_stream, &baton, pool));
202 SVN_ERR(svn_stream_close(contents));
203
204 return SVN_NO_ERROR;
205 }
206
207
208 /* Import file PATH as EDIT_PATH in the repository directory indicated
209 * by DIR_BATON in EDITOR.
210 *
211 * Accumulate file paths and their batons in FILES, which must be
212 * non-null. (These are used to send postfix textdeltas later).
213 *
214 * If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON
215 * for each file.
216 *
217 * Use POOL for any temporary allocation.
218 */
219 static svn_error_t *
import_file(const svn_delta_editor_t * editor,void * dir_baton,const char * local_abspath,const char * edit_path,const svn_io_dirent2_t * dirent,import_ctx_t * import_ctx,svn_client_ctx_t * ctx,apr_pool_t * pool)220 import_file(const svn_delta_editor_t *editor,
221 void *dir_baton,
222 const char *local_abspath,
223 const char *edit_path,
224 const svn_io_dirent2_t *dirent,
225 import_ctx_t *import_ctx,
226 svn_client_ctx_t *ctx,
227 apr_pool_t *pool)
228 {
229 void *file_baton;
230 const char *mimetype = NULL;
231 svn_checksum_t *result_md5_checksum;
232 const char *text_checksum;
233 apr_hash_t* properties;
234 apr_hash_index_t *hi;
235
236 SVN_ERR(svn_path_check_valid(local_abspath, pool));
237
238 /* Add the file, using the pool from the FILES hash. */
239 SVN_ERR(editor->add_file(edit_path, dir_baton, NULL, SVN_INVALID_REVNUM,
240 pool, &file_baton));
241
242 /* Remember that the repository was modified */
243 import_ctx->repos_changed = TRUE;
244
245 if (! dirent->special)
246 {
247 /* add automatic properties */
248 SVN_ERR(svn_client__get_paths_auto_props(&properties, &mimetype,
249 local_abspath,
250 import_ctx->magic_cookie,
251 import_ctx->autoprops,
252 ctx, pool, pool));
253 }
254 else
255 properties = apr_hash_make(pool);
256
257 if (properties)
258 {
259 for (hi = apr_hash_first(pool, properties); hi; hi = apr_hash_next(hi))
260 {
261 const char *pname = apr_hash_this_key(hi);
262 const svn_string_t *pval = apr_hash_this_val(hi);
263
264 SVN_ERR(editor->change_file_prop(file_baton, pname, pval, pool));
265 }
266 }
267
268 if (ctx->notify_func2)
269 {
270 svn_wc_notify_t *notify
271 = svn_wc_create_notify(local_abspath, svn_wc_notify_commit_added,
272 pool);
273 notify->kind = svn_node_file;
274 notify->mime_type = mimetype;
275 notify->content_state = notify->prop_state
276 = svn_wc_notify_state_inapplicable;
277 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
278 ctx->notify_func2(ctx->notify_baton2, notify, pool);
279 }
280
281 /* If this is a special file, we need to set the svn:special
282 property and create a temporary detranslated version in order to
283 send to the server. */
284 if (dirent->special)
285 {
286 svn_hash_sets(properties, SVN_PROP_SPECIAL,
287 svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool));
288 SVN_ERR(editor->change_file_prop(file_baton, SVN_PROP_SPECIAL,
289 svn_hash_gets(properties,
290 SVN_PROP_SPECIAL),
291 pool));
292 }
293
294 /* Now, transmit the file contents. */
295 SVN_ERR(send_file_contents(&result_md5_checksum, local_abspath,
296 file_baton, editor, properties, pool));
297
298 /* Finally, close the file. */
299 text_checksum = svn_checksum_to_cstring(result_md5_checksum, pool);
300 return svn_error_trace(editor->close_file(file_baton, text_checksum, pool));
301 }
302
303
304 /* Return in CHILDREN a mapping of basenames to dirents for the importable
305 * children of DIR_ABSPATH. EXCLUDES is a hash of absolute paths to filter
306 * out. IGNORES and GLOBAL_IGNORES, if non-NULL, are lists of basename
307 * patterns to filter out.
308 * FILTER_CALLBACK and FILTER_BATON will be called for each absolute path,
309 * allowing users to further filter the list of returned entries.
310 *
311 * Results are returned in RESULT_POOL; use SCRATCH_POOL for temporary data.*/
312 static svn_error_t *
get_filtered_children(apr_hash_t ** children,const char * dir_abspath,apr_hash_t * excludes,apr_array_header_t * ignores,apr_array_header_t * global_ignores,svn_client_import_filter_func_t filter_callback,void * filter_baton,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)313 get_filtered_children(apr_hash_t **children,
314 const char *dir_abspath,
315 apr_hash_t *excludes,
316 apr_array_header_t *ignores,
317 apr_array_header_t *global_ignores,
318 svn_client_import_filter_func_t filter_callback,
319 void *filter_baton,
320 svn_client_ctx_t *ctx,
321 apr_pool_t *result_pool,
322 apr_pool_t *scratch_pool)
323 {
324 apr_hash_t *dirents;
325 apr_hash_index_t *hi;
326 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
327
328 SVN_ERR(svn_io_get_dirents3(&dirents, dir_abspath, TRUE, result_pool,
329 scratch_pool));
330
331 for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
332 {
333 const char *base_name = apr_hash_this_key(hi);
334 const svn_io_dirent2_t *dirent = apr_hash_this_val(hi);
335 const char *local_abspath;
336
337 svn_pool_clear(iterpool);
338
339 local_abspath = svn_dirent_join(dir_abspath, base_name, iterpool);
340
341 if (svn_wc_is_adm_dir(base_name, iterpool))
342 {
343 /* If someone's trying to import a directory named the same
344 as our administrative directories, that's probably not
345 what they wanted to do. If they are importing a file
346 with that name, something is bound to blow up when they
347 checkout what they've imported. So, just skip items with
348 that name. */
349 if (ctx->notify_func2)
350 {
351 svn_wc_notify_t *notify
352 = svn_wc_create_notify(svn_dirent_join(local_abspath, base_name,
353 iterpool),
354 svn_wc_notify_skip, iterpool);
355 notify->kind = svn_node_dir;
356 notify->content_state = notify->prop_state
357 = svn_wc_notify_state_inapplicable;
358 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
359 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
360 }
361
362 svn_hash_sets(dirents, base_name, NULL);
363 continue;
364 }
365 /* If this is an excluded path, exclude it. */
366 if (svn_hash_gets(excludes, local_abspath))
367 {
368 svn_hash_sets(dirents, base_name, NULL);
369 continue;
370 }
371
372 if (ignores && svn_wc_match_ignore_list(base_name, ignores, iterpool))
373 {
374 svn_hash_sets(dirents, base_name, NULL);
375 continue;
376 }
377
378 if (global_ignores &&
379 svn_wc_match_ignore_list(base_name, global_ignores, iterpool))
380 {
381 svn_hash_sets(dirents, base_name, NULL);
382 continue;
383 }
384
385 if (filter_callback)
386 {
387 svn_boolean_t filter = FALSE;
388
389 SVN_ERR(filter_callback(filter_baton, &filter, local_abspath,
390 dirent, iterpool));
391
392 if (filter)
393 {
394 svn_hash_sets(dirents, base_name, NULL);
395 continue;
396 }
397 }
398 }
399 svn_pool_destroy(iterpool);
400
401 *children = dirents;
402 return SVN_NO_ERROR;
403 }
404
405 static svn_error_t *
406 import_dir(const svn_delta_editor_t *editor,
407 void *dir_baton,
408 const char *local_abspath,
409 const char *edit_path,
410 svn_depth_t depth,
411 apr_hash_t *excludes,
412 apr_array_header_t *global_ignores,
413 svn_boolean_t no_ignore,
414 svn_boolean_t no_autoprops,
415 svn_boolean_t ignore_unknown_node_types,
416 svn_client_import_filter_func_t filter_callback,
417 void *filter_baton,
418 import_ctx_t *import_ctx,
419 svn_client_ctx_t *ctx,
420 apr_pool_t *pool);
421
422
423 /* Import the children of DIR_ABSPATH, with other arguments similar to
424 * import_dir(). */
425 static svn_error_t *
import_children(const char * dir_abspath,const char * edit_path,apr_hash_t * dirents,const svn_delta_editor_t * editor,void * dir_baton,svn_depth_t depth,apr_hash_t * excludes,apr_array_header_t * global_ignores,svn_boolean_t no_ignore,svn_boolean_t no_autoprops,svn_boolean_t ignore_unknown_node_types,svn_client_import_filter_func_t filter_callback,void * filter_baton,import_ctx_t * import_ctx,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)426 import_children(const char *dir_abspath,
427 const char *edit_path,
428 apr_hash_t *dirents,
429 const svn_delta_editor_t *editor,
430 void *dir_baton,
431 svn_depth_t depth,
432 apr_hash_t *excludes,
433 apr_array_header_t *global_ignores,
434 svn_boolean_t no_ignore,
435 svn_boolean_t no_autoprops,
436 svn_boolean_t ignore_unknown_node_types,
437 svn_client_import_filter_func_t filter_callback,
438 void *filter_baton,
439 import_ctx_t *import_ctx,
440 svn_client_ctx_t *ctx,
441 apr_pool_t *scratch_pool)
442 {
443 apr_array_header_t *sorted_dirents;
444 int i;
445 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
446
447 sorted_dirents = svn_sort__hash(dirents, svn_sort_compare_items_lexically,
448 scratch_pool);
449 for (i = 0; i < sorted_dirents->nelts; i++)
450 {
451 const char *this_abspath, *this_edit_path;
452 svn_sort__item_t item = APR_ARRAY_IDX(sorted_dirents, i,
453 svn_sort__item_t);
454 const char *filename = item.key;
455 const svn_io_dirent2_t *dirent = item.value;
456
457 svn_pool_clear(iterpool);
458
459 if (ctx->cancel_func)
460 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
461
462 /* Typically, we started importing from ".", in which case
463 edit_path is "". So below, this_path might become "./blah",
464 and this_edit_path might become "blah", for example. */
465 this_abspath = svn_dirent_join(dir_abspath, filename, iterpool);
466 this_edit_path = svn_relpath_join(edit_path, filename, iterpool);
467
468 if (dirent->kind == svn_node_dir && depth >= svn_depth_immediates)
469 {
470 /* Recurse. */
471 svn_depth_t depth_below_here = depth;
472 if (depth == svn_depth_immediates)
473 depth_below_here = svn_depth_empty;
474
475 SVN_ERR(import_dir(editor, dir_baton, this_abspath,
476 this_edit_path, depth_below_here, excludes,
477 global_ignores, no_ignore, no_autoprops,
478 ignore_unknown_node_types, filter_callback,
479 filter_baton, import_ctx, ctx, iterpool));
480 }
481 else if (dirent->kind == svn_node_file && depth >= svn_depth_files)
482 {
483 SVN_ERR(import_file(editor, dir_baton, this_abspath,
484 this_edit_path, dirent,
485 import_ctx, ctx, iterpool));
486 }
487 else if (dirent->kind != svn_node_dir && dirent->kind != svn_node_file)
488 {
489 if (ignore_unknown_node_types)
490 {
491 /*## warn about it*/
492 if (ctx->notify_func2)
493 {
494 svn_wc_notify_t *notify
495 = svn_wc_create_notify(this_abspath,
496 svn_wc_notify_skip, iterpool);
497 notify->kind = svn_node_dir;
498 notify->content_state = notify->prop_state
499 = svn_wc_notify_state_inapplicable;
500 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
501 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
502 }
503 }
504 else
505 return svn_error_createf
506 (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
507 _("Unknown or unversionable type for '%s'"),
508 svn_dirent_local_style(this_abspath, iterpool));
509 }
510 }
511
512 svn_pool_destroy(iterpool);
513 return SVN_NO_ERROR;
514 }
515
516
517 /* Import directory LOCAL_ABSPATH into the repository directory indicated by
518 * DIR_BATON in EDITOR. EDIT_PATH is the path imported as the root
519 * directory, so all edits are relative to that.
520 *
521 * DEPTH is the depth at this point in the descent (it may be changed
522 * for recursive calls).
523 *
524 * Accumulate file paths and their batons in FILES, which must be
525 * non-null. (These are used to send postfix textdeltas later).
526 *
527 * EXCLUDES is a hash whose keys are absolute paths to exclude from
528 * the import (values are unused).
529 *
530 * GLOBAL_IGNORES is an array of const char * ignore patterns. Any child
531 * of LOCAL_ABSPATH which matches one or more of the patterns is not imported.
532 *
533 * If NO_IGNORE is FALSE, don't import files or directories that match
534 * ignore patterns.
535 *
536 * If FILTER_CALLBACK is not NULL, call it with FILTER_BATON on each to be
537 * imported node below LOCAL_ABSPATH to allow filtering nodes.
538 *
539 * If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON for each
540 * directory.
541 *
542 * Use POOL for any temporary allocation. */
543 static svn_error_t *
import_dir(const svn_delta_editor_t * editor,void * dir_baton,const char * local_abspath,const char * edit_path,svn_depth_t depth,apr_hash_t * excludes,apr_array_header_t * global_ignores,svn_boolean_t no_ignore,svn_boolean_t no_autoprops,svn_boolean_t ignore_unknown_node_types,svn_client_import_filter_func_t filter_callback,void * filter_baton,import_ctx_t * import_ctx,svn_client_ctx_t * ctx,apr_pool_t * pool)544 import_dir(const svn_delta_editor_t *editor,
545 void *dir_baton,
546 const char *local_abspath,
547 const char *edit_path,
548 svn_depth_t depth,
549 apr_hash_t *excludes,
550 apr_array_header_t *global_ignores,
551 svn_boolean_t no_ignore,
552 svn_boolean_t no_autoprops,
553 svn_boolean_t ignore_unknown_node_types,
554 svn_client_import_filter_func_t filter_callback,
555 void *filter_baton,
556 import_ctx_t *import_ctx,
557 svn_client_ctx_t *ctx,
558 apr_pool_t *pool)
559 {
560 apr_hash_t *dirents;
561 void *this_dir_baton;
562
563 SVN_ERR(svn_path_check_valid(local_abspath, pool));
564 SVN_ERR(get_filtered_children(&dirents, local_abspath, excludes, NULL,
565 global_ignores, filter_callback,
566 filter_baton, ctx, pool, pool));
567
568 /* Import this directory, but not yet its children. */
569 {
570 /* Add the new subdirectory, getting a descent baton from the editor. */
571 SVN_ERR(editor->add_directory(edit_path, dir_baton, NULL,
572 SVN_INVALID_REVNUM, pool, &this_dir_baton));
573
574 /* Remember that the repository was modified */
575 import_ctx->repos_changed = TRUE;
576
577 /* By notifying before the recursive call below, we display
578 a directory add before displaying adds underneath the
579 directory. To do it the other way around, just move this
580 after the recursive call. */
581 if (ctx->notify_func2)
582 {
583 svn_wc_notify_t *notify
584 = svn_wc_create_notify(local_abspath, svn_wc_notify_commit_added,
585 pool);
586 notify->kind = svn_node_dir;
587 notify->content_state = notify->prop_state
588 = svn_wc_notify_state_inapplicable;
589 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
590 ctx->notify_func2(ctx->notify_baton2, notify, pool);
591 }
592 }
593
594 /* Now import the children recursively. */
595 SVN_ERR(import_children(local_abspath, edit_path, dirents, editor,
596 this_dir_baton, depth, excludes, global_ignores,
597 no_ignore, no_autoprops, ignore_unknown_node_types,
598 filter_callback, filter_baton,
599 import_ctx, ctx, pool));
600
601 /* Finally, close the sub-directory. */
602 SVN_ERR(editor->close_directory(this_dir_baton, pool));
603
604 return SVN_NO_ERROR;
605 }
606
607
608 /* Recursively import LOCAL_ABSPATH to a repository using EDITOR and
609 * EDIT_BATON. LOCAL_ABSPATH can be a file or directory.
610 *
611 * Sets *UPDATED_REPOSITORY to TRUE when the repository was modified by
612 * a successfull commit, otherwise to FALSE.
613 *
614 * DEPTH is the depth at which to import LOCAL_ABSPATH; it behaves as for
615 * svn_client_import5().
616 *
617 * BASE_REV is the revision to use for the root of the commit. We
618 * checked the preconditions against this revision.
619 *
620 * NEW_ENTRIES is an ordered array of path components that must be
621 * created in the repository (where the ordering direction is
622 * parent-to-child). If LOCAL_ABSPATH is a directory, NEW_ENTRIES may be empty
623 * -- the result is an import which creates as many new entries in the
624 * top repository target directory as there are importable entries in
625 * the top of LOCAL_ABSPATH; but if NEW_ENTRIES is not empty, its last item is
626 * the name of a new subdirectory in the repository to hold the
627 * import. If LOCAL_ABSPATH is a file, NEW_ENTRIES may not be empty, and its
628 * last item is the name used for the file in the repository. If
629 * NEW_ENTRIES contains more than one item, all but the last item are
630 * the names of intermediate directories that are created before the
631 * real import begins. NEW_ENTRIES may NOT be NULL.
632 *
633 * EXCLUDES is a hash whose keys are absolute paths to exclude from
634 * the import (values are unused).
635 *
636 * AUTOPROPS is hash of all config file autoprops and
637 * svn:auto-props inherited by the import target, see the
638 * IMPORT_CTX member of the same name.
639 *
640 * LOCAL_IGNORES is an array of const char * ignore patterns which
641 * correspond to the svn:ignore property (if any) set on the root of the
642 * repository target and thus dictates which immediate children of that
643 * target should be ignored and not imported.
644 *
645 * GLOBAL_IGNORES is an array of const char * ignore patterns which
646 * correspond to the svn:global-ignores properties (if any) set on
647 * the root of the repository target or inherited by it.
648 *
649 * If NO_IGNORE is FALSE, don't import files or directories that match
650 * ignore patterns.
651 *
652 * If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON for
653 * each imported path, passing actions svn_wc_notify_commit_added.
654 *
655 * URL is used only in the 'commit_finalizing' notification.
656 *
657 * Use POOL for any temporary allocation.
658 *
659 * Note: the repository directory receiving the import was specified
660 * when the editor was fetched. (I.e, when EDITOR->open_root() is
661 * called, it returns a directory baton for that directory, which is
662 * not necessarily the root.)
663 */
664 static svn_error_t *
import(svn_boolean_t * updated_repository,const char * local_abspath,const char * url,const apr_array_header_t * new_entries,const svn_delta_editor_t * editor,void * edit_baton,svn_depth_t depth,svn_revnum_t base_rev,apr_hash_t * excludes,apr_hash_t * autoprops,apr_array_header_t * local_ignores,apr_array_header_t * global_ignores,svn_boolean_t no_ignore,svn_boolean_t no_autoprops,svn_boolean_t ignore_unknown_node_types,svn_client_import_filter_func_t filter_callback,void * filter_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)665 import(svn_boolean_t *updated_repository,
666 const char *local_abspath,
667 const char *url,
668 const apr_array_header_t *new_entries,
669 const svn_delta_editor_t *editor,
670 void *edit_baton,
671 svn_depth_t depth,
672 svn_revnum_t base_rev,
673 apr_hash_t *excludes,
674 apr_hash_t *autoprops,
675 apr_array_header_t *local_ignores,
676 apr_array_header_t *global_ignores,
677 svn_boolean_t no_ignore,
678 svn_boolean_t no_autoprops,
679 svn_boolean_t ignore_unknown_node_types,
680 svn_client_import_filter_func_t filter_callback,
681 void *filter_baton,
682 svn_client_ctx_t *ctx,
683 apr_pool_t *pool)
684 {
685 void *root_baton;
686 apr_array_header_t *batons = NULL;
687 const char *edit_path = "";
688 import_ctx_t import_ctx = { FALSE };
689 const svn_io_dirent2_t *dirent;
690
691 *updated_repository = FALSE;
692
693 import_ctx.autoprops = autoprops;
694 SVN_ERR(svn_magic__init(&import_ctx.magic_cookie, ctx->config, pool));
695
696 /* Get a root dir baton. We pass the revnum we used for testing our
697 assumptions and obtaining inherited properties. */
698 SVN_ERR(editor->open_root(edit_baton, base_rev, pool, &root_baton));
699
700 /* Import a file or a directory tree. */
701 SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, FALSE,
702 pool, pool));
703
704 /* Make the intermediate directory components necessary for properly
705 rooting our import source tree. */
706 if (new_entries->nelts)
707 {
708 int i;
709
710 batons = apr_array_make(pool, new_entries->nelts, sizeof(void *));
711 for (i = 0; i < new_entries->nelts; i++)
712 {
713 const char *component = APR_ARRAY_IDX(new_entries, i, const char *);
714 edit_path = svn_relpath_join(edit_path, component, pool);
715
716 /* If this is the last path component, and we're importing a
717 file, then this component is the name of the file, not an
718 intermediate directory. */
719 if ((i == new_entries->nelts - 1) && (dirent->kind == svn_node_file))
720 break;
721
722 APR_ARRAY_PUSH(batons, void *) = root_baton;
723 SVN_ERR(editor->add_directory(edit_path,
724 root_baton,
725 NULL, SVN_INVALID_REVNUM,
726 pool, &root_baton));
727
728 /* Remember that the repository was modified */
729 import_ctx.repos_changed = TRUE;
730 }
731 }
732 else if (dirent->kind == svn_node_file)
733 {
734 return svn_error_create
735 (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
736 _("New entry name required when importing a file"));
737 }
738
739 /* Note that there is no need to check whether PATH's basename is
740 the same name that we reserve for our administrative
741 subdirectories. It would be strange -- though not illegal -- to
742 import the contents of a directory of that name, because the
743 directory's own name is not part of those contents. Of course,
744 if something underneath it also has our reserved name, then we'll
745 error. */
746
747 if (dirent->kind == svn_node_file)
748 {
749 /* This code path ignores EXCLUDES and FILTER, but they don't make
750 much sense for a single file import anyway. */
751 svn_boolean_t ignores_match = FALSE;
752
753 if (!no_ignore)
754 ignores_match =
755 (svn_wc_match_ignore_list(local_abspath, global_ignores, pool)
756 || svn_wc_match_ignore_list(local_abspath, local_ignores, pool));
757
758 if (!ignores_match)
759 SVN_ERR(import_file(editor, root_baton, local_abspath, edit_path,
760 dirent, &import_ctx, ctx, pool));
761 }
762 else if (dirent->kind == svn_node_dir)
763 {
764 apr_hash_t *dirents;
765
766 /* If we are creating a new repository directory path to import to,
767 then we disregard any svn:ignore property. */
768 if (!no_ignore && new_entries->nelts)
769 local_ignores = NULL;
770
771 SVN_ERR(get_filtered_children(&dirents, local_abspath, excludes,
772 local_ignores, global_ignores,
773 filter_callback, filter_baton, ctx,
774 pool, pool));
775
776 SVN_ERR(import_children(local_abspath, edit_path, dirents, editor,
777 root_baton, depth, excludes, global_ignores,
778 no_ignore, no_autoprops,
779 ignore_unknown_node_types, filter_callback,
780 filter_baton, &import_ctx, ctx, pool));
781
782 }
783 else if (dirent->kind == svn_node_none
784 || dirent->kind == svn_node_unknown)
785 {
786 return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
787 _("'%s' does not exist"),
788 svn_dirent_local_style(local_abspath, pool));
789 }
790
791 /* Close up shop; it's time to go home. */
792 SVN_ERR(editor->close_directory(root_baton, pool));
793 if (batons && batons->nelts)
794 {
795 void **baton;
796 while ((baton = (void **) apr_array_pop(batons)))
797 {
798 SVN_ERR(editor->close_directory(*baton, pool));
799 }
800 }
801
802 if (import_ctx.repos_changed)
803 {
804 if (ctx->notify_func2)
805 {
806 svn_wc_notify_t *notify;
807 notify = svn_wc_create_notify_url(url,
808 svn_wc_notify_commit_finalizing,
809 pool);
810 ctx->notify_func2(ctx->notify_baton2, notify, pool);
811 }
812
813 SVN_ERR(editor->close_edit(edit_baton, pool));
814
815 *updated_repository = TRUE;
816 }
817
818 return SVN_NO_ERROR;
819 }
820
821
822 /*** Public Interfaces. ***/
823
824 svn_error_t *
svn_client_import5(const char * path,const char * url,svn_depth_t depth,svn_boolean_t no_ignore,svn_boolean_t no_autoprops,svn_boolean_t ignore_unknown_node_types,const apr_hash_t * revprop_table,svn_client_import_filter_func_t filter_callback,void * filter_baton,svn_commit_callback2_t commit_callback,void * commit_baton,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)825 svn_client_import5(const char *path,
826 const char *url,
827 svn_depth_t depth,
828 svn_boolean_t no_ignore,
829 svn_boolean_t no_autoprops,
830 svn_boolean_t ignore_unknown_node_types,
831 const apr_hash_t *revprop_table,
832 svn_client_import_filter_func_t filter_callback,
833 void *filter_baton,
834 svn_commit_callback2_t commit_callback,
835 void *commit_baton,
836 svn_client_ctx_t *ctx,
837 apr_pool_t *scratch_pool)
838 {
839 svn_error_t *err = SVN_NO_ERROR;
840 const char *log_msg = "";
841 const svn_delta_editor_t *editor;
842 void *edit_baton;
843 svn_ra_session_t *ra_session;
844 apr_hash_t *excludes = apr_hash_make(scratch_pool);
845 svn_node_kind_t kind;
846 const char *local_abspath;
847 apr_array_header_t *new_entries = apr_array_make(scratch_pool, 4,
848 sizeof(const char *));
849 apr_hash_t *commit_revprops;
850 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
851 apr_hash_t *autoprops = NULL;
852 apr_array_header_t *global_ignores;
853 apr_array_header_t *local_ignores_arr;
854 svn_revnum_t base_rev;
855 apr_array_header_t *inherited_props = NULL;
856 apr_hash_t *url_props = NULL;
857 svn_boolean_t updated_repository;
858
859 if (svn_path_is_url(path))
860 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
861 _("'%s' is not a local path"), path);
862
863 SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
864
865 SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
866
867 /* Create a new commit item and add it to the array. */
868 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
869 {
870 /* If there's a log message gatherer, create a temporary commit
871 item array solely to help generate the log message. The
872 array is not used for the import itself. */
873 svn_client_commit_item3_t *item;
874 const char *tmp_file;
875 apr_array_header_t *commit_items
876 = apr_array_make(scratch_pool, 1, sizeof(item));
877
878 item = svn_client_commit_item3_create(scratch_pool);
879 item->path = local_abspath;
880 item->url = url;
881 item->kind = kind;
882 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
883 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
884
885 SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
886 ctx, scratch_pool));
887 if (! log_msg)
888 return SVN_NO_ERROR;
889 if (tmp_file)
890 {
891 const char *abs_path;
892 SVN_ERR(svn_dirent_get_absolute(&abs_path, tmp_file, scratch_pool));
893 svn_hash_sets(excludes, abs_path, (void *)1);
894 }
895 }
896
897 SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
898 ctx, scratch_pool, iterpool));
899
900 SVN_ERR(svn_ra_get_latest_revnum(ra_session, &base_rev, iterpool));
901
902 /* Figure out all the path components we need to create just to have
903 a place to stick our imported tree. */
904 SVN_ERR(svn_ra_check_path(ra_session, "", base_rev, &kind, iterpool));
905
906 /* We can import into directories, but if a file already exists, that's
907 an error. */
908 if (kind == svn_node_file)
909 return svn_error_createf
910 (SVN_ERR_ENTRY_EXISTS, NULL,
911 _("Path '%s' already exists"), url);
912
913 while (kind == svn_node_none)
914 {
915 const char *dir;
916
917 svn_pool_clear(iterpool);
918
919 svn_uri_split(&url, &dir, url, scratch_pool);
920 APR_ARRAY_PUSH(new_entries, const char *) = dir;
921 SVN_ERR(svn_ra_reparent(ra_session, url, iterpool));
922
923 SVN_ERR(svn_ra_check_path(ra_session, "", base_rev, &kind, iterpool));
924 }
925
926 /* Reverse the order of the components we added to our NEW_ENTRIES array. */
927 svn_sort__array_reverse(new_entries, scratch_pool);
928
929 /* The repository doesn't know about the reserved administrative
930 directory. */
931 if (new_entries->nelts)
932 {
933 const char *last_component
934 = APR_ARRAY_IDX(new_entries, new_entries->nelts - 1, const char *);
935
936 if (svn_wc_is_adm_dir(last_component, scratch_pool))
937 return svn_error_createf
938 (SVN_ERR_CL_ADM_DIR_RESERVED, NULL,
939 _("'%s' is a reserved name and cannot be imported"),
940 svn_dirent_local_style(last_component, scratch_pool));
941 }
942
943 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
944 log_msg, ctx, scratch_pool));
945
946 /* Obtain properties before opening the commit editor, as at that point we are
947 not allowed to use the existing ra-session */
948 if (! no_ignore /*|| ! no_autoprops*/)
949 {
950 SVN_ERR(svn_ra_get_dir2(ra_session, NULL, NULL, &url_props, "",
951 base_rev, SVN_DIRENT_KIND, scratch_pool));
952
953 SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "", base_rev,
954 scratch_pool, iterpool));
955 }
956
957 /* Fetch RA commit editor. */
958 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
959 svn_client__get_shim_callbacks(ctx->wc_ctx,
960 NULL, scratch_pool)));
961 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
962 commit_revprops, commit_callback,
963 commit_baton, NULL, TRUE,
964 scratch_pool));
965
966 /* Get inherited svn:auto-props, svn:global-ignores, and
967 svn:ignores for the location we are importing to. */
968 if (!no_autoprops)
969 {
970 /* ### This should use inherited_props and url_props to avoid creating
971 another ra session to obtain the same values, but using a possibly
972 different HEAD revision */
973 SVN_ERR(svn_client__get_all_auto_props(&autoprops, url, ctx,
974 scratch_pool, iterpool));
975 }
976 if (no_ignore)
977 {
978 global_ignores = NULL;
979 local_ignores_arr = NULL;
980 }
981 else
982 {
983 apr_array_header_t *config_ignores;
984 svn_string_t *val;
985 int i;
986
987 global_ignores = apr_array_make(scratch_pool, 64, sizeof(const char *));
988
989 SVN_ERR(svn_wc_get_default_ignores(&config_ignores, ctx->config,
990 scratch_pool));
991 global_ignores = apr_array_append(scratch_pool, global_ignores,
992 config_ignores);
993
994 val = svn_hash_gets(url_props, SVN_PROP_INHERITABLE_IGNORES);
995 if (val)
996 svn_cstring_split_append(global_ignores, val->data, "\n\r\t\v ",
997 FALSE, scratch_pool);
998
999 for (i = 0; i < inherited_props->nelts; i++)
1000 {
1001 svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
1002 inherited_props, i, svn_prop_inherited_item_t *);
1003
1004 val = svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_IGNORES);
1005
1006 if (val)
1007 svn_cstring_split_append(global_ignores, val->data, "\n\r\t\v ",
1008 FALSE, scratch_pool);
1009 }
1010 local_ignores_arr = apr_array_make(scratch_pool, 1, sizeof(const char *));
1011
1012 val = svn_hash_gets(url_props, SVN_PROP_IGNORE);
1013
1014 if (val)
1015 {
1016 svn_cstring_split_append(local_ignores_arr, val->data,
1017 "\n\r\t\v ", FALSE, scratch_pool);
1018 }
1019 }
1020
1021 /* If an error occurred during the commit, properly abort the edit. */
1022 err = svn_error_trace(import(&updated_repository,
1023 local_abspath, url, new_entries, editor,
1024 edit_baton, depth, base_rev, excludes,
1025 autoprops, local_ignores_arr, global_ignores,
1026 no_ignore, no_autoprops,
1027 ignore_unknown_node_types, filter_callback,
1028 filter_baton, ctx, iterpool));
1029
1030 svn_pool_destroy(iterpool);
1031
1032 if (err || !updated_repository)
1033 {
1034 return svn_error_compose_create(
1035 err,
1036 editor->abort_edit(edit_baton, scratch_pool));
1037 }
1038
1039 return SVN_NO_ERROR;
1040 }
1041
1042