| /* |
| * commit_util.c: Driver for the WC commit process. |
| * |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| /* ==================================================================== */ |
| |
| |
| #include <string.h> |
| |
| #include <apr_pools.h> |
| #include <apr_hash.h> |
| #include <apr_md5.h> |
| |
| #include "client.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_path.h" |
| #include "svn_types.h" |
| #include "svn_pools.h" |
| #include "svn_props.h" |
| #include "svn_iter.h" |
| #include "svn_hash.h" |
| |
| #include <assert.h> |
| |
| #include "svn_private_config.h" |
| #include "private/svn_wc_private.h" |
| #include "private/svn_client_private.h" |
| #include "private/svn_sorts_private.h" |
| |
| /*** Uncomment this to turn on commit driver debugging. ***/ |
| /* |
| #define SVN_CLIENT_COMMIT_DEBUG |
| */ |
| |
| /* Wrap an RA error in a nicer error if one is available. */ |
| static svn_error_t * |
| fixup_commit_error(const char *local_abspath, |
| const char *base_url, |
| const char *path, |
| svn_node_kind_t kind, |
| svn_error_t *err, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *scratch_pool) |
| { |
| if (err->apr_err == SVN_ERR_FS_NOT_FOUND |
| || err->apr_err == SVN_ERR_FS_CONFLICT |
| || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS |
| || err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE |
| || err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND |
| || err->apr_err == SVN_ERR_RA_DAV_ALREADY_EXISTS |
| || err->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED |
| || svn_error_find_cause(err, SVN_ERR_RA_OUT_OF_DATE)) |
| { |
| if (ctx->notify_func2) |
| { |
| svn_wc_notify_t *notify; |
| |
| if (local_abspath) |
| notify = svn_wc_create_notify(local_abspath, |
| svn_wc_notify_failed_out_of_date, |
| scratch_pool); |
| else |
| notify = svn_wc_create_notify_url( |
| svn_path_url_add_component2(base_url, path, |
| scratch_pool), |
| svn_wc_notify_failed_out_of_date, |
| scratch_pool); |
| |
| notify->kind = kind; |
| notify->err = err; |
| |
| ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); |
| } |
| |
| return svn_error_createf(SVN_ERR_WC_NOT_UP_TO_DATE, err, |
| (kind == svn_node_dir |
| ? _("Directory '%s' is out of date") |
| : _("File '%s' is out of date")), |
| local_abspath |
| ? svn_dirent_local_style(local_abspath, |
| scratch_pool) |
| : svn_path_url_add_component2(base_url, |
| path, |
| scratch_pool)); |
| } |
| else if (svn_error_find_cause(err, SVN_ERR_FS_NO_LOCK_TOKEN) |
| || err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH |
| || err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN |
| || err->apr_err == SVN_ERR_RA_NOT_LOCKED) |
| { |
| if (ctx->notify_func2) |
| { |
| svn_wc_notify_t *notify; |
| |
| if (local_abspath) |
| notify = svn_wc_create_notify(local_abspath, |
| svn_wc_notify_failed_locked, |
| scratch_pool); |
| else |
| notify = svn_wc_create_notify_url( |
| svn_path_url_add_component2(base_url, path, |
| scratch_pool), |
| svn_wc_notify_failed_locked, |
| scratch_pool); |
| |
| notify->kind = kind; |
| notify->err = err; |
| |
| ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); |
| } |
| |
| return svn_error_createf(SVN_ERR_CLIENT_NO_LOCK_TOKEN, err, |
| (kind == svn_node_dir |
| ? _("Directory '%s' is locked in another working copy") |
| : _("File '%s' is locked in another working copy")), |
| local_abspath |
| ? svn_dirent_local_style(local_abspath, |
| scratch_pool) |
| : svn_path_url_add_component2(base_url, |
| path, |
| scratch_pool)); |
| } |
| else if (svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN) |
| || err->apr_err == SVN_ERR_AUTHZ_UNWRITABLE) |
| { |
| if (ctx->notify_func2) |
| { |
| svn_wc_notify_t *notify; |
| |
| if (local_abspath) |
| notify = svn_wc_create_notify( |
| local_abspath, |
| svn_wc_notify_failed_forbidden_by_server, |
| scratch_pool); |
| else |
| notify = svn_wc_create_notify_url( |
| svn_path_url_add_component2(base_url, path, |
| scratch_pool), |
| svn_wc_notify_failed_forbidden_by_server, |
| scratch_pool); |
| |
| notify->kind = kind; |
| notify->err = err; |
| |
| ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); |
| } |
| |
| return svn_error_createf(SVN_ERR_CLIENT_FORBIDDEN_BY_SERVER, err, |
| (kind == svn_node_dir |
| ? _("Changing directory '%s' is forbidden by the server") |
| : _("Changing file '%s' is forbidden by the server")), |
| local_abspath |
| ? svn_dirent_local_style(local_abspath, |
| scratch_pool) |
| : svn_path_url_add_component2(base_url, |
| path, |
| scratch_pool)); |
| } |
| else |
| return err; |
| } |
| |
| |
| /*** Harvesting Commit Candidates ***/ |
| |
| |
| /* Add a new commit candidate (described by all parameters except |
| `COMMITTABLES') to the COMMITTABLES hash. All of the commit item's |
| members are allocated out of RESULT_POOL. |
| |
| If the state flag specifies that a lock must be used, store the token in LOCK |
| in lock_tokens. |
| */ |
| static svn_error_t * |
| add_committable(svn_client__committables_t *committables, |
| const char *local_abspath, |
| svn_node_kind_t kind, |
| const char *repos_root_url, |
| const char *repos_relpath, |
| svn_revnum_t revision, |
| const char *copyfrom_relpath, |
| svn_revnum_t copyfrom_rev, |
| const char *moved_from_abspath, |
| apr_byte_t state_flags, |
| apr_hash_t *lock_tokens, |
| const svn_lock_t *lock, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| apr_array_header_t *array; |
| svn_client_commit_item3_t *new_item; |
| |
| /* Sanity checks. */ |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| SVN_ERR_ASSERT(repos_root_url && repos_relpath); |
| |
| /* ### todo: Get the canonical repository for this item, which will |
| be the real key for the COMMITTABLES hash, instead of the above |
| bogosity. */ |
| array = svn_hash_gets(committables->by_repository, repos_root_url); |
| |
| /* E-gads! There is no array for this repository yet! Oh, no |
| problem, we'll just create (and add to the hash) one. */ |
| if (array == NULL) |
| { |
| array = apr_array_make(result_pool, 1, sizeof(new_item)); |
| svn_hash_sets(committables->by_repository, |
| apr_pstrdup(result_pool, repos_root_url), array); |
| } |
| |
| /* Now update pointer values, ensuring that their allocations live |
| in POOL. */ |
| new_item = svn_client_commit_item3_create(result_pool); |
| new_item->path = apr_pstrdup(result_pool, local_abspath); |
| new_item->kind = kind; |
| new_item->url = svn_path_url_add_component2(repos_root_url, |
| repos_relpath, |
| result_pool); |
| new_item->revision = revision; |
| new_item->copyfrom_url = copyfrom_relpath |
| ? svn_path_url_add_component2(repos_root_url, |
| copyfrom_relpath, |
| result_pool) |
| : NULL; |
| new_item->copyfrom_rev = copyfrom_rev; |
| new_item->state_flags = state_flags; |
| new_item->incoming_prop_changes = apr_array_make(result_pool, 1, |
| sizeof(svn_prop_t *)); |
| |
| if (moved_from_abspath) |
| new_item->moved_from_abspath = apr_pstrdup(result_pool, |
| moved_from_abspath); |
| |
| /* Now, add the commit item to the array. */ |
| APR_ARRAY_PUSH(array, svn_client_commit_item3_t *) = new_item; |
| |
| /* ... and to the hash. */ |
| svn_hash_sets(committables->by_path, new_item->path, new_item); |
| |
| if (lock |
| && lock_tokens |
| && (state_flags & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)) |
| { |
| svn_hash_sets(lock_tokens, new_item->url, |
| apr_pstrdup(result_pool, lock->token)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* If there is a commit item for PATH in COMMITTABLES, return it, else |
| return NULL. Use POOL for temporary allocation only. */ |
| static svn_client_commit_item3_t * |
| look_up_committable(svn_client__committables_t *committables, |
| const char *path, |
| apr_pool_t *pool) |
| { |
| return (svn_client_commit_item3_t *) |
| svn_hash_gets(committables->by_path, path); |
| } |
| |
| /* Helper function for svn_client__harvest_committables(). |
| * Determine whether we are within a tree-conflicted subtree of the |
| * working copy and return an SVN_ERR_WC_FOUND_CONFLICT error if so. */ |
| static svn_error_t * |
| bail_on_tree_conflicted_ancestor(svn_wc_context_t *wc_ctx, |
| const char *local_abspath, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| apr_pool_t *scratch_pool) |
| { |
| const char *wcroot_abspath; |
| |
| SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, wc_ctx, local_abspath, |
| scratch_pool, scratch_pool)); |
| |
| local_abspath = svn_dirent_dirname(local_abspath, scratch_pool); |
| |
| while(svn_dirent_is_ancestor(wcroot_abspath, local_abspath)) |
| { |
| svn_boolean_t tree_conflicted; |
| |
| /* Check if the parent has tree conflicts */ |
| SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted, |
| wc_ctx, local_abspath, scratch_pool)); |
| if (tree_conflicted) |
| { |
| if (notify_func != NULL) |
| { |
| notify_func(notify_baton, |
| svn_wc_create_notify(local_abspath, |
| svn_wc_notify_failed_conflict, |
| scratch_pool), |
| scratch_pool); |
| } |
| |
| return svn_error_createf( |
| SVN_ERR_WC_FOUND_CONFLICT, NULL, |
| _("Aborting commit: '%s' remains in tree-conflict"), |
| svn_dirent_local_style(local_abspath, scratch_pool)); |
| } |
| |
| /* Step outwards */ |
| if (svn_dirent_is_root(local_abspath, strlen(local_abspath))) |
| break; |
| else |
| local_abspath = svn_dirent_dirname(local_abspath, scratch_pool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Recursively search for commit candidates in (and under) LOCAL_ABSPATH using |
| WC_CTX and add those candidates to COMMITTABLES. If in ADDS_ONLY modes, |
| only new additions are recognized. |
| |
| DEPTH indicates how to treat files and subdirectories of LOCAL_ABSPATH |
| when LOCAL_ABSPATH is itself a directory; see |
| svn_client__harvest_committables() for its behavior. |
| |
| Lock tokens of candidates will be added to LOCK_TOKENS, if |
| non-NULL. JUST_LOCKED indicates whether to treat non-modified items with |
| lock tokens as commit candidates. |
| |
| If COMMIT_RELPATH is not NULL, treat not-added nodes as if it is destined to |
| be added as COMMIT_RELPATH, and add 'deleted' entries to COMMITTABLES as |
| items to delete in the copy destination. COPY_MODE_ROOT should be set TRUE |
| for the first call for which COPY_MODE is TRUE, i.e. not for the |
| recursive calls, and FALSE otherwise. |
| |
| If CHANGELISTS is non-NULL, it is a hash whose keys are const char * |
| changelist names used as a restrictive filter |
| when harvesting committables; that is, don't add a path to |
| COMMITTABLES unless it's a member of one of those changelists. |
| |
| IS_EXPLICIT_TARGET should always be passed as TRUE, except when |
| harvest_committables() calls itself in recursion. This provides a way to |
| tell whether LOCAL_ABSPATH was an original target or whether it was reached |
| by recursing deeper into a dir target. (This is used to skip all file |
| externals that aren't explicit commit targets.) |
| |
| DANGLERS is a hash table mapping const char* absolute paths of a parent |
| to a const char * absolute path of a child. See the comment about |
| danglers at the top of svn_client__harvest_committables(). |
| |
| If CANCEL_FUNC is non-null, call it with CANCEL_BATON to see |
| if the user has cancelled the operation. |
| |
| Any items added to COMMITTABLES are allocated from the COMITTABLES |
| hash pool, not POOL. SCRATCH_POOL is used for temporary allocations. */ |
| |
| struct harvest_baton |
| { |
| /* Static data */ |
| const char *root_abspath; |
| svn_client__committables_t *committables; |
| apr_hash_t *lock_tokens; |
| const char *commit_relpath; /* Valid for the harvest root */ |
| svn_depth_t depth; |
| svn_boolean_t just_locked; |
| apr_hash_t *changelists; |
| apr_hash_t *danglers; |
| svn_client__check_url_kind_t check_url_func; |
| void *check_url_baton; |
| svn_wc_notify_func2_t notify_func; |
| void *notify_baton; |
| svn_wc_context_t *wc_ctx; |
| apr_pool_t *result_pool; |
| |
| /* Harvester state */ |
| const char *skip_below_abspath; /* If non-NULL, skip everything below */ |
| }; |
| |
| static svn_error_t * |
| harvest_status_callback(void *status_baton, |
| const char *local_abspath, |
| const svn_wc_status3_t *status, |
| apr_pool_t *scratch_pool); |
| |
| static svn_error_t * |
| harvest_committables(const char *local_abspath, |
| svn_client__committables_t *committables, |
| apr_hash_t *lock_tokens, |
| const char *copy_mode_relpath, |
| svn_depth_t depth, |
| svn_boolean_t just_locked, |
| apr_hash_t *changelists, |
| apr_hash_t *danglers, |
| svn_client__check_url_kind_t check_url_func, |
| void *check_url_baton, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| svn_wc_context_t *wc_ctx, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| struct harvest_baton baton; |
| |
| SVN_ERR_ASSERT((just_locked && lock_tokens) || !just_locked); |
| |
| baton.root_abspath = local_abspath; |
| baton.committables = committables; |
| baton.lock_tokens = lock_tokens; |
| baton.commit_relpath = copy_mode_relpath; |
| baton.depth = depth; |
| baton.just_locked = just_locked; |
| baton.changelists = changelists; |
| baton.danglers = danglers; |
| baton.check_url_func = check_url_func; |
| baton.check_url_baton = check_url_baton; |
| baton.notify_func = notify_func; |
| baton.notify_baton = notify_baton; |
| baton.wc_ctx = wc_ctx; |
| baton.result_pool = result_pool; |
| |
| baton.skip_below_abspath = NULL; |
| |
| SVN_ERR(svn_wc_walk_status(wc_ctx, |
| local_abspath, |
| depth, |
| (copy_mode_relpath != NULL) /* get_all */, |
| FALSE /* no_ignore */, |
| FALSE /* ignore_text_mods */, |
| NULL /* ignore_patterns */, |
| harvest_status_callback, |
| &baton, |
| cancel_func, cancel_baton, |
| scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| harvest_not_present_for_copy(svn_wc_context_t *wc_ctx, |
| const char *local_abspath, |
| svn_client__committables_t *committables, |
| const char *repos_root_url, |
| const char *commit_relpath, |
| svn_client__check_url_kind_t check_url_func, |
| void *check_url_baton, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const apr_array_header_t *children; |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| int i; |
| |
| SVN_ERR_ASSERT(commit_relpath != NULL); |
| |
| /* A function to retrieve not present children would be nice to have */ |
| SVN_ERR(svn_wc__node_get_not_present_children(&children, wc_ctx, |
| local_abspath, |
| scratch_pool, iterpool)); |
| |
| for (i = 0; i < children->nelts; i++) |
| { |
| const char *this_abspath = APR_ARRAY_IDX(children, i, const char *); |
| const char *name = svn_dirent_basename(this_abspath, NULL); |
| const char *this_commit_relpath; |
| svn_boolean_t not_present; |
| svn_node_kind_t kind; |
| |
| svn_pool_clear(iterpool); |
| |
| SVN_ERR(svn_wc__node_is_not_present(¬_present, NULL, NULL, wc_ctx, |
| this_abspath, FALSE, scratch_pool)); |
| |
| if (!not_present) |
| continue; /* Node is replaced */ |
| |
| this_commit_relpath = svn_relpath_join(commit_relpath, name, |
| iterpool); |
| |
| /* We should check if we should really add a delete operation */ |
| if (check_url_func) |
| { |
| svn_revnum_t parent_rev; |
| const char *parent_repos_relpath; |
| const char *parent_repos_root_url; |
| const char *node_url; |
| |
| /* Determine from what parent we would be the deleted child */ |
| SVN_ERR(svn_wc__node_get_origin( |
| NULL, &parent_rev, &parent_repos_relpath, |
| &parent_repos_root_url, NULL, NULL, NULL, |
| wc_ctx, |
| svn_dirent_dirname(this_abspath, |
| scratch_pool), |
| FALSE, scratch_pool, scratch_pool)); |
| |
| node_url = svn_path_url_add_component2( |
| svn_path_url_add_component2(parent_repos_root_url, |
| parent_repos_relpath, |
| scratch_pool), |
| svn_dirent_basename(this_abspath, NULL), |
| iterpool); |
| |
| SVN_ERR(check_url_func(check_url_baton, &kind, |
| node_url, parent_rev, iterpool)); |
| |
| if (kind == svn_node_none) |
| continue; /* This node can't be deleted */ |
| } |
| else |
| SVN_ERR(svn_wc_read_kind2(&kind, wc_ctx, this_abspath, |
| TRUE, TRUE, scratch_pool)); |
| |
| SVN_ERR(add_committable(committables, this_abspath, kind, |
| repos_root_url, |
| this_commit_relpath, |
| SVN_INVALID_REVNUM, |
| NULL /* copyfrom_relpath */, |
| SVN_INVALID_REVNUM /* copyfrom_rev */, |
| NULL /* moved_from_abspath */, |
| SVN_CLIENT_COMMIT_ITEM_DELETE, |
| NULL, NULL, |
| result_pool, scratch_pool)); |
| } |
| |
| svn_pool_destroy(iterpool); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Implements svn_wc_status_func4_t */ |
| static svn_error_t * |
| harvest_status_callback(void *status_baton, |
| const char *local_abspath, |
| const svn_wc_status3_t *status, |
| apr_pool_t *scratch_pool) |
| { |
| apr_byte_t state_flags = 0; |
| svn_revnum_t node_rev; |
| const char *cf_relpath = NULL; |
| svn_revnum_t cf_rev = SVN_INVALID_REVNUM; |
| svn_boolean_t matches_changelists; |
| svn_boolean_t is_added; |
| svn_boolean_t is_deleted; |
| svn_boolean_t is_replaced; |
| svn_boolean_t is_op_root; |
| svn_revnum_t original_rev; |
| const char *original_relpath; |
| svn_boolean_t copy_mode; |
| |
| struct harvest_baton *baton = status_baton; |
| svn_boolean_t is_harvest_root = |
| (strcmp(baton->root_abspath, local_abspath) == 0); |
| svn_client__committables_t *committables = baton->committables; |
| const char *repos_root_url = status->repos_root_url; |
| const char *commit_relpath = NULL; |
| svn_boolean_t copy_mode_root = (baton->commit_relpath && is_harvest_root); |
| svn_boolean_t just_locked = baton->just_locked; |
| apr_hash_t *changelists = baton->changelists; |
| svn_wc_notify_func2_t notify_func = baton->notify_func; |
| void *notify_baton = baton->notify_baton; |
| svn_wc_context_t *wc_ctx = baton->wc_ctx; |
| apr_pool_t *result_pool = baton->result_pool; |
| const char *moved_from_abspath = NULL; |
| |
| if (baton->commit_relpath) |
| commit_relpath = svn_relpath_join( |
| baton->commit_relpath, |
| svn_dirent_skip_ancestor(baton->root_abspath, |
| local_abspath), |
| scratch_pool); |
| |
| copy_mode = (commit_relpath != NULL); |
| |
| if (baton->skip_below_abspath |
| && svn_dirent_is_ancestor(baton->skip_below_abspath, local_abspath)) |
| { |
| return SVN_NO_ERROR; |
| } |
| else |
| baton->skip_below_abspath = NULL; /* We have left the skip tree */ |
| |
| /* Return early for nodes that don't have a committable status */ |
| switch (status->node_status) |
| { |
| case svn_wc_status_unversioned: |
| case svn_wc_status_ignored: |
| case svn_wc_status_external: |
| case svn_wc_status_none: |
| /* Unversioned nodes aren't committable, but are reported by the status |
| walker. |
| But if the unversioned node is the root of the walk, we have a user |
| error */ |
| if (is_harvest_root) |
| return svn_error_createf( |
| SVN_ERR_ILLEGAL_TARGET, NULL, |
| _("'%s' is not under version control"), |
| svn_dirent_local_style(local_abspath, scratch_pool)); |
| return SVN_NO_ERROR; |
| case svn_wc_status_normal: |
| /* Status normal nodes aren't modified, so we don't have to commit them |
| when we perform a normal commit. But if a node is conflicted we want |
| to stop the commit and if we are collecting lock tokens we want to |
| look further anyway. |
| |
| When in copy mode we need to compare the revision of the node against |
| the parent node to copy mixed-revision base nodes properly */ |
| if (!copy_mode && !status->conflicted |
| && !(just_locked && status->lock)) |
| return SVN_NO_ERROR; |
| break; |
| default: |
| /* Fall through */ |
| break; |
| } |
| |
| /* Early out if the item is already marked as committable. */ |
| if (look_up_committable(committables, local_abspath, scratch_pool)) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR_ASSERT((copy_mode && commit_relpath) |
| || (! copy_mode && ! commit_relpath)); |
| SVN_ERR_ASSERT((copy_mode_root && copy_mode) || ! copy_mode_root); |
| |
| /* Save the result for reuse. */ |
| matches_changelists = ((changelists == NULL) |
| || (status->changelist != NULL |
| && svn_hash_gets(changelists, status->changelist) |
| != NULL)); |
| |
| /* Early exit. */ |
| if (status->kind != svn_node_dir && ! matches_changelists) |
| { |
| return SVN_NO_ERROR; |
| } |
| |
| /* If NODE is in our changelist, then examine it for conflicts. We |
| need to bail out if any conflicts exist. |
| The status walker checked for conflict marker removal. */ |
| if (status->conflicted && matches_changelists) |
| { |
| if (notify_func != NULL) |
| { |
| notify_func(notify_baton, |
| svn_wc_create_notify(local_abspath, |
| svn_wc_notify_failed_conflict, |
| scratch_pool), |
| scratch_pool); |
| } |
| |
| return svn_error_createf( |
| SVN_ERR_WC_FOUND_CONFLICT, NULL, |
| _("Aborting commit: '%s' remains in conflict"), |
| svn_dirent_local_style(local_abspath, scratch_pool)); |
| } |
| else if (status->node_status == svn_wc_status_obstructed) |
| { |
| /* A node's type has changed before attempting to commit. |
| This also catches symlink vs non symlink changes */ |
| |
| if (notify_func != NULL) |
| { |
| notify_func(notify_baton, |
| svn_wc_create_notify(local_abspath, |
| svn_wc_notify_failed_obstruction, |
| scratch_pool), |
| scratch_pool); |
| } |
| |
| return svn_error_createf( |
| SVN_ERR_NODE_UNEXPECTED_KIND, NULL, |
| _("Node '%s' has unexpectedly changed kind"), |
| svn_dirent_local_style(local_abspath, scratch_pool)); |
| } |
| |
| if (status->conflicted && status->kind == svn_node_unknown) |
| return SVN_NO_ERROR; /* Ignore delete-delete conflict */ |
| |
| /* Return error on unknown path kinds. We check both the entry and |
| the node itself, since a path might have changed kind since its |
| entry was written. */ |
| SVN_ERR(svn_wc__node_get_commit_status(&is_added, &is_deleted, |
| &is_replaced, |
| &is_op_root, |
| &node_rev, |
| &original_rev, &original_relpath, |
| wc_ctx, local_abspath, |
| scratch_pool, scratch_pool)); |
| |
| /* Hande file externals only when passed as explicit target. Note that |
| * svn_client_commit6() passes all committable externals in as explicit |
| * targets iff they count. */ |
| if (status->file_external && !is_harvest_root) |
| { |
| return SVN_NO_ERROR; |
| } |
| |
| if (status->node_status == svn_wc_status_missing && matches_changelists) |
| { |
| /* Added files and directories must exist. See issue #3198. */ |
| if (is_added && is_op_root) |
| { |
| if (notify_func != NULL) |
| { |
| notify_func(notify_baton, |
| svn_wc_create_notify(local_abspath, |
| svn_wc_notify_failed_missing, |
| scratch_pool), |
| scratch_pool); |
| } |
| return svn_error_createf( |
| SVN_ERR_WC_PATH_NOT_FOUND, NULL, |
| _("'%s' is scheduled for addition, but is missing"), |
| svn_dirent_local_style(local_abspath, scratch_pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| if (is_deleted && !is_op_root /* && !is_added */) |
| return SVN_NO_ERROR; /* Not an operational delete and not an add. */ |
| |
| /* Check for the deletion case. |
| * We delete explicitly deleted nodes (duh!) |
| * We delete not-present children of copies |
| * We delete nodes that directly replace a node in its ancestor |
| */ |
| |
| if (is_deleted || is_replaced) |
| state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE; |
| |
| /* Check for adds and copies */ |
| if (is_added && is_op_root) |
| { |
| /* Root of local add or copy */ |
| state_flags |= SVN_CLIENT_COMMIT_ITEM_ADD; |
| |
| if (original_relpath) |
| { |
| /* Root of copy */ |
| state_flags |= SVN_CLIENT_COMMIT_ITEM_IS_COPY; |
| cf_relpath = original_relpath; |
| cf_rev = original_rev; |
| |
| if (status->moved_from_abspath && !copy_mode) |
| { |
| state_flags |= SVN_CLIENT_COMMIT_ITEM_MOVED_HERE; |
| moved_from_abspath = status->moved_from_abspath; |
| } |
| } |
| } |
| |
| /* Further copies may occur in copy mode. */ |
| else if (copy_mode |
| && !(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)) |
| { |
| svn_revnum_t dir_rev = SVN_INVALID_REVNUM; |
| const char *dir_repos_relpath = NULL; |
| |
| if (!copy_mode_root && !is_added) |
| SVN_ERR(svn_wc__node_get_base(NULL, &dir_rev, &dir_repos_relpath, NULL, |
| NULL, NULL, |
| wc_ctx, svn_dirent_dirname(local_abspath, |
| scratch_pool), |
| FALSE /* ignore_enoent */, |
| scratch_pool, scratch_pool)); |
| |
| if (copy_mode_root || status->switched || node_rev != dir_rev) |
| { |
| state_flags |= (SVN_CLIENT_COMMIT_ITEM_ADD |
| | SVN_CLIENT_COMMIT_ITEM_IS_COPY); |
| |
| if (status->copied) |
| { |
| /* Copy from original location */ |
| cf_rev = original_rev; |
| cf_relpath = original_relpath; |
| } |
| else |
| { |
| /* Copy BASE location, to represent a mixed-rev or switch copy */ |
| cf_rev = status->revision; |
| cf_relpath = status->repos_relpath; |
| } |
| |
| if (!copy_mode_root && !is_added && baton->check_url_func |
| && dir_repos_relpath) |
| { |
| svn_node_kind_t me_kind; |
| /* Maybe we need to issue an delete (mixed rev/switched) */ |
| |
| SVN_ERR(baton->check_url_func( |
| baton->check_url_baton, &me_kind, |
| svn_path_url_add_component2(repos_root_url, |
| svn_relpath_join(dir_repos_relpath, |
| svn_dirent_basename(local_abspath, |
| NULL), |
| scratch_pool), |
| scratch_pool), |
| dir_rev, scratch_pool)); |
| if (me_kind != svn_node_none) |
| state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE; |
| } |
| } |
| } |
| |
| if (!(state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) |
| || (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) |
| { |
| svn_boolean_t text_mod = FALSE; |
| svn_boolean_t prop_mod = FALSE; |
| |
| if (status->kind == svn_node_file) |
| { |
| /* Check for text modifications on files */ |
| if ((state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) |
| && ! (state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)) |
| { |
| text_mod = TRUE; /* Local added files are always modified */ |
| } |
| else |
| text_mod = (status->text_status != svn_wc_status_normal); |
| } |
| |
| prop_mod = (status->prop_status != svn_wc_status_normal |
| && status->prop_status != svn_wc_status_none); |
| |
| /* Set text/prop modification flags accordingly. */ |
| if (text_mod) |
| state_flags |= SVN_CLIENT_COMMIT_ITEM_TEXT_MODS; |
| if (prop_mod) |
| state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS; |
| } |
| |
| /* If the entry has a lock token and it is already a commit candidate, |
| or the caller wants unmodified locked items to be treated as |
| such, note this fact. */ |
| if (status->lock && baton->lock_tokens && (state_flags || just_locked)) |
| { |
| state_flags |= SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN; |
| } |
| |
| /* Now, if this is something to commit, add it to our list. */ |
| if (matches_changelists |
| && state_flags) |
| { |
| /* Finally, add the committable item. */ |
| SVN_ERR(add_committable(committables, local_abspath, |
| status->kind, |
| repos_root_url, |
| copy_mode |
| ? commit_relpath |
| : status->repos_relpath, |
| copy_mode |
| ? SVN_INVALID_REVNUM |
| : node_rev, |
| cf_relpath, |
| cf_rev, |
| moved_from_abspath, |
| state_flags, |
| baton->lock_tokens, status->lock, |
| result_pool, scratch_pool)); |
| } |
| |
| /* Fetch lock tokens for descendants of deleted BASE nodes. */ |
| if (matches_changelists |
| && (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) |
| && !copy_mode |
| && SVN_IS_VALID_REVNUM(node_rev) /* && BASE-kind = dir */ |
| && baton->lock_tokens) |
| { |
| apr_hash_t *local_relpath_tokens; |
| apr_hash_index_t *hi; |
| |
| SVN_ERR(svn_wc__node_get_lock_tokens_recursive( |
| &local_relpath_tokens, wc_ctx, local_abspath, |
| result_pool, scratch_pool)); |
| |
| /* Add tokens to existing hash. */ |
| for (hi = apr_hash_first(scratch_pool, local_relpath_tokens); |
| hi; |
| hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| apr_ssize_t klen; |
| void * val; |
| |
| apr_hash_this(hi, &key, &klen, &val); |
| |
| apr_hash_set(baton->lock_tokens, key, klen, val); |
| } |
| } |
| |
| /* Make sure we check for dangling children on additions |
| |
| We perform this operation on the harvest root, and on roots caused by |
| changelist filtering. |
| */ |
| if (matches_changelists |
| && (is_harvest_root || baton->changelists) |
| && state_flags |
| && (is_added || (is_deleted && is_op_root && status->copied)) |
| && baton->danglers) |
| { |
| /* If a node is added, its parent must exist in the repository at the |
| time of committing */ |
| apr_hash_t *danglers = baton->danglers; |
| svn_boolean_t parent_added; |
| const char *parent_abspath = svn_dirent_dirname(local_abspath, |
| scratch_pool); |
| |
| /* First check if parent is already in the list of commits |
| (Common case for GUI clients that provide a list of commit targets) */ |
| if (look_up_committable(committables, parent_abspath, scratch_pool)) |
| parent_added = FALSE; /* Skip all expensive checks */ |
| else |
| SVN_ERR(svn_wc__node_is_added(&parent_added, wc_ctx, parent_abspath, |
| scratch_pool)); |
| |
| if (parent_added) |
| { |
| const char *copy_root_abspath; |
| svn_boolean_t parent_is_copy; |
| |
| /* The parent is added, so either it is a copy, or a locally added |
| * directory. In either case, we require the op-root of the parent |
| * to be part of the commit. See issue #4059. */ |
| SVN_ERR(svn_wc__node_get_origin(&parent_is_copy, NULL, NULL, NULL, |
| NULL, NULL, ©_root_abspath, |
| wc_ctx, parent_abspath, |
| FALSE, scratch_pool, scratch_pool)); |
| |
| if (parent_is_copy) |
| parent_abspath = copy_root_abspath; |
| |
| if (!svn_hash_gets(danglers, parent_abspath)) |
| { |
| svn_hash_sets(danglers, apr_pstrdup(result_pool, parent_abspath), |
| apr_pstrdup(result_pool, local_abspath)); |
| } |
| } |
| } |
| |
| if (is_deleted && !is_added) |
| { |
| /* Skip all descendants */ |
| if (status->kind == svn_node_dir) |
| baton->skip_below_abspath = apr_pstrdup(baton->result_pool, |
| local_abspath); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Recursively handle each node according to depth, except when the |
| node is only being deleted, or is in an added tree (as added trees |
| use the normal commit handling). */ |
| if (copy_mode && !is_added && !is_deleted && status->kind == svn_node_dir) |
| { |
| SVN_ERR(harvest_not_present_for_copy(wc_ctx, local_abspath, committables, |
| repos_root_url, commit_relpath, |
| baton->check_url_func, |
| baton->check_url_baton, |
| result_pool, scratch_pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Baton for handle_descendants */ |
| struct handle_descendants_baton |
| { |
| svn_wc_context_t *wc_ctx; |
| svn_cancel_func_t cancel_func; |
| void *cancel_baton; |
| svn_client__check_url_kind_t check_url_func; |
| void *check_url_baton; |
| svn_client__committables_t *committables; |
| }; |
| |
| /* Helper for the commit harvesters */ |
| static svn_error_t * |
| handle_descendants(void *baton, |
| const void *key, apr_ssize_t klen, void *val, |
| apr_pool_t *pool) |
| { |
| struct handle_descendants_baton *hdb = baton; |
| apr_array_header_t *commit_items = val; |
| apr_pool_t *iterpool = svn_pool_create(pool); |
| const char *repos_root_url = key; |
| int i; |
| |
| 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 *); |
| const apr_array_header_t *absent_descendants; |
| int j; |
| |
| /* Is this a copy operation? */ |
| if (!(item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) |
| || ! item->copyfrom_url) |
| continue; |
| |
| if (hdb->cancel_func) |
| SVN_ERR(hdb->cancel_func(hdb->cancel_baton)); |
| |
| svn_pool_clear(iterpool); |
| |
| SVN_ERR(svn_wc__get_not_present_descendants(&absent_descendants, |
| hdb->wc_ctx, item->path, |
| iterpool, iterpool)); |
| |
| for (j = 0; j < absent_descendants->nelts; j++) |
| { |
| svn_node_kind_t kind; |
| svn_client_commit_item3_t *desc_item; |
| const char *relpath = APR_ARRAY_IDX(absent_descendants, j, |
| const char *); |
| const char *local_abspath = svn_dirent_join(item->path, relpath, |
| iterpool); |
| |
| /* ### Need a sub-iterpool? */ |
| |
| |
| /* We found a 'not present' descendant during a copy (at op_depth>0), |
| this is most commonly caused by copying some mixed revision tree. |
| |
| In this case not present can imply that the node does not exist |
| in the parent revision, or that the node does. But we want to copy |
| the working copy state in which it does not exist, but might be |
| replaced. */ |
| |
| desc_item = svn_hash_gets(hdb->committables->by_path, local_abspath); |
| |
| /* If the path has a commit operation (possibly at an higher |
| op_depth, we might want to turn an add in a replace. */ |
| if (desc_item) |
| { |
| const char *dir; |
| svn_boolean_t found_intermediate = FALSE; |
| |
| if (desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) |
| continue; /* We already have a delete or replace */ |
| else if (!(desc_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) |
| continue; /* Not a copy/add, just a modification */ |
| |
| dir = svn_dirent_dirname(local_abspath, iterpool); |
| |
| while (strcmp(dir, item->path)) |
| { |
| svn_client_commit_item3_t *i_item; |
| |
| i_item = svn_hash_gets(hdb->committables->by_path, dir); |
| |
| if (i_item) |
| { |
| if ((i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) |
| || (i_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) |
| { |
| found_intermediate = TRUE; |
| break; |
| } |
| } |
| dir = svn_dirent_dirname(dir, iterpool); |
| } |
| |
| if (found_intermediate) |
| continue; /* Some intermediate ancestor is an add or delete */ |
| |
| /* Fall through to detect if we need to turn the add in a |
| replace. */ |
| } |
| |
| if (hdb->check_url_func) |
| { |
| const char *from_url = svn_path_url_add_component2( |
| item->copyfrom_url, relpath, |
| iterpool); |
| |
| SVN_ERR(hdb->check_url_func(hdb->check_url_baton, |
| &kind, from_url, item->copyfrom_rev, |
| iterpool)); |
| |
| if (kind == svn_node_none) |
| continue; /* This node is already deleted */ |
| } |
| else |
| kind = svn_node_unknown; /* 'Ok' for a delete of something */ |
| |
| if (desc_item) |
| { |
| /* Extend the existing add/copy item to create a replace */ |
| desc_item->state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE; |
| continue; |
| } |
| |
| /* Add a new commit item that describes the delete */ |
| |
| SVN_ERR(add_committable(hdb->committables, |
| svn_dirent_join(item->path, relpath, |
| iterpool), |
| kind, |
| repos_root_url, |
| svn_uri_skip_ancestor( |
| repos_root_url, |
| svn_path_url_add_component2(item->url, |
| relpath, |
| iterpool), |
| iterpool), |
| SVN_INVALID_REVNUM, |
| NULL /* copyfrom_relpath */, |
| SVN_INVALID_REVNUM, |
| NULL /* moved_from_abspath */, |
| SVN_CLIENT_COMMIT_ITEM_DELETE, |
| NULL /* lock tokens */, |
| NULL /* lock */, |
| commit_items->pool, |
| iterpool)); |
| } |
| } |
| |
| svn_pool_destroy(iterpool); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Allocate and initialize the COMMITTABLES structure from POOL. |
| */ |
| static void |
| create_committables(svn_client__committables_t **committables, |
| apr_pool_t *pool) |
| { |
| *committables = apr_palloc(pool, sizeof(**committables)); |
| |
| (*committables)->by_repository = apr_hash_make(pool); |
| (*committables)->by_path = apr_hash_make(pool); |
| } |
| |
| svn_error_t * |
| svn_client__harvest_committables(svn_client__committables_t **committables, |
| apr_hash_t **lock_tokens, |
| const char *base_dir_abspath, |
| const apr_array_header_t *targets, |
| int depth_empty_start, |
| svn_depth_t depth, |
| svn_boolean_t just_locked, |
| const apr_array_header_t *changelists, |
| svn_client__check_url_kind_t check_url_func, |
| void *check_url_baton, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| int i; |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| apr_hash_t *changelist_hash = NULL; |
| struct handle_descendants_baton hdb; |
| apr_hash_index_t *hi; |
| |
| /* It's possible that one of the named targets has a parent that is |
| * itself scheduled for addition or replacement -- that is, the |
| * parent is not yet versioned in the repository. This is okay, as |
| * long as the parent itself is part of this same commit, either |
| * directly, or by virtue of a grandparent, great-grandparent, etc, |
| * being part of the commit. |
| * |
| * Since we don't know what's included in the commit until we've |
| * harvested all the targets, we can't reliably check this as we |
| * go. So in `danglers', we record named targets whose parents |
| * do not yet exist in the repository. Then after harvesting the total |
| * commit group, we check to make sure those parents are included. |
| * |
| * Each key of danglers is a parent which does not exist in the |
| * repository. The (const char *) value is one of that parent's |
| * children which is named as part of the commit; the child is |
| * included only to make a better error message. |
| * |
| * (The reason we don't bother to check unnamed -- i.e, implicit -- |
| * targets is that they can only join the commit if their parents |
| * did too, so this situation can't arise for them.) |
| */ |
| apr_hash_t *danglers = apr_hash_make(scratch_pool); |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(base_dir_abspath)); |
| |
| /* Create the COMMITTABLES structure. */ |
| create_committables(committables, result_pool); |
| |
| /* And the LOCK_TOKENS dito. */ |
| *lock_tokens = apr_hash_make(result_pool); |
| |
| /* If we have a list of changelists, convert that into a hash with |
| changelist keys. */ |
| if (changelists && changelists->nelts) |
| SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, |
| scratch_pool)); |
| |
| for (i = 0; i < targets->nelts; ++i) |
| { |
| const char *target_abspath; |
| |
| svn_pool_clear(iterpool); |
| |
| /* Add the relative portion to the base abspath. */ |
| target_abspath = svn_dirent_join(base_dir_abspath, |
| APR_ARRAY_IDX(targets, i, const char *), |
| iterpool); |
| |
| /* Handle our TARGET. */ |
| /* Make sure this isn't inside a working copy subtree that is |
| * marked as tree-conflicted. */ |
| SVN_ERR(bail_on_tree_conflicted_ancestor(ctx->wc_ctx, target_abspath, |
| ctx->notify_func2, |
| ctx->notify_baton2, |
| iterpool)); |
| |
| /* Are the remaining items externals with depth empty? */ |
| if (i == depth_empty_start) |
| depth = svn_depth_empty; |
| |
| SVN_ERR(harvest_committables(target_abspath, |
| *committables, *lock_tokens, |
| NULL /* COPY_MODE_RELPATH */, |
| depth, just_locked, changelist_hash, |
| danglers, |
| check_url_func, check_url_baton, |
| ctx->cancel_func, ctx->cancel_baton, |
| ctx->notify_func2, ctx->notify_baton2, |
| ctx->wc_ctx, result_pool, iterpool)); |
| } |
| |
| hdb.wc_ctx = ctx->wc_ctx; |
| hdb.cancel_func = ctx->cancel_func; |
| hdb.cancel_baton = ctx->cancel_baton; |
| hdb.check_url_func = check_url_func; |
| hdb.check_url_baton = check_url_baton; |
| hdb.committables = *committables; |
| |
| SVN_ERR(svn_iter_apr_hash(NULL, (*committables)->by_repository, |
| handle_descendants, &hdb, iterpool)); |
| |
| /* Make sure that every path in danglers is part of the commit. */ |
| for (hi = apr_hash_first(scratch_pool, danglers); hi; hi = apr_hash_next(hi)) |
| { |
| const char *dangling_parent = apr_hash_this_key(hi); |
| |
| svn_pool_clear(iterpool); |
| |
| if (! look_up_committable(*committables, dangling_parent, iterpool)) |
| { |
| const char *dangling_child = apr_hash_this_val(hi); |
| |
| if (ctx->notify_func2 != NULL) |
| { |
| svn_wc_notify_t *notify; |
| |
| notify = svn_wc_create_notify(dangling_child, |
| svn_wc_notify_failed_no_parent, |
| scratch_pool); |
| |
| ctx->notify_func2(ctx->notify_baton2, notify, iterpool); |
| } |
| |
| return svn_error_createf( |
| SVN_ERR_ILLEGAL_TARGET, NULL, |
| _("'%s' is not known to exist in the repository " |
| "and is not part of the commit, " |
| "yet its child '%s' is part of the commit"), |
| /* Probably one or both of these is an entry, but |
| safest to local_stylize just in case. */ |
| svn_dirent_local_style(dangling_parent, iterpool), |
| svn_dirent_local_style(dangling_child, iterpool)); |
| } |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| struct copy_committables_baton |
| { |
| svn_client_ctx_t *ctx; |
| svn_client__committables_t *committables; |
| apr_pool_t *result_pool; |
| svn_client__check_url_kind_t check_url_func; |
| void *check_url_baton; |
| }; |
| |
| static svn_error_t * |
| harvest_copy_committables(void *baton, void *item, apr_pool_t *pool) |
| { |
| struct copy_committables_baton *btn = baton; |
| svn_client__copy_pair_t *pair = *(svn_client__copy_pair_t **)item; |
| const char *repos_root_url; |
| const char *commit_relpath; |
| struct handle_descendants_baton hdb; |
| |
| /* Read the entry for this SRC. */ |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url)); |
| |
| SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &repos_root_url, NULL, |
| btn->ctx->wc_ctx, |
| pair->src_abspath_or_url, |
| pool, pool)); |
| |
| commit_relpath = svn_uri_skip_ancestor(repos_root_url, |
| pair->dst_abspath_or_url, pool); |
| |
| /* Handle this SRC. */ |
| SVN_ERR(harvest_committables(pair->src_abspath_or_url, |
| btn->committables, NULL, |
| commit_relpath, |
| svn_depth_infinity, |
| FALSE, /* JUST_LOCKED */ |
| NULL /* changelists */, |
| NULL, |
| btn->check_url_func, |
| btn->check_url_baton, |
| btn->ctx->cancel_func, |
| btn->ctx->cancel_baton, |
| btn->ctx->notify_func2, |
| btn->ctx->notify_baton2, |
| btn->ctx->wc_ctx, btn->result_pool, pool)); |
| |
| hdb.wc_ctx = btn->ctx->wc_ctx; |
| hdb.cancel_func = btn->ctx->cancel_func; |
| hdb.cancel_baton = btn->ctx->cancel_baton; |
| hdb.check_url_func = btn->check_url_func; |
| hdb.check_url_baton = btn->check_url_baton; |
| hdb.committables = btn->committables; |
| |
| SVN_ERR(svn_iter_apr_hash(NULL, btn->committables->by_repository, |
| handle_descendants, &hdb, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| svn_error_t * |
| svn_client__get_copy_committables(svn_client__committables_t **committables, |
| const apr_array_header_t *copy_pairs, |
| svn_client__check_url_kind_t check_url_func, |
| void *check_url_baton, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| struct copy_committables_baton btn; |
| |
| /* Create the COMMITTABLES structure. */ |
| create_committables(committables, result_pool); |
| |
| btn.ctx = ctx; |
| btn.committables = *committables; |
| btn.result_pool = result_pool; |
| |
| btn.check_url_func = check_url_func; |
| btn.check_url_baton = check_url_baton; |
| |
| /* For each copy pair, harvest the committables for that pair into the |
| committables hash. */ |
| return svn_iter_apr_array(NULL, copy_pairs, |
| harvest_copy_committables, &btn, scratch_pool); |
| } |
| |
| |
| /* A svn_sort__array()/qsort()-compatible sort routine for sorting |
| an array of svn_client_commit_item_t *'s by their URL member. */ |
| static int |
| sort_commit_item_urls(const void *a, const void *b) |
| { |
| const svn_client_commit_item3_t *item1 |
| = *((const svn_client_commit_item3_t * const *) a); |
| const svn_client_commit_item3_t *item2 |
| = *((const svn_client_commit_item3_t * const *) b); |
| return svn_path_compare_paths(item1->url, item2->url); |
| } |
| |
| |
| svn_error_t * |
| svn_client__condense_commit_items2(const char *base_url, |
| apr_array_header_t *commit_items, |
| apr_pool_t *pool) |
| { |
| apr_array_header_t *ci = commit_items; /* convenience */ |
| int i; |
| |
| /* Sort our commit items by their URLs. */ |
| svn_sort__array(ci, sort_commit_item_urls); |
| |
| /* Hack BASE_URL off each URL; store the result as session_relpath. */ |
| for (i = 0; i < ci->nelts; i++) |
| { |
| svn_client_commit_item3_t *this_item |
| = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); |
| |
| this_item->session_relpath = svn_uri_skip_ancestor(base_url, |
| this_item->url, pool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_client__condense_commit_items(const char **base_url, |
| apr_array_header_t *commit_items, |
| apr_pool_t *pool) |
| { |
| apr_array_header_t *ci = commit_items; /* convenience */ |
| const char *url; |
| svn_client_commit_item3_t *item, *last_item = NULL; |
| int i; |
| |
| SVN_ERR_ASSERT(ci && ci->nelts); |
| |
| /* Sort our commit items by their URLs. */ |
| svn_sort__array(ci, sort_commit_item_urls); |
| |
| /* Loop through the URLs, finding the longest usable ancestor common |
| to all of them, and making sure there are no duplicate URLs. */ |
| for (i = 0; i < ci->nelts; i++) |
| { |
| item = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); |
| url = item->url; |
| |
| if ((last_item) && (strcmp(last_item->url, url) == 0)) |
| return svn_error_createf |
| (SVN_ERR_CLIENT_DUPLICATE_COMMIT_URL, NULL, |
| _("Cannot commit both '%s' and '%s' as they refer to the same URL"), |
| svn_dirent_local_style(item->path, pool), |
| svn_dirent_local_style(last_item->path, pool)); |
| |
| /* In the first iteration, our BASE_URL is just our only |
| encountered commit URL to date. After that, we find the |
| longest ancestor between the current BASE_URL and the current |
| commit URL. */ |
| if (i == 0) |
| *base_url = apr_pstrdup(pool, url); |
| else |
| *base_url = svn_uri_get_longest_ancestor(*base_url, url, pool); |
| |
| /* If our BASE_URL is itself a to-be-committed item, and it is |
| anything other than an already-versioned directory with |
| property mods, we'll call its parent directory URL the |
| BASE_URL. Why? Because we can't have a file URL as our base |
| -- period -- and all other directory operations (removal, |
| addition, etc.) require that we open that directory's parent |
| dir first. */ |
| /* ### I don't understand the strlen()s here, hmmm. -kff */ |
| if ((strlen(*base_url) == strlen(url)) |
| && (! ((item->kind == svn_node_dir) |
| && item->state_flags == SVN_CLIENT_COMMIT_ITEM_PROP_MODS))) |
| *base_url = svn_uri_dirname(*base_url, pool); |
| |
| /* Stash our item here for the next iteration. */ |
| last_item = item; |
| } |
| |
| /* Now that we've settled on a *BASE_URL, go hack that base off |
| of all of our URLs and store it as session_relpath. */ |
| for (i = 0; i < ci->nelts; i++) |
| { |
| svn_client_commit_item3_t *this_item |
| = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); |
| |
| this_item->session_relpath = svn_uri_skip_ancestor(*base_url, |
| this_item->url, pool); |
| } |
| #ifdef SVN_CLIENT_COMMIT_DEBUG |
| /* ### TEMPORARY CODE ### */ |
| SVN_DBG(("COMMITTABLES: (base URL=%s)\n", *base_url)); |
| SVN_DBG((" FLAGS REV REL-URL (COPY-URL)\n")); |
| for (i = 0; i < ci->nelts; i++) |
| { |
| svn_client_commit_item3_t *this_item |
| = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); |
| char flags[6]; |
| flags[0] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) |
| ? 'a' : '-'; |
| flags[1] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) |
| ? 'd' : '-'; |
| flags[2] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) |
| ? 't' : '-'; |
| flags[3] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) |
| ? 'p' : '-'; |
| flags[4] = (this_item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) |
| ? 'c' : '-'; |
| flags[5] = '\0'; |
| SVN_DBG((" %s %6ld '%s' (%s)\n", |
| flags, |
| this_item->revision, |
| this_item->url ? this_item->url : "", |
| this_item->copyfrom_url ? this_item->copyfrom_url : "none")); |
| } |
| #endif /* SVN_CLIENT_COMMIT_DEBUG */ |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| struct file_mod_t |
| { |
| const svn_client_commit_item3_t *item; |
| void *file_baton; |
| apr_pool_t *file_pool; |
| }; |
| |
| |
| /* A baton for use while driving a path-based editor driver for commit */ |
| struct item_commit_baton |
| { |
| apr_hash_t *file_mods; /* hash: path->file_mod_t */ |
| const char *notify_path_prefix; /* notification path prefix |
| (NULL is okay, else abs path) */ |
| svn_client_ctx_t *ctx; /* client context baton */ |
| apr_hash_t *commit_items; /* the committables */ |
| const char *base_url; /* The session url for the commit */ |
| }; |
| |
| |
| /* Drive CALLBACK_BATON->editor with the change described by the item in |
| * CALLBACK_BATON->commit_items that is keyed by PATH. If the change |
| * includes a text mod, however, call the editor's file_open() function |
| * but do not send the text mod to the editor; instead, add a mapping of |
| * "item-url => (commit-item, file-baton)" into CALLBACK_BATON->file_mods. |
| * |
| * Before driving the editor, call the cancellation and notification |
| * callbacks in CALLBACK_BATON->ctx, if present. |
| * |
| * This implements svn_delta_path_driver_cb_func_t. */ |
| static svn_error_t * |
| do_item_commit(void **dir_baton, |
| const svn_delta_editor_t *editor, |
| void *edit_baton, |
| void *parent_baton, |
| void *callback_baton, |
| const char *path, |
| apr_pool_t *pool) |
| { |
| struct item_commit_baton *icb = callback_baton; |
| const svn_client_commit_item3_t *item = svn_hash_gets(icb->commit_items, |
| path); |
| svn_node_kind_t kind = item->kind; |
| void *file_baton = NULL; |
| apr_pool_t *file_pool = NULL; |
| apr_hash_t *file_mods = icb->file_mods; |
| svn_client_ctx_t *ctx = icb->ctx; |
| svn_error_t *err; |
| const char *local_abspath = NULL; |
| |
| /* Do some initializations. */ |
| *dir_baton = NULL; |
| if (item->kind != svn_node_none && item->path) |
| { |
| /* We always get an absolute path, see svn_client_commit_item3_t. */ |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path)); |
| local_abspath = item->path; |
| } |
| |
| /* If this is a file with textual mods, we'll be keeping its baton |
| around until the end of the commit. So just lump its memory into |
| a single, big, all-the-file-batons-in-here pool. Otherwise, we |
| can just use POOL, and trust our caller to clean that mess up. */ |
| if ((kind == svn_node_file) |
| && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)) |
| file_pool = apr_hash_pool_get(file_mods); |
| else |
| file_pool = pool; |
| |
| /* Subpools are cheap, but memory isn't */ |
| file_pool = svn_pool_create(file_pool); |
| |
| /* Call the cancellation function. */ |
| if (ctx->cancel_func) |
| SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); |
| |
| /* Validation. */ |
| if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY) |
| { |
| if (! item->copyfrom_url) |
| return svn_error_createf |
| (SVN_ERR_BAD_URL, NULL, |
| _("Commit item '%s' has copy flag but no copyfrom URL"), |
| svn_dirent_local_style(path, pool)); |
| if (! SVN_IS_VALID_REVNUM(item->copyfrom_rev)) |
| return svn_error_createf |
| (SVN_ERR_CLIENT_BAD_REVISION, NULL, |
| _("Commit item '%s' has copy flag but an invalid revision"), |
| svn_dirent_local_style(path, pool)); |
| } |
| |
| /* If a feedback table was supplied by the application layer, |
| describe what we're about to do to this item. */ |
| if (ctx->notify_func2 && item->path) |
| { |
| const char *npath = item->path; |
| svn_wc_notify_t *notify; |
| |
| if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) |
| && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)) |
| { |
| /* We don't print the "(bin)" notice for binary files when |
| replacing, only when adding. So we don't bother to get |
| the mime-type here. */ |
| if (item->copyfrom_url) |
| notify = svn_wc_create_notify(npath, |
| svn_wc_notify_commit_copied_replaced, |
| pool); |
| else |
| notify = svn_wc_create_notify(npath, svn_wc_notify_commit_replaced, |
| pool); |
| |
| } |
| else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) |
| { |
| notify = svn_wc_create_notify(npath, svn_wc_notify_commit_deleted, |
| pool); |
| } |
| else if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) |
| { |
| if (item->copyfrom_url) |
| notify = svn_wc_create_notify(npath, svn_wc_notify_commit_copied, |
| pool); |
| else |
| notify = svn_wc_create_notify(npath, svn_wc_notify_commit_added, |
| pool); |
| |
| if (item->kind == svn_node_file) |
| { |
| const svn_string_t *propval; |
| |
| SVN_ERR(svn_wc_prop_get2(&propval, ctx->wc_ctx, local_abspath, |
| SVN_PROP_MIME_TYPE, pool, pool)); |
| |
| if (propval) |
| notify->mime_type = propval->data; |
| } |
| } |
| else if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) |
| || (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS)) |
| { |
| notify = svn_wc_create_notify(npath, svn_wc_notify_commit_modified, |
| pool); |
| if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS) |
| notify->content_state = svn_wc_notify_state_changed; |
| else |
| notify->content_state = svn_wc_notify_state_unchanged; |
| if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) |
| notify->prop_state = svn_wc_notify_state_changed; |
| else |
| notify->prop_state = svn_wc_notify_state_unchanged; |
| } |
| else |
| notify = NULL; |
| |
| |
| if (notify) |
| { |
| notify->kind = item->kind; |
| notify->path_prefix = icb->notify_path_prefix; |
| ctx->notify_func2(ctx->notify_baton2, notify, pool); |
| } |
| } |
| |
| /* If this item is supposed to be deleted, do so. */ |
| if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) |
| { |
| SVN_ERR_ASSERT(parent_baton); |
| err = editor->delete_entry(path, item->revision, |
| parent_baton, pool); |
| |
| if (err) |
| goto fixup_error; |
| } |
| |
| /* If this item is supposed to be added, do so. */ |
| if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) |
| { |
| if (kind == svn_node_file) |
| { |
| SVN_ERR_ASSERT(parent_baton); |
| err = editor->add_file( |
| path, parent_baton, item->copyfrom_url, |
| item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM, |
| file_pool, &file_baton); |
| } |
| else /* May be svn_node_none when adding parent dirs for a copy. */ |
| { |
| SVN_ERR_ASSERT(parent_baton); |
| err = editor->add_directory( |
| path, parent_baton, item->copyfrom_url, |
| item->copyfrom_url ? item->copyfrom_rev : SVN_INVALID_REVNUM, |
| pool, dir_baton); |
| } |
| |
| if (err) |
| goto fixup_error; |
| |
| /* Set other prop-changes, if available in the baton */ |
| if (item->outgoing_prop_changes) |
| { |
| svn_prop_t *prop; |
| apr_array_header_t *prop_changes = item->outgoing_prop_changes; |
| int ctr; |
| for (ctr = 0; ctr < prop_changes->nelts; ctr++) |
| { |
| prop = APR_ARRAY_IDX(prop_changes, ctr, svn_prop_t *); |
| if (kind == svn_node_file) |
| { |
| err = editor->change_file_prop(file_baton, prop->name, |
| prop->value, pool); |
| } |
| else |
| { |
| err = editor->change_dir_prop(*dir_baton, prop->name, |
| prop->value, pool); |
| } |
| |
| if (err) |
| goto fixup_error; |
| } |
| } |
| } |
| |
| /* Now handle property mods. */ |
| if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_PROP_MODS) |
| { |
| if (kind == svn_node_file) |
| { |
| if (! file_baton) |
| { |
| SVN_ERR_ASSERT(parent_baton); |
| err = editor->open_file(path, parent_baton, |
| item->revision, |
| file_pool, &file_baton); |
| |
| if (err) |
| goto fixup_error; |
| } |
| } |
| else |
| { |
| if (! *dir_baton) |
| { |
| if (! parent_baton) |
| { |
| err = editor->open_root(edit_baton, item->revision, |
| pool, dir_baton); |
| } |
| else |
| { |
| err = editor->open_directory(path, parent_baton, |
| item->revision, |
| pool, dir_baton); |
| } |
| |
| if (err) |
| goto fixup_error; |
| } |
| } |
| |
| /* When committing a directory that no longer exists in the |
| repository, a "not found" error does not occur immediately |
| upon opening the directory. It appears here during the delta |
| transmisssion. */ |
| err = svn_wc_transmit_prop_deltas2( |
| ctx->wc_ctx, local_abspath, editor, |
| (kind == svn_node_dir) ? *dir_baton : file_baton, pool); |
| |
| if (err) |
| goto fixup_error; |
| |
| /* Make any additional client -> repository prop changes. */ |
| if (item->outgoing_prop_changes) |
| { |
| svn_prop_t *prop; |
| int i; |
| |
| for (i = 0; i < item->outgoing_prop_changes->nelts; i++) |
| { |
| prop = APR_ARRAY_IDX(item->outgoing_prop_changes, i, |
| svn_prop_t *); |
| if (kind == svn_node_file) |
| { |
| err = editor->change_file_prop(file_baton, prop->name, |
| prop->value, pool); |
| } |
| else |
| { |
| err = editor->change_dir_prop(*dir_baton, prop->name, |
| prop->value, pool); |
| } |
| |
| if (err) |
| goto fixup_error; |
| } |
| } |
| } |
| |
| /* Finally, handle text mods (in that we need to open a file if it |
| hasn't already been opened, and we need to put the file baton in |
| our FILES hash). */ |
| if ((kind == svn_node_file) |
| && (item->state_flags & SVN_CLIENT_COMMIT_ITEM_TEXT_MODS)) |
| { |
| struct file_mod_t *mod = apr_palloc(file_pool, sizeof(*mod)); |
| |
| if (! file_baton) |
| { |
| SVN_ERR_ASSERT(parent_baton); |
| err = editor->open_file(path, parent_baton, |
| item->revision, |
| file_pool, &file_baton); |
| |
| if (err) |
| goto fixup_error; |
| } |
| |
| /* Add this file mod to the FILE_MODS hash. */ |
| mod->item = item; |
| mod->file_baton = file_baton; |
| mod->file_pool = file_pool; |
| svn_hash_sets(file_mods, item->session_relpath, mod); |
| } |
| else if (file_baton) |
| { |
| /* Close any outstanding file batons that didn't get caught by |
| the "has local mods" conditional above. */ |
| err = editor->close_file(file_baton, NULL, file_pool); |
| svn_pool_destroy(file_pool); |
| if (err) |
| goto fixup_error; |
| } |
| |
| return SVN_NO_ERROR; |
| |
| fixup_error: |
| return svn_error_trace(fixup_commit_error(local_abspath, |
| icb->base_url, |
| path, kind, |
| err, ctx, pool)); |
| } |
| |
| svn_error_t * |
| svn_client__do_commit(const char *base_url, |
| const apr_array_header_t *commit_items, |
| const svn_delta_editor_t *editor, |
| void *edit_baton, |
| const char *notify_path_prefix, |
| apr_hash_t **sha1_checksums, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| apr_hash_t *file_mods = apr_hash_make(scratch_pool); |
| apr_hash_t *items_hash = apr_hash_make(scratch_pool); |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| apr_hash_index_t *hi; |
| int i; |
| struct item_commit_baton cb_baton; |
| apr_array_header_t *paths = |
| apr_array_make(scratch_pool, commit_items->nelts, sizeof(const char *)); |
| |
| /* Ditto for the checksums. */ |
| if (sha1_checksums) |
| *sha1_checksums = apr_hash_make(result_pool); |
| |
| /* Build a hash from our COMMIT_ITEMS array, keyed on the |
| relative paths (which come from the item URLs). And |
| keep an array of those decoded paths, too. */ |
| 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 *); |
| const char *path = item->session_relpath; |
| svn_hash_sets(items_hash, path, item); |
| APR_ARRAY_PUSH(paths, const char *) = path; |
| } |
| |
| /* Setup the callback baton. */ |
| cb_baton.file_mods = file_mods; |
| cb_baton.notify_path_prefix = notify_path_prefix; |
| cb_baton.ctx = ctx; |
| cb_baton.commit_items = items_hash; |
| cb_baton.base_url = base_url; |
| |
| /* Drive the commit editor! */ |
| SVN_ERR(svn_delta_path_driver3(editor, edit_baton, paths, TRUE, |
| do_item_commit, &cb_baton, scratch_pool)); |
| |
| /* Transmit outstanding text deltas. */ |
| for (hi = apr_hash_first(scratch_pool, file_mods); |
| hi; |
| hi = apr_hash_next(hi)) |
| { |
| struct file_mod_t *mod = apr_hash_this_val(hi); |
| const svn_client_commit_item3_t *item = mod->item; |
| const svn_checksum_t *new_text_base_md5_checksum; |
| const svn_checksum_t *new_text_base_sha1_checksum; |
| svn_boolean_t fulltext = FALSE; |
| svn_error_t *err; |
| |
| svn_pool_clear(iterpool); |
| |
| /* Transmit the entry. */ |
| if (ctx->cancel_func) |
| SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); |
| |
| if (ctx->notify_func2) |
| { |
| svn_wc_notify_t *notify; |
| notify = svn_wc_create_notify(item->path, |
| svn_wc_notify_commit_postfix_txdelta, |
| iterpool); |
| notify->kind = svn_node_file; |
| notify->path_prefix = notify_path_prefix; |
| ctx->notify_func2(ctx->notify_baton2, notify, iterpool); |
| } |
| |
| /* If the node has no history, transmit full text */ |
| if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) |
| && ! (item->state_flags & SVN_CLIENT_COMMIT_ITEM_IS_COPY)) |
| fulltext = TRUE; |
| |
| err = svn_wc_transmit_text_deltas3(&new_text_base_md5_checksum, |
| &new_text_base_sha1_checksum, |
| ctx->wc_ctx, item->path, |
| fulltext, editor, mod->file_baton, |
| result_pool, iterpool); |
| |
| if (err) |
| { |
| svn_pool_destroy(iterpool); /* Close tempfiles */ |
| return svn_error_trace(fixup_commit_error(item->path, |
| base_url, |
| item->session_relpath, |
| svn_node_file, |
| err, ctx, scratch_pool)); |
| } |
| |
| if (sha1_checksums) |
| svn_hash_sets(*sha1_checksums, item->path, new_text_base_sha1_checksum); |
| |
| svn_pool_destroy(mod->file_pool); |
| } |
| |
| if (ctx->notify_func2) |
| { |
| svn_wc_notify_t *notify; |
| notify = svn_wc_create_notify_url(base_url, |
| svn_wc_notify_commit_finalizing, |
| iterpool); |
| ctx->notify_func2(ctx->notify_baton2, notify, iterpool); |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| /* Close the edit. */ |
| return svn_error_trace(editor->close_edit(edit_baton, scratch_pool)); |
| } |
| |
| |
| svn_error_t * |
| svn_client__get_log_msg(const char **log_msg, |
| const char **tmp_file, |
| const apr_array_header_t *commit_items, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| if (ctx->log_msg_func3) |
| { |
| /* The client provided a callback function for the current API. |
| Forward the call to it directly. */ |
| return (*ctx->log_msg_func3)(log_msg, tmp_file, commit_items, |
| ctx->log_msg_baton3, pool); |
| } |
| else if (ctx->log_msg_func2 || ctx->log_msg_func) |
| { |
| /* The client provided a pre-1.5 (or pre-1.3) API callback |
| function. Convert the commit_items list to the appropriate |
| type, and forward call to it. */ |
| svn_error_t *err; |
| apr_pool_t *scratch_pool = svn_pool_create(pool); |
| apr_array_header_t *old_commit_items = |
| apr_array_make(scratch_pool, commit_items->nelts, sizeof(void*)); |
| |
| int i; |
| 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 *); |
| |
| if (ctx->log_msg_func2) |
| { |
| svn_client_commit_item2_t *old_item = |
| apr_pcalloc(scratch_pool, sizeof(*old_item)); |
| |
| old_item->path = item->path; |
| old_item->kind = item->kind; |
| old_item->url = item->url; |
| old_item->revision = item->revision; |
| old_item->copyfrom_url = item->copyfrom_url; |
| old_item->copyfrom_rev = item->copyfrom_rev; |
| old_item->state_flags = item->state_flags; |
| old_item->wcprop_changes = item->incoming_prop_changes; |
| |
| APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item2_t *) = |
| old_item; |
| } |
| else /* ctx->log_msg_func */ |
| { |
| svn_client_commit_item_t *old_item = |
| apr_pcalloc(scratch_pool, sizeof(*old_item)); |
| |
| old_item->path = item->path; |
| old_item->kind = item->kind; |
| old_item->url = item->url; |
| /* The pre-1.3 API used the revision field for copyfrom_rev |
| and revision depeding of copyfrom_url. */ |
| old_item->revision = item->copyfrom_url ? |
| item->copyfrom_rev : item->revision; |
| old_item->copyfrom_url = item->copyfrom_url; |
| old_item->state_flags = item->state_flags; |
| old_item->wcprop_changes = item->incoming_prop_changes; |
| |
| APR_ARRAY_PUSH(old_commit_items, svn_client_commit_item_t *) = |
| old_item; |
| } |
| } |
| |
| if (ctx->log_msg_func2) |
| err = (*ctx->log_msg_func2)(log_msg, tmp_file, old_commit_items, |
| ctx->log_msg_baton2, pool); |
| else |
| err = (*ctx->log_msg_func)(log_msg, tmp_file, old_commit_items, |
| ctx->log_msg_baton, pool); |
| svn_pool_destroy(scratch_pool); |
| return err; |
| } |
| else |
| { |
| /* No log message callback was provided by the client. */ |
| *log_msg = ""; |
| *tmp_file = NULL; |
| return SVN_NO_ERROR; |
| } |
| } |
| |
| svn_error_t * |
| svn_client__ensure_revprop_table(apr_hash_t **revprop_table_out, |
| const apr_hash_t *revprop_table_in, |
| const char *log_msg, |
| svn_client_ctx_t *ctx, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *new_revprop_table; |
| if (revprop_table_in) |
| { |
| if (svn_prop_has_svn_prop(revprop_table_in, pool)) |
| return svn_error_create(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, |
| _("Standard properties can't be set " |
| "explicitly as revision properties")); |
| new_revprop_table = apr_hash_copy(pool, revprop_table_in); |
| } |
| else |
| { |
| new_revprop_table = apr_hash_make(pool); |
| } |
| svn_hash_sets(new_revprop_table, SVN_PROP_REVISION_LOG, |
| svn_string_create(log_msg, pool)); |
| *revprop_table_out = new_revprop_table; |
| return SVN_NO_ERROR; |
| } |