diff options
Diffstat (limited to 'subversion/libsvn_wc/entries.c')
-rw-r--r-- | subversion/libsvn_wc/entries.c | 2738 |
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); +} |