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