diff options
Diffstat (limited to 'subversion/libsvn_client/commit.c')
-rw-r--r-- | subversion/libsvn_client/commit.c | 1085 |
1 files changed, 326 insertions, 759 deletions
diff --git a/subversion/libsvn_client/commit.c b/subversion/libsvn_client/commit.c index 13071ea..07fdce1 100644 --- a/subversion/libsvn_client/commit.c +++ b/subversion/libsvn_client/commit.c @@ -30,11 +30,9 @@ #include <string.h> #include <apr_strings.h> #include <apr_hash.h> -#include <apr_md5.h> +#include "svn_hash.h" #include "svn_wc.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" @@ -42,566 +40,14 @@ #include "svn_error_codes.h" #include "svn_dirent_uri.h" #include "svn_path.h" -#include "svn_io.h" -#include "svn_time.h" #include "svn_sorts.h" -#include "svn_props.h" #include "client.h" #include "private/svn_wc_private.h" -#include "private/svn_magic.h" +#include "private/svn_ra_private.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; -} import_ctx_t; - - -/* Apply PATH'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 *path, - 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 (apr_hash_get(properties, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING)) - 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_keywords2(&keywords, keywords_val->data, - APR_STRINGIFY(SVN_INVALID_REVNUM), - "", 0, "", pool)); - else - keywords = NULL; - - if (special) - { - SVN_ERR(svn_subst_read_specialfile(&contents, path, pool, pool)); - } - else - { - /* Open the working copy file. */ - SVN_ERR(svn_stream_open_readonly(&contents, path, 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, path, - 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 *path, - const char *edit_path, - 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_node_kind_t kind; - svn_boolean_t is_special; - - SVN_ERR(svn_path_check_valid(path, pool)); - - SVN_ERR(svn_io_check_special_path(path, &kind, &is_special, 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 (! is_special) - { - /* add automatic properties */ - SVN_ERR(svn_client__get_auto_props(&properties, &mimetype, path, - import_ctx->magic_cookie, - ctx, 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(path, 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 (is_special) - { - apr_hash_set(properties, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING, - svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool)); - SVN_ERR(editor->change_file_prop(file_baton, SVN_PROP_SPECIAL, - apr_hash_get(properties, - SVN_PROP_SPECIAL, - APR_HASH_KEY_STRING), - pool)); - } - - /* Now, transmit the file contents. */ - SVN_ERR(send_file_contents(path, file_baton, editor, - properties, digest, pool)); - - /* Finally, close the file. */ - text_checksum = - svn_checksum_to_cstring(svn_checksum__from_digest(digest, svn_checksum_md5, - pool), pool); - - return editor->close_file(file_baton, text_checksum, pool); -} - - -/* Import directory PATH 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). - * - * 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 - * directory. - * - * Use POOL for any temporary allocation. */ -static svn_error_t * -import_dir(const svn_delta_editor_t *editor, - void *dir_baton, - const char *path, - const char *edit_path, - svn_depth_t depth, - apr_hash_t *excludes, - svn_boolean_t no_ignore, - svn_boolean_t ignore_unknown_node_types, - import_ctx_t *import_ctx, - svn_client_ctx_t *ctx, - apr_pool_t *pool) -{ - apr_pool_t *subpool = svn_pool_create(pool); /* iteration pool */ - apr_hash_t *dirents; - apr_hash_index_t *hi; - apr_array_header_t *ignores; - - SVN_ERR(svn_path_check_valid(path, pool)); - - if (!no_ignore) - SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool)); - - SVN_ERR(svn_io_get_dirents3(&dirents, path, TRUE, pool, pool)); - - for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) - { - const char *this_path, *this_edit_path, *abs_path; - const char *filename = svn__apr_hash_index_key(hi); - const svn_io_dirent_t *dirent = svn__apr_hash_index_val(hi); - - svn_pool_clear(subpool); - - if (ctx->cancel_func) - SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); - - if (svn_wc_is_adm_dir(filename, subpool)) - { - /* 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(path, filename, - subpool), - svn_wc_notify_skip, subpool); - 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, subpool); - } - continue; - } - - /* 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_path = svn_dirent_join(path, filename, subpool); - this_edit_path = svn_relpath_join(edit_path, filename, subpool); - - /* If this is an excluded path, exclude it. */ - SVN_ERR(svn_dirent_get_absolute(&abs_path, this_path, subpool)); - if (apr_hash_get(excludes, abs_path, APR_HASH_KEY_STRING)) - continue; - - if ((!no_ignore) && svn_wc_match_ignore_list(filename, ignores, - subpool)) - continue; - - if (dirent->kind == svn_node_dir && depth >= svn_depth_immediates) - { - void *this_dir_baton; - - /* Add the new subdirectory, getting a descent baton from - the editor. */ - SVN_ERR(editor->add_directory(this_edit_path, dir_baton, - NULL, SVN_INVALID_REVNUM, subpool, - &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(this_path, svn_wc_notify_commit_added, - subpool); - 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, subpool); - } - - /* Recurse. */ - { - svn_depth_t depth_below_here = depth; - if (depth == svn_depth_immediates) - depth_below_here = svn_depth_empty; - - SVN_ERR(import_dir(editor, this_dir_baton, this_path, - this_edit_path, depth_below_here, excludes, - no_ignore, ignore_unknown_node_types, - import_ctx, ctx, - subpool)); - } - - /* Finally, close the sub-directory. */ - SVN_ERR(editor->close_directory(this_dir_baton, subpool)); - } - else if (dirent->kind == svn_node_file && depth >= svn_depth_files) - { - SVN_ERR(import_file(editor, dir_baton, this_path, - this_edit_path, import_ctx, ctx, subpool)); - } - 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_path, - svn_wc_notify_skip, subpool); - 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, subpool); - } - } - else - return svn_error_createf - (SVN_ERR_NODE_UNKNOWN_KIND, NULL, - _("Unknown or unversionable type for '%s'"), - svn_dirent_local_style(this_path, subpool)); - } - } - - svn_pool_destroy(subpool); - 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). - * - * 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 *path, - const apr_array_header_t *new_entries, - const svn_delta_editor_t *editor, - void *edit_baton, - svn_depth_t depth, - apr_hash_t *excludes, - svn_boolean_t no_ignore, - svn_boolean_t ignore_unknown_node_types, - svn_client_ctx_t *ctx, - apr_pool_t *pool) -{ - void *root_baton; - svn_node_kind_t kind; - apr_array_header_t *ignores; - apr_array_header_t *batons = NULL; - const char *edit_path = ""; - import_ctx_t *import_ctx = apr_pcalloc(pool, sizeof(*import_ctx)); - - 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_check_path(path, &kind, 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) && (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 (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 (kind == svn_node_file) - { - svn_boolean_t ignores_match = FALSE; - - if (!no_ignore) - { - SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool)); - ignores_match = svn_wc_match_ignore_list(path, ignores, pool); - } - if (!ignores_match) - SVN_ERR(import_file(editor, root_baton, path, edit_path, - import_ctx, ctx, pool)); - } - else if (kind == svn_node_dir) - { - SVN_ERR(import_dir(editor, root_baton, path, edit_path, - depth, excludes, no_ignore, - ignore_unknown_node_types, import_ctx, ctx, pool)); - - } - else if (kind == svn_node_none - || kind == svn_node_unknown) - { - return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL, - _("'%s' does not exist"), - svn_dirent_local_style(path, 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); -} - - struct capture_baton_t { svn_commit_callback2_t original_callback; void *original_baton; @@ -628,16 +74,13 @@ capture_commit_info(const svn_commit_info_t *commit_info, static svn_error_t * -get_ra_editor(svn_ra_session_t **ra_session, - const svn_delta_editor_t **editor, +get_ra_editor(const svn_delta_editor_t **editor, void **edit_baton, + svn_ra_session_t *ra_session, svn_client_ctx_t *ctx, - const char *base_url, - const char *base_dir_abspath, const char *log_msg, const apr_array_header_t *commit_items, const apr_hash_t *revprop_table, - svn_boolean_t is_commit, apr_hash_t *lock_tokens, svn_boolean_t keep_locks, svn_commit_callback2_t commit_callback, @@ -645,192 +88,52 @@ get_ra_editor(svn_ra_session_t **ra_session, apr_pool_t *pool) { apr_hash_t *commit_revprops; - - /* Open an RA session to URL. */ - SVN_ERR(svn_client__open_ra_session_internal(ra_session, NULL, base_url, - base_dir_abspath, commit_items, - is_commit, !is_commit, - ctx, pool)); - - /* If this is an import (aka, not a commit), we need to verify that - our repository URL exists. */ - if (! is_commit) - { - svn_node_kind_t kind; - - SVN_ERR(svn_ra_check_path(*ra_session, "", SVN_INVALID_REVNUM, - &kind, pool)); - if (kind == svn_node_none) - return svn_error_createf(SVN_ERR_FS_NO_SUCH_ENTRY, NULL, - _("Path '%s' does not exist"), - base_url); - } + apr_hash_t *relpath_map = NULL; SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, log_msg, ctx, pool)); - /* Fetch RA commit editor. */ - return svn_ra_get_commit_editor3(*ra_session, editor, edit_baton, - commit_revprops, commit_callback, - commit_baton, lock_tokens, keep_locks, - pool); -} - - -/*** Public Interfaces. ***/ - -svn_error_t * -svn_client_import4(const char *path, - const char *url, - svn_depth_t depth, - svn_boolean_t no_ignore, - svn_boolean_t ignore_unknown_node_types, - 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_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(pool); - svn_node_kind_t kind; - const char *local_abspath; - apr_array_header_t *new_entries = apr_array_make(pool, 4, - sizeof(const char *)); - const char *temp; - const char *dir; - apr_pool_t *subpool; - - 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, pool)); - - /* Create a new commit item and add it to the array. */ - if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) +#ifdef ENABLE_EV2_SHIMS + if (commit_items) { - /* 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(pool, 1, sizeof(item)); - - item = svn_client_commit_item3_create(pool); - item->path = apr_pstrdup(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, 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, pool)); - apr_hash_set(excludes, abs_path, APR_HASH_KEY_STRING, (void *)1); - } - } - - SVN_ERR(svn_io_check_path(local_abspath, &kind, pool)); - - /* Figure out all the path components we need to create just to have - a place to stick our imported tree. */ - subpool = svn_pool_create(pool); - do - { - svn_pool_clear(subpool); - - /* See if the user is interested in cancelling this operation. */ - if (ctx->cancel_func) - SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); + int i; + apr_pool_t *iterpool = svn_pool_create(pool); - if (err) + relpath_map = apr_hash_make(pool); + for (i = 0; i < commit_items->nelts; i++) { - /* If get_ra_editor below failed we either tried to open - an invalid url, or else some other kind of error. In case - the url was bad we back up a directory and try again. */ + svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i, + svn_client_commit_item3_t *); + const char *relpath; - if (err->apr_err != SVN_ERR_FS_NO_SUCH_ENTRY) - return err; - else - svn_error_clear(err); + if (!item->path) + continue; - svn_uri_split(&temp, &dir, url, pool); - APR_ARRAY_PUSH(new_entries, const char *) = dir; - url = temp; - } - } - while ((err = get_ra_editor(&ra_session, - &editor, &edit_baton, ctx, url, NULL, - log_msg, NULL, revprop_table, FALSE, NULL, TRUE, - commit_callback, commit_baton, subpool))); - - /* Reverse the order of the components we added to our NEW_ENTRIES array. */ - if (new_entries->nelts) - { - int i, j; - const char *component; - for (i = 0; i < (new_entries->nelts / 2); i++) - { - j = new_entries->nelts - i - 1; - component = - APR_ARRAY_IDX(new_entries, i, const char *); - APR_ARRAY_IDX(new_entries, i, const char *) = - APR_ARRAY_IDX(new_entries, j, const char *); - APR_ARRAY_IDX(new_entries, j, const char *) = - component; + svn_pool_clear(iterpool); + SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL, + ctx->wc_ctx, item->path, FALSE, pool, + iterpool)); + if (relpath) + svn_hash_sets(relpath_map, relpath, item->path); } + svn_pool_destroy(iterpool); } +#endif - /* An empty NEW_ENTRIES list the first call to get_ra_editor() above - succeeded. That means that URL corresponds to an already - existing filesystem entity. */ - if (kind == svn_node_file && (! new_entries->nelts)) - return svn_error_createf - (SVN_ERR_ENTRY_EXISTS, NULL, - _("Path '%s' already exists"), url); - - /* The repository doesn't know about the reserved administrative - directory. */ - if (new_entries->nelts - /* What's this, what's this? This assignment is here because we - use the value to construct the error message just below. It - may not be aesthetically pleasing, but it's less ugly than - calling APR_ARRAY_IDX twice. */ - && svn_wc_is_adm_dir(temp = APR_ARRAY_IDX(new_entries, - new_entries->nelts - 1, - const char *), - pool)) - return svn_error_createf - (SVN_ERR_CL_ADM_DIR_RESERVED, NULL, - _("'%s' is a reserved name and cannot be imported"), - /* ### Is svn_path_local_style() really necessary for this? */ - svn_dirent_local_style(temp, 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(path, new_entries, editor, edit_baton, - depth, excludes, no_ignore, - ignore_unknown_node_types, ctx, subpool))) - { - svn_error_clear(editor->abort_edit(edit_baton, subpool)); - return err; - } - - svn_pool_destroy(subpool); + /* Fetch RA commit editor. */ + SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session, + svn_client__get_shim_callbacks(ctx->wc_ctx, + relpath_map, pool))); + SVN_ERR(svn_ra_get_commit_editor3(ra_session, editor, edit_baton, + commit_revprops, commit_callback, + commit_baton, lock_tokens, keep_locks, + pool)); return SVN_NO_ERROR; } + +/*** Public Interfaces. ***/ static svn_error_t * reconcile_errors(svn_error_t *commit_err, @@ -902,12 +205,11 @@ collect_lock_tokens(apr_hash_t **result, { const char *url = svn__apr_hash_index_key(hi); const char *token = svn__apr_hash_index_val(hi); + const char *relpath = svn_uri_skip_ancestor(base_url, url, pool); - if (svn_uri__is_ancestor(base_url, url)) + if (relpath) { - url = svn_uri_skip_ancestor(base_url, url, pool); - - apr_hash_set(*result, url, APR_HASH_KEY_STRING, token); + svn_hash_sets(*result, relpath, token); } } @@ -938,6 +240,13 @@ post_process_commit_item(svn_wc_committed_queue_t *queue, remove_lock = (! keep_locks && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)); + /* When the node was deleted (or replaced), we need to always remove the + locks, as they're invalidated on the server. We cannot honor the + SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN flag here because it does not tell + us whether we have locked children. */ + if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) + remove_lock = TRUE; + return svn_wc_queue_committed3(queue, wc_ctx, item->path, loop_recurse, item->incoming_prop_changes, remove_lock, !keep_changelists, @@ -955,8 +264,8 @@ check_nonrecursive_dir_delete(svn_wc_context_t *wc_ctx, SVN_ERR_ASSERT(depth != svn_depth_infinity); - SVN_ERR(svn_wc_read_kind(&kind, wc_ctx, target_abspath, FALSE, - scratch_pool)); + SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, target_abspath, + TRUE, FALSE, scratch_pool)); /* ### TODO(sd): This check is slightly too strict. It should be @@ -1050,8 +359,8 @@ determine_lock_targets(apr_array_header_t **lock_targets, target_abspath = svn_dirent_join(base_abspath, target_relpath, scratch_pool); - err = svn_wc__get_wc_root(&wcroot_abspath, wc_ctx, target_abspath, - iterpool, iterpool); + err = svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, target_abspath, + iterpool, iterpool); if (err) { @@ -1063,13 +372,13 @@ determine_lock_targets(apr_array_header_t **lock_targets, return svn_error_trace(err); } - wc_targets = apr_hash_get(wc_items, wcroot_abspath, APR_HASH_KEY_STRING); + wc_targets = svn_hash_gets(wc_items, wcroot_abspath); if (! wc_targets) { wc_targets = apr_array_make(scratch_pool, 4, sizeof(const char *)); - apr_hash_set(wc_items, apr_pstrdup(scratch_pool, wcroot_abspath), - APR_HASH_KEY_STRING, wc_targets); + svn_hash_sets(wc_items, apr_pstrdup(scratch_pool, wcroot_abspath), + wc_targets); } APR_ARRAY_PUSH(wc_targets, const char *) = target_abspath; @@ -1156,8 +465,8 @@ check_url_kind(void *baton, /* If we don't have a session or can't use the session, get one */ if (!cukb->session || !svn_uri__is_ancestor(cukb->repos_root_url, url)) { - SVN_ERR(svn_client_open_ra_session(&cukb->session, url, cukb->ctx, - cukb->pool)); + SVN_ERR(svn_client_open_ra_session2(&cukb->session, url, NULL, cukb->ctx, + cukb->pool, scratch_pool)); SVN_ERR(svn_ra_get_repos_root2(cukb->session, &cukb->repos_root_url, cukb->pool)); } @@ -1169,12 +478,96 @@ check_url_kind(void *baton, kind, scratch_pool)); } +/* Recurse into every target in REL_TARGETS, finding committable externals + * nested within. Append these to REL_TARGETS itself. The paths in REL_TARGETS + * are assumed to be / will be created relative to BASE_ABSPATH. The remaining + * arguments correspond to those of svn_client_commit6(). */ +static svn_error_t* +append_externals_as_explicit_targets(apr_array_header_t *rel_targets, + const char *base_abspath, + svn_boolean_t include_file_externals, + svn_boolean_t include_dir_externals, + svn_depth_t depth, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int rel_targets_nelts_fixed; + int i; + apr_pool_t *iterpool; + + if (! (include_file_externals || include_dir_externals)) + return SVN_NO_ERROR; + + /* Easy part of applying DEPTH to externals. */ + if (depth == svn_depth_empty) + { + /* Don't recurse. */ + return SVN_NO_ERROR; + } + + /* Iterate *and* grow REL_TARGETS at the same time. */ + rel_targets_nelts_fixed = rel_targets->nelts; + + iterpool = svn_pool_create(scratch_pool); + + for (i = 0; i < rel_targets_nelts_fixed; i++) + { + int j; + const char *target; + apr_array_header_t *externals = NULL; + + svn_pool_clear(iterpool); + + target = svn_dirent_join(base_abspath, + APR_ARRAY_IDX(rel_targets, i, const char *), + iterpool); + + /* ### TODO: Possible optimization: No need to do this for file targets. + * ### But what's cheaper, stat'ing the file system or querying the db? + * ### --> future. */ + + SVN_ERR(svn_wc__committable_externals_below(&externals, ctx->wc_ctx, + target, depth, + iterpool, iterpool)); + + if (externals != NULL) + { + const char *rel_target; + + for (j = 0; j < externals->nelts; j++) + { + svn_wc__committable_external_info_t *xinfo = + APR_ARRAY_IDX(externals, j, + svn_wc__committable_external_info_t *); + + if ((xinfo->kind == svn_node_file && ! include_file_externals) + || (xinfo->kind == svn_node_dir && ! include_dir_externals)) + continue; + + rel_target = svn_dirent_skip_ancestor(base_abspath, + xinfo->local_abspath); + + SVN_ERR_ASSERT(rel_target != NULL && *rel_target != '\0'); + + APR_ARRAY_PUSH(rel_targets, const char *) = + apr_pstrdup(result_pool, rel_target); + } + } + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + svn_error_t * -svn_client_commit5(const apr_array_header_t *targets, +svn_client_commit6(const apr_array_header_t *targets, svn_depth_t depth, svn_boolean_t keep_locks, svn_boolean_t keep_changelists, svn_boolean_t commit_as_operations, + svn_boolean_t include_file_externals, + svn_boolean_t include_dir_externals, const apr_array_header_t *changelists, const apr_hash_t *revprop_table, svn_commit_callback2_t commit_callback, @@ -1200,10 +593,12 @@ svn_client_commit5(const apr_array_header_t *targets, svn_error_t *bump_err = SVN_NO_ERROR; svn_error_t *unlock_err = SVN_NO_ERROR; svn_boolean_t commit_in_progress = FALSE; + svn_boolean_t timestamp_sleep = FALSE; svn_commit_info_t *commit_info = NULL; apr_pool_t *iterpool = svn_pool_create(pool); const char *current_abspath; const char *notify_prefix; + int depth_empty_after = -1; int i; SVN_ERR_ASSERT(depth != svn_depth_unknown && depth != svn_depth_exclude); @@ -1234,6 +629,21 @@ svn_client_commit5(const apr_array_header_t *targets, if (rel_targets->nelts == 0) APR_ARRAY_PUSH(rel_targets, const char *) = ""; + if (include_file_externals || include_dir_externals) + { + if (depth != svn_depth_unknown && depth != svn_depth_infinity) + { + /* All targets after this will be handled as depth empty */ + depth_empty_after = rel_targets->nelts; + } + + SVN_ERR(append_externals_as_explicit_targets(rel_targets, base_abspath, + include_file_externals, + include_dir_externals, + depth, ctx, + pool, pool)); + } + SVN_ERR(determine_lock_targets(&lock_targets, ctx->wc_ctx, base_abspath, rel_targets, pool, iterpool)); @@ -1298,6 +708,7 @@ svn_client_commit5(const apr_array_header_t *targets, &lock_tokens, base_abspath, rel_targets, + depth_empty_after, depth, ! keep_locks, changelists, @@ -1355,6 +766,130 @@ svn_client_commit5(const apr_array_header_t *targets, goto cleanup; } + /* For every target that was moved verify that both halves of the + * move are part of the commit. */ + for (i = 0; i < commit_items->nelts; i++) + { + svn_client_commit_item3_t *item = + APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *); + + svn_pool_clear(iterpool); + + if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_MOVED_HERE) + { + /* ### item->moved_from_abspath contains the move origin */ + const char *moved_from_abspath; + const char *delete_op_root_abspath; + + cmt_err = svn_error_trace(svn_wc__node_was_moved_here( + &moved_from_abspath, + &delete_op_root_abspath, + ctx->wc_ctx, item->path, + iterpool, iterpool)); + if (cmt_err) + goto cleanup; + + if (moved_from_abspath && delete_op_root_abspath && + strcmp(moved_from_abspath, delete_op_root_abspath) == 0) + + { + svn_boolean_t found_delete_half = + (svn_hash_gets(committables->by_path, delete_op_root_abspath) + != NULL); + + if (!found_delete_half) + { + const char *delete_half_parent_abspath; + + /* The delete-half isn't in the commit target list. + * However, it might itself be the child of a deleted node, + * either because of another move or a deletion. + * + * For example, consider: mv A/B B; mv B/C C; commit; + * C's moved-from A/B/C is a child of the deleted A/B. + * A/B/C does not appear in the commit target list, but + * A/B does appear. + * (Note that moved-from information is always stored + * relative to the BASE tree, so we have 'C moved-from + * A/B/C', not 'C moved-from B/C'.) + * + * An example involving a move and a delete would be: + * mv A/B C; rm A; commit; + * Now C is moved-from A/B which does not appear in the + * commit target list, but A does appear. + */ + + /* Scan upwards for a deletion op-root from the + * delete-half's parent directory. */ + delete_half_parent_abspath = + svn_dirent_dirname(delete_op_root_abspath, iterpool); + if (strcmp(delete_op_root_abspath, + delete_half_parent_abspath) != 0) + { + const char *parent_delete_op_root_abspath; + + cmt_err = svn_error_trace( + svn_wc__node_get_deleted_ancestor( + &parent_delete_op_root_abspath, + ctx->wc_ctx, delete_half_parent_abspath, + iterpool, iterpool)); + if (cmt_err) + goto cleanup; + + if (parent_delete_op_root_abspath) + found_delete_half = + (svn_hash_gets(committables->by_path, + parent_delete_op_root_abspath) + != NULL); + } + } + + if (!found_delete_half) + { + cmt_err = svn_error_createf( + SVN_ERR_ILLEGAL_TARGET, NULL, + _("Cannot commit '%s' because it was moved from " + "'%s' which is not part of the commit; both " + "sides of the move must be committed together"), + svn_dirent_local_style(item->path, iterpool), + svn_dirent_local_style(delete_op_root_abspath, + iterpool)); + goto cleanup; + } + } + } + + if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) + { + const char *moved_to_abspath; + const char *copy_op_root_abspath; + + cmt_err = svn_error_trace(svn_wc__node_was_moved_away( + &moved_to_abspath, + ©_op_root_abspath, + ctx->wc_ctx, item->path, + iterpool, iterpool)); + if (cmt_err) + goto cleanup; + + if (moved_to_abspath && copy_op_root_abspath && + strcmp(moved_to_abspath, copy_op_root_abspath) == 0 && + svn_hash_gets(committables->by_path, copy_op_root_abspath) + == NULL) + { + cmt_err = svn_error_createf( + SVN_ERR_ILLEGAL_TARGET, NULL, + _("Cannot commit '%s' because it was moved to '%s' " + "which is not part of the commit; both sides of " + "the move must be committed together"), + svn_dirent_local_style(item->path, iterpool), + svn_dirent_local_style(copy_op_root_abspath, + iterpool)); + goto cleanup; + } + } + } + /* Go get a log message. If an error occurs, or no log message is specified, abort the operation. */ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) @@ -1390,12 +925,27 @@ svn_client_commit5(const apr_array_header_t *targets, cb.info = &commit_info; cb.pool = pool; + /* Get the RA editor from the first lock target, rather than BASE_ABSPATH. + * When committing from multiple WCs, BASE_ABSPATH might be an unrelated + * parent of nested working copies. We don't support commits to multiple + * repositories so using the first WC to get the RA session is safe. */ cmt_err = svn_error_trace( - get_ra_editor(&ra_session, &editor, &edit_baton, ctx, - base_url, base_abspath, log_msg, - commit_items, revprop_table, TRUE, lock_tokens, - keep_locks, capture_commit_info, - &cb, pool)); + svn_client__open_ra_session_internal(&ra_session, NULL, base_url, + APR_ARRAY_IDX(lock_targets, + 0, + const char *), + commit_items, + TRUE, TRUE, ctx, + pool, pool)); + + if (cmt_err) + goto cleanup; + + cmt_err = svn_error_trace( + get_ra_editor(&editor, &edit_baton, ra_session, ctx, + log_msg, commit_items, revprop_table, + lock_tokens, keep_locks, capture_commit_info, + &cb, pool)); if (cmt_err) goto cleanup; @@ -1403,11 +953,16 @@ svn_client_commit5(const apr_array_header_t *targets, /* Make a note that we have a commit-in-progress. */ commit_in_progress = TRUE; + /* We'll assume that, once we pass this point, we are going to need to + * sleep for timestamps. Really, we may not need to do unless and until + * we reach the point where we post-commit 'bump' the WC metadata. */ + timestamp_sleep = TRUE; + /* Perform the commit. */ cmt_err = svn_error_trace( - svn_client__do_commit(base_url, commit_items, editor, edit_baton, - notify_prefix, NULL, - &sha1_checksums, ctx, pool, iterpool)); + svn_client__do_commit(base_url, commit_items, editor, edit_baton, + notify_prefix, &sha1_checksums, ctx, pool, + iterpool)); /* Handle a successful commit. */ if ((! cmt_err) @@ -1427,9 +982,7 @@ svn_client_commit5(const apr_array_header_t *targets, bump_err = post_process_commit_item( queue, item, ctx->wc_ctx, keep_changelists, keep_locks, commit_as_operations, - apr_hash_get(sha1_checksums, - item->path, - APR_HASH_KEY_STRING), + svn_hash_gets(sha1_checksums, item->path), iterpool); if (bump_err) goto cleanup; @@ -1445,10 +998,24 @@ svn_client_commit5(const apr_array_header_t *targets, iterpool); } - /* Sleep to ensure timestamp integrity. */ - svn_io_sleep_for_timestamps(base_abspath, pool); - cleanup: + /* Sleep to ensure timestamp integrity. BASE_ABSPATH may have been + removed by the commit or it may the common ancestor of multiple + working copies. */ + if (timestamp_sleep) + { + const char *wcroot_abspath; + svn_error_t *err = svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + base_abspath, pool, pool); + if (err) + { + svn_error_clear(err); + wcroot_abspath = NULL; + } + + svn_io_sleep_for_timestamps(wcroot_abspath, pool); + } + /* Abort the commit if it is still in progress. */ svn_pool_clear(iterpool); /* Close open handles before aborting */ if (commit_in_progress) |