| /** |
| * @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; |
| |
| 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; |
| } |
| } |
| } |
| |
| /* ### 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_repos_info(NULL, &repos_relpath, |
| &tmpinfo->repos_root_URL, |
| &tmpinfo->repos_UUID, |
| db, local_abspath, |
| result_pool, scratch_pool)); |
| |
| tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL, |
| repos_relpath, result_pool); |
| } |
| else if (status == svn_wc__db_status_deleted) |
| { |
| svn_wc__db_status_t w_status; |
| |
| SVN_ERR(svn_wc__db_read_pristine_info(&w_status, &tmpinfo->kind, |
| &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)); |
| |
| if (w_status == svn_wc__db_status_deleted) |
| { |
| /* We have a working not-present status. We don't know anything |
| about this node, but it *is visible* in STATUS. |
| |
| Let's tell that it is excluded */ |
| |
| wc_info->depth = svn_depth_exclude; |
| tmpinfo->kind = svn_node_unknown; |
| } |
| |
| /* 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, |
| NULL, NULL, |
| db, local_abspath, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(svn_wc__db_read_repos_info(&tmpinfo->rev, &repos_relpath, |
| &tmpinfo->repos_root_URL, |
| &tmpinfo->repos_UUID, |
| db, local_abspath, |
| result_pool, scratch_pool)); |
| |
| wc_info->schedule = svn_wc_schedule_delete; |
| tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL, |
| repos_relpath, result_pool); |
| } |
| else if (status == svn_wc__db_status_not_present |
| || status == svn_wc__db_status_server_excluded) |
| { |
| *info = NULL; |
| return SVN_NO_ERROR; |
| } |
| else if (status == svn_wc__db_status_excluded && !repos_relpath) |
| { |
| /* We have a WORKING exclude. Avoid segfault on no repos info */ |
| |
| SVN_ERR(svn_wc__db_read_repos_info(NULL, &repos_relpath, |
| &tmpinfo->repos_root_URL, |
| &tmpinfo->repos_UUID, |
| db, local_abspath, |
| result_pool, scratch_pool)); |
| |
| wc_info->schedule = svn_wc_schedule_normal; |
| tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL, |
| repos_relpath, result_pool); |
| tmpinfo->wc_info->depth = svn_depth_exclude; |
| } |
| 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) |
| 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, NULL, |
| db, local_abspath, |
| FALSE /* create tempfiles */, |
| FALSE /* only tree conflicts */, |
| 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 -> empty string. */ |
| 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 = apr_hash_this_key(hi); |
| const svn_wc_conflict_description2_t *tree_conflict; |
| svn_wc__info2_t *info; |
| const apr_array_header_t *conflicts; |
| |
| svn_pool_clear(iterpool); |
| |
| SVN_ERR(build_info_for_unversioned(&info, iterpool)); |
| |
| if (!repos_root_url) |
| { |
| SVN_ERR(svn_wc__db_read_repos_info(NULL, NULL, |
| &repos_root_url, |
| &repos_uuid, |
| wc_ctx->db, |
| svn_dirent_dirname( |
| this_abspath, |
| iterpool), |
| scratch_pool, iterpool)); |
| } |
| |
| info->repos_root_URL = repos_root_url; |
| info->repos_UUID = repos_uuid; |
| |
| SVN_ERR(svn_wc__read_conflicts(&conflicts, NULL, |
| wc_ctx->db, this_abspath, |
| FALSE /* create tempfiles */, |
| FALSE /* only tree conflicts */, |
| iterpool, iterpool)); |
| if (! conflicts || ! conflicts->nelts) |
| continue; |
| |
| tree_conflict = APR_ARRAY_IDX(conflicts, 0, |
| const svn_wc_conflict_description2_t *); |
| |
| if (!depth_includes(local_abspath, depth, tree_conflict->local_abspath, |
| tree_conflict->node_kind, iterpool)) |
| continue; |
| |
| info->wc_info->conflicts = conflicts; |
| SVN_ERR(receiver(receiver_baton, this_abspath, info, iterpool)); |
| } |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |