summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_wc/info.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_wc/info.c')
-rw-r--r--subversion/libsvn_wc/info.c580
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;
+}
OpenPOWER on IntegriCloud