diff options
Diffstat (limited to 'subversion/libsvn_wc/info.c')
-rw-r--r-- | subversion/libsvn_wc/info.c | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/info.c b/subversion/libsvn_wc/info.c new file mode 100644 index 0000000..4a37e00 --- /dev/null +++ b/subversion/libsvn_wc/info.c @@ -0,0 +1,580 @@ +/** + * @copyright + * ==================================================================== + * 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. + * ==================================================================== + * @endcopyright + */ + +#include "svn_dirent_uri.h" +#include "svn_hash.h" +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_wc.h" + +#include "wc.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" + + + +svn_wc_info_t * +svn_wc_info_dup(const svn_wc_info_t *info, + apr_pool_t *pool) +{ + svn_wc_info_t *new_info = apr_pmemdup(pool, info, sizeof(*new_info)); + + if (info->changelist) + new_info->changelist = apr_pstrdup(pool, info->changelist); + new_info->checksum = svn_checksum_dup(info->checksum, pool); + if (info->conflicts) + { + int i; + + apr_array_header_t *new_conflicts + = apr_array_make(pool, info->conflicts->nelts, info->conflicts->elt_size); + for (i = 0; i < info->conflicts->nelts; i++) + { + APR_ARRAY_PUSH(new_conflicts, svn_wc_conflict_description2_t *) + = svn_wc__conflict_description2_dup( + APR_ARRAY_IDX(info->conflicts, i, + const svn_wc_conflict_description2_t *), + pool); + } + new_info->conflicts = new_conflicts; + } + if (info->copyfrom_url) + new_info->copyfrom_url = apr_pstrdup(pool, info->copyfrom_url); + if (info->wcroot_abspath) + new_info->wcroot_abspath = apr_pstrdup(pool, info->wcroot_abspath); + if (info->moved_from_abspath) + new_info->moved_from_abspath = apr_pstrdup(pool, info->moved_from_abspath); + if (info->moved_to_abspath) + new_info->moved_to_abspath = apr_pstrdup(pool, info->moved_to_abspath); + + return new_info; +} + + +/* Set *INFO to a new struct, allocated in RESULT_POOL, built from the WC + metadata of LOCAL_ABSPATH. Pointer fields are copied by reference, not + dup'd. */ +static svn_error_t * +build_info_for_node(svn_wc__info2_t **info, + svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__info2_t *tmpinfo; + const char *repos_relpath; + svn_wc__db_status_t status; + svn_node_kind_t db_kind; + const char *original_repos_relpath; + const char *original_repos_root_url; + const char *original_uuid; + svn_revnum_t original_revision; + svn_wc__db_lock_t *lock; + svn_boolean_t conflicted; + svn_boolean_t op_root; + svn_boolean_t have_base; + svn_boolean_t have_more_work; + svn_wc_info_t *wc_info; + + tmpinfo = apr_pcalloc(result_pool, sizeof(*tmpinfo)); + tmpinfo->kind = kind; + + wc_info = apr_pcalloc(result_pool, sizeof(*wc_info)); + tmpinfo->wc_info = wc_info; + + wc_info->copyfrom_rev = SVN_INVALID_REVNUM; + + SVN_ERR(svn_wc__db_read_info(&status, &db_kind, &tmpinfo->rev, + &repos_relpath, + &tmpinfo->repos_root_URL, &tmpinfo->repos_UUID, + &tmpinfo->last_changed_rev, + &tmpinfo->last_changed_date, + &tmpinfo->last_changed_author, + &wc_info->depth, &wc_info->checksum, NULL, + &original_repos_relpath, + &original_repos_root_url, &original_uuid, + &original_revision, &lock, + &wc_info->recorded_size, + &wc_info->recorded_time, + &wc_info->changelist, + &conflicted, &op_root, NULL, NULL, + &have_base, &have_more_work, NULL, + db, local_abspath, + result_pool, scratch_pool)); + + if (original_repos_root_url != NULL) + { + tmpinfo->repos_root_URL = original_repos_root_url; + tmpinfo->repos_UUID = original_uuid; + } + + if (status == svn_wc__db_status_added) + { + /* ### We should also just be fetching the true BASE revision + ### here, which means copied items would also not have a + ### revision to display. But WC-1 wants to show the revision of + ### copy targets as the copyfrom-rev. *sigh* */ + + if (original_repos_relpath) + { + /* Root or child of copy */ + tmpinfo->rev = original_revision; + repos_relpath = original_repos_relpath; + + if (op_root) + { + svn_error_t *err; + wc_info->copyfrom_url = + svn_path_url_add_component2(tmpinfo->repos_root_URL, + original_repos_relpath, + result_pool); + + wc_info->copyfrom_rev = original_revision; + + err = svn_wc__db_scan_moved(&wc_info->moved_from_abspath, + NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + svn_error_clear(err); + wc_info->moved_from_abspath = NULL; + } + } + } + else if (op_root) + { + /* Local addition */ + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, &repos_relpath, + &tmpinfo->repos_root_URL, + &tmpinfo->repos_UUID, + NULL, NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool)); + + if (have_base) + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &tmpinfo->rev, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + } + else + { + /* Child of copy. ### Not WC-NG like */ + SVN_ERR(svn_wc__internal_get_origin(NULL, &tmpinfo->rev, + &repos_relpath, + &tmpinfo->repos_root_URL, + &tmpinfo->repos_UUID, NULL, + db, local_abspath, TRUE, + result_pool, scratch_pool)); + } + + /* ### We should be able to avoid both these calls with the information + from read_info() in most cases */ + if (! op_root) + wc_info->schedule = svn_wc_schedule_normal; + else if (! have_more_work && ! have_base) + wc_info->schedule = svn_wc_schedule_add; + else + { + svn_wc__db_status_t below_working; + svn_boolean_t have_work; + + SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work, + &below_working, + db, local_abspath, + scratch_pool)); + + /* If the node is not present or deleted (read: not present + in working), then the node is not a replacement */ + if (below_working != svn_wc__db_status_not_present + && below_working != svn_wc__db_status_deleted) + { + wc_info->schedule = svn_wc_schedule_replace; + } + else + wc_info->schedule = svn_wc_schedule_add; + } + SVN_ERR(svn_wc__db_read_url(&tmpinfo->URL, db, local_abspath, + result_pool, scratch_pool)); + } + else if (status == svn_wc__db_status_deleted) + { + const char *work_del_abspath; + + SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, + &tmpinfo->last_changed_rev, + &tmpinfo->last_changed_date, + &tmpinfo->last_changed_author, + &wc_info->depth, + &wc_info->checksum, + NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool)); + + /* And now fetch the url and revision of what will be deleted */ + SVN_ERR(svn_wc__db_scan_deletion(NULL, &wc_info->moved_to_abspath, + &work_del_abspath, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + if (work_del_abspath != NULL) + { + /* This is a deletion within a copied subtree. Get the copied-from + * revision. */ + const char *added_abspath = svn_dirent_dirname(work_del_abspath, + scratch_pool); + + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, &repos_relpath, + &tmpinfo->repos_root_URL, + &tmpinfo->repos_UUID, + NULL, NULL, NULL, + &tmpinfo->rev, + db, added_abspath, + result_pool, scratch_pool)); + + tmpinfo->URL = svn_path_url_add_component2( + tmpinfo->repos_root_URL, + svn_relpath_join(repos_relpath, + svn_dirent_skip_ancestor(added_abspath, + local_abspath), + scratch_pool), + result_pool); + } + else + { + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &tmpinfo->rev, + &repos_relpath, + &tmpinfo->repos_root_URL, + &tmpinfo->repos_UUID, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool)); + + tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL, + repos_relpath, + result_pool); + } + + wc_info->schedule = svn_wc_schedule_delete; + } + else if (status == svn_wc__db_status_not_present + || status == svn_wc__db_status_server_excluded) + { + *info = NULL; + return SVN_NO_ERROR; + } + else + { + /* Just a BASE node. We have all the info we need */ + tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL, + repos_relpath, + result_pool); + wc_info->schedule = svn_wc_schedule_normal; + } + + if (status == svn_wc__db_status_excluded) + tmpinfo->wc_info->depth = svn_depth_exclude; + + /* A default */ + tmpinfo->size = SVN_INVALID_FILESIZE; + + SVN_ERR(svn_wc__db_get_wcroot(&tmpinfo->wc_info->wcroot_abspath, db, + local_abspath, result_pool, scratch_pool)); + + if (conflicted) + SVN_ERR(svn_wc__read_conflicts(&wc_info->conflicts, db, + local_abspath, + TRUE /* ### create tempfiles */, + result_pool, scratch_pool)); + else + wc_info->conflicts = NULL; + + /* lock stuff */ + if (lock != NULL) + { + tmpinfo->lock = apr_pcalloc(result_pool, sizeof(*(tmpinfo->lock))); + tmpinfo->lock->token = lock->token; + tmpinfo->lock->owner = lock->owner; + tmpinfo->lock->comment = lock->comment; + tmpinfo->lock->creation_date = lock->date; + } + + *info = tmpinfo; + return SVN_NO_ERROR; +} + + +/* Set *INFO to a new struct with minimal content, to be + used in reporting info for unversioned tree conflict victims. */ +/* ### Some fields we could fill out based on the parent dir's entry + or by looking at an obstructing item. */ +static svn_error_t * +build_info_for_unversioned(svn_wc__info2_t **info, + apr_pool_t *pool) +{ + svn_wc__info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo)); + svn_wc_info_t *wc_info = apr_pcalloc(pool, sizeof (*wc_info)); + + tmpinfo->URL = NULL; + tmpinfo->repos_UUID = NULL; + tmpinfo->repos_root_URL = NULL; + tmpinfo->rev = SVN_INVALID_REVNUM; + tmpinfo->kind = svn_node_none; + tmpinfo->size = SVN_INVALID_FILESIZE; + tmpinfo->last_changed_rev = SVN_INVALID_REVNUM; + tmpinfo->last_changed_date = 0; + tmpinfo->last_changed_author = NULL; + tmpinfo->lock = NULL; + + tmpinfo->wc_info = wc_info; + + wc_info->copyfrom_rev = SVN_INVALID_REVNUM; + wc_info->depth = svn_depth_unknown; + wc_info->recorded_size = SVN_INVALID_FILESIZE; + + *info = tmpinfo; + return SVN_NO_ERROR; +} + +/* Callback and baton for crawl_entries() walk over entries files. */ +struct found_entry_baton +{ + svn_wc__info_receiver2_t receiver; + void *receiver_baton; + svn_wc__db_t *db; + svn_boolean_t actual_only; + svn_boolean_t first; + /* The set of tree conflicts that have been found but not (yet) visited by + * the tree walker. Map of abspath -> svn_wc_conflict_description2_t. */ + apr_hash_t *tree_conflicts; + apr_pool_t *pool; +}; + +/* Call WALK_BATON->receiver with WALK_BATON->receiver_baton, passing to it + * info about the path LOCAL_ABSPATH. + * An svn_wc__node_found_func_t callback function. */ +static svn_error_t * +info_found_node_callback(const char *local_abspath, + svn_node_kind_t kind, + void *walk_baton, + apr_pool_t *scratch_pool) +{ + struct found_entry_baton *fe_baton = walk_baton; + svn_wc__info2_t *info; + + SVN_ERR(build_info_for_node(&info, fe_baton->db, local_abspath, + kind, scratch_pool, scratch_pool)); + + if (info == NULL) + { + if (!fe_baton->first) + return SVN_NO_ERROR; /* not present or server excluded descendant */ + + /* If the info root is not found, that is an error */ + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + fe_baton->first = FALSE; + + SVN_ERR_ASSERT(info->wc_info != NULL); + SVN_ERR(fe_baton->receiver(fe_baton->receiver_baton, local_abspath, + info, scratch_pool)); + + /* If this node is a versioned directory, make a note of any tree conflicts + * on all immediate children. Some of these may be visited later in this + * walk, at which point they will be removed from the list, while any that + * are not visited will remain in the list. */ + if (fe_baton->actual_only && kind == svn_node_dir) + { + const apr_array_header_t *victims; + int i; + + SVN_ERR(svn_wc__db_read_conflict_victims(&victims, + fe_baton->db, local_abspath, + scratch_pool, scratch_pool)); + + for (i = 0; i < victims->nelts; i++) + { + const char *this_basename = APR_ARRAY_IDX(victims, i, const char *); + + svn_hash_sets(fe_baton->tree_conflicts, + svn_dirent_join(local_abspath, this_basename, + fe_baton->pool), + ""); + } + } + + /* Delete this path which we are currently visiting from the list of tree + * conflicts. This relies on the walker visiting a directory before visiting + * its children. */ + svn_hash_sets(fe_baton->tree_conflicts, local_abspath, NULL); + + return SVN_NO_ERROR; +} + + +/* Return TRUE iff the subtree at ROOT_ABSPATH, restricted to depth DEPTH, + * would include the path CHILD_ABSPATH of kind CHILD_KIND. */ +static svn_boolean_t +depth_includes(const char *root_abspath, + svn_depth_t depth, + const char *child_abspath, + svn_node_kind_t child_kind, + apr_pool_t *scratch_pool) +{ + const char *parent_abspath = svn_dirent_dirname(child_abspath, scratch_pool); + + return (depth == svn_depth_infinity + || ((depth == svn_depth_immediates + || (depth == svn_depth_files && child_kind == svn_node_file)) + && strcmp(root_abspath, parent_abspath) == 0) + || strcmp(root_abspath, child_abspath) == 0); +} + + +svn_error_t * +svn_wc__get_info(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t fetch_excluded, + svn_boolean_t fetch_actual_only, + const apr_array_header_t *changelist_filter, + svn_wc__info_receiver2_t receiver, + void *receiver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + struct found_entry_baton fe_baton; + svn_error_t *err; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_index_t *hi; + const char *repos_root_url = NULL; + const char *repos_uuid = NULL; + + fe_baton.receiver = receiver; + fe_baton.receiver_baton = receiver_baton; + fe_baton.db = wc_ctx->db; + fe_baton.actual_only = fetch_actual_only; + fe_baton.first = TRUE; + fe_baton.tree_conflicts = apr_hash_make(scratch_pool); + fe_baton.pool = scratch_pool; + + err = svn_wc__internal_walk_children(wc_ctx->db, local_abspath, + fetch_excluded, + changelist_filter, + info_found_node_callback, + &fe_baton, depth, + cancel_func, cancel_baton, + iterpool); + + /* If the target root node is not present, svn_wc__internal_walk_children() + returns a PATH_NOT_FOUND error and doesn't call the callback. If there + is a tree conflict on this node, that is not an error. */ + if (fe_baton.first /* not visited by walk_children */ + && fetch_actual_only + && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_boolean_t tree_conflicted; + svn_error_t *err2; + + err2 = svn_wc__internal_conflicted_p(NULL, NULL, &tree_conflicted, + wc_ctx->db, local_abspath, + iterpool); + + if ((err2 && err2->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)) + { + svn_error_clear(err2); + return svn_error_trace(err); + } + else if (err2 || !tree_conflicted) + return svn_error_compose_create(err, err2); + + svn_error_clear(err); + + svn_hash_sets(fe_baton.tree_conflicts, local_abspath, ""); + } + else + SVN_ERR(err); + + /* If there are any tree conflicts that we have found but have not reported, + * send a minimal info struct for each one now. */ + for (hi = apr_hash_first(scratch_pool, fe_baton.tree_conflicts); hi; + hi = apr_hash_next(hi)) + { + const char *this_abspath = svn__apr_hash_index_key(hi); + const svn_wc_conflict_description2_t *tree_conflict; + svn_wc__info2_t *info; + + svn_pool_clear(iterpool); + + SVN_ERR(build_info_for_unversioned(&info, iterpool)); + + if (!repos_root_url) + { + SVN_ERR(svn_wc__internal_get_repos_info(NULL, NULL, + &repos_root_url, + &repos_uuid, + wc_ctx->db, + svn_dirent_dirname( + local_abspath, + iterpool), + scratch_pool, + iterpool)); + } + + info->repos_root_URL = repos_root_url; + info->repos_UUID = repos_uuid; + + SVN_ERR(svn_wc__read_conflicts(&info->wc_info->conflicts, + wc_ctx->db, this_abspath, + TRUE /* ### create tempfiles */, + iterpool, iterpool)); + + if (! info->wc_info->conflicts || ! info->wc_info->conflicts->nelts) + continue; + + tree_conflict = APR_ARRAY_IDX(info->wc_info->conflicts, 0, + svn_wc_conflict_description2_t *); + + if (!depth_includes(local_abspath, depth, tree_conflict->local_abspath, + tree_conflict->node_kind, iterpool)) + continue; + + SVN_ERR(receiver(receiver_baton, this_abspath, info, iterpool)); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} |