diff options
Diffstat (limited to 'subversion/libsvn_wc/adm_ops.c')
-rw-r--r-- | subversion/libsvn_wc/adm_ops.c | 1400 |
1 files changed, 1400 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/adm_ops.c b/subversion/libsvn_wc/adm_ops.c new file mode 100644 index 0000000..1f391fc --- /dev/null +++ b/subversion/libsvn_wc/adm_ops.c @@ -0,0 +1,1400 @@ +/* + * adm_ops.c: routines for affecting working copy administrative + * information. NOTE: this code doesn't know where the adm + * info is actually stored. Instead, generic handles to + * adm data are requested via a reference to some PATH + * (PATH being a regular, non-administrative directory or + * file in the working copy). + * + * ==================================================================== + * 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 <stdlib.h> + +#include <apr_pools.h> +#include <apr_hash.h> +#include <apr_time.h> +#include <apr_errno.h> + +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" +#include "svn_wc.h" +#include "svn_io.h" +#include "svn_time.h" +#include "svn_sorts.h" + +#include "wc.h" +#include "adm_files.h" +#include "conflicts.h" +#include "workqueue.h" + +#include "svn_private_config.h" +#include "private/svn_subr_private.h" +#include "private/svn_dep_compat.h" + + +struct svn_wc_committed_queue_t +{ + /* The pool in which ->queue is allocated. */ + apr_pool_t *pool; + /* Mapping (const char *) local_abspath to (committed_queue_item_t *). */ + apr_hash_t *queue; + /* Is any item in the queue marked as 'recursive'? */ + svn_boolean_t have_recursive; +}; + +typedef struct committed_queue_item_t +{ + const char *local_abspath; + svn_boolean_t recurse; + svn_boolean_t no_unlock; + svn_boolean_t keep_changelist; + + /* The pristine text checksum. */ + const svn_checksum_t *sha1_checksum; + + apr_hash_t *new_dav_cache; +} committed_queue_item_t; + + +apr_pool_t * +svn_wc__get_committed_queue_pool(const struct svn_wc_committed_queue_t *queue) +{ + return queue->pool; +} + + + +/*** Finishing updates and commits. ***/ + +/* Queue work items that will finish a commit of the file or directory + * LOCAL_ABSPATH in DB: + * - queue the removal of any "revert-base" props and text files; + * - queue an update of the DB entry for this node + * + * ### The Pristine Store equivalent should be: + * - remember the old BASE_NODE and WORKING_NODE pristine text c'sums; + * - queue an update of the DB entry for this node (incl. updating the + * BASE_NODE c'sum and setting the WORKING_NODE c'sum to NULL); + * - queue deletion of the old pristine texts by the remembered checksums. + * + * CHECKSUM is the checksum of the new text base for LOCAL_ABSPATH, and must + * be provided if there is one, else NULL. + * + * STATUS, KIND, PROP_MODS and OLD_CHECKSUM are the current in-db values of + * the node LOCAL_ABSPATH. + */ +static svn_error_t * +process_committed_leaf(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t via_recurse, + svn_wc__db_status_t status, + svn_node_kind_t kind, + svn_boolean_t prop_mods, + const svn_checksum_t *old_checksum, + svn_revnum_t new_revnum, + apr_time_t new_changed_date, + const char *new_changed_author, + apr_hash_t *new_dav_cache, + svn_boolean_t no_unlock, + svn_boolean_t keep_changelist, + const svn_checksum_t *checksum, + apr_pool_t *scratch_pool) +{ + svn_revnum_t new_changed_rev = new_revnum; + svn_skel_t *work_item = NULL; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + { + const char *adm_abspath; + + if (kind == svn_node_dir) + adm_abspath = local_abspath; + else + adm_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + SVN_ERR(svn_wc__write_check(db, adm_abspath, scratch_pool)); + } + + if (status == svn_wc__db_status_deleted) + { + return svn_error_trace( + svn_wc__db_base_remove( + db, local_abspath, + FALSE /* keep_as_working */, + FALSE /* queue_deletes */, + (! via_recurse) + ? new_revnum : SVN_INVALID_REVNUM, + NULL, NULL, + scratch_pool)); + } + else if (status == svn_wc__db_status_not_present) + { + /* We are committing the leaf of a copy operation. + We leave the not-present marker to allow pulling in excluded + children of a copy. + + The next update will remove the not-present marker. */ + + return SVN_NO_ERROR; + } + + SVN_ERR_ASSERT(status == svn_wc__db_status_normal + || status == svn_wc__db_status_incomplete + || status == svn_wc__db_status_added); + + if (kind != svn_node_dir) + { + /* If we sent a delta (meaning: post-copy modification), + then this file will appear in the queue and so we should have + its checksum already. */ + if (checksum == NULL) + { + /* It was copied and not modified. We must have a text + base for it. And the node should have a checksum. */ + SVN_ERR_ASSERT(old_checksum != NULL); + + checksum = old_checksum; + + /* Is the node completely unmodified and are we recursing? */ + if (via_recurse && !prop_mods) + { + /* If a copied node itself is not modified, but the op_root of + the copy is committed we have to make sure that changed_rev, + changed_date and changed_author don't change or the working + copy used for committing will show different last modified + information then a clean checkout of exactly the same + revisions. (Issue #3676) */ + + SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, + NULL, &new_changed_rev, + &new_changed_date, + &new_changed_author, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + } + } + + SVN_ERR(svn_wc__wq_build_file_commit(&work_item, + db, local_abspath, + prop_mods, + scratch_pool, scratch_pool)); + } + + /* The new text base will be found in the pristine store by its checksum. */ + SVN_ERR(svn_wc__db_global_commit(db, local_abspath, + new_revnum, new_changed_rev, + new_changed_date, new_changed_author, + checksum, + NULL /* new_children */, + new_dav_cache, + keep_changelist, + no_unlock, + work_item, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__process_committed_internal(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t recurse, + svn_boolean_t top_of_recurse, + svn_revnum_t new_revnum, + apr_time_t new_date, + const char *rev_author, + apr_hash_t *new_dav_cache, + svn_boolean_t no_unlock, + svn_boolean_t keep_changelist, + const svn_checksum_t *sha1_checksum, + const svn_wc_committed_queue_t *queue, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + const svn_checksum_t *old_checksum; + svn_boolean_t prop_mods; + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &old_checksum, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, &prop_mods, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + /* NOTE: be wary of making crazy semantic changes in this function, since + svn_wc_process_committed4() calls this. */ + + SVN_ERR(process_committed_leaf(db, local_abspath, !top_of_recurse, + status, kind, prop_mods, old_checksum, + new_revnum, new_date, rev_author, + new_dav_cache, + no_unlock, keep_changelist, + sha1_checksum, + scratch_pool)); + + /* Only check for recursion on nodes that have children */ + if (kind != svn_node_file + || status == svn_wc__db_status_not_present + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_server_excluded + /* Node deleted -> then no longer a directory */ + || status == svn_wc__db_status_deleted) + { + return SVN_NO_ERROR; + } + + if (recurse) + { + const apr_array_header_t *children; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + + /* Read PATH's entries; this is the absolute path. */ + SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath, + scratch_pool, iterpool)); + + /* Recursively loop over all children. */ + for (i = 0; i < children->nelts; i++) + { + const char *name = APR_ARRAY_IDX(children, i, const char *); + const char *this_abspath; + const committed_queue_item_t *cqi; + + svn_pool_clear(iterpool); + + this_abspath = svn_dirent_join(local_abspath, name, iterpool); + + sha1_checksum = NULL; + cqi = svn_hash_gets(queue->queue, this_abspath); + + if (cqi != NULL) + sha1_checksum = cqi->sha1_checksum; + + /* Recurse. Pass NULL for NEW_DAV_CACHE, because the + ones present in the current call are only applicable to + this one committed item. */ + SVN_ERR(svn_wc__process_committed_internal( + db, this_abspath, + TRUE /* recurse */, + FALSE /* top_of_recurse */, + new_revnum, new_date, + rev_author, + NULL /* new_dav_cache */, + TRUE /* no_unlock */, + keep_changelist, + sha1_checksum, + queue, + iterpool)); + } + + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + + +apr_hash_t * +svn_wc__prop_array_to_hash(const apr_array_header_t *props, + apr_pool_t *result_pool) +{ + int i; + apr_hash_t *prophash; + + if (props == NULL || props->nelts == 0) + return NULL; + + prophash = apr_hash_make(result_pool); + + for (i = 0; i < props->nelts; i++) + { + const svn_prop_t *prop = APR_ARRAY_IDX(props, i, const svn_prop_t *); + if (prop->value != NULL) + svn_hash_sets(prophash, prop->name, prop->value); + } + + return prophash; +} + + +svn_wc_committed_queue_t * +svn_wc_committed_queue_create(apr_pool_t *pool) +{ + svn_wc_committed_queue_t *q; + + q = apr_palloc(pool, sizeof(*q)); + q->pool = pool; + q->queue = apr_hash_make(pool); + q->have_recursive = FALSE; + + return q; +} + + +svn_error_t * +svn_wc_queue_committed3(svn_wc_committed_queue_t *queue, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t recurse, + const apr_array_header_t *wcprop_changes, + svn_boolean_t remove_lock, + svn_boolean_t remove_changelist, + const svn_checksum_t *sha1_checksum, + apr_pool_t *scratch_pool) +{ + committed_queue_item_t *cqi; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + queue->have_recursive |= recurse; + + /* Use the same pool as the one QUEUE was allocated in, + to prevent lifetime issues. Intermediate operations + should use SCRATCH_POOL. */ + + /* Add to the array with paths and options */ + cqi = apr_palloc(queue->pool, sizeof(*cqi)); + cqi->local_abspath = local_abspath; + cqi->recurse = recurse; + cqi->no_unlock = !remove_lock; + cqi->keep_changelist = !remove_changelist; + cqi->sha1_checksum = sha1_checksum; + cqi->new_dav_cache = svn_wc__prop_array_to_hash(wcprop_changes, queue->pool); + + svn_hash_sets(queue->queue, local_abspath, cqi); + + return SVN_NO_ERROR; +} + + +/* Return TRUE if any item of QUEUE is a parent of ITEM and will be + processed recursively, return FALSE otherwise. + + The algorithmic complexity of this search implementation is O(queue + length), but it's quite quick. +*/ +static svn_boolean_t +have_recursive_parent(apr_hash_t *queue, + const committed_queue_item_t *item, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + const char *local_abspath = item->local_abspath; + + for (hi = apr_hash_first(scratch_pool, queue); hi; hi = apr_hash_next(hi)) + { + const committed_queue_item_t *qi = svn__apr_hash_index_val(hi); + + if (qi == item) + continue; + + if (qi->recurse && svn_dirent_is_child(qi->local_abspath, local_abspath, + NULL)) + return TRUE; + } + + return FALSE; +} + + +svn_error_t * +svn_wc_process_committed_queue2(svn_wc_committed_queue_t *queue, + svn_wc_context_t *wc_ctx, + svn_revnum_t new_revnum, + const char *rev_date, + const char *rev_author, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *sorted_queue; + int i; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_time_t new_date; + apr_hash_t *run_wqs = apr_hash_make(scratch_pool); + apr_hash_index_t *hi; + + if (rev_date) + SVN_ERR(svn_time_from_cstring(&new_date, rev_date, iterpool)); + else + new_date = 0; + + /* Process the queued items in order of their paths. (The requirement is + * probably just that a directory must be processed before its children.) */ + sorted_queue = svn_sort__hash(queue->queue, svn_sort_compare_items_as_paths, + scratch_pool); + for (i = 0; i < sorted_queue->nelts; i++) + { + const svn_sort__item_t *sort_item + = &APR_ARRAY_IDX(sorted_queue, i, svn_sort__item_t); + const committed_queue_item_t *cqi = sort_item->value; + const char *wcroot_abspath; + + svn_pool_clear(iterpool); + + /* Skip this item if it is a child of a recursive item, because it has + been (or will be) accounted for when that recursive item was (or + will be) processed. */ + if (queue->have_recursive && have_recursive_parent(queue->queue, cqi, + iterpool)) + continue; + + SVN_ERR(svn_wc__process_committed_internal( + wc_ctx->db, cqi->local_abspath, + cqi->recurse, + TRUE /* top_of_recurse */, + new_revnum, new_date, rev_author, + cqi->new_dav_cache, + cqi->no_unlock, + cqi->keep_changelist, + cqi->sha1_checksum, queue, + iterpool)); + + /* Don't run the wq now, but remember that we must call it for this + working copy */ + SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, + wc_ctx->db, cqi->local_abspath, + iterpool, iterpool)); + + if (! svn_hash_gets(run_wqs, wcroot_abspath)) + { + wcroot_abspath = apr_pstrdup(scratch_pool, wcroot_abspath); + svn_hash_sets(run_wqs, wcroot_abspath, wcroot_abspath); + } + } + + /* Make sure nothing happens if this function is called again. */ + apr_hash_clear(queue->queue); + + /* Ok; everything is committed now. Now we can start calling callbacks */ + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + for (hi = apr_hash_first(scratch_pool, run_wqs); + hi; + hi = apr_hash_next(hi)) + { + const char *wcroot_abspath = svn__apr_hash_index_key(hi); + + svn_pool_clear(iterpool); + + SVN_ERR(svn_wc__wq_run(wc_ctx->db, wcroot_abspath, + cancel_func, cancel_baton, + iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Schedule the single node at LOCAL_ABSPATH, of kind KIND, for addition in + * its parent directory in the WC. It will have the regular properties + * provided in PROPS, or none if that is NULL. + * + * If the node is a file, set its on-disk executable and read-only bits to + * match its properties and lock state, + * ### only if it has an svn:executable or svn:needs-lock property. + * ### This is to match the previous behaviour of setting its props + * afterwards by calling svn_wc_prop_set4(), but is not very clean. + * + * Sync the on-disk executable and read-only bits accordingly. + */ +static svn_error_t * +add_from_disk(svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + const apr_hash_t *props, + apr_pool_t *scratch_pool) +{ + if (kind == svn_node_file) + { + svn_skel_t *work_item = NULL; + + if (props && (svn_prop_get_value(props, SVN_PROP_EXECUTABLE) + || svn_prop_get_value(props, SVN_PROP_NEEDS_LOCK))) + SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_op_add_file(db, local_abspath, props, work_item, + scratch_pool)); + if (work_item) + SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool)); + } + else + { + SVN_ERR(svn_wc__db_op_add_directory(db, local_abspath, props, NULL, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +/* Set *REPOS_ROOT_URL and *REPOS_UUID to the repository of the parent of + LOCAL_ABSPATH. REPOS_ROOT_URL and/or REPOS_UUID may be NULL if not + wanted. Check that the parent of LOCAL_ABSPATH is a versioned directory + in a state in which a new child node can be scheduled for addition; + return an error if not. */ +static svn_error_t * +check_can_add_to_parent(const char **repos_root_url, + const char **repos_uuid, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + svn_wc__db_status_t parent_status; + svn_node_kind_t parent_kind; + svn_error_t *err; + + SVN_ERR(svn_wc__write_check(db, parent_abspath, scratch_pool)); + + err = svn_wc__db_read_info(&parent_status, &parent_kind, NULL, + NULL, repos_root_url, repos_uuid, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, parent_abspath, result_pool, scratch_pool); + + if (err + || parent_status == svn_wc__db_status_not_present + || parent_status == svn_wc__db_status_excluded + || parent_status == svn_wc__db_status_server_excluded) + { + return + svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, err, + _("Can't find parent directory's node while" + " trying to add '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + else if (parent_status == svn_wc__db_status_deleted) + { + return + svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL, + _("Can't add '%s' to a parent directory" + " scheduled for deletion"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + else if (parent_kind != svn_node_dir) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Can't schedule an addition of '%s'" + " below a not-directory node"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + /* If we haven't found the repository info yet, find it now. */ + if ((repos_root_url && ! *repos_root_url) + || (repos_uuid && ! *repos_uuid)) + { + if (parent_status == svn_wc__db_status_added) + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, + repos_root_url, repos_uuid, NULL, + NULL, NULL, NULL, + db, parent_abspath, + result_pool, scratch_pool)); + else + SVN_ERR(svn_wc__db_scan_base_repos(NULL, + repos_root_url, repos_uuid, + db, parent_abspath, + result_pool, scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +/* Check that the on-disk item at LOCAL_ABSPATH can be scheduled for + * addition to its WC parent directory. + * + * Set *KIND_P to the kind of node to be added, *DB_ROW_EXISTS_P to whether + * it is already a versioned path, and if so, *IS_WC_ROOT_P to whether it's + * a WC root. + * + * ### The checks here, and the outputs, are geared towards svn_wc_add4(). + */ +static svn_error_t * +check_can_add_node(svn_node_kind_t *kind_p, + svn_boolean_t *db_row_exists_p, + svn_boolean_t *is_wc_root_p, + svn_wc__db_t *db, + const char *local_abspath, + const char *copyfrom_url, + svn_revnum_t copyfrom_rev, + apr_pool_t *scratch_pool) +{ + const char *base_name = svn_dirent_basename(local_abspath, scratch_pool); + svn_boolean_t is_wc_root; + svn_node_kind_t kind; + svn_boolean_t is_special; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(!copyfrom_url || (svn_uri_is_canonical(copyfrom_url, + scratch_pool) + && SVN_IS_VALID_REVNUM(copyfrom_rev))); + + /* Check that the proposed node has an acceptable name. */ + if (svn_wc_is_adm_dir(base_name, scratch_pool)) + return svn_error_createf + (SVN_ERR_ENTRY_FORBIDDEN, NULL, + _("Can't create an entry with a reserved name while trying to add '%s'"), + svn_dirent_local_style(local_abspath, scratch_pool)); + + SVN_ERR(svn_path_check_valid(local_abspath, scratch_pool)); + + /* Make sure something's there; set KIND and *KIND_P. */ + SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special, + scratch_pool)); + if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("'%s' not found"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + if (kind == svn_node_unknown) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Unsupported node kind for path '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + if (kind_p) + *kind_p = kind; + + /* Determine whether a DB row for this node EXISTS, and whether it + IS_WC_ROOT. If it exists, check that it is in an acceptable state for + adding the new node; if not, return an error. */ + { + svn_wc__db_status_t status; + svn_boolean_t conflicted; + svn_boolean_t exists; + svn_error_t *err + = svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + &conflicted, + NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + exists = FALSE; + is_wc_root = FALSE; + } + else + { + is_wc_root = FALSE; + exists = TRUE; + + /* Note that the node may be in conflict even if it does not + * exist on disk (certain tree conflict scenarios). */ + if (conflicted) + return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL, + _("'%s' is an existing item in conflict; " + "please mark the conflict as resolved " + "before adding a new item here"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + switch (status) + { + case svn_wc__db_status_not_present: + break; + case svn_wc__db_status_deleted: + /* A working copy root should never have a WORKING_NODE */ + SVN_ERR_ASSERT(!is_wc_root); + break; + case svn_wc__db_status_normal: + SVN_ERR(svn_wc__db_is_wcroot(&is_wc_root, db, local_abspath, + scratch_pool)); + + if (is_wc_root && copyfrom_url) + { + /* Integrate a sub working copy in a parent working copy + (legacy behavior) */ + break; + } + else if (is_wc_root && is_special) + { + /* Adding a symlink to a working copy root. + (special_tests.py 23: externals as symlink targets) */ + break; + } + /* else: Fall through in default error */ + + default: + return svn_error_createf( + SVN_ERR_ENTRY_EXISTS, NULL, + _("'%s' is already under version control"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + } /* err */ + + if (db_row_exists_p) + *db_row_exists_p = exists; + if (is_wc_root_p) + *is_wc_root_p = is_wc_root; + } + + return SVN_NO_ERROR; +} + + +/* Convert the nested pristine working copy rooted at LOCAL_ABSPATH into + * a copied subtree in the outer working copy. + * + * LOCAL_ABSPATH must be the root of a nested working copy that has no + * local modifications. The parent directory of LOCAL_ABSPATH must be a + * versioned directory in the outer WC, and must belong to the same + * repository as the nested WC. The nested WC will be integrated into the + * parent's WC, and will no longer be a separate WC. */ +static svn_error_t * +integrate_nested_wc_as_copy(svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + const char *moved_abspath; + + /* Drop any references to the wc that is to be rewritten */ + SVN_ERR(svn_wc__db_drop_root(db, local_abspath, scratch_pool)); + + /* Move the admin dir from the wc to a temporary location: MOVED_ABSPATH */ + { + const char *tmpdir_abspath; + const char *moved_adm_abspath; + const char *adm_abspath; + + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db, + svn_dirent_dirname(local_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_open_unique_file3(NULL, &moved_abspath, tmpdir_abspath, + svn_io_file_del_on_close, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_dir_make(moved_abspath, APR_OS_DEFAULT, scratch_pool)); + + adm_abspath = svn_wc__adm_child(local_abspath, "", scratch_pool); + moved_adm_abspath = svn_wc__adm_child(moved_abspath, "", scratch_pool); + SVN_ERR(svn_io_file_move(adm_abspath, moved_adm_abspath, scratch_pool)); + } + + /* Copy entries from temporary location into the main db */ + SVN_ERR(svn_wc_copy3(wc_ctx, moved_abspath, local_abspath, + TRUE /* metadata_only */, + NULL, NULL, NULL, NULL, scratch_pool)); + + /* Cleanup the temporary admin dir */ + SVN_ERR(svn_wc__db_drop_root(db, moved_abspath, scratch_pool)); + SVN_ERR(svn_io_remove_dir2(moved_abspath, FALSE, NULL, NULL, + scratch_pool)); + + /* The subdir is now part of our parent working copy. Our caller assumes + that we return the new node locked, so obtain a lock if we didn't + receive the lock via our depth infinity lock */ + { + svn_boolean_t owns_lock; + + SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, local_abspath, + FALSE, scratch_pool)); + if (!owns_lock) + SVN_ERR(svn_wc__db_wclock_obtain(db, local_abspath, 0, FALSE, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_add4(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + const char *copyfrom_url, + svn_revnum_t copyfrom_rev, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + svn_node_kind_t kind; + svn_boolean_t db_row_exists; + svn_boolean_t is_wc_root; + const char *repos_root_url; + const char *repos_uuid; + + SVN_ERR(check_can_add_node(&kind, &db_row_exists, &is_wc_root, + db, local_abspath, copyfrom_url, copyfrom_rev, + scratch_pool)); + + /* Get REPOS_ROOT_URL and REPOS_UUID. Check that the + parent is a versioned directory in an acceptable state. */ + SVN_ERR(check_can_add_to_parent(&repos_root_url, &repos_uuid, + db, local_abspath, scratch_pool, + scratch_pool)); + + /* If we're performing a repos-to-WC copy, check that the copyfrom + repository is the same as the parent dir's repository. */ + if (copyfrom_url && !svn_uri__is_ancestor(repos_root_url, copyfrom_url)) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("The URL '%s' has a different repository " + "root than its parent"), copyfrom_url); + + /* Verify that we can actually integrate the inner working copy */ + if (is_wc_root) + { + const char *repos_relpath, *inner_repos_root_url, *inner_repos_uuid; + const char *inner_url; + + SVN_ERR(svn_wc__db_scan_base_repos(&repos_relpath, + &inner_repos_root_url, + &inner_repos_uuid, + db, local_abspath, + scratch_pool, scratch_pool)); + + if (strcmp(inner_repos_uuid, repos_uuid) + || strcmp(repos_root_url, inner_repos_root_url)) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Can't schedule the working copy at '%s' " + "from repository '%s' with uuid '%s' " + "for addition under a working copy from " + "repository '%s' with uuid '%s'."), + svn_dirent_local_style(local_abspath, + scratch_pool), + inner_repos_root_url, inner_repos_uuid, + repos_root_url, repos_uuid); + + inner_url = svn_path_url_add_component2(repos_root_url, repos_relpath, + scratch_pool); + + if (strcmp(copyfrom_url, inner_url)) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Can't add '%s' with URL '%s', but with " + "the data from '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool), + copyfrom_url, inner_url); + } + + if (!copyfrom_url) /* Case 2a: It's a simple add */ + { + SVN_ERR(add_from_disk(db, local_abspath, kind, NULL, + scratch_pool)); + if (kind == svn_node_dir && !db_row_exists) + { + /* If using the legacy 1.6 interface the parent lock may not + be recursive and add is expected to lock the new dir. + + ### Perhaps the lock should be created in the same + transaction that adds the node? */ + svn_boolean_t owns_lock; + + SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, local_abspath, + FALSE, scratch_pool)); + if (!owns_lock) + SVN_ERR(svn_wc__db_wclock_obtain(db, local_abspath, 0, FALSE, + scratch_pool)); + } + } + else if (!is_wc_root) /* Case 2b: It's a copy from the repository */ + { + if (kind == svn_node_file) + { + /* This code should never be used, as it doesn't install proper + pristine and/or properties. But it was not an error in the old + version of this function. + + ===> Use svn_wc_add_repos_file4() directly! */ + svn_stream_t *content = svn_stream_empty(scratch_pool); + + SVN_ERR(svn_wc_add_repos_file4(wc_ctx, local_abspath, + content, NULL, NULL, NULL, + copyfrom_url, copyfrom_rev, + cancel_func, cancel_baton, + scratch_pool)); + } + else + { + const char *repos_relpath = + svn_uri_skip_ancestor(repos_root_url, copyfrom_url, scratch_pool); + + SVN_ERR(svn_wc__db_op_copy_dir(db, local_abspath, + apr_hash_make(scratch_pool), + copyfrom_rev, 0, NULL, + repos_relpath, + repos_root_url, repos_uuid, + copyfrom_rev, + NULL /* children */, FALSE, depth, + NULL /* conflicts */, + NULL /* work items */, + scratch_pool)); + } + } + else /* Case 1: Integrating a separate WC into this one, in place */ + { + SVN_ERR(integrate_nested_wc_as_copy(wc_ctx, local_abspath, + scratch_pool)); + } + + /* Report the addition to the caller. */ + if (notify_func != NULL) + { + svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, + svn_wc_notify_add, + scratch_pool); + notify->kind = kind; + (*notify_func)(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_add_from_disk2(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const apr_hash_t *props, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t kind; + + SVN_ERR(check_can_add_node(&kind, NULL, NULL, wc_ctx->db, local_abspath, + NULL, SVN_INVALID_REVNUM, scratch_pool)); + SVN_ERR(check_can_add_to_parent(NULL, NULL, wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + /* Canonicalize and check the props */ + if (props) + { + apr_hash_t *new_props; + + SVN_ERR(svn_wc__canonicalize_props( + &new_props, + local_abspath, kind, props, FALSE /* skip_some_checks */, + scratch_pool, scratch_pool)); + props = new_props; + } + + /* Add to the DB and maybe update on-disk executable read-only bits */ + SVN_ERR(add_from_disk(wc_ctx->db, local_abspath, kind, props, + scratch_pool)); + + /* Report the addition to the caller. */ + if (notify_func != NULL) + { + svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, + svn_wc_notify_add, + scratch_pool); + notify->kind = kind; + notify->mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); + (*notify_func)(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; +} + +/* Return a path where nothing exists on disk, within the admin directory + belonging to the WCROOT_ABSPATH directory. */ +static const char * +nonexistent_path(const char *wcroot_abspath, apr_pool_t *scratch_pool) +{ + return svn_wc__adm_child(wcroot_abspath, SVN_WC__ADM_NONEXISTENT_PATH, + scratch_pool); +} + + +svn_error_t * +svn_wc_get_pristine_copy_path(const char *path, + const char **pristine_path, + apr_pool_t *pool) +{ + svn_wc__db_t *db; + const char *local_abspath; + svn_error_t *err; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + + SVN_ERR(svn_wc__db_open(&db, NULL, FALSE, TRUE, pool, pool)); + /* DB is now open. This is seemingly a "light" function that a caller + may use repeatedly despite error return values. The rest of this + function should aggressively close DB, even in the error case. */ + + err = svn_wc__text_base_path_to_read(pristine_path, db, local_abspath, + pool, pool); + if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + { + /* The node doesn't exist, so return a non-existent path located + in WCROOT/.svn/ */ + const char *wcroot_abspath; + + svn_error_clear(err); + + err = svn_wc__db_get_wcroot(&wcroot_abspath, db, local_abspath, + pool, pool); + if (err == NULL) + *pristine_path = nonexistent_path(wcroot_abspath, pool); + } + + return svn_error_compose_create(err, svn_wc__db_close(db)); +} + + +svn_error_t * +svn_wc_get_pristine_contents2(svn_stream_t **contents, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__get_pristine_contents(contents, NULL, + wc_ctx->db, + local_abspath, + result_pool, + scratch_pool)); +} + + +typedef struct get_pristine_lazyopen_baton_t +{ + svn_wc_context_t *wc_ctx; + const char *wri_abspath; + const svn_checksum_t *checksum; + +} get_pristine_lazyopen_baton_t; + + +/* Implements svn_stream_lazyopen_func_t */ +static svn_error_t * +get_pristine_lazyopen_func(svn_stream_t **stream, + void *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + get_pristine_lazyopen_baton_t *b = baton; + const svn_checksum_t *sha1_checksum; + + /* svn_wc__db_pristine_read() wants a SHA1, so if we have an MD5, + we'll use it to lookup the SHA1. */ + if (b->checksum->kind == svn_checksum_sha1) + sha1_checksum = b->checksum; + else + SVN_ERR(svn_wc__db_pristine_get_sha1(&sha1_checksum, b->wc_ctx->db, + b->wri_abspath, b->checksum, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_pristine_read(stream, NULL, b->wc_ctx->db, + b->wri_abspath, sha1_checksum, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__get_pristine_contents_by_checksum(svn_stream_t **contents, + svn_wc_context_t *wc_ctx, + const char *wri_abspath, + const svn_checksum_t *checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t present; + + *contents = NULL; + + SVN_ERR(svn_wc__db_pristine_check(&present, wc_ctx->db, wri_abspath, + checksum, scratch_pool)); + + if (present) + { + get_pristine_lazyopen_baton_t *gpl_baton; + + gpl_baton = apr_pcalloc(result_pool, sizeof(*gpl_baton)); + gpl_baton->wc_ctx = wc_ctx; + gpl_baton->wri_abspath = wri_abspath; + gpl_baton->checksum = checksum; + + *contents = svn_stream_lazyopen_create(get_pristine_lazyopen_func, + gpl_baton, FALSE, result_pool); + } + + return SVN_NO_ERROR; +} + + + +svn_error_t * +svn_wc_add_lock2(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const svn_lock_t *lock, + apr_pool_t *scratch_pool) +{ + svn_wc__db_lock_t db_lock; + svn_error_t *err; + const svn_string_t *needs_lock; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* ### Enable after fixing callers */ + /*SVN_ERR(svn_wc__write_check(wc_ctx->db, + svn_dirent_dirname(local_abspath, scratch_pool), + scratch_pool));*/ + + db_lock.token = lock->token; + db_lock.owner = lock->owner; + db_lock.comment = lock->comment; + db_lock.date = lock->creation_date; + err = svn_wc__db_lock_add(wc_ctx->db, local_abspath, &db_lock, scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + /* Remap the error. */ + svn_error_clear(err); + return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, + _("'%s' is not under version control"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + /* if svn:needs-lock is present, then make the file read-write. */ + err = svn_wc__internal_propget(&needs_lock, wc_ctx->db, local_abspath, + SVN_PROP_NEEDS_LOCK, scratch_pool, + scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + { + /* The node has non wc representation (e.g. deleted), so + we don't want to touch the in-wc file */ + svn_error_clear(err); + return SVN_NO_ERROR; + } + SVN_ERR(err); + + if (needs_lock) + SVN_ERR(svn_io_set_file_read_write(local_abspath, FALSE, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_remove_lock2(svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + const svn_string_t *needs_lock; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* ### Enable after fixing callers */ + /*SVN_ERR(svn_wc__write_check(wc_ctx->db, + svn_dirent_dirname(local_abspath, scratch_pool), + scratch_pool));*/ + + err = svn_wc__db_lock_remove(wc_ctx->db, local_abspath, scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + /* Remap the error. */ + svn_error_clear(err); + return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, + _("'%s' is not under version control"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + /* if svn:needs-lock is present, then make the file read-only. */ + err = svn_wc__internal_propget(&needs_lock, wc_ctx->db, local_abspath, + SVN_PROP_NEEDS_LOCK, scratch_pool, + scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + + svn_error_clear(err); + return SVN_NO_ERROR; /* Node is shadowed and/or deleted, + so we shouldn't apply its lock */ + } + + if (needs_lock) + SVN_ERR(svn_io_set_file_read_only(local_abspath, FALSE, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_set_changelist2(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *new_changelist, + svn_depth_t depth, + const apr_array_header_t *changelist_filter, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + /* Assert that we aren't being asked to set an empty changelist. */ + SVN_ERR_ASSERT(! (new_changelist && new_changelist[0] == '\0')); + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_op_set_changelist(wc_ctx->db, local_abspath, + new_changelist, changelist_filter, + depth, notify_func, notify_baton, + cancel_func, cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +struct get_cl_fn_baton +{ + svn_wc__db_t *db; + apr_hash_t *clhash; + svn_changelist_receiver_t callback_func; + void *callback_baton; +}; + + +static svn_error_t * +get_node_changelist(const char *local_abspath, + svn_node_kind_t kind, + void *baton, + apr_pool_t *scratch_pool) +{ + struct get_cl_fn_baton *b = baton; + const char *changelist; + + SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, &changelist, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + b->db, local_abspath, + scratch_pool, scratch_pool)); + + if (svn_wc__internal_changelist_match(b->db, local_abspath, b->clhash, + scratch_pool)) + SVN_ERR(b->callback_func(b->callback_baton, local_abspath, + changelist, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_get_changelists(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + const apr_array_header_t *changelist_filter, + svn_changelist_receiver_t callback_func, + void *callback_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + struct get_cl_fn_baton gnb; + + gnb.db = wc_ctx->db; + gnb.clhash = NULL; + gnb.callback_func = callback_func; + gnb.callback_baton = callback_baton; + + if (changelist_filter) + SVN_ERR(svn_hash_from_cstring_keys(&gnb.clhash, changelist_filter, + scratch_pool)); + + return svn_error_trace( + svn_wc__internal_walk_children(wc_ctx->db, local_abspath, FALSE, + changelist_filter, get_node_changelist, + &gnb, depth, + cancel_func, cancel_baton, + scratch_pool)); + +} + + +svn_boolean_t +svn_wc__internal_changelist_match(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *clhash, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + const char *changelist; + + if (clhash == NULL) + return TRUE; + + err = svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, &changelist, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool); + if (err) + { + svn_error_clear(err); + return FALSE; + } + + return (changelist + && svn_hash_gets((apr_hash_t *)clhash, changelist) != NULL); +} + + +svn_boolean_t +svn_wc__changelist_match(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const apr_hash_t *clhash, + apr_pool_t *scratch_pool) +{ + return svn_wc__internal_changelist_match(wc_ctx->db, local_abspath, clhash, + scratch_pool); +} |