1/*
2 * import.c:  wrappers around import functionality.
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* ==================================================================== */
25
26
27
28/*** Includes. ***/
29
30#include <string.h>
31#include <apr_strings.h>
32#include <apr_hash.h>
33#include <apr_md5.h>
34
35#include "svn_hash.h"
36#include "svn_ra.h"
37#include "svn_delta.h"
38#include "svn_subst.h"
39#include "svn_client.h"
40#include "svn_string.h"
41#include "svn_pools.h"
42#include "svn_error_codes.h"
43#include "svn_dirent_uri.h"
44#include "svn_path.h"
45#include "svn_io.h"
46#include "svn_sorts.h"
47#include "svn_props.h"
48
49#include "client.h"
50#include "private/svn_ra_private.h"
51#include "private/svn_sorts_private.h"
52#include "private/svn_subr_private.h"
53#include "private/svn_magic.h"
54
55#include "svn_private_config.h"
56
57/* Import context baton. */
58
59typedef 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
76typedef 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 */
83static svn_error_t *
84open_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
114static svn_error_t *
115send_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 */
219static svn_error_t *
220import_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.*/
312static svn_error_t *
313get_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
405static svn_error_t *
406import_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(). */
425static svn_error_t *
426import_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.  */
543static svn_error_t *
544import_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 */
664static svn_error_t *
665import(svn_boolean_t *updated_repository,
666       const char *local_abspath,
667       const char *url,
668       const apr_array_header_t *new_entries,
669       const svn_delta_editor_t *editor,
670       void *edit_baton,
671       svn_depth_t depth,
672       svn_revnum_t base_rev,
673       apr_hash_t *excludes,
674       apr_hash_t *autoprops,
675       apr_array_header_t *local_ignores,
676       apr_array_header_t *global_ignores,
677       svn_boolean_t no_ignore,
678       svn_boolean_t no_autoprops,
679       svn_boolean_t ignore_unknown_node_types,
680       svn_client_import_filter_func_t filter_callback,
681       void *filter_baton,
682       svn_client_ctx_t *ctx,
683       apr_pool_t *pool)
684{
685  void *root_baton;
686  apr_array_header_t *batons = NULL;
687  const char *edit_path = "";
688  import_ctx_t import_ctx = { FALSE };
689  const svn_io_dirent2_t *dirent;
690
691  *updated_repository = FALSE;
692
693  import_ctx.autoprops = autoprops;
694  SVN_ERR(svn_magic__init(&import_ctx.magic_cookie, ctx->config, pool));
695
696  /* Get a root dir baton.  We pass the revnum we used for testing our
697     assumptions and obtaining inherited properties. */
698  SVN_ERR(editor->open_root(edit_baton, base_rev, pool, &root_baton));
699
700  /* Import a file or a directory tree. */
701  SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, FALSE,
702                              pool, pool));
703
704  /* Make the intermediate directory components necessary for properly
705     rooting our import source tree.  */
706  if (new_entries->nelts)
707    {
708      int i;
709
710      batons = apr_array_make(pool, new_entries->nelts, sizeof(void *));
711      for (i = 0; i < new_entries->nelts; i++)
712        {
713          const char *component = APR_ARRAY_IDX(new_entries, i, const char *);
714          edit_path = svn_relpath_join(edit_path, component, pool);
715
716          /* If this is the last path component, and we're importing a
717             file, then this component is the name of the file, not an
718             intermediate directory. */
719          if ((i == new_entries->nelts - 1) && (dirent->kind == svn_node_file))
720            break;
721
722          APR_ARRAY_PUSH(batons, void *) = root_baton;
723          SVN_ERR(editor->add_directory(edit_path,
724                                        root_baton,
725                                        NULL, SVN_INVALID_REVNUM,
726                                        pool, &root_baton));
727
728          /* Remember that the repository was modified */
729          import_ctx.repos_changed = TRUE;
730        }
731    }
732  else if (dirent->kind == svn_node_file)
733    {
734      return svn_error_create
735        (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
736         _("New entry name required when importing a file"));
737    }
738
739  /* Note that there is no need to check whether PATH's basename is
740     the same name that we reserve for our administrative
741     subdirectories.  It would be strange -- though not illegal -- to
742     import the contents of a directory of that name, because the
743     directory's own name is not part of those contents.  Of course,
744     if something underneath it also has our reserved name, then we'll
745     error. */
746
747  if (dirent->kind == svn_node_file)
748    {
749      /* This code path ignores EXCLUDES and FILTER, but they don't make
750         much sense for a single file import anyway. */
751      svn_boolean_t ignores_match = FALSE;
752
753      if (!no_ignore)
754        ignores_match =
755          (svn_wc_match_ignore_list(local_abspath, global_ignores, pool)
756           || svn_wc_match_ignore_list(local_abspath, local_ignores, pool));
757
758      if (!ignores_match)
759        SVN_ERR(import_file(editor, root_baton, local_abspath, edit_path,
760                            dirent, &import_ctx, ctx, pool));
761    }
762  else if (dirent->kind == svn_node_dir)
763    {
764      apr_hash_t *dirents;
765
766      /* If we are creating a new repository directory path to import to,
767         then we disregard any svn:ignore property. */
768      if (!no_ignore && new_entries->nelts)
769        local_ignores = NULL;
770
771      SVN_ERR(get_filtered_children(&dirents, local_abspath, excludes,
772                                    local_ignores, global_ignores,
773                                    filter_callback, filter_baton, ctx,
774                                    pool, pool));
775
776      SVN_ERR(import_children(local_abspath, edit_path, dirents, editor,
777                              root_baton, depth, excludes, global_ignores,
778                              no_ignore, no_autoprops,
779                              ignore_unknown_node_types, filter_callback,
780                              filter_baton, &import_ctx, ctx, pool));
781
782    }
783  else if (dirent->kind == svn_node_none
784           || dirent->kind == svn_node_unknown)
785    {
786      return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
787                               _("'%s' does not exist"),
788                               svn_dirent_local_style(local_abspath, pool));
789    }
790
791  /* Close up shop; it's time to go home. */
792  SVN_ERR(editor->close_directory(root_baton, pool));
793  if (batons && batons->nelts)
794    {
795      void **baton;
796      while ((baton = (void **) apr_array_pop(batons)))
797        {
798          SVN_ERR(editor->close_directory(*baton, pool));
799        }
800    }
801
802  if (import_ctx.repos_changed)
803    {
804      if (ctx->notify_func2)
805        {
806          svn_wc_notify_t *notify;
807          notify = svn_wc_create_notify_url(url,
808                                            svn_wc_notify_commit_finalizing,
809                                            pool);
810          ctx->notify_func2(ctx->notify_baton2, notify, pool);
811        }
812
813      SVN_ERR(editor->close_edit(edit_baton, pool));
814
815      *updated_repository = TRUE;
816    }
817
818  return SVN_NO_ERROR;
819}
820
821
822/*** Public Interfaces. ***/
823
824svn_error_t *
825svn_client_import5(const char *path,
826                   const char *url,
827                   svn_depth_t depth,
828                   svn_boolean_t no_ignore,
829                   svn_boolean_t no_autoprops,
830                   svn_boolean_t ignore_unknown_node_types,
831                   const apr_hash_t *revprop_table,
832                   svn_client_import_filter_func_t filter_callback,
833                   void *filter_baton,
834                   svn_commit_callback2_t commit_callback,
835                   void *commit_baton,
836                   svn_client_ctx_t *ctx,
837                   apr_pool_t *scratch_pool)
838{
839  svn_error_t *err = SVN_NO_ERROR;
840  const char *log_msg = "";
841  const svn_delta_editor_t *editor;
842  void *edit_baton;
843  svn_ra_session_t *ra_session;
844  apr_hash_t *excludes = apr_hash_make(scratch_pool);
845  svn_node_kind_t kind;
846  const char *local_abspath;
847  apr_array_header_t *new_entries = apr_array_make(scratch_pool, 4,
848                                                   sizeof(const char *));
849  apr_hash_t *commit_revprops;
850  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
851  apr_hash_t *autoprops = NULL;
852  apr_array_header_t *global_ignores;
853  apr_array_header_t *local_ignores_arr;
854  svn_revnum_t base_rev;
855  apr_array_header_t *inherited_props = NULL;
856  apr_hash_t *url_props = NULL;
857  svn_boolean_t updated_repository;
858
859  if (svn_path_is_url(path))
860    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
861                             _("'%s' is not a local path"), path);
862
863  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
864
865  SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
866
867  /* Create a new commit item and add it to the array. */
868  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
869    {
870      /* If there's a log message gatherer, create a temporary commit
871         item array solely to help generate the log message.  The
872         array is not used for the import itself. */
873      svn_client_commit_item3_t *item;
874      const char *tmp_file;
875      apr_array_header_t *commit_items
876        = apr_array_make(scratch_pool, 1, sizeof(item));
877
878      item = svn_client_commit_item3_create(scratch_pool);
879      item->path = local_abspath;
880      item->url = url;
881      item->kind = kind;
882      item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
883      APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
884
885      SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
886                                      ctx, scratch_pool));
887      if (! log_msg)
888        return SVN_NO_ERROR;
889      if (tmp_file)
890        {
891          const char *abs_path;
892          SVN_ERR(svn_dirent_get_absolute(&abs_path, tmp_file, scratch_pool));
893          svn_hash_sets(excludes, abs_path, (void *)1);
894        }
895    }
896
897  SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
898                                      ctx, scratch_pool, iterpool));
899
900  SVN_ERR(svn_ra_get_latest_revnum(ra_session, &base_rev, iterpool));
901
902  /* Figure out all the path components we need to create just to have
903     a place to stick our imported tree. */
904  SVN_ERR(svn_ra_check_path(ra_session, "", base_rev, &kind, iterpool));
905
906  /* We can import into directories, but if a file already exists, that's
907     an error. */
908  if (kind == svn_node_file)
909    return svn_error_createf
910      (SVN_ERR_ENTRY_EXISTS, NULL,
911       _("Path '%s' already exists"), url);
912
913  while (kind == svn_node_none)
914    {
915      const char *dir;
916
917      svn_pool_clear(iterpool);
918
919      svn_uri_split(&url, &dir, url, scratch_pool);
920      APR_ARRAY_PUSH(new_entries, const char *) = dir;
921      SVN_ERR(svn_ra_reparent(ra_session, url, iterpool));
922
923      SVN_ERR(svn_ra_check_path(ra_session, "", base_rev, &kind, iterpool));
924    }
925
926  /* Reverse the order of the components we added to our NEW_ENTRIES array. */
927  svn_sort__array_reverse(new_entries, scratch_pool);
928
929  /* The repository doesn't know about the reserved administrative
930     directory. */
931  if (new_entries->nelts)
932    {
933      const char *last_component
934        = APR_ARRAY_IDX(new_entries, new_entries->nelts - 1, const char *);
935
936      if (svn_wc_is_adm_dir(last_component, scratch_pool))
937        return svn_error_createf
938          (SVN_ERR_CL_ADM_DIR_RESERVED, NULL,
939           _("'%s' is a reserved name and cannot be imported"),
940           svn_dirent_local_style(last_component, scratch_pool));
941    }
942
943  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
944                                           log_msg, ctx, scratch_pool));
945
946  /* Obtain properties before opening the commit editor, as at that point we are
947     not allowed to use the existing ra-session */
948  if (! no_ignore /*|| ! no_autoprops*/)
949    {
950      SVN_ERR(svn_ra_get_dir2(ra_session, NULL, NULL, &url_props, "",
951                              base_rev, SVN_DIRENT_KIND, scratch_pool));
952
953      SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "", base_rev,
954                                         scratch_pool, iterpool));
955    }
956
957  /* Fetch RA commit editor. */
958  SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
959                        svn_client__get_shim_callbacks(ctx->wc_ctx,
960                                                       NULL, scratch_pool)));
961  SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
962                                    commit_revprops, commit_callback,
963                                    commit_baton, NULL, TRUE,
964                                    scratch_pool));
965
966  /* Get inherited svn:auto-props, svn:global-ignores, and
967     svn:ignores for the location we are importing to. */
968  if (!no_autoprops)
969    {
970      /* ### This should use inherited_props and url_props to avoid creating
971             another ra session to obtain the same values, but using a possibly
972             different HEAD revision */
973      SVN_ERR(svn_client__get_all_auto_props(&autoprops, url, ctx,
974                                             scratch_pool, iterpool));
975    }
976  if (no_ignore)
977    {
978      global_ignores = NULL;
979      local_ignores_arr = NULL;
980    }
981  else
982    {
983      apr_array_header_t *config_ignores;
984      svn_string_t *val;
985      int i;
986
987      global_ignores = apr_array_make(scratch_pool, 64, sizeof(const char *));
988
989      SVN_ERR(svn_wc_get_default_ignores(&config_ignores, ctx->config,
990                                         scratch_pool));
991      global_ignores = apr_array_append(scratch_pool, global_ignores,
992                                        config_ignores);
993
994      val = svn_hash_gets(url_props, SVN_PROP_INHERITABLE_IGNORES);
995      if (val)
996        svn_cstring_split_append(global_ignores, val->data, "\n\r\t\v ",
997                                 FALSE, scratch_pool);
998
999      for (i = 0; i < inherited_props->nelts; i++)
1000        {
1001          svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
1002            inherited_props, i, svn_prop_inherited_item_t *);
1003
1004          val = svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_IGNORES);
1005
1006          if (val)
1007            svn_cstring_split_append(global_ignores, val->data, "\n\r\t\v ",
1008                                     FALSE, scratch_pool);
1009        }
1010      local_ignores_arr = apr_array_make(scratch_pool, 1, sizeof(const char *));
1011
1012      val = svn_hash_gets(url_props, SVN_PROP_IGNORE);
1013
1014      if (val)
1015        {
1016          svn_cstring_split_append(local_ignores_arr, val->data,
1017                                   "\n\r\t\v ", FALSE, scratch_pool);
1018        }
1019    }
1020
1021  /* If an error occurred during the commit, properly abort the edit.  */
1022  err = svn_error_trace(import(&updated_repository,
1023                               local_abspath, url, new_entries, editor,
1024                               edit_baton, depth, base_rev, excludes,
1025                               autoprops, local_ignores_arr, global_ignores,
1026                               no_ignore, no_autoprops,
1027                               ignore_unknown_node_types, filter_callback,
1028                               filter_baton, ctx, iterpool));
1029
1030  svn_pool_destroy(iterpool);
1031
1032  if (err || !updated_repository)
1033    {
1034      return svn_error_compose_create(
1035                    err,
1036                    editor->abort_edit(edit_baton, scratch_pool));
1037    }
1038
1039  return SVN_NO_ERROR;
1040}
1041
1042