summaryrefslogtreecommitdiff
path: root/subversion/libsvn_client/commit.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/commit.c')
-rw-r--r--subversion/libsvn_client/commit.c1085
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,
+ &copy_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)