]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/import.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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_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 *
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 *
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 *
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 *
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 *
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 *
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 \f
792 /*** Public Interfaces. ***/
793
794 svn_error_t *
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