summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_client/add.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/add.c')
-rw-r--r--subversion/libsvn_client/add.c1326
1 files changed, 1326 insertions, 0 deletions
diff --git a/subversion/libsvn_client/add.c b/subversion/libsvn_client/add.c
new file mode 100644
index 0000000..f121bc8
--- /dev/null
+++ b/subversion/libsvn_client/add.c
@@ -0,0 +1,1326 @@
+/*
+ * add.c: wrappers around wc add/mkdir 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_lib.h>
+#include <apr_fnmatch.h>
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_io.h"
+#include "svn_config.h"
+#include "svn_props.h"
+#include "svn_hash.h"
+#include "svn_sorts.h"
+#include "client.h"
+#include "svn_ctype.h"
+
+#include "private/svn_client_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_ra_private.h"
+#include "private/svn_magic.h"
+
+#include "svn_private_config.h"
+
+
+
+/*** Code. ***/
+
+/* Remove leading and trailing white space from a C string, in place. */
+static void
+trim_string(char **pstr)
+{
+ char *str = *pstr;
+ size_t i;
+
+ while (svn_ctype_isspace(*str))
+ str++;
+ *pstr = str;
+ i = strlen(str);
+ while ((i > 0) && svn_ctype_isspace(str[i-1]))
+ i--;
+ str[i] = '\0';
+}
+
+/* Remove leading and trailing single- or double quotes from a C string,
+ * in place. */
+static void
+unquote_string(char **pstr)
+{
+ char *str = *pstr;
+ size_t i = strlen(str);
+
+ if (i > 0 && ((*str == '"' && str[i - 1] == '"') ||
+ (*str == '\'' && str[i - 1] == '\'')))
+ {
+ str[i - 1] = '\0';
+ str++;
+ }
+ *pstr = str;
+}
+
+/* Split PROPERTY and store each individual value in PROPS.
+ Allocates from POOL. */
+static void
+split_props(apr_array_header_t **props,
+ const char *property,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *temp_props;
+ char *new_prop;
+ int i = 0;
+ int j = 0;
+
+ temp_props = apr_array_make(pool, 4, sizeof(char *));
+ new_prop = apr_palloc(pool, strlen(property)+1);
+
+ for (i = 0; property[i] != '\0'; i++)
+ {
+ if (property[i] != ';')
+ {
+ new_prop[j] = property[i];
+ j++;
+ }
+ else if (property[i] == ';')
+ {
+ /* ";;" becomes ";" */
+ if (property[i+1] == ';')
+ {
+ new_prop[j] = ';';
+ j++;
+ i++;
+ }
+ else
+ {
+ new_prop[j] = '\0';
+ APR_ARRAY_PUSH(temp_props, char *) = new_prop;
+ new_prop += j + 1;
+ j = 0;
+ }
+ }
+ }
+ new_prop[j] = '\0';
+ APR_ARRAY_PUSH(temp_props, char *) = new_prop;
+ *props = temp_props;
+}
+
+/* PROPVALS is a hash mapping char * property names to const char * property
+ values. PROPERTIES can be empty but not NULL.
+
+ If FILENAME doesn't match the filename pattern PATTERN case insensitively,
+ the do nothing. Otherwise for each 'name':'value' pair in PROPVALS, add
+ a new entry mappying 'name' to a svn_string_t * wrapping the 'value' in
+ PROPERTIES. The svn_string_t is allocated in the pool used to allocate
+ PROPERTIES, but the char *'s from PROPVALS are re-used in PROPERTIES.
+ If PROPVALS contains a 'svn:mime-type' mapping, then set *MIMETYPE to
+ the mapped value. Likewise if PROPVALS contains a mapping for
+ svn:executable, then set *HAVE_EXECUTABLE to TRUE.
+
+ Use SCRATCH_POOL for temporary allocations.
+*/
+static void
+get_auto_props_for_pattern(apr_hash_t *properties,
+ const char **mimetype,
+ svn_boolean_t *have_executable,
+ const char *filename,
+ const char *pattern,
+ apr_hash_t *propvals,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ /* check if filename matches and return if it doesn't */
+ if (apr_fnmatch(pattern, filename,
+ APR_FNM_CASE_BLIND) == APR_FNM_NOMATCH)
+ return;
+
+ for (hi = apr_hash_first(scratch_pool, propvals);
+ hi != NULL;
+ hi = apr_hash_next(hi))
+ {
+ const char *propname = svn__apr_hash_index_key(hi);
+ const char *propval = svn__apr_hash_index_val(hi);
+ svn_string_t *propval_str =
+ svn_string_create_empty(apr_hash_pool_get(properties));
+
+ propval_str->data = propval;
+ propval_str->len = strlen(propval);
+
+ svn_hash_sets(properties, propname, propval_str);
+ if (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)
+ *mimetype = propval;
+ else if (strcmp(propname, SVN_PROP_EXECUTABLE) == 0)
+ *have_executable = TRUE;
+ }
+}
+
+svn_error_t *
+svn_client__get_paths_auto_props(apr_hash_t **properties,
+ const char **mimetype,
+ const char *path,
+ svn_magic__cookie_t *magic_cookie,
+ apr_hash_t *autoprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+ svn_boolean_t have_executable = FALSE;
+
+ *properties = apr_hash_make(result_pool);
+ *mimetype = NULL;
+
+ if (autoprops)
+ {
+ for (hi = apr_hash_first(scratch_pool, autoprops);
+ hi != NULL;
+ hi = apr_hash_next(hi))
+ {
+ const char *pattern = svn__apr_hash_index_key(hi);
+ apr_hash_t *propvals = svn__apr_hash_index_val(hi);
+
+ get_auto_props_for_pattern(*properties, mimetype, &have_executable,
+ svn_dirent_basename(path, scratch_pool),
+ pattern, propvals, scratch_pool);
+ }
+ }
+
+ /* if mimetype has not been set check the file */
+ if (! *mimetype)
+ {
+ SVN_ERR(svn_io_detect_mimetype2(mimetype, path, ctx->mimetypes_map,
+ result_pool));
+
+ /* If we got no mime-type, or if it is "application/octet-stream",
+ * try to get the mime-type from libmagic. */
+ if (magic_cookie &&
+ (!*mimetype ||
+ strcmp(*mimetype, "application/octet-stream") == 0))
+ {
+ const char *magic_mimetype;
+
+ /* Since libmagic usually treats UTF-16 files as "text/plain",
+ * svn_magic__detect_binary_mimetype() will return NULL for such
+ * files. This is fine for now since we currently don't support
+ * UTF-16-encoded text files (issue #2194).
+ * Once we do support UTF-16 this code path will fail to detect
+ * them as text unless the svn_io_detect_mimetype2() call above
+ * returns "text/plain" for them. */
+ SVN_ERR(svn_magic__detect_binary_mimetype(&magic_mimetype,
+ path, magic_cookie,
+ result_pool,
+ scratch_pool));
+ if (magic_mimetype)
+ *mimetype = magic_mimetype;
+ }
+
+ if (*mimetype)
+ apr_hash_set(*properties, SVN_PROP_MIME_TYPE,
+ strlen(SVN_PROP_MIME_TYPE),
+ svn_string_create(*mimetype, result_pool));
+ }
+
+ /* if executable has not been set check the file */
+ if (! have_executable)
+ {
+ svn_boolean_t executable = FALSE;
+ SVN_ERR(svn_io_is_file_executable(&executable, path, scratch_pool));
+ if (executable)
+ apr_hash_set(*properties, SVN_PROP_EXECUTABLE,
+ strlen(SVN_PROP_EXECUTABLE),
+ svn_string_create_empty(result_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Only call this if the on-disk node kind is a file. */
+static svn_error_t *
+add_file(const char *local_abspath,
+ svn_magic__cookie_t *magic_cookie,
+ apr_hash_t *autoprops,
+ svn_boolean_t no_autoprops,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_hash_t *properties;
+ const char *mimetype;
+ svn_node_kind_t kind;
+ svn_boolean_t is_special;
+
+ /* Check to see if this is a special file. */
+ SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special, pool));
+
+ /* Determine the properties that the file should have */
+ if (is_special)
+ {
+ mimetype = NULL;
+ properties = apr_hash_make(pool);
+ svn_hash_sets(properties, SVN_PROP_SPECIAL,
+ svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool));
+ }
+ else
+ {
+ apr_hash_t *file_autoprops = NULL;
+
+ /* Get automatic properties */
+ /* If we are setting autoprops grab the inherited svn:auto-props and
+ config file auto-props for this file if we haven't already got them
+ when iterating over the file's unversioned parents. */
+ if (!no_autoprops)
+ {
+ if (autoprops == NULL)
+ SVN_ERR(svn_client__get_all_auto_props(
+ &file_autoprops, svn_dirent_dirname(local_abspath,pool),
+ ctx, pool, pool));
+ else
+ file_autoprops = autoprops;
+ }
+
+ /* This may fail on write-only files:
+ we open them to estimate file type. */
+ SVN_ERR(svn_client__get_paths_auto_props(&properties, &mimetype,
+ local_abspath, magic_cookie,
+ file_autoprops, ctx, pool,
+ pool));
+ }
+
+ /* Add the file */
+ SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, local_abspath, properties,
+ ctx->notify_func2, ctx->notify_baton2, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Schedule directory DIR_ABSPATH, and some of the tree under it, for
+ * addition. DEPTH is the depth at this point in the descent (it may
+ * be changed for recursive calls).
+ *
+ * If DIR_ABSPATH (or any item below DIR_ABSPATH) is already scheduled for
+ * addition, add will fail and return an error unless FORCE is TRUE.
+ *
+ * Use MAGIC_COOKIE (which may be NULL) to detect the mime-type of files
+ * if necessary.
+ *
+ * If not NULL, CONFIG_AUTOPROPS is a hash representing the config file and
+ * svn:auto-props autoprops which apply to DIR_ABSPATH. It maps
+ * const char * file patterns to another hash which maps const char *
+ * property names to const char *property values. If CONFIG_AUTOPROPS is
+ * NULL and the config file and svn:auto-props autoprops are required by this
+ * function, then such will be obtained.
+ *
+ * If IGNORES is not NULL, then it is an array of const char * ignore patterns
+ * that apply to any children of DIR_ABSPATH. If REFRESH_IGNORES is TRUE, then
+ * the passed in value of IGNORES (if any) is itself ignored and this function
+ * will gather all ignore patterns applicable to DIR_ABSPATH itself (allocated in
+ * RESULT_POOL). Any recursive calls to this function get the refreshed ignore
+ * patterns. If IGNORES is NULL and REFRESH_IGNORES is FALSE, then all children of DIR_ABSPATH
+ * are unconditionally added.
+ *
+ * If CTX->CANCEL_FUNC is non-null, call it with CTX->CANCEL_BATON to allow
+ * the user to cancel the operation.
+ *
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+add_dir_recursive(const char *dir_abspath,
+ svn_depth_t depth,
+ svn_boolean_t force,
+ svn_boolean_t no_autoprops,
+ svn_magic__cookie_t *magic_cookie,
+ apr_hash_t *config_autoprops,
+ svn_boolean_t refresh_ignores,
+ apr_array_header_t *ignores,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ apr_pool_t *iterpool;
+ apr_hash_t *dirents;
+ apr_hash_index_t *hi;
+ svn_boolean_t entry_exists = FALSE;
+
+ /* Check cancellation; note that this catches recursive calls too. */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* Add this directory to revision control. */
+ err = svn_wc_add_from_disk2(ctx->wc_ctx, dir_abspath, NULL /*props*/,
+ ctx->notify_func2, ctx->notify_baton2,
+ iterpool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_ENTRY_EXISTS && force)
+ {
+ svn_error_clear(err);
+ entry_exists = TRUE;
+ }
+ else if (err)
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ /* Fetch ignores after adding to handle ignores on the directory itself
+ and ancestors via the single db optimization in libsvn_wc */
+ if (refresh_ignores)
+ SVN_ERR(svn_wc_get_ignores2(&ignores, ctx->wc_ctx, dir_abspath,
+ ctx->config, result_pool, iterpool));
+
+ /* If DIR_ABSPATH is the root of an unversioned subtree then get the
+ following "autoprops":
+
+ 1) Explicit and inherited svn:auto-props properties on
+ DIR_ABSPATH
+ 2) auto-props from the CTX->CONFIG hash
+
+ Since this set of autoprops applies to all unversioned children of
+ DIR_ABSPATH, we will pass these along to any recursive calls to
+ add_dir_recursive() and calls to add_file() below. Thus sparing
+ these callees from looking up the same information. */
+ if (!entry_exists && config_autoprops == NULL)
+ {
+ SVN_ERR(svn_client__get_all_auto_props(&config_autoprops, dir_abspath,
+ ctx, scratch_pool, iterpool));
+ }
+
+ SVN_ERR(svn_io_get_dirents3(&dirents, dir_abspath, TRUE, scratch_pool,
+ iterpool));
+
+ /* Read the directory entries one by one and add those things to
+ version control. */
+ for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi);
+ const char *abspath;
+
+ svn_pool_clear(iterpool);
+
+ /* Check cancellation so you can cancel during an
+ * add of a directory with lots of files. */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ /* Skip over SVN admin directories. */
+ if (svn_wc_is_adm_dir(name, iterpool))
+ continue;
+
+ if (ignores
+ && svn_wc_match_ignore_list(name, ignores, iterpool))
+ continue;
+
+ /* Construct the full path of the entry. */
+ abspath = svn_dirent_join(dir_abspath, name, iterpool);
+
+ /* Recurse on directories; add files; ignore the rest. */
+ if (dirent->kind == svn_node_dir && depth >= svn_depth_immediates)
+ {
+ svn_depth_t depth_below_here = depth;
+ if (depth == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ /* When DIR_ABSPATH is the root of an unversioned subtree then
+ it and all of its children have the same set of ignores. So
+ save any recursive calls the extra work of finding the same
+ set of ignores. */
+ if (refresh_ignores && !entry_exists)
+ refresh_ignores = FALSE;
+
+ SVN_ERR(add_dir_recursive(abspath, depth_below_here,
+ force, no_autoprops,
+ magic_cookie, config_autoprops,
+ refresh_ignores, ignores, ctx,
+ result_pool, iterpool));
+ }
+ else if ((dirent->kind == svn_node_file || dirent->special)
+ && depth >= svn_depth_files)
+ {
+ err = add_file(abspath, magic_cookie, config_autoprops,
+ no_autoprops, ctx, iterpool);
+ if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force)
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+ }
+ }
+
+ /* Destroy the per-iteration pool. */
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* This structure is used as baton for collecting the config entries
+ in the auto-props section and any inherited svn:auto-props
+ properties.
+*/
+typedef struct collect_auto_props_baton_t
+{
+ /* the hash table for storing the property name/value pairs */
+ apr_hash_t *autoprops;
+
+ /* a pool used for allocating memory */
+ apr_pool_t *result_pool;
+} collect_auto_props_baton_t;
+
+/* Implements svn_config_enumerator2_t callback.
+
+ For one auto-props config entry (NAME, VALUE), stash a copy of
+ NAME and VALUE, allocated in BATON->POOL, in BATON->AUTOPROP.
+ BATON must point to an collect_auto_props_baton_t.
+*/
+static svn_boolean_t
+all_auto_props_collector(const char *name,
+ const char *value,
+ void *baton,
+ apr_pool_t *pool)
+{
+ collect_auto_props_baton_t *autoprops_baton = baton;
+ apr_array_header_t *autoprops;
+ int i;
+
+ /* nothing to do here without a value */
+ if (*value == 0)
+ return TRUE;
+
+ split_props(&autoprops, value, pool);
+
+ for (i = 0; i < autoprops->nelts; i ++)
+ {
+ size_t len;
+ const char *this_value;
+ char *property = APR_ARRAY_IDX(autoprops, i, char *);
+ char *equal_sign = strchr(property, '=');
+
+ if (equal_sign)
+ {
+ *equal_sign = '\0';
+ equal_sign++;
+ trim_string(&equal_sign);
+ unquote_string(&equal_sign);
+ this_value = equal_sign;
+ }
+ else
+ {
+ this_value = "";
+ }
+ trim_string(&property);
+ len = strlen(property);
+
+ if (len > 0)
+ {
+ apr_hash_t *pattern_hash = svn_hash_gets(autoprops_baton->autoprops,
+ name);
+ svn_string_t *propval;
+
+ /* Force reserved boolean property values to '*'. */
+ if (svn_prop_is_boolean(property))
+ {
+ /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */
+ propval = svn_string_create("*", autoprops_baton->result_pool);
+ }
+ else
+ {
+ propval = svn_string_create(this_value,
+ autoprops_baton->result_pool);
+ }
+
+ if (!pattern_hash)
+ {
+ pattern_hash = apr_hash_make(autoprops_baton->result_pool);
+ svn_hash_sets(autoprops_baton->autoprops,
+ apr_pstrdup(autoprops_baton->result_pool, name),
+ pattern_hash);
+ }
+ svn_hash_sets(pattern_hash,
+ apr_pstrdup(autoprops_baton->result_pool, property),
+ propval->data);
+ }
+ }
+ return TRUE;
+}
+
+/* Go up the directory tree from LOCAL_ABSPATH, looking for a versioned
+ * directory. If found, return its path in *EXISTING_PARENT_ABSPATH.
+ * Otherwise, return SVN_ERR_CLIENT_NO_VERSIONED_PARENT. */
+static svn_error_t *
+find_existing_parent(const char **existing_parent_abspath,
+ svn_client_ctx_t *ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ const char *parent_abspath;
+ svn_wc_context_t *wc_ctx = ctx->wc_ctx;
+
+ SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, local_abspath,
+ FALSE, FALSE, scratch_pool));
+
+ if (kind == svn_node_dir)
+ {
+ *existing_parent_abspath = apr_pstrdup(result_pool, local_abspath);
+ return SVN_NO_ERROR;
+ }
+
+ if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
+ return svn_error_create(SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL, NULL);
+
+ if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, scratch_pool),
+ scratch_pool))
+ return svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, NULL,
+ _("'%s' ends in a reserved name"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ SVN_ERR(find_existing_parent(existing_parent_abspath, ctx, parent_abspath,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__get_all_auto_props(apr_hash_t **autoprops,
+ const char *path_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_array_header_t *inherited_config_auto_props;
+ apr_hash_t *props;
+ svn_opt_revision_t rev;
+ svn_string_t *config_auto_prop;
+ svn_boolean_t use_autoprops;
+ collect_auto_props_baton_t autoprops_baton;
+ svn_error_t *err = NULL;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_boolean_t target_is_url = svn_path_is_url(path_or_url);
+ svn_config_t *cfg = ctx->config ? svn_hash_gets(ctx->config,
+ SVN_CONFIG_CATEGORY_CONFIG)
+ : NULL;
+ *autoprops = apr_hash_make(result_pool);
+ autoprops_baton.result_pool = result_pool;
+ autoprops_baton.autoprops = *autoprops;
+
+
+ /* Are "traditional" auto-props enabled? If so grab them from the
+ config. This is our starting set auto-props, which may be overriden
+ by svn:auto-props. */
+ SVN_ERR(svn_config_get_bool(cfg, &use_autoprops,
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS, FALSE));
+ if (use_autoprops)
+ svn_config_enumerate2(cfg, SVN_CONFIG_SECTION_AUTO_PROPS,
+ all_auto_props_collector, &autoprops_baton,
+ scratch_pool);
+
+ /* Convert the config file setting (if any) into a hash mapping file
+ patterns to as hash of prop-->val mappings. */
+ if (svn_path_is_url(path_or_url))
+ rev.kind = svn_opt_revision_head;
+ else
+ rev.kind = svn_opt_revision_working;
+
+ /* If PATH_OR_URL is a WC path, then it might be unversioned, in which case
+ we find it's nearest versioned parent. */
+ do
+ {
+ err = svn_client_propget5(&props, &inherited_config_auto_props,
+ SVN_PROP_INHERITABLE_AUTO_PROPS, path_or_url,
+ &rev, &rev, NULL, svn_depth_empty, NULL, ctx,
+ scratch_pool, iterpool);
+ if (err)
+ {
+ if (target_is_url || err->apr_err != SVN_ERR_UNVERSIONED_RESOURCE)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ err = NULL;
+ SVN_ERR(find_existing_parent(&path_or_url, ctx, path_or_url,
+ scratch_pool, iterpool));
+ }
+ else
+ {
+ break;
+ }
+ }
+ while (err == NULL);
+
+ /* Stash any explicit PROPS for PARENT_PATH into the inherited props array,
+ since these are actually inherited props for LOCAL_ABSPATH. */
+ config_auto_prop = svn_hash_gets(props, path_or_url);
+
+ if (config_auto_prop)
+ {
+ svn_prop_inherited_item_t *new_iprop =
+ apr_palloc(scratch_pool, sizeof(*new_iprop));
+ new_iprop->path_or_url = path_or_url;
+ new_iprop->prop_hash = apr_hash_make(scratch_pool);
+ svn_hash_sets(new_iprop->prop_hash, SVN_PROP_INHERITABLE_AUTO_PROPS,
+ config_auto_prop);
+ APR_ARRAY_PUSH(inherited_config_auto_props,
+ svn_prop_inherited_item_t *) = new_iprop;
+ }
+
+ for (i = 0; i < inherited_config_auto_props->nelts; i++)
+ {
+ apr_hash_index_t *hi;
+ svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
+ inherited_config_auto_props, i, svn_prop_inherited_item_t *);
+
+ for (hi = apr_hash_first(scratch_pool, elt->prop_hash);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const svn_string_t *propval = svn__apr_hash_index_val(hi);
+ const char *ch = propval->data;
+ svn_stringbuf_t *config_auto_prop_pattern;
+ svn_stringbuf_t *config_auto_prop_val;
+
+ svn_pool_clear(iterpool);
+
+ config_auto_prop_pattern = svn_stringbuf_create_empty(iterpool);
+ config_auto_prop_val = svn_stringbuf_create_empty(iterpool);
+
+ /* Parse svn:auto-props value. */
+ while (*ch != '\0')
+ {
+ svn_stringbuf_setempty(config_auto_prop_pattern);
+ svn_stringbuf_setempty(config_auto_prop_val);
+
+ /* Parse the file pattern. */
+ while (*ch != '\0' && *ch != '=' && *ch != '\n')
+ {
+ svn_stringbuf_appendbyte(config_auto_prop_pattern, *ch);
+ ch++;
+ }
+
+ svn_stringbuf_strip_whitespace(config_auto_prop_pattern);
+
+ /* Parse the auto-prop group. */
+ while (*ch != '\0' && *ch != '\n')
+ {
+ svn_stringbuf_appendbyte(config_auto_prop_val, *ch);
+ ch++;
+ }
+
+ /* Strip leading '=' and whitespace from auto-prop group. */
+ if (config_auto_prop_val->data[0] == '=')
+ svn_stringbuf_remove(config_auto_prop_val, 0, 1);
+ svn_stringbuf_strip_whitespace(config_auto_prop_val);
+
+ all_auto_props_collector(config_auto_prop_pattern->data,
+ config_auto_prop_val->data,
+ &autoprops_baton,
+ scratch_pool);
+
+ /* Skip to next line if any. */
+ while (*ch != '\0' && *ch != '\n')
+ ch++;
+ if (*ch == '\n')
+ ch++;
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_client__get_inherited_ignores(apr_array_header_t **ignores,
+ const char *path_or_url,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_opt_revision_t rev;
+ apr_hash_t *explicit_ignores;
+ apr_array_header_t *inherited_ignores;
+ svn_boolean_t target_is_url = svn_path_is_url(path_or_url);
+ svn_string_t *explicit_prop;
+ int i;
+
+ if (target_is_url)
+ rev.kind = svn_opt_revision_head;
+ else
+ rev.kind = svn_opt_revision_working;
+
+ SVN_ERR(svn_client_propget5(&explicit_ignores, &inherited_ignores,
+ SVN_PROP_INHERITABLE_IGNORES, path_or_url,
+ &rev, &rev, NULL, svn_depth_empty, NULL, ctx,
+ scratch_pool, scratch_pool));
+
+ explicit_prop = svn_hash_gets(explicit_ignores, path_or_url);
+
+ if (explicit_prop)
+ {
+ svn_prop_inherited_item_t *new_iprop =
+ apr_palloc(scratch_pool, sizeof(*new_iprop));
+ new_iprop->path_or_url = path_or_url;
+ new_iprop->prop_hash = apr_hash_make(scratch_pool);
+ svn_hash_sets(new_iprop->prop_hash, SVN_PROP_INHERITABLE_IGNORES,
+ explicit_prop);
+ APR_ARRAY_PUSH(inherited_ignores,
+ svn_prop_inherited_item_t *) = new_iprop;
+ }
+
+ *ignores = apr_array_make(result_pool, 16, sizeof(const char *));
+
+ for (i = 0; i < inherited_ignores->nelts; i++)
+ {
+ svn_prop_inherited_item_t *elt = APR_ARRAY_IDX(
+ inherited_ignores, i, svn_prop_inherited_item_t *);
+ svn_string_t *ignore_val = svn_hash_gets(elt->prop_hash,
+ SVN_PROP_INHERITABLE_IGNORES);
+ if (ignore_val)
+ svn_cstring_split_append(*ignores, ignore_val->data, "\n\r\t\v ",
+ FALSE, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* The main logic of the public svn_client_add5.
+ *
+ * EXISTING_PARENT_ABSPATH is the absolute path to the first existing
+ * parent directory of local_abspath. If not NULL, all missing parents
+ * of LOCAL_ABSPATH must be created before LOCAL_ABSPATH can be added. */
+static svn_error_t *
+add(const char *local_abspath,
+ svn_depth_t depth,
+ svn_boolean_t force,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ const char *existing_parent_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ svn_error_t *err;
+ svn_magic__cookie_t *magic_cookie;
+ apr_array_header_t *ignores = NULL;
+
+ svn_magic__init(&magic_cookie, scratch_pool);
+
+ if (existing_parent_abspath)
+ {
+ const char *parent_abspath;
+ const char *child_relpath;
+ apr_array_header_t *components;
+ int i;
+ apr_pool_t *iterpool;
+
+ parent_abspath = existing_parent_abspath;
+ child_relpath = svn_dirent_is_child(existing_parent_abspath,
+ local_abspath, NULL);
+ components = svn_path_decompose(child_relpath, scratch_pool);
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < components->nelts - 1; i++)
+ {
+ const char *component;
+ svn_node_kind_t disk_kind;
+
+ svn_pool_clear(iterpool);
+
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ component = APR_ARRAY_IDX(components, i, const char *);
+ parent_abspath = svn_dirent_join(parent_abspath, component,
+ scratch_pool);
+ SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, iterpool));
+ if (disk_kind != svn_node_none && disk_kind != svn_node_dir)
+ return svn_error_createf(SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL,
+ _("'%s' prevents creating parent of '%s'"),
+ parent_abspath, local_abspath);
+
+ SVN_ERR(svn_io_make_dir_recursively(parent_abspath, scratch_pool));
+ SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, parent_abspath,
+ NULL /*props*/,
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool));
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool));
+ if (kind == svn_node_dir)
+ {
+ /* We use add_dir_recursive for all directory targets
+ and pass depth along no matter what it is, so that the
+ target's depth will be set correctly. */
+ err = add_dir_recursive(local_abspath, depth, force,
+ no_autoprops, magic_cookie, NULL,
+ !no_ignore, ignores, ctx,
+ scratch_pool, scratch_pool);
+ }
+ else if (kind == svn_node_file)
+ err = add_file(local_abspath, magic_cookie, NULL,
+ no_autoprops, ctx, scratch_pool);
+ else if (kind == svn_node_none)
+ {
+ svn_boolean_t tree_conflicted;
+
+ /* Provide a meaningful error message if the node does not exist
+ * on disk but is a tree conflict victim. */
+ err = svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted,
+ ctx->wc_ctx, local_abspath,
+ scratch_pool);
+ if (err)
+ svn_error_clear(err);
+ else if (tree_conflicted)
+ return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
+ _("'%s' is an existing item in conflict; "
+ "please mark the conflict as resolved "
+ "before adding a new item here"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("'%s' not found"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ else
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Unsupported node kind for path '%s'"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ /* Ignore SVN_ERR_ENTRY_EXISTS when FORCE is set. */
+ if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force)
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+}
+
+
+
+svn_error_t *
+svn_client_add5(const char *path,
+ svn_depth_t depth,
+ svn_boolean_t force,
+ svn_boolean_t no_ignore,
+ svn_boolean_t no_autoprops,
+ svn_boolean_t add_parents,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const char *parent_abspath;
+ const char *local_abspath;
+ const char *existing_parent_abspath;
+ svn_boolean_t is_wc_root;
+ svn_error_t *err;
+
+ 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));
+
+ /* See if we're being asked to add a wc-root. That's typically not
+ okay, unless we're in "force" mode. svn_wc__is_wcroot()
+ will return TRUE even if LOCAL_ABSPATH is a *symlink* to a working
+ copy root, which is a scenario we want to treat differently. */
+ err = svn_wc__is_wcroot(&is_wc_root, ctx->wc_ctx, local_abspath,
+ scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
+ && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+ {
+ return svn_error_trace(err);
+ }
+
+ svn_error_clear(err);
+ err = NULL; /* SVN_NO_ERROR */
+ is_wc_root = FALSE;
+ }
+ if (is_wc_root)
+ {
+#ifdef HAVE_SYMLINK
+ svn_node_kind_t disk_kind;
+ svn_boolean_t is_special;
+
+ SVN_ERR(svn_io_check_special_path(local_abspath, &disk_kind, &is_special,
+ scratch_pool));
+
+ /* A symlink can be an unversioned target and a wcroot. Lets try to add
+ the symlink, which can't be a wcroot. */
+ if (is_special)
+ is_wc_root = FALSE;
+ else
+#endif
+ {
+ if (! force)
+ return svn_error_createf(
+ SVN_ERR_ENTRY_EXISTS, NULL,
+ _("'%s' is already under version control"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ }
+
+ if (is_wc_root)
+ parent_abspath = local_abspath; /* We will only add children */
+ else
+ parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+
+ existing_parent_abspath = NULL;
+ if (add_parents && !is_wc_root)
+ {
+ apr_pool_t *subpool;
+ const char *existing_parent_abspath2;
+
+ subpool = svn_pool_create(scratch_pool);
+ SVN_ERR(find_existing_parent(&existing_parent_abspath2, ctx,
+ parent_abspath, scratch_pool, subpool));
+ if (strcmp(existing_parent_abspath2, parent_abspath) != 0)
+ existing_parent_abspath = existing_parent_abspath2;
+ svn_pool_destroy(subpool);
+ }
+
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ add(local_abspath, depth, force, no_ignore, no_autoprops,
+ existing_parent_abspath, ctx, scratch_pool),
+ ctx->wc_ctx, (existing_parent_abspath ? existing_parent_abspath
+ : parent_abspath),
+ FALSE /* lock_anchor */, scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+path_driver_cb_func(void **dir_baton,
+ void *parent_baton,
+ void *callback_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const svn_delta_editor_t *editor = callback_baton;
+ SVN_ERR(svn_path_check_valid(path, pool));
+ return editor->add_directory(path, parent_baton, NULL,
+ SVN_INVALID_REVNUM, pool, dir_baton);
+}
+
+/* Append URL, and all it's non-existent parent directories, to TARGETS.
+ Use TEMPPOOL for temporary allocations and POOL for any additions to
+ TARGETS. */
+static svn_error_t *
+add_url_parents(svn_ra_session_t *ra_session,
+ const char *url,
+ apr_array_header_t *targets,
+ apr_pool_t *temppool,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ const char *parent_url = svn_uri_dirname(url, pool);
+
+ SVN_ERR(svn_ra_reparent(ra_session, parent_url, temppool));
+ SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
+ temppool));
+
+ if (kind == svn_node_none)
+ SVN_ERR(add_url_parents(ra_session, parent_url, targets, temppool, pool));
+
+ APR_ARRAY_PUSH(targets, const char *) = url;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+mkdir_urls(const apr_array_header_t *urls,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *ra_session = NULL;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ const char *log_msg;
+ apr_array_header_t *targets;
+ apr_hash_t *targets_hash;
+ apr_hash_t *commit_revprops;
+ svn_error_t *err;
+ const char *common;
+ int i;
+
+ /* Find any non-existent parent directories */
+ if (make_parents)
+ {
+ apr_array_header_t *all_urls = apr_array_make(pool, urls->nelts,
+ sizeof(const char *));
+ const char *first_url = APR_ARRAY_IDX(urls, 0, const char *);
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, first_url, NULL,
+ ctx, pool, iterpool));
+
+ for (i = 0; i < urls->nelts; i++)
+ {
+ const char *url = APR_ARRAY_IDX(urls, i, const char *);
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(add_url_parents(ra_session, url, all_urls, iterpool, pool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ urls = all_urls;
+ }
+
+ /* Condense our list of mkdir targets. */
+ SVN_ERR(svn_uri_condense_targets(&common, &targets, urls, FALSE,
+ pool, pool));
+
+ /*Remove duplicate targets introduced by make_parents with more targets. */
+ SVN_ERR(svn_hash_from_cstring_keys(&targets_hash, targets, pool));
+ SVN_ERR(svn_hash_keys(&targets, targets_hash, pool));
+
+ if (! targets->nelts)
+ {
+ const char *bname;
+ svn_uri_split(&common, &bname, common, pool);
+ APR_ARRAY_PUSH(targets, const char *) = bname;
+
+ if (*bname == '\0')
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("There is no valid URI above '%s'"),
+ common);
+ }
+ else
+ {
+ svn_boolean_t resplit = FALSE;
+
+ /* We can't "mkdir" the root of an editor drive, so if one of
+ our targets is the empty string, we need to back everything
+ up by a path component. */
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(targets, i, const char *);
+ if (! *path)
+ {
+ resplit = TRUE;
+ break;
+ }
+ }
+ if (resplit)
+ {
+ const char *bname;
+
+ svn_uri_split(&common, &bname, common, pool);
+
+ if (*bname == '\0')
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("There is no valid URI above '%s'"),
+ common);
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(targets, i, const char *);
+ path = svn_relpath_join(bname, path, pool);
+ APR_ARRAY_IDX(targets, i, const char *) = path;
+ }
+ }
+ }
+ qsort(targets->elts, targets->nelts, targets->elt_size,
+ svn_sort_compare_paths);
+
+ /* ### This reparent may be problematic in limited-authz-to-common-parent
+ ### scenarios (compare issue #3242). See also issue #3649. */
+ if (ra_session)
+ SVN_ERR(svn_ra_reparent(ra_session, common, pool));
+
+ /* Create new commit items and add them to the array. */
+ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
+ {
+ svn_client_commit_item3_t *item;
+ const char *tmp_file;
+ apr_array_header_t *commit_items
+ = apr_array_make(pool, targets->nelts, sizeof(item));
+
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(targets, i, const char *);
+
+ item = svn_client_commit_item3_create(pool);
+ item->url = svn_path_url_add_component2(common, path, pool);
+ 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, pool));
+
+ if (! log_msg)
+ return SVN_NO_ERROR;
+ }
+ else
+ log_msg = "";
+
+ SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
+ log_msg, ctx, pool));
+
+ /* Open an RA session for the URL. Note that we don't have a local
+ directory, nor a place to put temp files. */
+ if (!ra_session)
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, common, NULL, ctx,
+ pool, pool));
+ else
+ SVN_ERR(svn_ra_reparent(ra_session, common, pool));
+
+
+ /* Fetch RA commit editor */
+ SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
+ svn_client__get_shim_callbacks(ctx->wc_ctx, NULL,
+ pool)));
+ SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
+ commit_revprops,
+ commit_callback,
+ commit_baton,
+ NULL, TRUE, /* No lock tokens */
+ pool));
+
+ /* Call the path-based editor driver. */
+ err = svn_delta_path_driver2(editor, edit_baton, targets, TRUE,
+ path_driver_cb_func, (void *)editor, pool);
+
+ if (err)
+ {
+ /* At least try to abort the edit (and fs txn) before throwing err. */
+ return svn_error_compose_create(
+ err,
+ editor->abort_edit(edit_baton, pool));
+ }
+
+ /* Close the edit. */
+ return editor->close_edit(edit_baton, pool);
+}
+
+
+
+svn_error_t *
+svn_client__make_local_parents(const char *path,
+ svn_boolean_t make_parents,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_node_kind_t orig_kind;
+ SVN_ERR(svn_io_check_path(path, &orig_kind, pool));
+ if (make_parents)
+ SVN_ERR(svn_io_make_dir_recursively(path, pool));
+ else
+ SVN_ERR(svn_io_dir_make(path, APR_OS_DEFAULT, pool));
+
+ /* Should no longer use svn_depth_empty to indicate that only the directory
+ itself is added, since it not only constraints the operation depth, but
+ also defines the depth of the target directory now. Moreover, the new
+ directory will have no children at all.*/
+ err = svn_client_add5(path, svn_depth_infinity, FALSE, FALSE, FALSE,
+ make_parents, ctx, pool);
+
+ /* If we created a new directory, but couldn't add it to version
+ control, then delete it. */
+ if (err && (orig_kind == svn_node_none))
+ {
+ /* ### If this returns an error, should we link it onto
+ err instead, so that the user is warned that we just
+ created an unversioned directory? */
+
+ svn_error_clear(svn_io_remove_dir2(path, FALSE, NULL, NULL, pool));
+ }
+
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_client_mkdir4(const apr_array_header_t *paths,
+ svn_boolean_t make_parents,
+ const apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ if (! paths->nelts)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_client__assert_homogeneous_target_type(paths));
+
+ if (svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *)))
+ {
+ SVN_ERR(mkdir_urls(paths, make_parents, revprop_table, commit_callback,
+ commit_baton, ctx, pool));
+ }
+ else
+ {
+ /* This is a regular "mkdir" + "svn add" */
+ apr_pool_t *subpool = svn_pool_create(pool);
+ int i;
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+
+ svn_pool_clear(subpool);
+
+ /* See if the user wants us to stop. */
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
+ SVN_ERR(svn_client__make_local_parents(path, make_parents, ctx,
+ subpool));
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
OpenPOWER on IntegriCloud