summaryrefslogtreecommitdiff
path: root/subversion/libsvn_client/import.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/import.c')
-rw-r--r--subversion/libsvn_client/import.c964
1 files changed, 964 insertions, 0 deletions
diff --git a/subversion/libsvn_client/import.c b/subversion/libsvn_client/import.c
new file mode 100644
index 0000000..43e0d79
--- /dev/null
+++ b/subversion/libsvn_client/import.c
@@ -0,0 +1,964 @@
+/*
+ * import.c: wrappers around import functionality.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include <string.h>
+#include <apr_strings.h>
+#include <apr_hash.h>
+#include <apr_md5.h>
+
+#include "svn_hash.h"
+#include "svn_ra.h"
+#include "svn_delta.h"
+#include "svn_subst.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_error_codes.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_io.h"
+#include "svn_sorts.h"
+#include "svn_props.h"
+
+#include "client.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_ra_private.h"
+#include "private/svn_magic.h"
+
+#include "svn_private_config.h"
+
+/* Import context baton.
+
+ ### TODO: Add the following items to this baton:
+ /` import editor/baton. `/
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+
+ /` Client context baton `/
+ svn_client_ctx_t `ctx;
+
+ /` Paths (keys) excluded from the import (values ignored) `/
+ apr_hash_t *excludes;
+*/
+typedef struct import_ctx_t
+{
+ /* Whether any changes were made to the repository */
+ svn_boolean_t repos_changed;
+
+ /* A magic cookie for mime-type detection. */
+ svn_magic__cookie_t *magic_cookie;
+
+ /* Collection of all possible configuration file dictated auto-props and
+ svn:auto-props. A hash mapping const char * file patterns to a
+ second hash which maps const char * property names to const char *
+ property values. Properties which don't have a value, e.g.
+ svn:executable, simply map the property name to an empty string.
+ May be NULL if autoprops are disabled. */
+ apr_hash_t *autoprops;
+} import_ctx_t;
+
+
+/* Apply LOCAL_ABSPATH's contents (as a delta against the empty string) to
+ FILE_BATON in EDITOR. Use POOL for any temporary allocation.
+ PROPERTIES is the set of node properties set on this file.
+
+ Fill DIGEST with the md5 checksum of the sent file; DIGEST must be
+ at least APR_MD5_DIGESTSIZE bytes long. */
+
+/* ### how does this compare against svn_wc_transmit_text_deltas2() ??? */
+
+static svn_error_t *
+send_file_contents(const char *local_abspath,
+ void *file_baton,
+ const svn_delta_editor_t *editor,
+ apr_hash_t *properties,
+ unsigned char *digest,
+ apr_pool_t *pool)
+{
+ svn_stream_t *contents;
+ svn_txdelta_window_handler_t handler;
+ void *handler_baton;
+ const svn_string_t *eol_style_val = NULL, *keywords_val = NULL;
+ svn_boolean_t special = FALSE;
+ svn_subst_eol_style_t eol_style;
+ const char *eol;
+ apr_hash_t *keywords;
+
+ /* If there are properties, look for EOL-style and keywords ones. */
+ if (properties)
+ {
+ eol_style_val = apr_hash_get(properties, SVN_PROP_EOL_STYLE,
+ sizeof(SVN_PROP_EOL_STYLE) - 1);
+ keywords_val = apr_hash_get(properties, SVN_PROP_KEYWORDS,
+ sizeof(SVN_PROP_KEYWORDS) - 1);
+ if (svn_hash_gets(properties, SVN_PROP_SPECIAL))
+ special = TRUE;
+ }
+
+ /* Get an editor func that wants to consume the delta stream. */
+ SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool,
+ &handler, &handler_baton));
+
+ if (eol_style_val)
+ svn_subst_eol_style_from_value(&eol_style, &eol, eol_style_val->data);
+ else
+ {
+ eol = NULL;
+ eol_style = svn_subst_eol_style_none;
+ }
+
+ if (keywords_val)
+ SVN_ERR(svn_subst_build_keywords3(&keywords, keywords_val->data,
+ APR_STRINGIFY(SVN_INVALID_REVNUM),
+ "", "", 0, "", pool));
+ else
+ keywords = NULL;
+
+ if (special)
+ {
+ SVN_ERR(svn_subst_read_specialfile(&contents, local_abspath,
+ pool, pool));
+ }
+ else
+ {
+ /* Open the working copy file. */
+ SVN_ERR(svn_stream_open_readonly(&contents, local_abspath, pool, pool));
+
+ /* If we have EOL styles or keywords, then detranslate the file. */
+ if (svn_subst_translation_required(eol_style, eol, keywords,
+ FALSE, TRUE))
+ {
+ if (eol_style == svn_subst_eol_style_unknown)
+ return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
+ _("%s property on '%s' contains "
+ "unrecognized EOL-style '%s'"),
+ SVN_PROP_EOL_STYLE,
+ svn_dirent_local_style(local_abspath,
+ pool),
+ eol_style_val->data);
+
+ /* We're importing, so translate files with 'native' eol-style to
+ * repository-normal form, not to this platform's native EOL. */
+ if (eol_style == svn_subst_eol_style_native)
+ eol = SVN_SUBST_NATIVE_EOL_STR;
+
+ /* Wrap the working copy stream with a filter to detranslate it. */
+ contents = svn_subst_stream_translated(contents,
+ eol,
+ TRUE /* repair */,
+ keywords,
+ FALSE /* expand */,
+ pool);
+ }
+ }
+
+ /* Send the file's contents to the delta-window handler. */
+ return svn_error_trace(svn_txdelta_send_stream(contents, handler,
+ handler_baton, digest,
+ pool));
+}
+
+
+/* Import file PATH as EDIT_PATH in the repository directory indicated
+ * by DIR_BATON in EDITOR.
+ *
+ * Accumulate file paths and their batons in FILES, which must be
+ * non-null. (These are used to send postfix textdeltas later).
+ *
+ * If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON
+ * for each file.
+ *
+ * Use POOL for any temporary allocation.
+ */
+static svn_error_t *
+import_file(const svn_delta_editor_t *editor,
+ void *dir_baton,
+ const char *local_abspath,
+ const char *edit_path,
+ const svn_io_dirent2_t *dirent,
+ import_ctx_t *import_ctx,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ void *file_baton;
+ const char *mimetype = NULL;
+ unsigned char digest[APR_MD5_DIGESTSIZE];
+ const char *text_checksum;
+ apr_hash_t* properties;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_path_check_valid(local_abspath, pool));
+
+ /* Add the file, using the pool from the FILES hash. */
+ SVN_ERR(editor->add_file(edit_path, dir_baton, NULL, SVN_INVALID_REVNUM,
+ pool, &file_baton));
+
+ /* Remember that the repository was modified */
+ import_ctx->repos_changed = TRUE;
+
+ if (! dirent->special)
+ {
+ /* add automatic properties */
+ SVN_ERR(svn_client__get_paths_auto_props(&properties, &mimetype,
+ local_abspath,
+ import_ctx->magic_cookie,
+ import_ctx->autoprops,
+ ctx, pool, pool));
+ }
+ else
+ properties = apr_hash_make(pool);
+
+ if (properties)
+ {
+ for (hi = apr_hash_first(pool, properties); hi; hi = apr_hash_next(hi))
+ {
+ const char *pname = svn__apr_hash_index_key(hi);
+ const svn_string_t *pval = svn__apr_hash_index_val(hi);
+
+ SVN_ERR(editor->change_file_prop(file_baton, pname, pval, pool));
+ }
+ }
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(local_abspath, svn_wc_notify_commit_added,
+ pool);
+ notify->kind = svn_node_file;
+ notify->mime_type = mimetype;
+ notify->content_state = notify->prop_state
+ = svn_wc_notify_state_inapplicable;
+ notify->lock_state = svn_wc_notify_lock_state_inapplicable;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ }
+
+ /* If this is a special file, we need to set the svn:special
+ property and create a temporary detranslated version in order to
+ send to the server. */
+ if (dirent->special)
+ {
+ svn_hash_sets(properties, SVN_PROP_SPECIAL,
+ svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool));
+ SVN_ERR(editor->change_file_prop(file_baton, SVN_PROP_SPECIAL,
+ svn_hash_gets(properties,
+ SVN_PROP_SPECIAL),
+ pool));
+ }
+
+ /* Now, transmit the file contents. */
+ SVN_ERR(send_file_contents(local_abspath, file_baton, editor,
+ properties, digest, pool));
+
+ /* Finally, close the file. */
+ text_checksum =
+ svn_checksum_to_cstring(svn_checksum__from_digest_md5(digest, pool), pool);
+
+ return editor->close_file(file_baton, text_checksum, pool);
+}
+
+
+/* Return in CHILDREN a mapping of basenames to dirents for the importable
+ * children of DIR_ABSPATH. EXCLUDES is a hash of absolute paths to filter
+ * out. IGNORES and GLOBAL_IGNORES, if non-NULL, are lists of basename
+ * patterns to filter out.
+ * FILTER_CALLBACK and FILTER_BATON will be called for each absolute path,
+ * allowing users to further filter the list of returned entries.
+ *
+ * Results are returned in RESULT_POOL; use SCRATCH_POOL for temporary data.*/
+static svn_error_t *
+get_filtered_children(apr_hash_t **children,
+ const char *dir_abspath,
+ apr_hash_t *excludes,
+ apr_array_header_t *ignores,
+ apr_array_header_t *global_ignores,
+ svn_client_import_filter_func_t filter_callback,
+ void *filter_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *dirents;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ SVN_ERR(svn_io_get_dirents3(&dirents, dir_abspath, TRUE, result_pool,
+ scratch_pool));
+
+ for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ const char *base_name = svn__apr_hash_index_key(hi);
+ const svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi);
+ const char *local_abspath;
+
+ svn_pool_clear(iterpool);
+
+ local_abspath = svn_dirent_join(dir_abspath, base_name, iterpool);
+
+ if (svn_wc_is_adm_dir(base_name, iterpool))
+ {
+ /* If someone's trying to import a directory named the same
+ as our administrative directories, that's probably not
+ what they wanted to do. If they are importing a file
+ with that name, something is bound to blow up when they
+ checkout what they've imported. So, just skip items with
+ that name. */
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(svn_dirent_join(local_abspath, base_name,
+ iterpool),
+ svn_wc_notify_skip, iterpool);
+ notify->kind = svn_node_dir;
+ notify->content_state = notify->prop_state
+ = svn_wc_notify_state_inapplicable;
+ notify->lock_state = svn_wc_notify_lock_state_inapplicable;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, iterpool);
+ }
+
+ svn_hash_sets(dirents, base_name, NULL);
+ continue;
+ }
+ /* If this is an excluded path, exclude it. */
+ if (svn_hash_gets(excludes, local_abspath))
+ {
+ svn_hash_sets(dirents, base_name, NULL);
+ continue;
+ }
+
+ if (ignores && svn_wc_match_ignore_list(base_name, ignores, iterpool))
+ {
+ svn_hash_sets(dirents, base_name, NULL);
+ continue;
+ }
+
+ if (global_ignores &&
+ svn_wc_match_ignore_list(base_name, global_ignores, iterpool))
+ {
+ svn_hash_sets(dirents, base_name, NULL);
+ continue;
+ }
+
+ if (filter_callback)
+ {
+ svn_boolean_t filter = FALSE;
+
+ SVN_ERR(filter_callback(filter_baton, &filter, local_abspath,
+ dirent, iterpool));
+
+ if (filter)
+ {
+ svn_hash_sets(dirents, base_name, NULL);
+ continue;
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ *children = dirents;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+import_dir(const svn_delta_editor_t *editor,
+ void *dir_baton,
+ const char *local_abspath,
+ const char *edit_path,
+ svn_depth_t depth,
+ apr_hash_t *excludes,
+ apr_array_header_t *global_ignores,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ svn_boolean_t ignore_unknown_node_types,
+ svn_client_import_filter_func_t filter_callback,
+ void *filter_baton,
+ import_ctx_t *import_ctx,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool);
+
+
+/* Import the children of DIR_ABSPATH, with other arguments similar to
+ * import_dir(). */
+static svn_error_t *
+import_children(const char *dir_abspath,
+ const char *edit_path,
+ apr_hash_t *dirents,
+ const svn_delta_editor_t *editor,
+ void *dir_baton,
+ svn_depth_t depth,
+ apr_hash_t *excludes,
+ apr_array_header_t *global_ignores,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ svn_boolean_t ignore_unknown_node_types,
+ svn_client_import_filter_func_t filter_callback,
+ void *filter_baton,
+ import_ctx_t *import_ctx,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *sorted_dirents;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ sorted_dirents = svn_sort__hash(dirents, svn_sort_compare_items_lexically,
+ scratch_pool);
+ for (i = 0; i < sorted_dirents->nelts; i++)
+ {
+ const char *this_abspath, *this_edit_path;
+ svn_sort__item_t item = APR_ARRAY_IDX(sorted_dirents, i,
+ svn_sort__item_t);
+ const char *filename = item.key;
+ const svn_io_dirent2_t *dirent = item.value;
+
+ svn_pool_clear(iterpool);
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ /* Typically, we started importing from ".", in which case
+ edit_path is "". So below, this_path might become "./blah",
+ and this_edit_path might become "blah", for example. */
+ this_abspath = svn_dirent_join(dir_abspath, filename, iterpool);
+ this_edit_path = svn_relpath_join(edit_path, filename, iterpool);
+
+ if (dirent->kind == svn_node_dir && depth >= svn_depth_immediates)
+ {
+ /* Recurse. */
+ svn_depth_t depth_below_here = depth;
+ if (depth == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ SVN_ERR(import_dir(editor, dir_baton, this_abspath,
+ this_edit_path, depth_below_here, excludes,
+ global_ignores, no_ignore, no_autoprops,
+ ignore_unknown_node_types, filter_callback,
+ filter_baton, import_ctx, ctx, iterpool));
+ }
+ else if (dirent->kind == svn_node_file && depth >= svn_depth_files)
+ {
+ SVN_ERR(import_file(editor, dir_baton, this_abspath,
+ this_edit_path, dirent,
+ import_ctx, ctx, iterpool));
+ }
+ else if (dirent->kind != svn_node_dir && dirent->kind != svn_node_file)
+ {
+ if (ignore_unknown_node_types)
+ {
+ /*## warn about it*/
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(this_abspath,
+ svn_wc_notify_skip, iterpool);
+ notify->kind = svn_node_dir;
+ notify->content_state = notify->prop_state
+ = svn_wc_notify_state_inapplicable;
+ notify->lock_state = svn_wc_notify_lock_state_inapplicable;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, iterpool);
+ }
+ }
+ else
+ return svn_error_createf
+ (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("Unknown or unversionable type for '%s'"),
+ svn_dirent_local_style(this_abspath, iterpool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Import directory LOCAL_ABSPATH into the repository directory indicated by
+ * DIR_BATON in EDITOR. EDIT_PATH is the path imported as the root
+ * directory, so all edits are relative to that.
+ *
+ * DEPTH is the depth at this point in the descent (it may be changed
+ * for recursive calls).
+ *
+ * Accumulate file paths and their batons in FILES, which must be
+ * non-null. (These are used to send postfix textdeltas later).
+ *
+ * EXCLUDES is a hash whose keys are absolute paths to exclude from
+ * the import (values are unused).
+ *
+ * GLOBAL_IGNORES is an array of const char * ignore patterns. Any child
+ * of LOCAL_ABSPATH which matches one or more of the patterns is not imported.
+ *
+ * If NO_IGNORE is FALSE, don't import files or directories that match
+ * ignore patterns.
+ *
+ * If FILTER_CALLBACK is not NULL, call it with FILTER_BATON on each to be
+ * imported node below LOCAL_ABSPATH to allow filtering nodes.
+ *
+ * If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON for each
+ * directory.
+ *
+ * Use POOL for any temporary allocation. */
+static svn_error_t *
+import_dir(const svn_delta_editor_t *editor,
+ void *dir_baton,
+ const char *local_abspath,
+ const char *edit_path,
+ svn_depth_t depth,
+ apr_hash_t *excludes,
+ apr_array_header_t *global_ignores,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ svn_boolean_t ignore_unknown_node_types,
+ svn_client_import_filter_func_t filter_callback,
+ void *filter_baton,
+ import_ctx_t *import_ctx,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_hash_t *dirents;
+ void *this_dir_baton;
+
+ SVN_ERR(svn_path_check_valid(local_abspath, pool));
+ SVN_ERR(get_filtered_children(&dirents, local_abspath, excludes, NULL,
+ global_ignores, filter_callback,
+ filter_baton, ctx, pool, pool));
+
+ /* Import this directory, but not yet its children. */
+ {
+ /* Add the new subdirectory, getting a descent baton from the editor. */
+ SVN_ERR(editor->add_directory(edit_path, dir_baton, NULL,
+ SVN_INVALID_REVNUM, pool, &this_dir_baton));
+
+ /* Remember that the repository was modified */
+ import_ctx->repos_changed = TRUE;
+
+ /* By notifying before the recursive call below, we display
+ a directory add before displaying adds underneath the
+ directory. To do it the other way around, just move this
+ after the recursive call. */
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(local_abspath, svn_wc_notify_commit_added,
+ pool);
+ notify->kind = svn_node_dir;
+ notify->content_state = notify->prop_state
+ = svn_wc_notify_state_inapplicable;
+ notify->lock_state = svn_wc_notify_lock_state_inapplicable;
+ (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+ }
+ }
+
+ /* Now import the children recursively. */
+ SVN_ERR(import_children(local_abspath, edit_path, dirents, editor,
+ this_dir_baton, depth, excludes, global_ignores,
+ no_ignore, no_autoprops, ignore_unknown_node_types,
+ filter_callback, filter_baton,
+ import_ctx, ctx, pool));
+
+ /* Finally, close the sub-directory. */
+ SVN_ERR(editor->close_directory(this_dir_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Recursively import PATH to a repository using EDITOR and
+ * EDIT_BATON. PATH can be a file or directory.
+ *
+ * DEPTH is the depth at which to import PATH; it behaves as for
+ * svn_client_import4().
+ *
+ * NEW_ENTRIES is an ordered array of path components that must be
+ * created in the repository (where the ordering direction is
+ * parent-to-child). If PATH is a directory, NEW_ENTRIES may be empty
+ * -- the result is an import which creates as many new entries in the
+ * top repository target directory as there are importable entries in
+ * the top of PATH; but if NEW_ENTRIES is not empty, its last item is
+ * the name of a new subdirectory in the repository to hold the
+ * import. If PATH is a file, NEW_ENTRIES may not be empty, and its
+ * last item is the name used for the file in the repository. If
+ * NEW_ENTRIES contains more than one item, all but the last item are
+ * the names of intermediate directories that are created before the
+ * real import begins. NEW_ENTRIES may NOT be NULL.
+ *
+ * EXCLUDES is a hash whose keys are absolute paths to exclude from
+ * the import (values are unused).
+ *
+ * AUTOPROPS is hash of all config file autoprops and
+ * svn:auto-props inherited by the import target, see the
+ * IMPORT_CTX member of the same name.
+ *
+ * LOCAL_IGNORES is an array of const char * ignore patterns which
+ * correspond to the svn:ignore property (if any) set on the root of the
+ * repository target and thus dictates which immediate children of that
+ * target should be ignored and not imported.
+ *
+ * GLOBAL_IGNORES is an array of const char * ignore patterns which
+ * correspond to the svn:global-ignores properties (if any) set on
+ * the root of the repository target or inherited by it.
+ *
+ * If NO_IGNORE is FALSE, don't import files or directories that match
+ * ignore patterns.
+ *
+ * If CTX->NOTIFY_FUNC is non-null, invoke it with CTX->NOTIFY_BATON for
+ * each imported path, passing actions svn_wc_notify_commit_added.
+ *
+ * Use POOL for any temporary allocation.
+ *
+ * Note: the repository directory receiving the import was specified
+ * when the editor was fetched. (I.e, when EDITOR->open_root() is
+ * called, it returns a directory baton for that directory, which is
+ * not necessarily the root.)
+ */
+static svn_error_t *
+import(const char *local_abspath,
+ const apr_array_header_t *new_entries,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_depth_t depth,
+ apr_hash_t *excludes,
+ apr_hash_t *autoprops,
+ apr_array_header_t *local_ignores,
+ apr_array_header_t *global_ignores,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ svn_boolean_t ignore_unknown_node_types,
+ svn_client_import_filter_func_t filter_callback,
+ void *filter_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ void *root_baton;
+ apr_array_header_t *batons = NULL;
+ const char *edit_path = "";
+ import_ctx_t *import_ctx = apr_pcalloc(pool, sizeof(*import_ctx));
+ const svn_io_dirent2_t *dirent;
+
+ import_ctx->autoprops = autoprops;
+ svn_magic__init(&import_ctx->magic_cookie, pool);
+
+ /* Get a root dir baton. We pass an invalid revnum to open_root
+ to mean "base this on the youngest revision". Should we have an
+ SVN_YOUNGEST_REVNUM defined for these purposes? */
+ SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM,
+ pool, &root_baton));
+
+ /* Import a file or a directory tree. */
+ SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, FALSE,
+ pool, pool));
+
+ /* Make the intermediate directory components necessary for properly
+ rooting our import source tree. */
+ if (new_entries->nelts)
+ {
+ int i;
+
+ batons = apr_array_make(pool, new_entries->nelts, sizeof(void *));
+ for (i = 0; i < new_entries->nelts; i++)
+ {
+ const char *component = APR_ARRAY_IDX(new_entries, i, const char *);
+ edit_path = svn_relpath_join(edit_path, component, pool);
+
+ /* If this is the last path component, and we're importing a
+ file, then this component is the name of the file, not an
+ intermediate directory. */
+ if ((i == new_entries->nelts - 1) && (dirent->kind == svn_node_file))
+ break;
+
+ APR_ARRAY_PUSH(batons, void *) = root_baton;
+ SVN_ERR(editor->add_directory(edit_path,
+ root_baton,
+ NULL, SVN_INVALID_REVNUM,
+ pool, &root_baton));
+
+ /* Remember that the repository was modified */
+ import_ctx->repos_changed = TRUE;
+ }
+ }
+ else if (dirent->kind == svn_node_file)
+ {
+ return svn_error_create
+ (SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("New entry name required when importing a file"));
+ }
+
+ /* Note that there is no need to check whether PATH's basename is
+ the same name that we reserve for our administrative
+ subdirectories. It would be strange -- though not illegal -- to
+ import the contents of a directory of that name, because the
+ directory's own name is not part of those contents. Of course,
+ if something underneath it also has our reserved name, then we'll
+ error. */
+
+ if (dirent->kind == svn_node_file)
+ {
+ /* This code path ignores EXCLUDES and FILTER, but they don't make
+ much sense for a single file import anyway. */
+ svn_boolean_t ignores_match = FALSE;
+
+ if (!no_ignore)
+ ignores_match =
+ (svn_wc_match_ignore_list(local_abspath, global_ignores, pool)
+ || svn_wc_match_ignore_list(local_abspath, local_ignores, pool));
+
+ if (!ignores_match)
+ SVN_ERR(import_file(editor, root_baton, local_abspath, edit_path,
+ dirent, import_ctx, ctx, pool));
+ }
+ else if (dirent->kind == svn_node_dir)
+ {
+ apr_hash_t *dirents;
+
+ /* If we are creating a new repository directory path to import to,
+ then we disregard any svn:ignore property. */
+ if (!no_ignore && new_entries->nelts)
+ local_ignores = NULL;
+
+ SVN_ERR(get_filtered_children(&dirents, local_abspath, excludes,
+ local_ignores, global_ignores,
+ filter_callback, filter_baton, ctx,
+ pool, pool));
+
+ SVN_ERR(import_children(local_abspath, edit_path, dirents, editor,
+ root_baton, depth, excludes, global_ignores,
+ no_ignore, no_autoprops,
+ ignore_unknown_node_types, filter_callback,
+ filter_baton, import_ctx, ctx, pool));
+
+ }
+ else if (dirent->kind == svn_node_none
+ || dirent->kind == svn_node_unknown)
+ {
+ return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("'%s' does not exist"),
+ svn_dirent_local_style(local_abspath, pool));
+ }
+
+ /* Close up shop; it's time to go home. */
+ SVN_ERR(editor->close_directory(root_baton, pool));
+ if (batons && batons->nelts)
+ {
+ void **baton;
+ while ((baton = (void **) apr_array_pop(batons)))
+ {
+ SVN_ERR(editor->close_directory(*baton, pool));
+ }
+ }
+
+ if (import_ctx->repos_changed)
+ return editor->close_edit(edit_baton, pool);
+ else
+ return editor->abort_edit(edit_baton, pool);
+}
+
+
+/*** Public Interfaces. ***/
+
+svn_error_t *
+svn_client_import5(const char *path,
+ const char *url,
+ svn_depth_t depth,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ svn_boolean_t ignore_unknown_node_types,
+ const apr_hash_t *revprop_table,
+ svn_client_import_filter_func_t filter_callback,
+ void *filter_baton,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ const char *log_msg = "";
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ svn_ra_session_t *ra_session;
+ apr_hash_t *excludes = apr_hash_make(scratch_pool);
+ svn_node_kind_t kind;
+ const char *local_abspath;
+ apr_array_header_t *new_entries = apr_array_make(scratch_pool, 4,
+ sizeof(const char *));
+ apr_hash_t *commit_revprops;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *autoprops = NULL;
+ apr_array_header_t *global_ignores;
+ apr_array_header_t *local_ignores_arr;
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
+
+ /* Create a new commit item and add it to the array. */
+ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
+ {
+ /* If there's a log message gatherer, create a temporary commit
+ item array solely to help generate the log message. The
+ array is not used for the import itself. */
+ svn_client_commit_item3_t *item;
+ const char *tmp_file;
+ apr_array_header_t *commit_items
+ = apr_array_make(scratch_pool, 1, sizeof(item));
+
+ item = svn_client_commit_item3_create(scratch_pool);
+ item->path = apr_pstrdup(scratch_pool, path);
+ item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
+ APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
+
+ SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
+ ctx, scratch_pool));
+ if (! log_msg)
+ return SVN_NO_ERROR;
+ if (tmp_file)
+ {
+ const char *abs_path;
+ SVN_ERR(svn_dirent_get_absolute(&abs_path, tmp_file, scratch_pool));
+ svn_hash_sets(excludes, abs_path, (void *)1);
+ }
+ }
+
+ SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
+
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
+ ctx, scratch_pool, iterpool));
+
+ /* Figure out all the path components we need to create just to have
+ a place to stick our imported tree. */
+ SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
+ iterpool));
+
+ /* We can import into directories, but if a file already exists, that's
+ an error. */
+ if (kind == svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_ENTRY_EXISTS, NULL,
+ _("Path '%s' already exists"), url);
+
+ while (kind == svn_node_none)
+ {
+ const char *dir;
+
+ svn_pool_clear(iterpool);
+
+ svn_uri_split(&url, &dir, url, scratch_pool);
+ APR_ARRAY_PUSH(new_entries, const char *) = dir;
+ SVN_ERR(svn_ra_reparent(ra_session, url, iterpool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
+ iterpool));
+ }
+
+ /* Reverse the order of the components we added to our NEW_ENTRIES array. */
+ svn_sort__array_reverse(new_entries, scratch_pool);
+
+ /* The repository doesn't know about the reserved administrative
+ directory. */
+ if (new_entries->nelts)
+ {
+ const char *last_component
+ = APR_ARRAY_IDX(new_entries, new_entries->nelts - 1, const char *);
+
+ if (svn_wc_is_adm_dir(last_component, scratch_pool))
+ return svn_error_createf
+ (SVN_ERR_CL_ADM_DIR_RESERVED, NULL,
+ _("'%s' is a reserved name and cannot be imported"),
+ svn_dirent_local_style(last_component, scratch_pool));
+ }
+
+ SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
+ log_msg, ctx, scratch_pool));
+
+ /* Fetch RA commit editor. */
+ SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
+ svn_client__get_shim_callbacks(ctx->wc_ctx,
+ NULL, scratch_pool)));
+ SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
+ commit_revprops, commit_callback,
+ commit_baton, NULL, TRUE,
+ scratch_pool));
+
+ /* Get inherited svn:auto-props, svn:global-ignores, and
+ svn:ignores for the location we are importing to. */
+ if (!no_autoprops)
+ SVN_ERR(svn_client__get_all_auto_props(&autoprops, url, ctx,
+ scratch_pool, iterpool));
+ if (no_ignore)
+ {
+ global_ignores = NULL;
+ local_ignores_arr = NULL;
+ }
+ else
+ {
+ svn_opt_revision_t rev;
+ apr_array_header_t *config_ignores;
+ apr_hash_t *local_ignores_hash;
+
+ SVN_ERR(svn_client__get_inherited_ignores(&global_ignores, url, ctx,
+ scratch_pool, iterpool));
+ SVN_ERR(svn_wc_get_default_ignores(&config_ignores, ctx->config,
+ scratch_pool));
+ global_ignores = apr_array_append(scratch_pool, global_ignores,
+ config_ignores);
+
+ rev.kind = svn_opt_revision_head;
+ SVN_ERR(svn_client_propget5(&local_ignores_hash, NULL, SVN_PROP_IGNORE, url,
+ &rev, &rev, NULL, svn_depth_empty, NULL, ctx,
+ scratch_pool, scratch_pool));
+ local_ignores_arr = apr_array_make(scratch_pool, 1, sizeof(const char *));
+
+ if (apr_hash_count(local_ignores_hash))
+ {
+ svn_string_t *propval = svn_hash_gets(local_ignores_hash, url);
+ if (propval)
+ {
+ svn_cstring_split_append(local_ignores_arr, propval->data,
+ "\n\r\t\v ", FALSE, scratch_pool);
+ }
+ }
+ }
+
+ /* If an error occurred during the commit, abort the edit and return
+ the error. We don't even care if the abort itself fails. */
+ if ((err = import(local_abspath, new_entries, editor, edit_baton,
+ depth, excludes, autoprops, local_ignores_arr,
+ global_ignores, no_ignore, no_autoprops,
+ ignore_unknown_node_types, filter_callback,
+ filter_baton, ctx, iterpool)))
+ {
+ return svn_error_compose_create(
+ err,
+ editor->abort_edit(edit_baton, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+