| /* |
| * add.c: wrappers around wc add/mkdir functionality. |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2007 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| /* ==================================================================== */ |
| |
| |
| |
| /*** 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_path.h" |
| #include "svn_io.h" |
| #include "svn_config.h" |
| #include "svn_props.h" |
| #include "client.h" |
| |
| #include "svn_private_config.h" |
| |
| |
| |
| /*** Code. ***/ |
| |
| /* This structure is used as baton for enumerating the config entries |
| in the auto-props section. |
| */ |
| typedef struct |
| { |
| /* the file name for which properties are searched */ |
| const char *filename; |
| |
| /* when this flag is set the hash contains svn:executable */ |
| svn_boolean_t have_executable; |
| |
| /* when mimetype is not NULL is set the hash contains svn:mime-type */ |
| const char *mimetype; |
| |
| /* the hash table for storing the property name/value pairs */ |
| apr_hash_t *properties; |
| |
| /* a pool used for allocating memory */ |
| apr_pool_t *pool; |
| } auto_props_baton_t; |
| |
| /* Remove leading and trailing white space from a C string, in place. */ |
| static void |
| trim_string(char **pstr) |
| { |
| char *str = *pstr; |
| int i; |
| |
| while (apr_isspace(*str)) |
| str++; |
| *pstr = str; |
| i = strlen(str); |
| while ((i > 0) && apr_isspace(str[i-1])) |
| i--; |
| str[i] = '\0'; |
| } |
| |
| /* For one auto-props config entry (NAME, VALUE), if the filename pattern |
| NAME matches BATON->filename case insensitively then add the properties |
| listed in VALUE into BATON->properties. |
| BATON must point to an auto_props_baton_t. |
| */ |
| static svn_boolean_t |
| auto_props_enumerator(const char *name, |
| const char *value, |
| void *baton, |
| apr_pool_t *pool) |
| { |
| auto_props_baton_t *autoprops = baton; |
| char *property; |
| char *last_token; |
| |
| /* nothing to do here without a value */ |
| if (strlen(value) == 0) |
| return TRUE; |
| |
| /* check if filename matches and return if it doesn't */ |
| if (apr_fnmatch(name, autoprops->filename, APR_FNM_CASE_BLIND) == APR_FNM_NOMATCH) |
| return TRUE; |
| |
| /* parse the value (we dup it first to effectively lose the |
| 'const', and to avoid messing up the original value) */ |
| property = apr_pstrdup(autoprops->pool, value); |
| property = apr_strtok(property, ";", &last_token); |
| while (property) |
| { |
| int len; |
| const char *this_value; |
| char *equal_sign = strchr(property, '='); |
| |
| if (equal_sign) |
| { |
| *equal_sign = '\0'; |
| equal_sign++; |
| trim_string(&equal_sign); |
| this_value = equal_sign; |
| } |
| else |
| { |
| this_value = ""; |
| } |
| trim_string(&property); |
| len = strlen(property); |
| if (len > 0) |
| { |
| svn_string_t *propval = svn_string_create(this_value, |
| autoprops->pool); |
| |
| apr_hash_set(autoprops->properties, property, len, propval); |
| if (strcmp(property, SVN_PROP_MIME_TYPE) == 0) |
| autoprops->mimetype = this_value; |
| else if (strcmp(property, SVN_PROP_EXECUTABLE) == 0) |
| autoprops->have_executable = TRUE; |
| } |
| property = apr_strtok(NULL, ";", &last_token); |
| } |
| return TRUE; |
| } |
| |
| svn_error_t * |
| svn_client__get_auto_props(apr_hash_t **properties, |
| const char **mimetype, |
| const char *path, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| svn_config_t *cfg; |
| svn_boolean_t use_autoprops; |
| auto_props_baton_t autoprops; |
| |
| /* initialisation */ |
| autoprops.properties = apr_hash_make(pool); |
| autoprops.filename = svn_path_basename(path, pool); |
| autoprops.pool = pool; |
| autoprops.mimetype = NULL; |
| autoprops.have_executable = FALSE; |
| *properties = autoprops.properties; |
| |
| cfg = ctx->config ? apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG, |
| APR_HASH_KEY_STRING) : NULL; |
| |
| /* check that auto props is enabled */ |
| SVN_ERR(svn_config_get_bool(cfg, &use_autoprops, |
| SVN_CONFIG_SECTION_MISCELLANY, |
| SVN_CONFIG_OPTION_ENABLE_AUTO_PROPS, FALSE)); |
| |
| /* search for auto props */ |
| if (use_autoprops) |
| svn_config_enumerate2(cfg, SVN_CONFIG_SECTION_AUTO_PROPS, |
| auto_props_enumerator, &autoprops, pool); |
| |
| /* if mimetype has not been set check the file */ |
| if (! autoprops.mimetype) |
| { |
| SVN_ERR(svn_io_detect_mimetype2(&autoprops.mimetype, path, |
| ctx->mimetypes_map, pool)); |
| if (autoprops.mimetype) |
| apr_hash_set(autoprops.properties, SVN_PROP_MIME_TYPE, |
| strlen(SVN_PROP_MIME_TYPE), |
| svn_string_create(autoprops.mimetype, pool)); |
| } |
| |
| /* Don't automatically set the svn:executable property on added items |
| * on OS400. While OS400 supports the executable permission its use is |
| * inconsistent at best. */ |
| #ifndef AS400 |
| /* if executable has not been set check the file */ |
| if (! autoprops.have_executable) |
| { |
| svn_boolean_t executable = FALSE; |
| SVN_ERR(svn_io_is_file_executable(&executable, path, pool)); |
| if (executable) |
| apr_hash_set(autoprops.properties, SVN_PROP_EXECUTABLE, |
| strlen(SVN_PROP_EXECUTABLE), |
| svn_string_create("", pool)); |
| } |
| #endif |
| |
| *mimetype = autoprops.mimetype; |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| add_file(const char *path, |
| svn_client_ctx_t *ctx, |
| svn_wc_adm_access_t *adm_access, |
| apr_pool_t *pool) |
| { |
| apr_hash_t* properties; |
| apr_hash_index_t *hi; |
| 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(path, &kind, &is_special, pool)); |
| |
| if (is_special) |
| mimetype = NULL; |
| else |
| /* Get automatic properties */ |
| /* This may fail on write-only files: |
| we open them to estimate file type. |
| That's why we postpone the add until after this step. */ |
| SVN_ERR(svn_client__get_auto_props(&properties, &mimetype, path, ctx, |
| pool)); |
| |
| /* Add the file */ |
| SVN_ERR(svn_wc_add2(path, adm_access, NULL, SVN_INVALID_REVNUM, |
| ctx->cancel_func, ctx->cancel_baton, |
| NULL, NULL, pool)); |
| |
| if (is_special) |
| /* This must be a special file. */ |
| SVN_ERR(svn_wc_prop_set2 |
| (SVN_PROP_SPECIAL, |
| svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool), |
| path, adm_access, FALSE, pool)); |
| else if (properties) |
| { |
| /* loop through the hashtable and add the properties */ |
| for (hi = apr_hash_first(pool, properties); |
| hi != NULL; hi = apr_hash_next(hi)) |
| { |
| const void *pname; |
| void *pval; |
| |
| apr_hash_this(hi, &pname, NULL, &pval); |
| /* It's probably best to pass 0 for force, so that if |
| the autoprops say to set some weird combination, |
| we just error and let the user sort it out. */ |
| SVN_ERR(svn_wc_prop_set2(pname, pval, path, |
| adm_access, FALSE, pool)); |
| } |
| } |
| |
| /* Report the addition to the caller. */ |
| if (ctx->notify_func2 != NULL) |
| { |
| svn_wc_notify_t *notify = svn_wc_create_notify(path, svn_wc_notify_add, |
| pool); |
| notify->kind = svn_node_file; |
| notify->mime_type = mimetype; |
| (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Schedule directory DIRNAME, and some of the tree under it, for |
| * addition with access baton ADM_ACCESS. DEPTH is the depth at this |
| * point in the descent (it may be changed for recursive calls). |
| * |
| * If DIRNAME (or any item below directory DIRNAME) is already scheduled for |
| * addition, add will fail and return an error unless FORCE is TRUE. |
| * |
| * Files and directories that match ignore patterns will not be added unless |
| * NO_IGNORE is TRUE. |
| * |
| * If CTX->CANCEL_FUNC is non-null, call it with CTX->CANCEL_BATON to allow |
| * the user to cancel the operation |
| */ |
| static svn_error_t * |
| add_dir_recursive(const char *dirname, |
| svn_wc_adm_access_t *adm_access, |
| svn_depth_t depth, |
| svn_boolean_t force, |
| svn_boolean_t no_ignore, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| apr_dir_t *dir; |
| apr_finfo_t this_entry; |
| svn_error_t *err; |
| apr_pool_t *subpool; |
| apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME; |
| svn_wc_adm_access_t *dir_access; |
| apr_array_header_t *ignores; |
| |
| /* Check cancellation; note that this catches recursive calls too. */ |
| if (ctx->cancel_func) |
| SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); |
| |
| /* Add this directory to revision control. */ |
| err = svn_wc_add2(dirname, adm_access, |
| NULL, SVN_INVALID_REVNUM, |
| ctx->cancel_func, ctx->cancel_baton, |
| ctx->notify_func2, ctx->notify_baton2, pool); |
| if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force) |
| svn_error_clear(err); |
| else if (err) |
| return err; |
| |
| SVN_ERR(svn_wc_adm_retrieve(&dir_access, adm_access, dirname, pool)); |
| |
| if (!no_ignore) |
| SVN_ERR(svn_wc_get_ignores(&ignores, ctx->config, dir_access, pool)); |
| |
| subpool = svn_pool_create(pool); |
| |
| SVN_ERR(svn_io_dir_open(&dir, dirname, pool)); |
| |
| /* Read the directory entries one by one and add those things to |
| version control. */ |
| while (1) |
| { |
| const char *fullpath; |
| |
| svn_pool_clear(subpool); |
| |
| err = svn_io_dir_read(&this_entry, flags, dir, subpool); |
| |
| if (err) |
| { |
| /* Check if we're done reading the dir's entries. */ |
| if (APR_STATUS_IS_ENOENT(err->apr_err)) |
| { |
| apr_status_t apr_err; |
| |
| svn_error_clear(err); |
| apr_err = apr_dir_close(dir); |
| if (apr_err) |
| return svn_error_wrap_apr |
| (apr_err, _("Can't close directory '%s'"), |
| svn_path_local_style(dirname, subpool)); |
| break; |
| } |
| else |
| { |
| return svn_error_createf |
| (err->apr_err, err, |
| _("Error during add of '%s'"), |
| svn_path_local_style(dirname, subpool)); |
| } |
| } |
| |
| /* Skip entries for this dir and its parent. */ |
| if (this_entry.name[0] == '.' |
| && (this_entry.name[1] == '\0' |
| || (this_entry.name[1] == '.' && this_entry.name[2] == '\0'))) |
| continue; |
| |
| /* 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(this_entry.name, subpool)) |
| continue; |
| |
| if ((!no_ignore) && svn_wc_match_ignore_list(this_entry.name, |
| ignores, subpool)) |
| continue; |
| |
| /* Construct the full path of the entry. */ |
| fullpath = svn_path_join(dirname, this_entry.name, subpool); |
| |
| /* Recurse on directories; add files; ignore the rest. */ |
| if (this_entry.filetype == APR_DIR && depth >= svn_depth_immediates) |
| { |
| svn_depth_t depth_below_here = depth; |
| if (depth == svn_depth_immediates) |
| depth_below_here = svn_depth_empty; |
| |
| SVN_ERR(add_dir_recursive(fullpath, dir_access, depth_below_here, |
| force, no_ignore, ctx, subpool)); |
| } |
| else if (this_entry.filetype != APR_UNKFILE |
| && this_entry.filetype != APR_DIR |
| && depth >= svn_depth_files) |
| { |
| err = add_file(fullpath, ctx, dir_access, subpool); |
| if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force) |
| svn_error_clear(err); |
| else if (err) |
| return err; |
| } |
| } |
| |
| /* Opened by svn_wc_add */ |
| SVN_ERR(svn_wc_adm_close(dir_access)); |
| |
| /* Destroy the per-iteration pool. */ |
| svn_pool_destroy(subpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* The main logic of the public svn_client_add4; the only difference |
| is that this function uses an existing access baton. |
| (svn_client_add4 just generates an access baton and calls this func.) */ |
| static svn_error_t * |
| add(const char *path, |
| svn_depth_t depth, |
| svn_boolean_t force, |
| svn_boolean_t no_ignore, |
| svn_wc_adm_access_t *adm_access, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| svn_node_kind_t kind; |
| svn_error_t *err; |
| |
| SVN_ERR(svn_io_check_path(path, &kind, pool)); |
| if (kind == svn_node_dir && depth >= svn_depth_files) |
| err = add_dir_recursive(path, adm_access, depth, |
| force, no_ignore, ctx, pool); |
| else if (kind == svn_node_file) |
| err = add_file(path, ctx, adm_access, pool); |
| else |
| err = svn_wc_add2(path, adm_access, NULL, SVN_INVALID_REVNUM, |
| ctx->cancel_func, ctx->cancel_baton, |
| ctx->notify_func2, ctx->notify_baton2, 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 err; |
| } |
| |
| |
| /* Go up the directory tree, looking for a versioned directory. If found, |
| add all the intermediate directories. Otherwise, return |
| SVN_ERR_CLIENT_NO_VERSIONED_PARENT. */ |
| static svn_error_t * |
| add_parent_dirs(const char *path, |
| svn_wc_adm_access_t **parent_access, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| svn_wc_adm_access_t *adm_access; |
| svn_error_t *err; |
| |
| err = svn_wc_adm_open3(&adm_access, NULL, path, TRUE, 0, |
| ctx->cancel_func, ctx->cancel_baton, pool); |
| |
| if (err && err->apr_err == SVN_ERR_WC_NOT_DIRECTORY) |
| { |
| if (svn_dirent_is_root(path, strlen(path))) |
| { |
| svn_error_clear(err); |
| |
| return svn_error_create |
| (SVN_ERR_CLIENT_NO_VERSIONED_PARENT, NULL, NULL); |
| } |
| else |
| { |
| const char *parent_path = svn_path_dirname(path, pool); |
| |
| svn_error_clear(err); |
| SVN_ERR(add_parent_dirs(parent_path, &adm_access, ctx, pool)); |
| SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, parent_path, |
| pool)); |
| SVN_ERR(svn_wc_add2(path, adm_access, NULL, SVN_INVALID_REVNUM, |
| ctx->cancel_func, ctx->cancel_baton, |
| ctx->notify_func2, ctx->notify_baton2, pool)); |
| } |
| } |
| else if (err) |
| { |
| return err; |
| } |
| |
| if (parent_access) |
| *parent_access = adm_access; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| svn_error_t * |
| svn_client_add4(const char *path, |
| svn_depth_t depth, |
| svn_boolean_t force, |
| svn_boolean_t no_ignore, |
| svn_boolean_t add_parents, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| svn_error_t *err, *err2; |
| svn_wc_adm_access_t *adm_access; |
| const char *parent_dir; |
| |
| if (add_parents) |
| { |
| apr_pool_t *subpool; |
| |
| SVN_ERR(svn_path_get_absolute(&path, path, pool)); |
| parent_dir = svn_path_dirname(path, pool); |
| |
| subpool = svn_pool_create(pool); |
| SVN_ERR(add_parent_dirs(parent_dir, &adm_access, ctx, subpool)); |
| SVN_ERR(svn_wc_adm_close(adm_access)); |
| svn_pool_destroy(subpool); |
| |
| SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, parent_dir, |
| TRUE, 0, ctx->cancel_func, ctx->cancel_baton, |
| pool)); |
| } |
| else |
| { |
| parent_dir = svn_path_dirname(path, pool); |
| SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, parent_dir, |
| TRUE, 0, ctx->cancel_func, ctx->cancel_baton, |
| pool)); |
| } |
| |
| err = add(path, depth, force, no_ignore, adm_access, ctx, pool); |
| |
| err2 = svn_wc_adm_close(adm_access); |
| if (err2) |
| { |
| if (err) |
| svn_error_clear(err2); |
| else |
| err = err2; |
| } |
| |
| return err; |
| } |
| |
| svn_error_t * |
| svn_client_add3(const char *path, |
| svn_boolean_t recursive, |
| svn_boolean_t force, |
| svn_boolean_t no_ignore, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| return svn_client_add4(path, SVN_DEPTH_INFINITY_OR_FILES(recursive), |
| force, no_ignore, FALSE, ctx, |
| pool); |
| } |
| |
| svn_error_t * |
| svn_client_add2(const char *path, |
| svn_boolean_t recursive, |
| svn_boolean_t force, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| return svn_client_add3(path, recursive, force, FALSE, ctx, pool); |
| } |
| |
| svn_error_t * |
| svn_client_add(const char *path, |
| svn_boolean_t recursive, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| return svn_client_add3(path, recursive, FALSE, FALSE, ctx, pool); |
| } |
| |
| |
| 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_path_split(url, &parent_url, NULL, 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(svn_commit_info_t **commit_info_p, |
| const apr_array_header_t *urls, |
| svn_boolean_t make_parents, |
| 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; |
| void *commit_baton; |
| const char *log_msg; |
| apr_hash_t *revprop_table; |
| apr_array_header_t *targets; |
| 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_session_internal(&ra_session, first_url, |
| NULL, NULL, NULL, FALSE, |
| TRUE, ctx, pool)); |
| |
| 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_path_condense_targets(&common, &targets, urls, FALSE, pool)); |
| |
| if (! targets->nelts) |
| { |
| const char *bname; |
| svn_path_split(common, &common, &bname, pool); |
| APR_ARRAY_PUSH(targets, const char *) = bname; |
| } |
| 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_path_split(common, &common, &bname, pool); |
| for (i = 0; i < targets->nelts; i++) |
| { |
| const char *path = APR_ARRAY_IDX(targets, i, const char *); |
| path = svn_path_join(bname, path, pool); |
| APR_ARRAY_IDX(targets, i, const char *) = path; |
| } |
| } |
| } |
| |
| /* 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 *); |
| SVN_ERR(svn_client_commit_item_create |
| ((const svn_client_commit_item3_t **) &item, pool)); |
| item->url = svn_path_join(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__get_revprop_table(&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_session_internal(&ra_session, common, NULL, |
| NULL, NULL, FALSE, TRUE, |
| ctx, pool)); |
| |
| /* URI-decode each target. */ |
| for (i = 0; i < targets->nelts; i++) |
| { |
| const char *path = APR_ARRAY_IDX(targets, i, const char *); |
| path = svn_path_uri_decode(path, pool); |
| APR_ARRAY_IDX(targets, i, const char *) = path; |
| } |
| |
| /* Fetch RA commit editor */ |
| SVN_ERR(svn_client__commit_get_baton(&commit_baton, commit_info_p, pool)); |
| SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton, |
| revprop_table, |
| svn_client__commit_callback, |
| commit_baton, |
| NULL, TRUE, /* No lock tokens */ |
| pool)); |
| |
| /* Call the path-based editor driver. */ |
| err = svn_delta_path_driver(editor, edit_baton, SVN_INVALID_REVNUM, |
| targets, path_driver_cb_func, |
| (void *)editor, pool); |
| if (err) |
| { |
| /* At least try to abort the edit (and fs txn) before throwing err. */ |
| svn_error_clear(editor->abort_edit(edit_baton, pool)); |
| return err; |
| } |
| |
| /* Close the edit. */ |
| SVN_ERR(editor->close_edit(edit_baton, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| 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; |
| |
| if (make_parents) |
| SVN_ERR(svn_io_make_dir_recursively(path, pool)); |
| else |
| SVN_ERR(svn_io_dir_make(path, APR_OS_DEFAULT, pool)); |
| |
| err = svn_client_add4(path, svn_depth_empty, FALSE, FALSE, |
| make_parents, ctx, pool); |
| |
| /* We just created a new directory, but couldn't add it to |
| version control. Don't leave unversioned directories behind. */ |
| if (err) |
| { |
| /* ### 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_dir(path, pool)); |
| } |
| |
| return err; |
| } |
| |
| |
| svn_error_t * |
| svn_client_mkdir3(svn_commit_info_t **commit_info_p, |
| const apr_array_header_t *paths, |
| svn_boolean_t make_parents, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| if (! paths->nelts) |
| return SVN_NO_ERROR; |
| |
| if (svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *))) |
| { |
| SVN_ERR(mkdir_urls(commit_info_p, paths, make_parents, 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; |
| } |
| |
| |
| svn_error_t * |
| svn_client_mkdir2(svn_commit_info_t **commit_info_p, |
| const apr_array_header_t *paths, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| return svn_client_mkdir3(commit_info_p, paths, FALSE, ctx, pool); |
| } |
| |
| |
| svn_error_t * |
| svn_client_mkdir(svn_client_commit_info_t **commit_info_p, |
| const apr_array_header_t *paths, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| svn_commit_info_t *commit_info = NULL; |
| svn_error_t *err; |
| |
| err = svn_client_mkdir2(&commit_info, paths, ctx, pool); |
| /* These structs have the same layout for the common fields. */ |
| *commit_info_p = (svn_client_commit_info_t *) commit_info; |
| return err; |
| } |