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