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