summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_wc/entries.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_wc/entries.c')
-rw-r--r--subversion/libsvn_wc/entries.c2738
1 files changed, 2738 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/entries.c b/subversion/libsvn_wc/entries.c
new file mode 100644
index 0000000..f6a73bf
--- /dev/null
+++ b/subversion/libsvn_wc/entries.c
@@ -0,0 +1,2738 @@
+/*
+ * entries.c : manipulating the administrative `entries' file.
+ *
+ * ====================================================================
+ * 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 <assert.h>
+
+#include <apr_strings.h>
+
+#include "svn_error.h"
+#include "svn_types.h"
+#include "svn_time.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_ctype.h"
+#include "svn_string.h"
+#include "svn_hash.h"
+
+#include "wc.h"
+#include "adm_files.h"
+#include "conflicts.h"
+#include "entries.h"
+#include "lock.h"
+#include "tree_conflicts.h"
+#include "wc_db.h"
+#include "wc-queries.h" /* for STMT_* */
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_sqlite.h"
+
+#define MAYBE_ALLOC(x,p) ((x) ? (x) : apr_pcalloc((p), sizeof(*(x))))
+
+
+/* Temporary structures which mirror the tables in wc-metadata.sql.
+ For detailed descriptions of each field, see that file. */
+typedef struct db_node_t {
+ apr_int64_t wc_id;
+ const char *local_relpath;
+ int op_depth;
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+ const char *parent_relpath;
+ svn_wc__db_status_t presence;
+ svn_revnum_t revision;
+ svn_node_kind_t kind;
+ svn_checksum_t *checksum;
+ svn_filesize_t recorded_size;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ svn_depth_t depth;
+ apr_time_t recorded_time;
+ apr_hash_t *properties;
+ svn_boolean_t file_external;
+ apr_array_header_t *inherited_props;
+} db_node_t;
+
+typedef struct db_actual_node_t {
+ apr_int64_t wc_id;
+ const char *local_relpath;
+ const char *parent_relpath;
+ apr_hash_t *properties;
+ const char *conflict_old;
+ const char *conflict_new;
+ const char *conflict_working;
+ const char *prop_reject;
+ const char *changelist;
+ /* ### enum for text_mod */
+ const char *tree_conflict_data;
+} db_actual_node_t;
+
+
+
+/*** reading and writing the entries file ***/
+
+
+/* */
+static svn_wc_entry_t *
+alloc_entry(apr_pool_t *pool)
+{
+ svn_wc_entry_t *entry = apr_pcalloc(pool, sizeof(*entry));
+ entry->revision = SVN_INVALID_REVNUM;
+ entry->copyfrom_rev = SVN_INVALID_REVNUM;
+ entry->cmt_rev = SVN_INVALID_REVNUM;
+ entry->kind = svn_node_none;
+ entry->working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN;
+ entry->depth = svn_depth_infinity;
+ entry->file_external_peg_rev.kind = svn_opt_revision_unspecified;
+ entry->file_external_rev.kind = svn_opt_revision_unspecified;
+ return entry;
+}
+
+
+/* Is the entry in a 'hidden' state in the sense of the 'show_hidden'
+ * switches on svn_wc_entries_read(), svn_wc_walk_entries*(), etc.? */
+svn_error_t *
+svn_wc__entry_is_hidden(svn_boolean_t *hidden, const svn_wc_entry_t *entry)
+{
+ /* In English, the condition is: "the entry is not present, and I haven't
+ scheduled something over the top of it." */
+ if (entry->deleted
+ || entry->absent
+ || entry->depth == svn_depth_exclude)
+ {
+ /* These kinds of nodes cannot be marked for deletion (which also
+ means no "replace" either). */
+ SVN_ERR_ASSERT(entry->schedule == svn_wc_schedule_add
+ || entry->schedule == svn_wc_schedule_normal);
+
+ /* Hidden if something hasn't been added over it.
+
+ ### is this even possible with absent or excluded nodes? */
+ *hidden = entry->schedule != svn_wc_schedule_add;
+ }
+ else
+ *hidden = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Hit the database to check the file external information for the given
+ entry. The entry will be modified in place. */
+static svn_error_t *
+check_file_external(svn_wc_entry_t *entry,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ const char *wri_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc__db_status_t status;
+ svn_node_kind_t kind;
+ const char *repos_relpath;
+ svn_revnum_t peg_revision;
+ svn_revnum_t revision;
+ svn_error_t *err;
+
+ err = svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL,
+ &repos_relpath, &peg_revision, &revision,
+ db, local_abspath, wri_abspath,
+ result_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ if (status == svn_wc__db_status_normal
+ && kind == svn_node_file)
+ {
+ entry->file_external_path = repos_relpath;
+ if (SVN_IS_VALID_REVNUM(peg_revision))
+ {
+ entry->file_external_peg_rev.kind = svn_opt_revision_number;
+ entry->file_external_peg_rev.value.number = peg_revision;
+ entry->file_external_rev = entry->file_external_peg_rev;
+ }
+ if (SVN_IS_VALID_REVNUM(revision))
+ {
+ entry->file_external_rev.kind = svn_opt_revision_number;
+ entry->file_external_rev.value.number = revision;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Fill in the following fields of ENTRY:
+
+ REVISION
+ REPOS
+ UUID
+ CMT_REV
+ CMT_DATE
+ CMT_AUTHOR
+ DEPTH
+ DELETED
+
+ Return: KIND, REPOS_RELPATH, CHECKSUM
+*/
+static svn_error_t *
+get_info_for_deleted(svn_wc_entry_t *entry,
+ svn_node_kind_t *kind,
+ const char **repos_relpath,
+ const svn_checksum_t **checksum,
+ svn_wc__db_lock_t **lock,
+ svn_wc__db_t *db,
+ const char *entry_abspath,
+ const svn_wc_entry_t *parent_entry,
+ svn_boolean_t have_base,
+ svn_boolean_t have_more_work,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (have_base && !have_more_work)
+ {
+ /* This is the delete of a BASE node */
+ SVN_ERR(svn_wc__db_base_get_info(NULL, kind,
+ &entry->revision,
+ repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ &entry->cmt_rev,
+ &entry->cmt_date,
+ &entry->cmt_author,
+ &entry->depth,
+ checksum,
+ NULL,
+ lock,
+ &entry->has_props, NULL,
+ NULL,
+ db,
+ entry_abspath,
+ result_pool,
+ scratch_pool));
+ }
+ else
+ {
+ const char *work_del_abspath;
+ const char *parent_repos_relpath;
+ const char *parent_abspath;
+
+ /* This is a deleted child of a copy/move-here,
+ so we need to scan up the WORKING tree to find the root of
+ the deletion. Then examine its parent to discover its
+ future location in the repository. */
+ SVN_ERR(svn_wc__db_read_pristine_info(NULL, kind,
+ &entry->cmt_rev,
+ &entry->cmt_date,
+ &entry->cmt_author,
+ &entry->depth,
+ checksum,
+ NULL,
+ &entry->has_props, NULL,
+ db,
+ entry_abspath,
+ result_pool,
+ scratch_pool));
+ /* working_size and text_time unavailable */
+
+ SVN_ERR(svn_wc__db_scan_deletion(NULL,
+ NULL,
+ &work_del_abspath, NULL,
+ db, entry_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(work_del_abspath != NULL);
+ parent_abspath = svn_dirent_dirname(work_del_abspath, scratch_pool);
+
+ /* The parent directory of the delete root must be added, so we
+ can find the required information there */
+ SVN_ERR(svn_wc__db_scan_addition(NULL, NULL,
+ &parent_repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ NULL, NULL, NULL, NULL,
+ db, parent_abspath,
+ result_pool, scratch_pool));
+
+ /* Now glue it all together */
+ *repos_relpath = svn_relpath_join(parent_repos_relpath,
+ svn_dirent_is_child(parent_abspath,
+ entry_abspath,
+ NULL),
+ result_pool);
+
+
+ /* Even though this is the delete of a WORKING node, there might still
+ be a BASE node somewhere below with an interesting revision */
+ if (have_base)
+ {
+ svn_wc__db_status_t status;
+ SVN_ERR(svn_wc__db_base_get_info(&status, NULL, &entry->revision,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, lock, NULL, NULL,
+ NULL,
+ db, entry_abspath,
+ result_pool, scratch_pool));
+
+ if (status == svn_wc__db_status_not_present)
+ entry->deleted = TRUE;
+ }
+ }
+
+ /* Do some extra work for the child nodes. */
+ if (!SVN_IS_VALID_REVNUM(entry->revision) && parent_entry != NULL)
+ {
+ /* For child nodes without a revision, pick up the parent's
+ revision. */
+ entry->revision = parent_entry->revision;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/*
+ * Encode tree conflict descriptions into a single string.
+ *
+ * Set *CONFLICT_DATA to a string, allocated in POOL, that encodes the tree
+ * conflicts in CONFLICTS in a form suitable for storage in a single string
+ * field in a WC entry. CONFLICTS is a hash of zero or more pointers to
+ * svn_wc_conflict_description2_t objects, index by their basenames. All of the
+ * conflict victim paths must be siblings.
+ *
+ * Do all allocations in POOL.
+ *
+ * @see svn_wc__read_tree_conflicts()
+ */
+static svn_error_t *
+write_tree_conflicts(const char **conflict_data,
+ apr_hash_t *conflicts,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel = svn_skel__make_empty_list(pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, conflicts); hi; hi = apr_hash_next(hi))
+ {
+ svn_skel_t *c_skel;
+
+ SVN_ERR(svn_wc__serialize_conflict(&c_skel, svn__apr_hash_index_val(hi),
+ pool, pool));
+ svn_skel__prepend(c_skel, skel);
+ }
+
+ *conflict_data = svn_skel__unparse(skel, pool)->data;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Read one entry from wc_db. It will be allocated in RESULT_POOL and
+ returned in *NEW_ENTRY.
+
+ DIR_ABSPATH is the name of the directory to read this entry from, and
+ it will be named NAME (use "" for "this dir").
+
+ DB specifies the wc_db database, and WC_ID specifies which working copy
+ this information is being read from.
+
+ If this node is "this dir", then PARENT_ENTRY should be NULL. Otherwise,
+ it should refer to the entry for the child's parent directory.
+
+ Temporary allocations are made in SCRATCH_POOL. */
+static svn_error_t *
+read_one_entry(const svn_wc_entry_t **new_entry,
+ svn_wc__db_t *db,
+ apr_int64_t wc_id,
+ const char *dir_abspath,
+ const char *name,
+ const svn_wc_entry_t *parent_entry,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+ svn_wc__db_status_t status;
+ svn_wc__db_lock_t *lock;
+ const char *repos_relpath;
+ const svn_checksum_t *checksum;
+ svn_filesize_t translated_size;
+ svn_wc_entry_t *entry = alloc_entry(result_pool);
+ const char *entry_abspath;
+ const char *original_repos_relpath;
+ const char *original_root_url;
+ svn_boolean_t conflicted;
+ svn_boolean_t have_base;
+ svn_boolean_t have_more_work;
+
+ entry->name = name;
+
+ entry_abspath = svn_dirent_join(dir_abspath, entry->name, scratch_pool);
+
+ SVN_ERR(svn_wc__db_read_info(
+ &status,
+ &kind,
+ &entry->revision,
+ &repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ &entry->cmt_rev,
+ &entry->cmt_date,
+ &entry->cmt_author,
+ &entry->depth,
+ &checksum,
+ NULL,
+ &original_repos_relpath,
+ &original_root_url,
+ NULL,
+ &entry->copyfrom_rev,
+ &lock,
+ &translated_size,
+ &entry->text_time,
+ &entry->changelist,
+ &conflicted,
+ NULL /* op_root */,
+ &entry->has_props /* have_props */,
+ &entry->has_prop_mods /* props_mod */,
+ &have_base,
+ &have_more_work,
+ NULL /* have_work */,
+ db,
+ entry_abspath,
+ result_pool,
+ scratch_pool));
+
+ if (entry->has_prop_mods)
+ entry->has_props = TRUE;
+
+ if (strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) == 0)
+ {
+ /* get the tree conflict data. */
+ apr_hash_t *tree_conflicts = NULL;
+ const apr_array_header_t *conflict_victims;
+ int k;
+
+ SVN_ERR(svn_wc__db_read_conflict_victims(&conflict_victims, db,
+ dir_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ for (k = 0; k < conflict_victims->nelts; k++)
+ {
+ int j;
+ const apr_array_header_t *child_conflicts;
+ const char *child_name;
+ const char *child_abspath;
+
+ child_name = APR_ARRAY_IDX(conflict_victims, k, const char *);
+ child_abspath = svn_dirent_join(dir_abspath, child_name,
+ scratch_pool);
+
+ SVN_ERR(svn_wc__read_conflicts(&child_conflicts,
+ db, child_abspath,
+ FALSE /* create tempfiles */,
+ scratch_pool, scratch_pool));
+
+ for (j = 0; j < child_conflicts->nelts; j++)
+ {
+ const svn_wc_conflict_description2_t *conflict =
+ APR_ARRAY_IDX(child_conflicts, j,
+ svn_wc_conflict_description2_t *);
+
+ if (conflict->kind == svn_wc_conflict_kind_tree)
+ {
+ if (!tree_conflicts)
+ tree_conflicts = apr_hash_make(scratch_pool);
+ svn_hash_sets(tree_conflicts, child_name, conflict);
+ }
+ }
+ }
+
+ if (tree_conflicts)
+ {
+ SVN_ERR(write_tree_conflicts(&entry->tree_conflict_data,
+ tree_conflicts, result_pool));
+ }
+ }
+
+ if (status == svn_wc__db_status_normal
+ || status == svn_wc__db_status_incomplete)
+ {
+ /* Plain old BASE node. */
+ entry->schedule = svn_wc_schedule_normal;
+
+ /* Grab inherited repository information, if necessary. */
+ if (repos_relpath == NULL)
+ {
+ SVN_ERR(svn_wc__db_scan_base_repos(&repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ db,
+ entry_abspath,
+ result_pool,
+ scratch_pool));
+ }
+
+ entry->incomplete = (status == svn_wc__db_status_incomplete);
+ }
+ else if (status == svn_wc__db_status_deleted)
+ {
+ svn_node_kind_t path_kind;
+
+ /* ### we don't have to worry about moves, so this is a delete. */
+ entry->schedule = svn_wc_schedule_delete;
+
+ /* If there are multiple working layers or no BASE layer, then
+ this is a WORKING delete or WORKING not-present. */
+ if (have_more_work || !have_base)
+ entry->copied = TRUE;
+ else if (have_base && !have_more_work)
+ entry->copied = FALSE;
+ else
+ {
+ const char *work_del_abspath;
+ SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL,
+ &work_del_abspath, NULL,
+ db, entry_abspath,
+ scratch_pool, scratch_pool));
+
+ if (work_del_abspath)
+ entry->copied = TRUE;
+ }
+
+ /* If there is still a directory on-disk we keep it, if not it is
+ already deleted. Simple, isn't it?
+
+ Before single-db we had to keep the administative area alive until
+ after the commit really deletes it. Setting keep alive stopped the
+ commit processing from deleting the directory. We don't delete it
+ any more, so all we have to do is provide some 'sane' value.
+ */
+ SVN_ERR(svn_io_check_path(entry_abspath, &path_kind, scratch_pool));
+ entry->keep_local = (path_kind == svn_node_dir);
+ }
+ else if (status == svn_wc__db_status_added)
+ {
+ svn_wc__db_status_t work_status;
+ const char *op_root_abspath;
+ const char *scanned_original_relpath;
+ svn_revnum_t original_revision;
+
+ /* For child nodes, pick up the parent's revision. */
+ if (*entry->name != '\0')
+ {
+ assert(parent_entry != NULL);
+ assert(entry->revision == SVN_INVALID_REVNUM);
+
+ entry->revision = parent_entry->revision;
+ }
+
+ if (have_base)
+ {
+ svn_wc__db_status_t base_status;
+
+ /* ENTRY->REVISION is overloaded. When a node is schedule-add
+ or -replace, then REVISION refers to the BASE node's revision
+ that is being overwritten. We need to fetch it now. */
+ SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL,
+ &entry->revision,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL,
+ db, entry_abspath,
+ scratch_pool,
+ scratch_pool));
+
+ if (base_status == svn_wc__db_status_not_present)
+ {
+ /* The underlying node is DELETED in this revision. */
+ entry->deleted = TRUE;
+
+ /* This is an add since there isn't a node to replace. */
+ entry->schedule = svn_wc_schedule_add;
+ }
+ else
+ entry->schedule = svn_wc_schedule_replace;
+ }
+ else
+ {
+ /* There is NO 'not-present' BASE_NODE for this node.
+ Therefore, we are looking at some kind of add/copy
+ rather than a replace. */
+
+ /* ### if this looks like a plain old add, then rev=0. */
+ if (!SVN_IS_VALID_REVNUM(entry->copyfrom_rev)
+ && !SVN_IS_VALID_REVNUM(entry->cmt_rev))
+ entry->revision = 0;
+
+ entry->schedule = svn_wc_schedule_add;
+ }
+
+ /* If we don't have "real" data from the entry (obstruction),
+ then we cannot begin a scan for data. The original node may
+ have important data. Set up stuff to kill that idea off,
+ and finish up this entry. */
+ {
+ SVN_ERR(svn_wc__db_scan_addition(&work_status,
+ &op_root_abspath,
+ &repos_relpath,
+ &entry->repos,
+ &entry->uuid,
+ &scanned_original_relpath,
+ NULL, NULL, /* original_root|uuid */
+ &original_revision,
+ db,
+ entry_abspath,
+ result_pool, scratch_pool));
+
+ /* In wc.db we want to keep the valid revision of the not-present
+ BASE_REV, but when we used entries we set the revision to 0
+ when adding a new node over a not present base node. */
+ if (work_status == svn_wc__db_status_added
+ && entry->deleted)
+ entry->revision = 0;
+ }
+
+ if (!SVN_IS_VALID_REVNUM(entry->cmt_rev)
+ && scanned_original_relpath == NULL)
+ {
+ /* There is NOT a last-changed revision (last-changed date and
+ author may be unknown, but we can always check the rev).
+ The absence of a revision implies this node was added WITHOUT
+ any history. Avoid the COPIED checks in the else block. */
+ /* ### scan_addition may need to be updated to avoid returning
+ ### status_copied in this case. */
+ }
+ /* For backwards-compatiblity purposes we treat moves just like
+ * regular copies. */
+ else if (work_status == svn_wc__db_status_copied ||
+ work_status == svn_wc__db_status_moved_here)
+ {
+ entry->copied = TRUE;
+
+ /* If this is a child of a copied subtree, then it should be
+ schedule_normal. */
+ if (original_repos_relpath == NULL)
+ {
+ /* ### what if there is a BASE node under there? */
+ entry->schedule = svn_wc_schedule_normal;
+ }
+
+ /* Copied nodes need to mirror their copyfrom_rev, if they
+ don't have a revision of their own already. */
+ if (!SVN_IS_VALID_REVNUM(entry->revision)
+ || entry->revision == 0 /* added */)
+ entry->revision = original_revision;
+ }
+
+ /* Does this node have copyfrom_* information? */
+ if (scanned_original_relpath != NULL)
+ {
+ svn_boolean_t is_copied_child;
+ svn_boolean_t is_mixed_rev = FALSE;
+
+ SVN_ERR_ASSERT(work_status == svn_wc__db_status_copied ||
+ work_status == svn_wc__db_status_moved_here);
+
+ /* If this node inherits copyfrom information from an
+ ancestor node, then it must be a copied child. */
+ is_copied_child = (original_repos_relpath == NULL);
+
+ /* If this node has copyfrom information on it, then it may
+ be an actual copy-root, or it could be participating in
+ a mixed-revision copied tree. So if we don't already know
+ this is a copied child, then we need to look for this
+ mixed-revision situation. */
+ if (!is_copied_child)
+ {
+ const char *parent_abspath;
+ svn_error_t *err;
+ const char *parent_repos_relpath;
+ const char *parent_root_url;
+
+ /* When we insert entries into the database, we will
+ construct additional copyfrom records for mixed-revision
+ copies. The old entries would simply record the different
+ revision in the entry->revision field. That is not
+ available within wc-ng, so additional copies are made
+ (see the logic inside write_entry()). However, when
+ reading these back *out* of the database, the additional
+ copies look like new "Added" nodes rather than a simple
+ mixed-rev working copy.
+
+ That would be a behavior change if we did not compensate.
+ If there is copyfrom information for this node, then the
+ code below looks at the parent to detect if it *also* has
+ copyfrom information, and if the copyfrom_url would align
+ properly. If it *does*, then we omit storing copyfrom_url
+ and copyfrom_rev (ie. inherit the copyfrom info like a
+ normal child), and update entry->revision with the
+ copyfrom_rev in order to (re)create the mixed-rev copied
+ subtree that was originally presented for storage. */
+
+ /* Get the copyfrom information from our parent.
+
+ Note that the parent could be added/copied/moved-here.
+ There is no way for it to be deleted/moved-away and
+ have *this* node appear as copied. */
+ parent_abspath = svn_dirent_dirname(entry_abspath,
+ scratch_pool);
+ err = svn_wc__db_scan_addition(NULL,
+ &op_root_abspath,
+ NULL, NULL, NULL,
+ &parent_repos_relpath,
+ &parent_root_url,
+ NULL, NULL,
+ db, parent_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);
+ }
+ else if (parent_root_url != NULL
+ && strcmp(original_root_url, parent_root_url) == 0)
+ {
+ const char *relpath_to_entry = svn_dirent_is_child(
+ op_root_abspath, entry_abspath, NULL);
+ const char *entry_repos_relpath = svn_relpath_join(
+ parent_repos_relpath, relpath_to_entry, scratch_pool);
+
+ /* The copyfrom repos roots matched.
+
+ Now we look to see if the copyfrom path of the parent
+ would align with our own path. If so, then it means
+ this copyfrom was spontaneously created and inserted
+ for mixed-rev purposes and can be eliminated without
+ changing the semantics of a mixed-rev copied subtree.
+
+ See notes/api-errata/wc003.txt for some additional
+ detail, and potential issues. */
+ if (strcmp(entry_repos_relpath,
+ original_repos_relpath) == 0)
+ {
+ is_copied_child = TRUE;
+ is_mixed_rev = TRUE;
+ }
+ }
+ }
+
+ if (is_copied_child)
+ {
+ /* We won't be settig the copyfrom_url, yet need to
+ clear out the copyfrom_rev. Thus, this node becomes a
+ child of a copied subtree (rather than its own root). */
+ entry->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* Children in a copied subtree are schedule normal
+ since we don't plan to actually *do* anything with
+ them. Their operation is implied by ancestors. */
+ entry->schedule = svn_wc_schedule_normal;
+
+ /* And *finally* we turn this entry into the mixed
+ revision node that it was intended to be. This
+ node's revision is taken from the copyfrom record
+ that we spontaneously constructed. */
+ if (is_mixed_rev)
+ entry->revision = original_revision;
+ }
+ else if (original_repos_relpath != NULL)
+ {
+ entry->copyfrom_url =
+ svn_path_url_add_component2(original_root_url,
+ original_repos_relpath,
+ result_pool);
+ }
+ else
+ {
+ /* NOTE: if original_repos_relpath == NULL, then the
+ second call to scan_addition() will not have occurred.
+ Thus, this use of OP_ROOT_ABSPATH still contains the
+ original value where we fetched a value for
+ SCANNED_REPOS_RELPATH. */
+ const char *relpath_to_entry = svn_dirent_is_child(
+ op_root_abspath, entry_abspath, NULL);
+ const char *entry_repos_relpath = svn_relpath_join(
+ scanned_original_relpath, relpath_to_entry, scratch_pool);
+
+ entry->copyfrom_url =
+ svn_path_url_add_component2(original_root_url,
+ entry_repos_relpath,
+ result_pool);
+ }
+ }
+ }
+ else if (status == svn_wc__db_status_not_present)
+ {
+ /* ### buh. 'deleted' nodes are actually supposed to be
+ ### schedule "normal" since we aren't going to actually *do*
+ ### anything to this node at commit time. */
+ entry->schedule = svn_wc_schedule_normal;
+ entry->deleted = TRUE;
+ }
+ else if (status == svn_wc__db_status_server_excluded)
+ {
+ entry->absent = TRUE;
+ }
+ else if (status == svn_wc__db_status_excluded)
+ {
+ entry->schedule = svn_wc_schedule_normal;
+ entry->depth = svn_depth_exclude;
+ }
+ else
+ {
+ /* ### we should have handled all possible status values. */
+ SVN_ERR_MALFUNCTION();
+ }
+
+ /* ### higher levels want repos information about deleted nodes, even
+ ### tho they are not "part of" a repository any more. */
+ if (entry->schedule == svn_wc_schedule_delete)
+ {
+ SVN_ERR(get_info_for_deleted(entry,
+ &kind,
+ &repos_relpath,
+ &checksum,
+ &lock,
+ db, entry_abspath,
+ parent_entry,
+ have_base, have_more_work,
+ result_pool, scratch_pool));
+ }
+
+ /* ### default to the infinite depth if we don't know it. */
+ if (entry->depth == svn_depth_unknown)
+ entry->depth = svn_depth_infinity;
+
+ if (kind == svn_node_dir)
+ entry->kind = svn_node_dir;
+ else if (kind == svn_node_file)
+ entry->kind = svn_node_file;
+ else if (kind == svn_node_symlink)
+ entry->kind = svn_node_file; /* ### no symlink kind */
+ else
+ entry->kind = svn_node_unknown;
+
+ /* We should always have a REPOS_RELPATH, except for:
+ - deleted nodes
+ - certain obstructed nodes
+ - not-present nodes
+ - absent nodes
+ - excluded nodes
+
+ ### the last three should probably have an "implied" REPOS_RELPATH
+ */
+ SVN_ERR_ASSERT(repos_relpath != NULL
+ || entry->schedule == svn_wc_schedule_delete
+ || status == svn_wc__db_status_not_present
+ || status == svn_wc__db_status_server_excluded
+ || status == svn_wc__db_status_excluded);
+ if (repos_relpath)
+ entry->url = svn_path_url_add_component2(entry->repos,
+ repos_relpath,
+ result_pool);
+
+ if (checksum)
+ {
+ /* We got a SHA-1, get the corresponding MD-5. */
+ if (checksum->kind != svn_checksum_md5)
+ SVN_ERR(svn_wc__db_pristine_get_md5(&checksum, db,
+ entry_abspath, checksum,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(checksum->kind == svn_checksum_md5);
+ entry->checksum = svn_checksum_to_cstring(checksum, result_pool);
+ }
+
+ if (conflicted)
+ {
+ svn_skel_t *conflict;
+ svn_boolean_t text_conflicted;
+ svn_boolean_t prop_conflicted;
+ SVN_ERR(svn_wc__db_read_conflict(&conflict, db, entry_abspath,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, &text_conflicted,
+ &prop_conflicted, NULL,
+ db, dir_abspath, conflict,
+ scratch_pool, scratch_pool));
+
+ if (text_conflicted)
+ {
+ const char *my_abspath;
+ const char *their_old_abspath;
+ const char *their_abspath;
+ SVN_ERR(svn_wc__conflict_read_text_conflict(&my_abspath,
+ &their_old_abspath,
+ &their_abspath,
+ db, dir_abspath,
+ conflict, scratch_pool,
+ scratch_pool));
+
+ if (my_abspath)
+ entry->conflict_wrk = svn_dirent_basename(my_abspath, result_pool);
+
+ if (their_old_abspath)
+ entry->conflict_old = svn_dirent_basename(their_old_abspath,
+ result_pool);
+
+ if (their_abspath)
+ entry->conflict_new = svn_dirent_basename(their_abspath,
+ result_pool);
+ }
+
+ if (prop_conflicted)
+ {
+ const char *prej_abspath;
+
+ SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath, NULL,
+ NULL, NULL, NULL,
+ db, dir_abspath,
+ conflict, scratch_pool,
+ scratch_pool));
+
+ if (prej_abspath)
+ entry->prejfile = svn_dirent_basename(prej_abspath, result_pool);
+ }
+ }
+
+ if (lock)
+ {
+ entry->lock_token = lock->token;
+ entry->lock_owner = lock->owner;
+ entry->lock_comment = lock->comment;
+ entry->lock_creation_date = lock->date;
+ }
+
+ /* Let's check for a file external. ugh. */
+ if (status == svn_wc__db_status_normal
+ && kind == svn_node_file)
+ SVN_ERR(check_file_external(entry, db, entry_abspath, dir_abspath,
+ result_pool, scratch_pool));
+
+ entry->working_size = translated_size;
+
+ *new_entry = entry;
+
+ return SVN_NO_ERROR;
+}
+
+/* Read entries for PATH/LOCAL_ABSPATH from DB. The entries
+ will be allocated in RESULT_POOL, with temporary allocations in
+ SCRATCH_POOL. The entries are returned in RESULT_ENTRIES. */
+static svn_error_t *
+read_entries_new(apr_hash_t **result_entries,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *entries;
+ const apr_array_header_t *children;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+ const svn_wc_entry_t *parent_entry;
+ apr_int64_t wc_id = 1; /* ### hacky. should remove. */
+
+ entries = apr_hash_make(result_pool);
+
+ SVN_ERR(read_one_entry(&parent_entry, db, wc_id, local_abspath,
+ "" /* name */,
+ NULL /* parent_entry */,
+ result_pool, iterpool));
+ svn_hash_sets(entries, "", parent_entry);
+
+ /* Use result_pool so that the child names (used by reference, rather
+ than copied) appear in result_pool. */
+ SVN_ERR(svn_wc__db_read_children(&children, db,
+ local_abspath,
+ result_pool, iterpool));
+ for (i = children->nelts; i--; )
+ {
+ const char *name = APR_ARRAY_IDX(children, i, const char *);
+ const svn_wc_entry_t *entry;
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(read_one_entry(&entry,
+ db, wc_id, local_abspath, name, parent_entry,
+ result_pool, iterpool));
+ svn_hash_sets(entries, entry->name, entry);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ *result_entries = entries;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Read a pair of entries from wc_db in the directory DIR_ABSPATH. Return
+ the directory's entry in *PARENT_ENTRY and NAME's entry in *ENTRY. The
+ two returned pointers will be the same if NAME=="" ("this dir").
+
+ The parent entry must exist.
+
+ The requested entry MAY exist. If it does not, then NULL will be returned.
+
+ The resulting entries are allocated in RESULT_POOL, and all temporary
+ allocations are made in SCRATCH_POOL. */
+static svn_error_t *
+read_entry_pair(const svn_wc_entry_t **parent_entry,
+ const svn_wc_entry_t **entry,
+ svn_wc__db_t *db,
+ const char *dir_abspath,
+ const char *name,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_int64_t wc_id = 1; /* ### hacky. should remove. */
+
+ SVN_ERR(read_one_entry(parent_entry, db, wc_id, dir_abspath,
+ "" /* name */,
+ NULL /* parent_entry */,
+ result_pool, scratch_pool));
+
+ /* If we need the entry for "this dir", then return the parent_entry
+ in both outputs. Otherwise, read the child node. */
+ if (*name == '\0')
+ {
+ /* If the retrieved node is a FILE, then we have a problem. We asked
+ for a directory. This implies there is an obstructing, unversioned
+ directory where a FILE should be. We navigated from the obstructing
+ subdir up to the parent dir, then returned the FILE found there.
+
+ Let's return WC_MISSING cuz the caller thought we had a dir, but
+ that (versioned subdir) isn't there. */
+ if ((*parent_entry)->kind == svn_node_file)
+ {
+ *parent_entry = NULL;
+ return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
+ _("'%s' is not a versioned working copy"),
+ svn_dirent_local_style(dir_abspath,
+ scratch_pool));
+ }
+
+ *entry = *parent_entry;
+ }
+ else
+ {
+ const apr_array_header_t *children;
+ int i;
+
+ /* Default to not finding the child. */
+ *entry = NULL;
+
+ /* Determine whether the parent KNOWS about this child. If it does
+ not, then we should not attempt to look for it.
+
+ For example: the parent doesn't "know" about the child, but the
+ versioned directory *does* exist on disk. We don't want to look
+ into that subdir. */
+ SVN_ERR(svn_wc__db_read_children(&children, db, dir_abspath,
+ scratch_pool, scratch_pool));
+ for (i = children->nelts; i--; )
+ {
+ const char *child = APR_ARRAY_IDX(children, i, const char *);
+
+ if (strcmp(child, name) == 0)
+ {
+ svn_error_t *err;
+
+ err = read_one_entry(entry,
+ db, wc_id, dir_abspath, name, *parent_entry,
+ result_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+
+ /* No problem. Clear the error and leave the default value
+ of "missing". */
+ svn_error_clear(err);
+ }
+
+ /* Found it. No need to keep searching. */
+ break;
+ }
+ }
+ /* if the loop ends without finding a child, then we have the default
+ ENTRY value of NULL. */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+read_entries(apr_hash_t **entries,
+ svn_wc__db_t *db,
+ const char *wcroot_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int wc_format;
+
+ SVN_ERR(svn_wc__db_temp_get_format(&wc_format, db, wcroot_abspath,
+ scratch_pool));
+
+ if (wc_format < SVN_WC__WC_NG_VERSION)
+ return svn_error_trace(svn_wc__read_entries_old(entries,
+ wcroot_abspath,
+ result_pool,
+ scratch_pool));
+
+ return svn_error_trace(read_entries_new(entries, db, wcroot_abspath,
+ result_pool, scratch_pool));
+}
+
+
+/* For a given LOCAL_ABSPATH, using DB, set *ADM_ABSPATH to the directory in
+ which the entry information is located, and *ENTRY_NAME to the entry name
+ to access that entry.
+
+ KIND is as in svn_wc__get_entry().
+
+ Return the results in RESULT_POOL and use SCRATCH_POOL for temporary
+ allocations. */
+static svn_error_t *
+get_entry_access_info(const char **adm_abspath,
+ const char **entry_name,
+ 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_adm_access_t *adm_access;
+ svn_boolean_t read_from_subdir = FALSE;
+
+ /* If the caller didn't know the node kind, then stat the path. Maybe
+ it is really there, and we can speed up the steps below. */
+ if (kind == svn_node_unknown)
+ {
+ svn_node_kind_t on_disk;
+
+ /* Do we already have an access baton for LOCAL_ABSPATH? */
+ adm_access = svn_wc__adm_retrieve_internal2(db, local_abspath,
+ scratch_pool);
+ if (adm_access)
+ {
+ /* Sweet. The node is a directory. */
+ on_disk = svn_node_dir;
+ }
+ else
+ {
+ svn_boolean_t special;
+
+ /* What's on disk? */
+ SVN_ERR(svn_io_check_special_path(local_abspath, &on_disk, &special,
+ scratch_pool));
+ }
+
+ if (on_disk != svn_node_dir)
+ {
+ /* If this is *anything* besides a directory (FILE, NONE, or
+ UNKNOWN), then we cannot treat it as a versioned directory
+ containing entries to read. Leave READ_FROM_SUBDIR as FALSE,
+ so that the parent will be examined.
+
+ For NONE and UNKNOWN, it may be that metadata exists for the
+ node, even though on-disk is unhelpful.
+
+ If NEED_PARENT_STUB is TRUE, and the entry is not a DIRECTORY,
+ then we'll error.
+
+ If NEED_PARENT_STUB if FALSE, and we successfully read a stub,
+ then this on-disk node is obstructing the read. */
+ }
+ else
+ {
+ /* We found a directory for this UNKNOWN node. Determine whether
+ we need to read inside it. */
+ read_from_subdir = TRUE;
+ }
+ }
+ else if (kind == svn_node_dir)
+ {
+ read_from_subdir = TRUE;
+ }
+
+ if (read_from_subdir)
+ {
+ /* KIND must be a DIR or UNKNOWN (and we found a subdir). We want
+ the "real" data, so treat LOCAL_ABSPATH as a versioned directory. */
+ *adm_abspath = apr_pstrdup(result_pool, local_abspath);
+ *entry_name = "";
+ }
+ else
+ {
+ /* FILE node needs to read the parent directory. Or a DIR node
+ needs to read from the parent to get at the stub entry. Or this
+ is an UNKNOWN node, and we need to examine the parent. */
+ svn_dirent_split(adm_abspath, entry_name, local_abspath, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__get_entry(const svn_wc_entry_t **entry,
+ svn_wc__db_t *db,
+ const char *local_abspath,
+ svn_boolean_t allow_unversioned,
+ svn_node_kind_t kind,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *dir_abspath;
+ const char *entry_name;
+
+ SVN_ERR(get_entry_access_info(&dir_abspath, &entry_name, db, local_abspath,
+ kind, scratch_pool, scratch_pool));
+
+ {
+ const svn_wc_entry_t *parent_entry;
+ svn_error_t *err;
+
+ /* NOTE: if KIND is UNKNOWN and we decided to examine the *parent*
+ directory, then it is possible we moved out of the working copy.
+ If the on-disk node is a DIR, and we asked for a stub, then we
+ obviously can't provide that (parent has no info). If the on-disk
+ node is a FILE/NONE/UNKNOWN, then it is obstructing the real
+ LOCAL_ABSPATH (or it was never a versioned item). In all these
+ cases, the read_entries() will (properly) throw an error.
+
+ NOTE: if KIND is a DIR and we asked for the real data, but it is
+ obstructed on-disk by some other node kind (NONE, FILE, UNKNOWN),
+ then this will throw an error. */
+
+ err = read_entry_pair(&parent_entry, entry,
+ db, dir_abspath, entry_name,
+ result_pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_MISSING || kind != svn_node_unknown
+ || *entry_name != '\0')
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* The caller didn't know the node type, we saw a directory there,
+ we attempted to read IN that directory, and then wc_db reports
+ that it is NOT a working copy directory. It is possible that
+ one of two things has happened:
+
+ 1) a directory is obstructing a file in the parent
+ 2) the (versioned) directory's contents have been removed
+
+ Let's assume situation (1); if that is true, then we can just
+ return the newly-found data.
+
+ If we assumed (2), then a valid result still won't help us
+ since the caller asked for the actual contents, not the stub
+ (which is why we read *into* the directory). However, if we
+ assume (1) and get back a stub, then we have verified a
+ missing, versioned directory, and can return an error
+ describing that.
+
+ Redo the fetch, but "insist" we are trying to find a file.
+ This will read from the parent directory of the "file". */
+ err = svn_wc__get_entry(entry, db, local_abspath, allow_unversioned,
+ svn_node_file, result_pool, scratch_pool);
+ if (err == SVN_NO_ERROR)
+ return SVN_NO_ERROR;
+ if (err->apr_err != SVN_ERR_NODE_UNEXPECTED_KIND)
+ return svn_error_trace(err);
+ svn_error_clear(err);
+
+ /* We asked for a FILE, but the node found is a DIR. Thus, we
+ are looking at a stub. Originally, we tried to read into the
+ subdir because NEED_PARENT_STUB is FALSE. The stub we just
+ read is not going to work for the caller, so inform them of
+ the missing subdirectory. */
+ SVN_ERR_ASSERT(*entry != NULL && (*entry)->kind == svn_node_dir);
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("Admin area of '%s' is missing"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+ }
+
+ if (*entry == NULL)
+ {
+ if (allow_unversioned)
+ return SVN_NO_ERROR;
+ return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+ }
+
+ /* The caller had the wrong information. */
+ if ((kind == svn_node_file && (*entry)->kind != svn_node_file)
+ || (kind == svn_node_dir && (*entry)->kind != svn_node_dir))
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("'%s' is not of the right kind"),
+ svn_dirent_local_style(local_abspath,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* TODO ### Rewrite doc string to mention ENTRIES_ALL; not ADM_ACCESS.
+
+ Prune the deleted entries from the cached entries in ADM_ACCESS, and
+ return that collection in *ENTRIES_PRUNED. SCRATCH_POOL is used for local,
+ short term, memory allocation, RESULT_POOL for permanent stuff. */
+static svn_error_t *
+prune_deleted(apr_hash_t **entries_pruned,
+ apr_hash_t *entries_all,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ if (!entries_all)
+ {
+ *entries_pruned = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* I think it will be common for there to be no deleted entries, so
+ it is worth checking for that case as we can optimise it. */
+ for (hi = apr_hash_first(scratch_pool, entries_all);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_boolean_t hidden;
+
+ SVN_ERR(svn_wc__entry_is_hidden(&hidden,
+ svn__apr_hash_index_val(hi)));
+ if (hidden)
+ break;
+ }
+
+ if (! hi)
+ {
+ /* There are no deleted entries, so we can use the full hash */
+ *entries_pruned = entries_all;
+ return SVN_NO_ERROR;
+ }
+
+ /* Construct pruned hash without deleted entries */
+ *entries_pruned = apr_hash_make(result_pool);
+ for (hi = apr_hash_first(scratch_pool, entries_all);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key = svn__apr_hash_index_key(hi);
+ const svn_wc_entry_t *entry = svn__apr_hash_index_val(hi);
+ svn_boolean_t hidden;
+
+ SVN_ERR(svn_wc__entry_is_hidden(&hidden, entry));
+ if (!hidden)
+ svn_hash_sets(*entries_pruned, key, entry);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct entries_read_baton_t
+{
+ apr_hash_t **new_entries;
+ svn_wc__db_t *db;
+ const char *local_abspath;
+ apr_pool_t *result_pool;
+};
+
+static svn_error_t *
+entries_read_txn(void *baton, svn_sqlite__db_t *db, apr_pool_t *scratch_pool)
+{
+ struct entries_read_baton_t *erb = baton;
+
+ SVN_ERR(read_entries(erb->new_entries, erb->db, erb->local_abspath,
+ erb->result_pool, scratch_pool));
+
+ return NULL;
+}
+
+svn_error_t *
+svn_wc__entries_read_internal(apr_hash_t **entries,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool)
+{
+ apr_hash_t *new_entries;
+
+ new_entries = svn_wc__adm_access_entries(adm_access);
+ if (! new_entries)
+ {
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+ const char *local_abspath = svn_wc__adm_access_abspath(adm_access);
+ apr_pool_t *result_pool = svn_wc__adm_access_pool_internal(adm_access);
+ svn_sqlite__db_t *sdb;
+ struct entries_read_baton_t erb;
+
+ /* ### Use the borrow DB api to handle all calls in a single read
+ ### transaction. This api is used extensively in our test suite
+ ### via the entries-read application. */
+
+ SVN_ERR(svn_wc__db_temp_borrow_sdb(&sdb, db, local_abspath, pool));
+
+ erb.db = db;
+ erb.local_abspath = local_abspath;
+ erb.new_entries = &new_entries;
+ erb.result_pool = result_pool;
+
+ SVN_ERR(svn_sqlite__with_lock(sdb, entries_read_txn, &erb, pool));
+
+ svn_wc__adm_access_set_entries(adm_access, new_entries);
+ }
+
+ if (show_hidden)
+ *entries = new_entries;
+ else
+ SVN_ERR(prune_deleted(entries, new_entries,
+ svn_wc__adm_access_pool_internal(adm_access),
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc_entries_read(apr_hash_t **entries,
+ svn_wc_adm_access_t *adm_access,
+ svn_boolean_t show_hidden,
+ apr_pool_t *pool)
+{
+ return svn_error_trace(svn_wc__entries_read_internal(entries, adm_access,
+ show_hidden, pool));
+}
+
+/* No transaction required: called from write_entry which is itself
+ transaction-wrapped. */
+static svn_error_t *
+insert_node(svn_sqlite__db_t *sdb,
+ const db_node_t *node,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR_ASSERT(node->op_depth > 0 || node->repos_relpath);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnnnsnrisnnni",
+ node->wc_id,
+ node->local_relpath,
+ node->op_depth,
+ node->parent_relpath,
+ /* Setting depth for files? */
+ svn_depth_to_word(node->depth),
+ node->changed_rev,
+ node->changed_date,
+ node->changed_author,
+ node->recorded_time));
+
+ if (node->repos_relpath)
+ {
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 5,
+ node->repos_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 6,
+ node->repos_relpath));
+ SVN_ERR(svn_sqlite__bind_revnum(stmt, 7, node->revision));
+ }
+
+ if (node->presence == svn_wc__db_status_normal)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "normal"));
+ else if (node->presence == svn_wc__db_status_not_present)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "not-present"));
+ else if (node->presence == svn_wc__db_status_base_deleted)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "base-deleted"));
+ else if (node->presence == svn_wc__db_status_incomplete)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "incomplete"));
+ else if (node->presence == svn_wc__db_status_excluded)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "excluded"));
+ else if (node->presence == svn_wc__db_status_server_excluded)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, "server-excluded"));
+
+ if (node->kind == svn_node_none)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 10, "unknown"));
+ else
+ SVN_ERR(svn_sqlite__bind_text(stmt, 10,
+ svn_node_kind_to_word(node->kind)));
+
+ if (node->kind == svn_node_file)
+ {
+ if (!node->checksum
+ && node->op_depth == 0
+ && node->presence != svn_wc__db_status_not_present
+ && node->presence != svn_wc__db_status_excluded
+ && node->presence != svn_wc__db_status_server_excluded)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("The file '%s' has no checksum"),
+ svn_dirent_local_style(node->local_relpath,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, node->checksum,
+ scratch_pool));
+ }
+
+ if (node->properties) /* ### Never set, props done later */
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 15, node->properties,
+ scratch_pool));
+
+ if (node->recorded_size != SVN_INVALID_FILESIZE)
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 16, node->recorded_size));
+
+ if (node->file_external)
+ SVN_ERR(svn_sqlite__bind_int(stmt, 20, 1));
+
+ if (node->inherited_props)
+ SVN_ERR(svn_sqlite__bind_iprops(stmt, 23, node->inherited_props,
+ scratch_pool));
+
+ SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* */
+static svn_error_t *
+insert_actual_node(svn_sqlite__db_t *sdb,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ const db_actual_node_t *actual_node,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_skel_t *conflict_data = NULL;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_ACTUAL_NODE));
+
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 1, actual_node->wc_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 2, actual_node->local_relpath));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 3, actual_node->parent_relpath));
+
+ if (actual_node->properties)
+ SVN_ERR(svn_sqlite__bind_properties(stmt, 4, actual_node->properties,
+ scratch_pool));
+
+ if (actual_node->changelist)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 5, actual_node->changelist));
+
+ SVN_ERR(svn_wc__upgrade_conflict_skel_from_raw(
+ &conflict_data,
+ db, wri_abspath,
+ actual_node->local_relpath,
+ actual_node->conflict_old,
+ actual_node->conflict_working,
+ actual_node->conflict_new,
+ actual_node->prop_reject,
+ actual_node->tree_conflict_data,
+ actual_node->tree_conflict_data
+ ? strlen(actual_node->tree_conflict_data)
+ : 0,
+ scratch_pool, scratch_pool));
+
+ if (conflict_data)
+ {
+ svn_stringbuf_t *data = svn_skel__unparse(conflict_data, scratch_pool);
+
+ SVN_ERR(svn_sqlite__bind_blob(stmt, 6, data->data, data->len));
+ }
+
+ /* Execute and reset the insert clause. */
+ return svn_error_trace(svn_sqlite__insert(NULL, stmt));
+}
+
+static svn_boolean_t
+is_switched(db_node_t *parent,
+ db_node_t *child,
+ apr_pool_t *scratch_pool)
+{
+ if (parent && child)
+ {
+ if (parent->repos_id != child->repos_id)
+ return TRUE;
+
+ if (parent->repos_relpath && child->repos_relpath)
+ {
+ const char *unswitched
+ = svn_relpath_join(parent->repos_relpath,
+ svn_relpath_basename(child->local_relpath,
+ scratch_pool),
+ scratch_pool);
+ if (strcmp(unswitched, child->repos_relpath))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+struct write_baton {
+ db_node_t *base;
+ db_node_t *work;
+ db_node_t *below_work;
+ apr_hash_t *tree_conflicts;
+};
+
+#define WRITE_ENTRY_ASSERT(expr) \
+ if (!(expr)) \
+ return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL, \
+ _("Unable to upgrade '%s' at line %d"), \
+ svn_dirent_local_style( \
+ svn_dirent_join(root_abspath, \
+ local_relpath, \
+ scratch_pool), \
+ scratch_pool), __LINE__)
+
+/* Write the information for ENTRY to WC_DB. The WC_ID, REPOS_ID and
+ REPOS_ROOT will all be used for writing ENTRY.
+ ### transitioning from straight sql to using the wc_db APIs. For the
+ ### time being, we'll need both parameters. */
+static svn_error_t *
+write_entry(struct write_baton **entry_node,
+ const struct write_baton *parent_node,
+ svn_wc__db_t *db,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t wc_id,
+ apr_int64_t repos_id,
+ const svn_wc_entry_t *entry,
+ const svn_wc__text_base_info_t *text_base_info,
+ const char *local_relpath,
+ const char *tmp_entry_abspath,
+ const char *root_abspath,
+ const svn_wc_entry_t *this_dir,
+ svn_boolean_t create_locks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ db_node_t *base_node = NULL;
+ db_node_t *working_node = NULL, *below_working_node = NULL;
+ db_actual_node_t *actual_node = NULL;
+ const char *parent_relpath;
+ apr_hash_t *tree_conflicts;
+
+ if (*local_relpath == '\0')
+ parent_relpath = NULL;
+ else
+ parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool);
+
+ /* This is how it should work, it doesn't work like this yet because
+ we need proper op_depth to layer the working nodes.
+
+ Using "svn add", "svn rm", "svn cp" only files can be replaced
+ pre-wcng; directories can only be normal, deleted or added.
+ Files cannot be replaced within a deleted directory, so replaced
+ files can only exist in a normal directory, or a directory that
+ is added+copied. In a normal directory a replaced file needs a
+ base node and a working node, in an added+copied directory a
+ replaced file needs two working nodes at different op-depths.
+
+ With just the above operations the conversion for files and
+ directories is straightforward:
+
+ pre-wcng wcng
+ parent child parent child
+
+ normal normal base base
+ add+copied normal+copied work work
+ normal+copied normal+copied work work
+ normal delete base base+work
+ delete delete base+work base+work
+ add+copied delete work work
+ normal add base work
+ add add work work
+ add+copied add work work
+ normal add+copied base work
+ add add+copied work work
+ add+copied add+copied work work
+ normal replace base base+work
+ add+copied replace work work+work
+ normal replace+copied base base+work
+ add+copied replace+copied work work+work
+
+ However "svn merge" make this more complicated. The pre-wcng
+ "svn merge" is capable of replacing a directory, that is it can
+ mark the whole tree deleted, and then copy another tree on top.
+ The entries then represent the replacing tree overlayed on the
+ deleted tree.
+
+ original replace schedule in
+ tree tree combined tree
+
+ A A replace+copied
+ A/f delete+copied
+ A/g A/g replace+copied
+ A/h add+copied
+ A/B A/B replace+copied
+ A/B/f delete+copied
+ A/B/g A/B/g replace+copied
+ A/B/h add+copied
+ A/C delete+copied
+ A/C/f delete+copied
+ A/D add+copied
+ A/D/f add+copied
+
+ The original tree could be normal tree, or an add+copied tree.
+ Committing such a merge generally worked, but making further tree
+ modifications before commit sometimes failed.
+
+ The root of the replace is handled like the file replace:
+
+ pre-wcng wcng
+ parent child parent child
+
+ normal replace+copied base base+work
+ add+copied replace+copied work work+work
+
+ although obviously the node is a directory rather then a file.
+ There are then more conversion states where the parent is
+ replaced.
+
+ pre-wcng wcng
+ parent child parent child
+
+ replace+copied add [base|work]+work work
+ replace+copied add+copied [base|work]+work work
+ replace+copied delete+copied [base|work]+work [base|work]+work
+ delete+copied delete+copied [base|work]+work [base|work]+work
+ replace+copied replace+copied [base|work]+work [base|work]+work
+ */
+
+ WRITE_ENTRY_ASSERT(parent_node || entry->schedule == svn_wc_schedule_normal);
+
+ WRITE_ENTRY_ASSERT(!parent_node || parent_node->base
+ || parent_node->below_work || parent_node->work);
+
+ switch (entry->schedule)
+ {
+ case svn_wc_schedule_normal:
+ if (entry->copied ||
+ (entry->depth == svn_depth_exclude
+ && parent_node && !parent_node->base && parent_node->work))
+ working_node = MAYBE_ALLOC(working_node, result_pool);
+ else
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ break;
+
+ case svn_wc_schedule_add:
+ working_node = MAYBE_ALLOC(working_node, result_pool);
+ if (entry->deleted)
+ {
+ if (parent_node->base)
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ else
+ below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
+ }
+ break;
+
+ case svn_wc_schedule_delete:
+ working_node = MAYBE_ALLOC(working_node, result_pool);
+ if (parent_node->base)
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ if (parent_node->work)
+ below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
+ break;
+
+ case svn_wc_schedule_replace:
+ working_node = MAYBE_ALLOC(working_node, result_pool);
+ if (parent_node->base)
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ else
+ below_working_node = MAYBE_ALLOC(below_working_node, result_pool);
+ break;
+ }
+
+ /* Something deleted in this revision means there should always be a
+ BASE node to indicate the not-present node. */
+ if (entry->deleted)
+ {
+ WRITE_ENTRY_ASSERT(base_node || below_working_node);
+ WRITE_ENTRY_ASSERT(!entry->incomplete);
+ if (base_node)
+ base_node->presence = svn_wc__db_status_not_present;
+ else
+ below_working_node->presence = svn_wc__db_status_not_present;
+ }
+ else if (entry->absent)
+ {
+ WRITE_ENTRY_ASSERT(base_node && !working_node && !below_working_node);
+ WRITE_ENTRY_ASSERT(!entry->incomplete);
+ base_node->presence = svn_wc__db_status_server_excluded;
+ }
+
+ if (entry->copied)
+ {
+ if (entry->copyfrom_url)
+ {
+ working_node->repos_id = repos_id;
+ working_node->repos_relpath = svn_uri_skip_ancestor(
+ this_dir->repos, entry->copyfrom_url,
+ result_pool);
+ working_node->revision = entry->copyfrom_rev;
+ working_node->op_depth
+ = svn_wc__db_op_depth_for_upgrade(local_relpath);
+ }
+ else if (parent_node->work && parent_node->work->repos_relpath)
+ {
+ working_node->repos_id = repos_id;
+ working_node->repos_relpath
+ = svn_relpath_join(parent_node->work->repos_relpath,
+ svn_relpath_basename(local_relpath, NULL),
+ result_pool);
+ working_node->revision = parent_node->work->revision;
+ working_node->op_depth = parent_node->work->op_depth;
+ }
+ else if (parent_node->below_work
+ && parent_node->below_work->repos_relpath)
+ {
+ working_node->repos_id = repos_id;
+ working_node->repos_relpath
+ = svn_relpath_join(parent_node->below_work->repos_relpath,
+ svn_relpath_basename(local_relpath, NULL),
+ result_pool);
+ working_node->revision = parent_node->below_work->revision;
+ working_node->op_depth = parent_node->below_work->op_depth;
+ }
+ else
+ return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
+ _("No copyfrom URL for '%s'"),
+ svn_dirent_local_style(local_relpath,
+ scratch_pool));
+ }
+
+ if (entry->conflict_old)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ if (parent_relpath && entry->conflict_old)
+ actual_node->conflict_old = svn_relpath_join(parent_relpath,
+ entry->conflict_old,
+ scratch_pool);
+ else
+ actual_node->conflict_old = entry->conflict_old;
+ if (parent_relpath && entry->conflict_new)
+ actual_node->conflict_new = svn_relpath_join(parent_relpath,
+ entry->conflict_new,
+ scratch_pool);
+ else
+ actual_node->conflict_new = entry->conflict_new;
+ if (parent_relpath && entry->conflict_wrk)
+ actual_node->conflict_working = svn_relpath_join(parent_relpath,
+ entry->conflict_wrk,
+ scratch_pool);
+ else
+ actual_node->conflict_working = entry->conflict_wrk;
+ }
+
+ if (entry->prejfile)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ actual_node->prop_reject = svn_relpath_join((entry->kind == svn_node_dir
+ ? local_relpath
+ : parent_relpath),
+ entry->prejfile,
+ scratch_pool);
+ }
+
+ if (entry->changelist)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ actual_node->changelist = entry->changelist;
+ }
+
+ /* ### set the text_mod value? */
+
+ if (entry_node && entry->tree_conflict_data)
+ {
+ /* Issues #3840/#3916: 1.6 stores multiple tree conflicts on the
+ parent node, 1.7 stores them directly on the conflited nodes.
+ So "((skel1) (skel2))" becomes "(skel1)" and "(skel2)" */
+ svn_skel_t *skel;
+
+ skel = svn_skel__parse(entry->tree_conflict_data,
+ strlen(entry->tree_conflict_data),
+ scratch_pool);
+ tree_conflicts = apr_hash_make(result_pool);
+ skel = skel->children;
+ while(skel)
+ {
+ svn_wc_conflict_description2_t *conflict;
+ svn_skel_t *new_skel;
+ const char *key;
+
+ /* *CONFLICT is allocated so it is safe to use a non-const pointer */
+ SVN_ERR(svn_wc__deserialize_conflict(
+ (const svn_wc_conflict_description2_t**)&conflict,
+ skel,
+ svn_dirent_join(root_abspath,
+ local_relpath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+
+ WRITE_ENTRY_ASSERT(conflict->kind == svn_wc_conflict_kind_tree);
+
+ /* Fix dubious data stored by old clients, local adds don't have
+ a repository URL. */
+ if (conflict->reason == svn_wc_conflict_reason_added)
+ conflict->src_left_version = NULL;
+
+ SVN_ERR(svn_wc__serialize_conflict(&new_skel, conflict,
+ scratch_pool, scratch_pool));
+
+ /* Store in hash to be retrieved when writing the child
+ row. */
+ key = svn_dirent_skip_ancestor(root_abspath, conflict->local_abspath);
+ svn_hash_sets(tree_conflicts, apr_pstrdup(result_pool, key),
+ svn_skel__unparse(new_skel, result_pool)->data);
+ skel = skel->next;
+ }
+ }
+ else
+ tree_conflicts = NULL;
+
+ if (parent_node && parent_node->tree_conflicts)
+ {
+ const char *tree_conflict_data =
+ svn_hash_gets(parent_node->tree_conflicts, local_relpath);
+ if (tree_conflict_data)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ actual_node->tree_conflict_data = tree_conflict_data;
+ }
+
+ /* Reset hash so that we don't write the row again when writing
+ actual-only nodes */
+ svn_hash_sets(parent_node->tree_conflicts, local_relpath, NULL);
+ }
+
+ if (entry->file_external_path != NULL)
+ {
+ base_node = MAYBE_ALLOC(base_node, result_pool);
+ }
+
+
+ /* Insert the base node. */
+ if (base_node)
+ {
+ base_node->wc_id = wc_id;
+ base_node->local_relpath = local_relpath;
+ base_node->op_depth = 0;
+ base_node->parent_relpath = parent_relpath;
+ base_node->revision = entry->revision;
+ base_node->recorded_time = entry->text_time;
+ base_node->recorded_size = entry->working_size;
+
+ if (entry->depth != svn_depth_exclude)
+ base_node->depth = entry->depth;
+ else
+ {
+ base_node->presence = svn_wc__db_status_excluded;
+ base_node->depth = svn_depth_infinity;
+ }
+
+ if (entry->deleted)
+ {
+ WRITE_ENTRY_ASSERT(base_node->presence
+ == svn_wc__db_status_not_present);
+ /* ### should be svn_node_unknown, but let's store what we have. */
+ base_node->kind = entry->kind;
+ }
+ else if (entry->absent)
+ {
+ WRITE_ENTRY_ASSERT(base_node->presence
+ == svn_wc__db_status_server_excluded);
+ /* ### should be svn_node_unknown, but let's store what we have. */
+ base_node->kind = entry->kind;
+
+ /* Store the most likely revision in the node to avoid
+ base nodes without a valid revision. Of course
+ we remember that the data is still incomplete. */
+ if (!SVN_IS_VALID_REVNUM(base_node->revision) && parent_node->base)
+ base_node->revision = parent_node->base->revision;
+ }
+ else
+ {
+ base_node->kind = entry->kind;
+
+ if (base_node->presence != svn_wc__db_status_excluded)
+ {
+ /* All subdirs are initially incomplete, they stop being
+ incomplete when the entries file in the subdir is
+ upgraded and remain incomplete if that doesn't happen. */
+ if (entry->kind == svn_node_dir
+ && strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR))
+ {
+ base_node->presence = svn_wc__db_status_incomplete;
+
+ /* Store the most likely revision in the node to avoid
+ base nodes without a valid revision. Of course
+ we remember that the data is still incomplete. */
+ if (parent_node->base)
+ base_node->revision = parent_node->base->revision;
+ }
+ else if (entry->incomplete)
+ {
+ /* ### nobody should have set the presence. */
+ WRITE_ENTRY_ASSERT(base_node->presence
+ == svn_wc__db_status_normal);
+ base_node->presence = svn_wc__db_status_incomplete;
+ }
+ }
+ }
+
+ if (entry->kind == svn_node_dir)
+ base_node->checksum = NULL;
+ else
+ {
+ if (text_base_info && text_base_info->revert_base.sha1_checksum)
+ base_node->checksum = text_base_info->revert_base.sha1_checksum;
+ else if (text_base_info && text_base_info->normal_base.sha1_checksum)
+ base_node->checksum = text_base_info->normal_base.sha1_checksum;
+ else
+ base_node->checksum = NULL;
+
+ /* The base MD5 checksum is available in the entry, unless there
+ * is a copied WORKING node. If possible, verify that the entry
+ * checksum matches the base file that we found. */
+ if (! (working_node && entry->copied))
+ {
+ svn_checksum_t *entry_md5_checksum, *found_md5_checksum;
+ SVN_ERR(svn_checksum_parse_hex(&entry_md5_checksum,
+ svn_checksum_md5,
+ entry->checksum, scratch_pool));
+ if (text_base_info && text_base_info->revert_base.md5_checksum)
+ found_md5_checksum = text_base_info->revert_base.md5_checksum;
+ else if (text_base_info
+ && text_base_info->normal_base.md5_checksum)
+ found_md5_checksum = text_base_info->normal_base.md5_checksum;
+ else
+ found_md5_checksum = NULL;
+ if (entry_md5_checksum && found_md5_checksum &&
+ !svn_checksum_match(entry_md5_checksum, found_md5_checksum))
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Bad base MD5 checksum for '%s'; "
+ "expected: '%s'; found '%s'; "),
+ svn_dirent_local_style(
+ svn_dirent_join(root_abspath,
+ local_relpath,
+ scratch_pool),
+ scratch_pool),
+ svn_checksum_to_cstring_display(
+ entry_md5_checksum, scratch_pool),
+ svn_checksum_to_cstring_display(
+ found_md5_checksum, scratch_pool));
+ else
+ {
+ /* ### Not sure what conditions this should cover. */
+ /* SVN_ERR_ASSERT(entry->deleted || ...); */
+ }
+ }
+ }
+
+ if (this_dir->repos)
+ {
+ base_node->repos_id = repos_id;
+
+ if (entry->url != NULL)
+ {
+ base_node->repos_relpath = svn_uri_skip_ancestor(
+ this_dir->repos, entry->url,
+ result_pool);
+ }
+ else
+ {
+ const char *relpath = svn_uri_skip_ancestor(this_dir->repos,
+ this_dir->url,
+ scratch_pool);
+ if (relpath == NULL || *relpath == '\0')
+ base_node->repos_relpath = entry->name;
+ else
+ base_node->repos_relpath =
+ svn_dirent_join(relpath, entry->name, result_pool);
+ }
+ }
+
+ /* TODO: These values should always be present, if they are missing
+ during an upgrade, set a flag, and then ask the user to talk to the
+ server.
+
+ Note: cmt_rev is the distinguishing value. The others may be 0 or
+ NULL if the corresponding revprop has been deleted. */
+ base_node->changed_rev = entry->cmt_rev;
+ base_node->changed_date = entry->cmt_date;
+ base_node->changed_author = entry->cmt_author;
+
+ if (entry->file_external_path)
+ base_node->file_external = TRUE;
+
+ /* Switched nodes get an empty iprops cache. */
+ if (parent_node
+ && is_switched(parent_node->base, base_node, scratch_pool))
+ base_node->inherited_props
+ = apr_array_make(scratch_pool, 0, sizeof(svn_prop_inherited_item_t*));
+
+ SVN_ERR(insert_node(sdb, base_node, scratch_pool));
+
+ /* We have to insert the lock after the base node, because the node
+ must exist to lookup various bits of repos related information for
+ the abs path. */
+ if (entry->lock_token && create_locks)
+ {
+ svn_wc__db_lock_t lock;
+
+ lock.token = entry->lock_token;
+ lock.owner = entry->lock_owner;
+ lock.comment = entry->lock_comment;
+ lock.date = entry->lock_creation_date;
+
+ SVN_ERR(svn_wc__db_lock_add(db, tmp_entry_abspath, &lock,
+ scratch_pool));
+ }
+ }
+
+ if (below_working_node)
+ {
+ db_node_t *work
+ = parent_node->below_work ? parent_node->below_work : parent_node->work;
+
+ below_working_node->wc_id = wc_id;
+ below_working_node->local_relpath = local_relpath;
+ below_working_node->op_depth = work->op_depth;
+ below_working_node->parent_relpath = parent_relpath;
+ below_working_node->presence = svn_wc__db_status_normal;
+ below_working_node->kind = entry->kind;
+ below_working_node->repos_id = work->repos_id;
+
+ /* This is just guessing. If the node below would have been switched
+ or if it was updated to a different version, the guess would
+ fail. But we don't have better information pre wc-ng :( */
+ if (work->repos_relpath)
+ below_working_node->repos_relpath
+ = svn_relpath_join(work->repos_relpath, entry->name,
+ result_pool);
+ else
+ below_working_node->repos_relpath = NULL;
+ below_working_node->revision = parent_node->work->revision;
+
+ /* The revert_base checksum isn't available in the entry structure,
+ so the caller provides it. */
+
+ /* text_base_info is NULL for files scheduled to be added. */
+ below_working_node->checksum = NULL;
+ if (text_base_info)
+ {
+ if (entry->schedule == svn_wc_schedule_delete)
+ below_working_node->checksum =
+ text_base_info->normal_base.sha1_checksum;
+ else
+ below_working_node->checksum =
+ text_base_info->revert_base.sha1_checksum;
+ }
+ below_working_node->recorded_size = 0;
+ below_working_node->changed_rev = SVN_INVALID_REVNUM;
+ below_working_node->changed_date = 0;
+ below_working_node->changed_author = NULL;
+ below_working_node->depth = svn_depth_infinity;
+ below_working_node->recorded_time = 0;
+ below_working_node->properties = NULL;
+
+ if (working_node
+ && entry->schedule == svn_wc_schedule_delete
+ && working_node->repos_relpath)
+ {
+ /* We are lucky, our guesses above are not necessary. The known
+ correct information is in working. But our op_depth design
+ expects more information here */
+ below_working_node->repos_relpath = working_node->repos_relpath;
+ below_working_node->repos_id = working_node->repos_id;
+ below_working_node->revision = working_node->revision;
+
+ /* Nice for 'svn status' */
+ below_working_node->changed_rev = entry->cmt_rev;
+ below_working_node->changed_date = entry->cmt_date;
+ below_working_node->changed_author = entry->cmt_author;
+
+ /* And now remove it from WORKING, because in wc-ng code
+ should read it from the lower layer */
+ working_node->repos_relpath = NULL;
+ working_node->repos_id = 0;
+ working_node->revision = SVN_INVALID_REVNUM;
+ }
+
+ SVN_ERR(insert_node(sdb, below_working_node, scratch_pool));
+ }
+
+ /* Insert the working node. */
+ if (working_node)
+ {
+ working_node->wc_id = wc_id;
+ working_node->local_relpath = local_relpath;
+ working_node->parent_relpath = parent_relpath;
+ working_node->changed_rev = SVN_INVALID_REVNUM;
+ working_node->recorded_time = entry->text_time;
+ working_node->recorded_size = entry->working_size;
+
+ if (entry->depth != svn_depth_exclude)
+ working_node->depth = entry->depth;
+ else
+ {
+ working_node->presence = svn_wc__db_status_excluded;
+ working_node->depth = svn_depth_infinity;
+ }
+
+ if (entry->kind == svn_node_dir)
+ working_node->checksum = NULL;
+ else
+ {
+ working_node->checksum = NULL;
+ /* text_base_info is NULL for files scheduled to be added. */
+ if (text_base_info)
+ working_node->checksum = text_base_info->normal_base.sha1_checksum;
+
+
+ /* If an MD5 checksum is present in the entry, we can verify that
+ * it matches the MD5 of the base file we found earlier. */
+#ifdef SVN_DEBUG
+ if (entry->checksum && text_base_info)
+ {
+ svn_checksum_t *md5_checksum;
+ SVN_ERR(svn_checksum_parse_hex(&md5_checksum, svn_checksum_md5,
+ entry->checksum, result_pool));
+ SVN_ERR_ASSERT(
+ md5_checksum && text_base_info->normal_base.md5_checksum);
+ SVN_ERR_ASSERT(svn_checksum_match(
+ md5_checksum, text_base_info->normal_base.md5_checksum));
+ }
+#endif
+ }
+
+ working_node->kind = entry->kind;
+ if (working_node->presence != svn_wc__db_status_excluded)
+ {
+ /* All subdirs start of incomplete, and stop being incomplete
+ when the entries file in the subdir is upgraded. */
+ if (entry->kind == svn_node_dir
+ && strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR))
+ {
+ working_node->presence = svn_wc__db_status_incomplete;
+ working_node->kind = svn_node_dir;
+ }
+ else if (entry->schedule == svn_wc_schedule_delete)
+ {
+ working_node->presence = svn_wc__db_status_base_deleted;
+ working_node->kind = entry->kind;
+ }
+ else
+ {
+ /* presence == normal */
+ working_node->kind = entry->kind;
+
+ if (entry->incomplete)
+ {
+ /* We shouldn't be overwriting another status. */
+ WRITE_ENTRY_ASSERT(working_node->presence
+ == svn_wc__db_status_normal);
+ working_node->presence = svn_wc__db_status_incomplete;
+ }
+ }
+ }
+
+ /* These should generally be unset for added and deleted files,
+ and contain whatever information we have for copied files. Let's
+ just store whatever we have.
+
+ Note: cmt_rev is the distinguishing value. The others may be 0 or
+ NULL if the corresponding revprop has been deleted. */
+ if (working_node->presence != svn_wc__db_status_base_deleted)
+ {
+ working_node->changed_rev = entry->cmt_rev;
+ working_node->changed_date = entry->cmt_date;
+ working_node->changed_author = entry->cmt_author;
+ }
+
+ if (entry->schedule == svn_wc_schedule_delete
+ && parent_node->work
+ && parent_node->work->presence == svn_wc__db_status_base_deleted)
+ {
+ working_node->op_depth = parent_node->work->op_depth;
+ }
+ else if (!entry->copied)
+ {
+ working_node->op_depth
+ = svn_wc__db_op_depth_for_upgrade(local_relpath);
+ }
+
+ SVN_ERR(insert_node(sdb, working_node, scratch_pool));
+ }
+
+ /* Insert the actual node. */
+ if (actual_node)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+
+ actual_node->wc_id = wc_id;
+ actual_node->local_relpath = local_relpath;
+ actual_node->parent_relpath = parent_relpath;
+
+ SVN_ERR(insert_actual_node(sdb, db, tmp_entry_abspath,
+ actual_node, scratch_pool));
+ }
+
+ if (entry_node)
+ {
+ *entry_node = apr_palloc(result_pool, sizeof(**entry_node));
+ (*entry_node)->base = base_node;
+ (*entry_node)->work = working_node;
+ (*entry_node)->below_work = below_working_node;
+ (*entry_node)->tree_conflicts = tree_conflicts;
+ }
+
+ if (entry->file_external_path)
+ {
+ /* TODO: Maybe add a file external registration inside EXTERNALS here,
+ to allow removing file externals that aren't referenced from
+ svn:externals.
+
+ The svn:externals values are processed anyway after everything is
+ upgraded */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_actual_only_entries(apr_hash_t *tree_conflicts,
+ svn_sqlite__db_t *sdb,
+ svn_wc__db_t *db,
+ const char *wri_abspath,
+ apr_int64_t wc_id,
+ const char *parent_relpath,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, tree_conflicts);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ db_actual_node_t *actual_node = NULL;
+
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ actual_node->wc_id = wc_id;
+ actual_node->local_relpath = svn__apr_hash_index_key(hi);
+ actual_node->parent_relpath = parent_relpath;
+ actual_node->tree_conflict_data = svn__apr_hash_index_val(hi);
+
+ SVN_ERR(insert_actual_node(sdb, db, wri_abspath, actual_node,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__write_upgraded_entries(void **dir_baton,
+ void *parent_baton,
+ svn_wc__db_t *db,
+ svn_sqlite__db_t *sdb,
+ apr_int64_t repos_id,
+ apr_int64_t wc_id,
+ const char *dir_abspath,
+ const char *new_root_abspath,
+ apr_hash_t *entries,
+ apr_hash_t *text_bases_info,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const svn_wc_entry_t *this_dir;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ const char *old_root_abspath, *dir_relpath;
+ struct write_baton *parent_node = parent_baton;
+ struct write_baton *dir_node;
+
+ /* Get a copy of the "this dir" entry for comparison purposes. */
+ this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
+
+ /* If there is no "this dir" entry, something is wrong. */
+ if (! this_dir)
+ return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("No default entry in directory '%s'"),
+ svn_dirent_local_style(dir_abspath,
+ iterpool));
+ old_root_abspath = svn_dirent_get_longest_ancestor(dir_abspath,
+ new_root_abspath,
+ scratch_pool);
+
+ SVN_ERR_ASSERT(old_root_abspath[0]);
+
+ dir_relpath = svn_dirent_skip_ancestor(old_root_abspath, dir_abspath);
+
+ /* Write out "this dir" */
+ SVN_ERR(write_entry(&dir_node, parent_node, db, sdb,
+ wc_id, repos_id, this_dir, NULL, dir_relpath,
+ svn_dirent_join(new_root_abspath, dir_relpath,
+ iterpool),
+ old_root_abspath,
+ this_dir, FALSE, result_pool, iterpool));
+
+ for (hi = apr_hash_first(scratch_pool, entries); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const svn_wc_entry_t *this_entry = svn__apr_hash_index_val(hi);
+ const char *child_abspath, *child_relpath;
+ svn_wc__text_base_info_t *text_base_info
+ = svn_hash_gets(text_bases_info, name);
+
+ svn_pool_clear(iterpool);
+
+ /* Don't rewrite the "this dir" entry! */
+ if (strcmp(name, SVN_WC_ENTRY_THIS_DIR) == 0)
+ continue;
+
+ /* Write the entry. Pass TRUE for create locks, because we still
+ use this function for upgrading old working copies. */
+ child_abspath = svn_dirent_join(dir_abspath, name, iterpool);
+ child_relpath = svn_dirent_skip_ancestor(old_root_abspath, child_abspath);
+ SVN_ERR(write_entry(NULL, dir_node, db, sdb,
+ wc_id, repos_id,
+ this_entry, text_base_info, child_relpath,
+ svn_dirent_join(new_root_abspath, child_relpath,
+ iterpool),
+ old_root_abspath,
+ this_dir, TRUE, iterpool, iterpool));
+ }
+
+ if (dir_node->tree_conflicts)
+ SVN_ERR(write_actual_only_entries(dir_node->tree_conflicts, sdb, db,
+ new_root_abspath, wc_id, dir_relpath,
+ iterpool));
+
+ *dir_baton = dir_node;
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_wc_entry_t *
+svn_wc_entry_dup(const svn_wc_entry_t *entry, apr_pool_t *pool)
+{
+ svn_wc_entry_t *dupentry = apr_palloc(pool, sizeof(*dupentry));
+
+ /* Perform a trivial copy ... */
+ *dupentry = *entry;
+
+ /* ...and then re-copy stuff that needs to be duped into our pool. */
+ if (entry->name)
+ dupentry->name = apr_pstrdup(pool, entry->name);
+ if (entry->url)
+ dupentry->url = apr_pstrdup(pool, entry->url);
+ if (entry->repos)
+ dupentry->repos = apr_pstrdup(pool, entry->repos);
+ if (entry->uuid)
+ dupentry->uuid = apr_pstrdup(pool, entry->uuid);
+ if (entry->copyfrom_url)
+ dupentry->copyfrom_url = apr_pstrdup(pool, entry->copyfrom_url);
+ if (entry->conflict_old)
+ dupentry->conflict_old = apr_pstrdup(pool, entry->conflict_old);
+ if (entry->conflict_new)
+ dupentry->conflict_new = apr_pstrdup(pool, entry->conflict_new);
+ if (entry->conflict_wrk)
+ dupentry->conflict_wrk = apr_pstrdup(pool, entry->conflict_wrk);
+ if (entry->prejfile)
+ dupentry->prejfile = apr_pstrdup(pool, entry->prejfile);
+ if (entry->checksum)
+ dupentry->checksum = apr_pstrdup(pool, entry->checksum);
+ if (entry->cmt_author)
+ dupentry->cmt_author = apr_pstrdup(pool, entry->cmt_author);
+ if (entry->lock_token)
+ dupentry->lock_token = apr_pstrdup(pool, entry->lock_token);
+ if (entry->lock_owner)
+ dupentry->lock_owner = apr_pstrdup(pool, entry->lock_owner);
+ if (entry->lock_comment)
+ dupentry->lock_comment = apr_pstrdup(pool, entry->lock_comment);
+ if (entry->changelist)
+ dupentry->changelist = apr_pstrdup(pool, entry->changelist);
+
+ /* NOTE: we do not dup cachable_props or present_props since they
+ are deprecated. Use "" to indicate "nothing cachable or cached". */
+ dupentry->cachable_props = "";
+ dupentry->present_props = "";
+
+ if (entry->tree_conflict_data)
+ dupentry->tree_conflict_data = apr_pstrdup(pool,
+ entry->tree_conflict_data);
+ if (entry->file_external_path)
+ dupentry->file_external_path = apr_pstrdup(pool,
+ entry->file_external_path);
+ return dupentry;
+}
+
+
+/*** Generic Entry Walker */
+
+/* A recursive entry-walker, helper for svn_wc_walk_entries3().
+ *
+ * For this directory (DIRPATH, ADM_ACCESS), call the "found_entry" callback
+ * in WALK_CALLBACKS, passing WALK_BATON to it. Then, for each versioned
+ * entry in this directory, call the "found entry" callback and then recurse
+ * (if it is a directory and if DEPTH allows).
+ *
+ * If SHOW_HIDDEN is true, include entries that are in a 'deleted' or
+ * 'absent' state (and not scheduled for re-addition), else skip them.
+ *
+ * Call CANCEL_FUNC with CANCEL_BATON to allow cancellation.
+ */
+static svn_error_t *
+walker_helper(const char *dirpath,
+ svn_wc_adm_access_t *adm_access,
+ const svn_wc_entry_callbacks2_t *walk_callbacks,
+ void *walk_baton,
+ svn_depth_t depth,
+ svn_boolean_t show_hidden,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ svn_wc_entry_t *dot_entry;
+ svn_error_t *err;
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+
+ err = svn_wc__entries_read_internal(&entries, adm_access, show_hidden,
+ pool);
+
+ if (err)
+ SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool));
+
+ /* As promised, always return the '.' entry first. */
+ dot_entry = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR);
+ if (! dot_entry)
+ return walk_callbacks->handle_error
+ (dirpath, svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("Directory '%s' has no THIS_DIR entry"),
+ svn_dirent_local_style(dirpath, pool)),
+ walk_baton, pool);
+
+ /* Call the "found entry" callback for this directory as a "this dir"
+ * entry. Note that if this directory has been reached by recursion, this
+ * is the second visit as it will already have been visited once as a
+ * child entry of its parent. */
+
+ err = walk_callbacks->found_entry(dirpath, dot_entry, walk_baton, subpool);
+
+
+ if(err)
+ SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool));
+
+ if (depth == svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ /* Loop over each of the other entries. */
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ const svn_wc_entry_t *current_entry = svn__apr_hash_index_val(hi);
+ const char *entrypath;
+ const char *entry_abspath;
+ svn_boolean_t hidden;
+
+ svn_pool_clear(subpool);
+
+ /* See if someone wants to cancel this operation. */
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
+
+ /* Skip the "this dir" entry. */
+ if (strcmp(current_entry->name, SVN_WC_ENTRY_THIS_DIR) == 0)
+ continue;
+
+ entrypath = svn_dirent_join(dirpath, name, subpool);
+ SVN_ERR(svn_wc__entry_is_hidden(&hidden, current_entry));
+ SVN_ERR(svn_dirent_get_absolute(&entry_abspath, entrypath, subpool));
+
+ /* Call the "found entry" callback for this entry. (For a directory,
+ * this is the first visit: as a child.) */
+ if (current_entry->kind == svn_node_file
+ || depth >= svn_depth_immediates)
+ {
+ err = walk_callbacks->found_entry(entrypath, current_entry,
+ walk_baton, subpool);
+
+ if (err)
+ SVN_ERR(walk_callbacks->handle_error(entrypath, err,
+ walk_baton, pool));
+ }
+
+ /* Recurse into this entry if appropriate. */
+ if (current_entry->kind == svn_node_dir
+ && !hidden
+ && depth >= svn_depth_immediates)
+ {
+ svn_wc_adm_access_t *entry_access;
+ svn_depth_t depth_below_here = depth;
+
+ if (depth == svn_depth_immediates)
+ depth_below_here = svn_depth_empty;
+
+ entry_access = svn_wc__adm_retrieve_internal2(db, entry_abspath,
+ subpool);
+
+ if (entry_access)
+ SVN_ERR(walker_helper(entrypath, entry_access,
+ walk_callbacks, walk_baton,
+ depth_below_here, show_hidden,
+ cancel_func, cancel_baton,
+ subpool));
+ }
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__walker_default_error_handler(const char *path,
+ svn_error_t *err,
+ void *walk_baton,
+ apr_pool_t *pool)
+{
+ /* Note: don't trace this. We don't want to insert a false "stack frame"
+ onto an error generated elsewhere. */
+ return svn_error_trace(err);
+}
+
+
+/* The public API. */
+svn_error_t *
+svn_wc_walk_entries3(const char *path,
+ svn_wc_adm_access_t *adm_access,
+ const svn_wc_entry_callbacks2_t *walk_callbacks,
+ void *walk_baton,
+ svn_depth_t walk_depth,
+ svn_boolean_t show_hidden,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ const char *local_abspath;
+ svn_wc__db_t *db = svn_wc__adm_get_db(adm_access);
+ svn_error_t *err;
+ svn_node_kind_t kind;
+ svn_depth_t depth;
+
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ err = svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, &depth, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL,
+ db, local_abspath,
+ pool, pool);
+ if (err)
+ {
+ if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+ return svn_error_trace(err);
+ /* Remap into SVN_ERR_UNVERSIONED_RESOURCE. */
+ svn_error_clear(err);
+ return walk_callbacks->handle_error(
+ path, svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath, pool)),
+ walk_baton, pool);
+ }
+
+ if (kind == svn_node_file || depth == svn_depth_exclude)
+ {
+ const svn_wc_entry_t *entry;
+
+ /* ### we should stop passing out entry structures.
+ ###
+ ### we should not call handle_error for an error the *callback*
+ ### gave us. let it deal with the problem before returning. */
+
+ if (!show_hidden)
+ {
+ svn_boolean_t hidden;
+ SVN_ERR(svn_wc__db_node_hidden(&hidden, db, local_abspath, pool));
+
+ if (hidden)
+ {
+ /* The fool asked to walk a "hidden" node. Report the node as
+ unversioned.
+
+ ### this is incorrect behavior. see depth_test 36. the walk
+ ### API will be revamped to avoid entry structures. we should
+ ### be able to solve the problem with the new API. (since we
+ ### shouldn't return a hidden entry here) */
+ return walk_callbacks->handle_error(
+ path, svn_error_createf(
+ SVN_ERR_UNVERSIONED_RESOURCE, NULL,
+ _("'%s' is not under version control"),
+ svn_dirent_local_style(local_abspath, pool)),
+ walk_baton, pool);
+ }
+ }
+
+ SVN_ERR(svn_wc__get_entry(&entry, db, local_abspath, FALSE,
+ svn_node_file, pool, pool));
+
+ err = walk_callbacks->found_entry(path, entry, walk_baton, pool);
+ if (err)
+ return walk_callbacks->handle_error(path, err, walk_baton, pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ if (kind == svn_node_dir)
+ return walker_helper(path, adm_access, walk_callbacks, walk_baton,
+ walk_depth, show_hidden, cancel_func, cancel_baton,
+ pool);
+
+ return walk_callbacks->handle_error(
+ path, svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
+ _("'%s' has an unrecognized node kind"),
+ svn_dirent_local_style(local_abspath, pool)),
+ walk_baton, pool);
+}
OpenPOWER on IntegriCloud