diff options
author | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
commit | d25dac7fcc6acc838b71bbda8916fd9665c709ab (patch) | |
tree | 135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_wc/wc_db.c | |
download | FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz |
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_wc/wc_db.c')
-rw-r--r-- | subversion/libsvn_wc/wc_db.c | 15050 |
1 files changed, 15050 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/wc_db.c b/subversion/libsvn_wc/wc_db.c new file mode 100644 index 0000000..7e1e877 --- /dev/null +++ b/subversion/libsvn_wc/wc_db.c @@ -0,0 +1,15050 @@ +/* + * wc_db.c : manipulating the administrative database + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +#define SVN_WC__I_AM_WC_DB + +#include <assert.h> +#include <apr_pools.h> +#include <apr_hash.h> + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" +#include "svn_sorts.h" +#include "svn_wc.h" +#include "svn_checksum.h" +#include "svn_pools.h" + +#include "wc.h" +#include "wc_db.h" +#include "adm_files.h" +#include "wc-queries.h" +#include "entries.h" +#include "lock.h" +#include "conflicts.h" +#include "wc_db_private.h" +#include "workqueue.h" +#include "token-map.h" + +#include "svn_private_config.h" +#include "private/svn_sqlite.h" +#include "private/svn_skel.h" +#include "private/svn_wc_private.h" +#include "private/svn_token.h" + + +#define NOT_IMPLEMENTED() SVN__NOT_IMPLEMENTED() + + +/* + * Some filename constants. + */ +#define SDB_FILE "wc.db" + +#define WCROOT_TEMPDIR_RELPATH "tmp" + + +/* + * PARAMETER ASSERTIONS + * + * Every (semi-)public entrypoint in this file has a set of assertions on + * the parameters passed into the function. Since this is a brand new API, + * we want to make sure that everybody calls it properly. The original WC + * code had years to catch stray bugs, but we do not have that luxury in + * the wc-nb rewrite. Any extra assurances that we can find will be + * welcome. The asserts will ensure we have no doubt about the values + * passed into the function. + * + * Some parameters are *not* specifically asserted. Typically, these are + * params that will be used immediately, so something like a NULL value + * will be obvious. + * + * ### near 1.7 release, it would be a Good Thing to review the assertions + * ### and decide if any can be removed or switched to assert() in order + * ### to remove their runtime cost in the production release. + * + * + * DATABASE OPERATIONS + * + * Each function should leave the database in a consistent state. If it + * does *not*, then the implication is some other function needs to be + * called to restore consistency. Subtle requirements like that are hard + * to maintain over a long period of time, so this API will not allow it. + * + * + * STANDARD VARIABLE NAMES + * + * db working copy database (this module) + * sdb SQLite database (not to be confused with 'db') + * wc_id a WCROOT id associated with a node + */ + +#define INVALID_REPOS_ID ((apr_int64_t) -1) +#define UNKNOWN_WC_ID ((apr_int64_t) -1) +#define FORMAT_FROM_SDB (-1) + +/* Check if column number I, a property-skel column, contains a non-empty + set of properties. The empty set of properties is stored as "()", so we + have properties if the size of the column is larger than 2. */ +#define SQLITE_PROPERTIES_AVAILABLE(stmt, i) \ + (svn_sqlite__column_bytes(stmt, i) > 2) + +int +svn_wc__db_op_depth_for_upgrade(const char *local_relpath) +{ + return relpath_depth(local_relpath); +} + + +/* Representation of a new base row for the NODES table */ +typedef struct insert_base_baton_t { + /* common to all insertions into BASE */ + svn_wc__db_status_t status; + svn_node_kind_t kind; + apr_int64_t repos_id; + const char *repos_relpath; + svn_revnum_t revision; + + /* Only used when repos_id == INVALID_REPOS_ID */ + const char *repos_root_url; + const char *repos_uuid; + + /* common to all "normal" presence insertions */ + const apr_hash_t *props; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + const apr_hash_t *dav_cache; + + /* for inserting directories */ + const apr_array_header_t *children; + svn_depth_t depth; + + /* for inserting files */ + const svn_checksum_t *checksum; + + /* for inserting symlinks */ + const char *target; + + svn_boolean_t file_external; + + /* may need to insert/update ACTUAL to record a conflict */ + const svn_skel_t *conflict; + + /* may need to insert/update ACTUAL to record new properties */ + svn_boolean_t update_actual_props; + const apr_hash_t *new_actual_props; + + /* A depth-first ordered array of svn_prop_inherited_item_t * + structures representing the properties inherited by the base + node. */ + apr_array_header_t *iprops; + + /* maybe we should copy information from a previous record? */ + svn_boolean_t keep_recorded_info; + + /* insert a base-deleted working node as well as a base node */ + svn_boolean_t insert_base_deleted; + + /* delete the current working nodes above BASE */ + svn_boolean_t delete_working; + + /* may have work items to queue in this transaction */ + const svn_skel_t *work_items; + +} insert_base_baton_t; + + +/* Representation of a new working row for the NODES table */ +typedef struct insert_working_baton_t { + /* common to all insertions into WORKING (including NODE_DATA) */ + svn_wc__db_status_t presence; + svn_node_kind_t kind; + int op_depth; + + /* common to all "normal" presence insertions */ + const apr_hash_t *props; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + apr_int64_t original_repos_id; + const char *original_repos_relpath; + svn_revnum_t original_revnum; + svn_boolean_t moved_here; + + /* for inserting directories */ + const apr_array_header_t *children; + svn_depth_t depth; + + /* for inserting (copied/moved-here) files */ + const svn_checksum_t *checksum; + + /* for inserting symlinks */ + const char *target; + + svn_boolean_t update_actual_props; + const apr_hash_t *new_actual_props; + + /* may have work items to queue in this transaction */ + const svn_skel_t *work_items; + + /* may have conflict to install in this transaction */ + const svn_skel_t *conflict; + + /* If the value is > 0 and < op_depth, also insert a not-present + at op-depth NOT_PRESENT_OP_DEPTH, based on this same information */ + int not_present_op_depth; + +} insert_working_baton_t; + +/* Representation of a new row for the EXTERNALS table */ +typedef struct insert_external_baton_t { + /* common to all insertions into EXTERNALS */ + svn_node_kind_t kind; + svn_wc__db_status_t presence; + + /* The repository of the external */ + apr_int64_t repos_id; + /* for file and symlink externals */ + const char *repos_relpath; + svn_revnum_t revision; + + /* Only used when repos_id == INVALID_REPOS_ID */ + const char *repos_root_url; + const char *repos_uuid; + + /* for file and symlink externals */ + const apr_hash_t *props; + apr_array_header_t *iprops; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + const apr_hash_t *dav_cache; + + /* for inserting files */ + const svn_checksum_t *checksum; + + /* for inserting symlinks */ + const char *target; + + const char *record_ancestor_relpath; + const char *recorded_repos_relpath; + svn_revnum_t recorded_peg_revision; + svn_revnum_t recorded_revision; + + /* may need to insert/update ACTUAL to record a conflict */ + const svn_skel_t *conflict; + + /* may need to insert/update ACTUAL to record new properties */ + svn_boolean_t update_actual_props; + const apr_hash_t *new_actual_props; + + /* maybe we should copy information from a previous record? */ + svn_boolean_t keep_recorded_info; + + /* may have work items to queue in this transaction */ + const svn_skel_t *work_items; + +} insert_external_baton_t; + + +/* Forward declarations */ +static svn_error_t * +add_work_items(svn_sqlite__db_t *sdb, + const svn_skel_t *skel, + apr_pool_t *scratch_pool); + +static svn_error_t * +set_actual_props(apr_int64_t wc_id, + const char *local_relpath, + apr_hash_t *props, + svn_sqlite__db_t *db, + apr_pool_t *scratch_pool); + +static svn_error_t * +insert_incomplete_children(svn_sqlite__db_t *sdb, + apr_int64_t wc_id, + const char *local_relpath, + apr_int64_t repos_id, + const char *repos_relpath, + svn_revnum_t revision, + const apr_array_header_t *children, + int op_depth, + apr_pool_t *scratch_pool); + +static svn_error_t * +db_read_pristine_props(apr_hash_t **props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_boolean_t deleted_ok, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +static svn_error_t * +read_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + const char **original_repos_relpath, + apr_int64_t *original_repos_id, + svn_revnum_t *original_revision, + svn_wc__db_lock_t **lock, + svn_filesize_t *recorded_size, + apr_time_t *recorded_time, + const char **changelist, + svn_boolean_t *conflicted, + svn_boolean_t *op_root, + svn_boolean_t *had_props, + svn_boolean_t *props_mod, + svn_boolean_t *have_base, + svn_boolean_t *have_more_work, + svn_boolean_t *have_work, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +static svn_error_t * +scan_addition(svn_wc__db_status_t *status, + const char **op_root_relpath, + const char **repos_relpath, + apr_int64_t *repos_id, + const char **original_repos_relpath, + apr_int64_t *original_repos_id, + svn_revnum_t *original_revision, + const char **moved_from_relpath, + const char **moved_from_op_root_relpath, + int *moved_from_op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +static svn_error_t * +convert_to_working_status(svn_wc__db_status_t *working_status, + svn_wc__db_status_t status); + +static svn_error_t * +wclock_owns_lock(svn_boolean_t *own_lock, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_boolean_t exact, + apr_pool_t *scratch_pool); + +static svn_error_t * +db_is_switched(svn_boolean_t *is_switched, + svn_node_kind_t *kind, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool); + + +/* Return the absolute path, in local path style, of LOCAL_RELPATH + in WCROOT. */ +static const char * +path_for_error_message(const svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool) +{ + const char *local_abspath + = svn_dirent_join(wcroot->abspath, local_relpath, result_pool); + + return svn_dirent_local_style(local_abspath, result_pool); +} + + +/* Return a file size from column SLOT of the SQLITE statement STMT, or + SVN_INVALID_FILESIZE if the column value is NULL. */ +static svn_filesize_t +get_recorded_size(svn_sqlite__stmt_t *stmt, int slot) +{ + if (svn_sqlite__column_is_null(stmt, slot)) + return SVN_INVALID_FILESIZE; + return svn_sqlite__column_int64(stmt, slot); +} + + +/* Return a lock info structure constructed from the given columns of the + SQLITE statement STMT, or return NULL if the token column value is null. */ +static svn_wc__db_lock_t * +lock_from_columns(svn_sqlite__stmt_t *stmt, + int col_token, + int col_owner, + int col_comment, + int col_date, + apr_pool_t *result_pool) +{ + svn_wc__db_lock_t *lock; + + if (svn_sqlite__column_is_null(stmt, col_token)) + { + lock = NULL; + } + else + { + lock = apr_pcalloc(result_pool, sizeof(svn_wc__db_lock_t)); + lock->token = svn_sqlite__column_text(stmt, col_token, result_pool); + lock->owner = svn_sqlite__column_text(stmt, col_owner, result_pool); + lock->comment = svn_sqlite__column_text(stmt, col_comment, result_pool); + lock->date = svn_sqlite__column_int64(stmt, col_date); + } + return lock; +} + + +svn_error_t * +svn_wc__db_fetch_repos_info(const char **repos_root_url, + const char **repos_uuid, + svn_sqlite__db_t *sdb, + apr_int64_t repos_id, + apr_pool_t *result_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + if (!repos_root_url && !repos_uuid) + return SVN_NO_ERROR; + + if (repos_id == INVALID_REPOS_ID) + { + if (repos_root_url) + *repos_root_url = NULL; + if (repos_uuid) + *repos_uuid = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_SELECT_REPOSITORY_BY_ID)); + SVN_ERR(svn_sqlite__bindf(stmt, "i", repos_id)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + return svn_error_createf(SVN_ERR_WC_CORRUPT, svn_sqlite__reset(stmt), + _("No REPOSITORY table entry for id '%ld'"), + (long int)repos_id); + + if (repos_root_url) + *repos_root_url = svn_sqlite__column_text(stmt, 0, result_pool); + if (repos_uuid) + *repos_uuid = svn_sqlite__column_text(stmt, 1, result_pool); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +/* Set *REPOS_ID, *REVISION and *REPOS_RELPATH from the given columns of the + SQLITE statement STMT, or to NULL/SVN_INVALID_REVNUM if the respective + column value is null. Any of the output parameters may be NULL if not + required. */ +static void +repos_location_from_columns(apr_int64_t *repos_id, + svn_revnum_t *revision, + const char **repos_relpath, + svn_sqlite__stmt_t *stmt, + int col_repos_id, + int col_revision, + int col_repos_relpath, + apr_pool_t *result_pool) +{ + if (repos_id) + { + /* Fetch repository information via REPOS_ID. */ + if (svn_sqlite__column_is_null(stmt, col_repos_id)) + *repos_id = INVALID_REPOS_ID; + else + *repos_id = svn_sqlite__column_int64(stmt, col_repos_id); + } + if (revision) + { + *revision = svn_sqlite__column_revnum(stmt, col_revision); + } + if (repos_relpath) + { + *repos_relpath = svn_sqlite__column_text(stmt, col_repos_relpath, + result_pool); + } +} + + +/* Get the statement given by STMT_IDX, and bind the appropriate wc_id and + local_relpath based upon LOCAL_ABSPATH. Store it in *STMT, and use + SCRATCH_POOL for temporary allocations. + + Note: WC_ID and LOCAL_RELPATH must be arguments 1 and 2 in the statement. */ +static svn_error_t * +get_statement_for_path(svn_sqlite__stmt_t **stmt, + svn_wc__db_t *db, + const char *local_abspath, + int stmt_idx, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(stmt, wcroot->sdb, stmt_idx)); + SVN_ERR(svn_sqlite__bindf(*stmt, "is", wcroot->wc_id, local_relpath)); + + return SVN_NO_ERROR; +} + + +/* For a given REPOS_ROOT_URL/REPOS_UUID pair, return the existing REPOS_ID + value. If one does not exist, then create a new one. */ +static svn_error_t * +create_repos_id(apr_int64_t *repos_id, + const char *repos_root_url, + const char *repos_uuid, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *get_stmt; + svn_sqlite__stmt_t *insert_stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&get_stmt, sdb, STMT_SELECT_REPOSITORY)); + SVN_ERR(svn_sqlite__bindf(get_stmt, "s", repos_root_url)); + SVN_ERR(svn_sqlite__step(&have_row, get_stmt)); + + if (have_row) + { + *repos_id = svn_sqlite__column_int64(get_stmt, 0); + return svn_error_trace(svn_sqlite__reset(get_stmt)); + } + SVN_ERR(svn_sqlite__reset(get_stmt)); + + /* NOTE: strictly speaking, there is a race condition between the + above query and the insertion below. We're simply going to ignore + that, as it means two processes are *modifying* the working copy + at the same time, *and* new repositores are becoming visible. + This is rare enough, let alone the miniscule chance of hitting + this race condition. Further, simply failing out will leave the + database in a consistent state, and the user can just re-run the + failed operation. */ + + SVN_ERR(svn_sqlite__get_statement(&insert_stmt, sdb, + STMT_INSERT_REPOSITORY)); + SVN_ERR(svn_sqlite__bindf(insert_stmt, "ss", repos_root_url, repos_uuid)); + return svn_error_trace(svn_sqlite__insert(repos_id, insert_stmt)); +} + + +/* Initialize the baton with appropriate "blank" values. This allows the + insertion function to leave certain columns null. */ +static void +blank_ibb(insert_base_baton_t *pibb) +{ + memset(pibb, 0, sizeof(*pibb)); + pibb->revision = SVN_INVALID_REVNUM; + pibb->changed_rev = SVN_INVALID_REVNUM; + pibb->depth = svn_depth_infinity; + pibb->repos_id = INVALID_REPOS_ID; +} + + +svn_error_t * +svn_wc__db_extend_parent_delete(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_node_kind_t kind, + int op_depth, + apr_pool_t *scratch_pool) +{ + svn_boolean_t have_row; + svn_sqlite__stmt_t *stmt; + int parent_op_depth; + const char *parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool); + + SVN_ERR_ASSERT(local_relpath[0]); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_LOWEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, parent_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + parent_op_depth = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + { + int existing_op_depth; + + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + existing_op_depth = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + if (!have_row || parent_op_depth < existing_op_depth) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSTALL_WORKING_NODE_FOR_DELETE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdst", wcroot->wc_id, + local_relpath, parent_op_depth, + parent_relpath, kind_map, kind)); + SVN_ERR(svn_sqlite__update(NULL, stmt)); + } + } + + return SVN_NO_ERROR; +} + + +/* This is the reverse of svn_wc__db_extend_parent_delete. + + When removing a node if the parent has a higher working node then + the parent node and this node are both deleted or replaced and any + delete over this node must be removed. + */ +svn_error_t * +svn_wc__db_retract_parent_delete(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_LOWEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + + + +/* Insert the base row represented by (insert_base_baton_t *) BATON. */ +static svn_error_t * +insert_base_node(const insert_base_baton_t *pibb, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + apr_int64_t repos_id = pibb->repos_id; + svn_sqlite__stmt_t *stmt; + svn_filesize_t recorded_size = SVN_INVALID_FILESIZE; + apr_int64_t recorded_time; + + /* The directory at the WCROOT has a NULL parent_relpath. Otherwise, + bind the appropriate parent_relpath. */ + const char *parent_relpath = + (*local_relpath == '\0') ? NULL + : svn_relpath_dirname(local_relpath, scratch_pool); + + if (pibb->repos_id == INVALID_REPOS_ID) + SVN_ERR(create_repos_id(&repos_id, pibb->repos_root_url, pibb->repos_uuid, + wcroot->sdb, scratch_pool)); + + SVN_ERR_ASSERT(repos_id != INVALID_REPOS_ID); + SVN_ERR_ASSERT(pibb->repos_relpath != NULL); + + if (pibb->keep_recorded_info) + { + svn_boolean_t have_row; + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_BASE_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + /* Preserve size and modification time if caller asked us to. */ + recorded_size = get_recorded_size(stmt, 6); + recorded_time = svn_sqlite__column_int64(stmt, 12); + } + SVN_ERR(svn_sqlite__reset(stmt)); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdsisr" + "tstr" /* 8 - 11 */ + "isnnnnns", /* 12 - 19 */ + wcroot->wc_id, /* 1 */ + local_relpath, /* 2 */ + 0, /* op_depth is 0 for base */ + parent_relpath, /* 4 */ + repos_id, + pibb->repos_relpath, + pibb->revision, + presence_map, pibb->status, /* 8 */ + (pibb->kind == svn_node_dir) ? /* 9 */ + svn_token__to_word(depth_map, pibb->depth) : NULL, + kind_map, pibb->kind, /* 10 */ + pibb->changed_rev, /* 11 */ + pibb->changed_date, /* 12 */ + pibb->changed_author, /* 13 */ + (pibb->kind == svn_node_symlink) ? + pibb->target : NULL)); /* 19 */ + if (pibb->kind == svn_node_file) + { + if (!pibb->checksum + && pibb->status != svn_wc__db_status_not_present + && pibb->status != svn_wc__db_status_excluded + && pibb->status != svn_wc__db_status_server_excluded) + return svn_error_createf(SVN_ERR_WC_CORRUPT, svn_sqlite__reset(stmt), + _("The file '%s' has no checksum."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, pibb->checksum, + scratch_pool)); + + if (recorded_size != SVN_INVALID_FILESIZE) + { + SVN_ERR(svn_sqlite__bind_int64(stmt, 16, recorded_size)); + SVN_ERR(svn_sqlite__bind_int64(stmt, 17, recorded_time)); + } + } + + /* Set properties. Must be null if presence not normal or incomplete. */ + assert(pibb->status == svn_wc__db_status_normal + || pibb->status == svn_wc__db_status_incomplete + || pibb->props == NULL); + SVN_ERR(svn_sqlite__bind_properties(stmt, 15, pibb->props, + scratch_pool)); + + SVN_ERR(svn_sqlite__bind_iprops(stmt, 23, pibb->iprops, + scratch_pool)); + + if (pibb->dav_cache) + SVN_ERR(svn_sqlite__bind_properties(stmt, 18, pibb->dav_cache, + scratch_pool)); + + if (pibb->file_external) + SVN_ERR(svn_sqlite__bind_int(stmt, 20, 1)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + + if (pibb->update_actual_props) + { + /* Cast away const, to allow calling property helpers */ + apr_hash_t *base_props = (apr_hash_t *)pibb->props; + apr_hash_t *new_actual_props = (apr_hash_t *)pibb->new_actual_props; + + if (base_props != NULL + && new_actual_props != NULL + && (apr_hash_count(base_props) == apr_hash_count(new_actual_props))) + { + apr_array_header_t *diffs; + + SVN_ERR(svn_prop_diffs(&diffs, new_actual_props, base_props, + scratch_pool)); + + if (diffs->nelts == 0) + new_actual_props = NULL; + } + + SVN_ERR(set_actual_props(wcroot->wc_id, local_relpath, new_actual_props, + wcroot->sdb, scratch_pool)); + } + + if (pibb->kind == svn_node_dir && pibb->children) + SVN_ERR(insert_incomplete_children(wcroot->sdb, wcroot->wc_id, + local_relpath, + repos_id, + pibb->repos_relpath, + pibb->revision, + pibb->children, + 0 /* BASE */, + scratch_pool)); + + /* When this is not the root node, check shadowing behavior */ + if (*local_relpath) + { + if (parent_relpath + && ((pibb->status == svn_wc__db_status_normal) + || (pibb->status == svn_wc__db_status_incomplete)) + && ! pibb->file_external) + { + SVN_ERR(svn_wc__db_extend_parent_delete(wcroot, local_relpath, + pibb->kind, 0, + scratch_pool)); + } + else if (pibb->status == svn_wc__db_status_not_present + || pibb->status == svn_wc__db_status_server_excluded + || pibb->status == svn_wc__db_status_excluded) + { + SVN_ERR(svn_wc__db_retract_parent_delete(wcroot, local_relpath, 0, + scratch_pool)); + } + } + + if (pibb->delete_working) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + if (pibb->insert_base_deleted) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_DELETE_FROM_BASE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wcroot->wc_id, local_relpath, + relpath_depth(local_relpath))); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + SVN_ERR(add_work_items(wcroot->sdb, pibb->work_items, scratch_pool)); + if (pibb->conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + pibb->conflict, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* Initialize the baton with appropriate "blank" values. This allows the + insertion function to leave certain columns null. */ +static void +blank_iwb(insert_working_baton_t *piwb) +{ + memset(piwb, 0, sizeof(*piwb)); + piwb->changed_rev = SVN_INVALID_REVNUM; + piwb->depth = svn_depth_infinity; + + /* ORIGINAL_REPOS_ID and ORIGINAL_REVNUM could use some kind of "nil" + value, but... meh. We'll avoid them if ORIGINAL_REPOS_RELPATH==NULL. */ +} + + +/* Insert a row in NODES for each (const char *) child name in CHILDREN, + whose parent directory is LOCAL_RELPATH, at op_depth=OP_DEPTH. Set each + child's presence to 'incomplete', kind to 'unknown', repos_id to REPOS_ID, + repos_path by appending the child name to REPOS_PATH, and revision to + REVISION (which should match the parent's revision). + + If REPOS_ID is INVALID_REPOS_ID, set each child's repos_id to null. */ +static svn_error_t * +insert_incomplete_children(svn_sqlite__db_t *sdb, + apr_int64_t wc_id, + const char *local_relpath, + apr_int64_t repos_id, + const char *repos_path, + svn_revnum_t revision, + const apr_array_header_t *children, + int op_depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int i; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_t *moved_to_relpaths = apr_hash_make(scratch_pool); + + SVN_ERR_ASSERT(repos_path != NULL || op_depth > 0); + SVN_ERR_ASSERT((repos_id != INVALID_REPOS_ID) + == (repos_path != NULL)); + + /* If we're inserting WORKING nodes, we might be replacing existing + * nodes which were moved-away. We need to retain the moved-to relpath of + * such nodes in order not to lose move information during replace. */ + if (op_depth > 0) + { + for (i = children->nelts; i--; ) + { + const char *name = APR_ARRAY_IDX(children, i, const char *); + svn_boolean_t have_row; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, + svn_relpath_join(local_relpath, name, + iterpool))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row && !svn_sqlite__column_is_null(stmt, 14)) + svn_hash_sets(moved_to_relpaths, name, + svn_sqlite__column_text(stmt, 14, scratch_pool)); + + SVN_ERR(svn_sqlite__reset(stmt)); + } + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE)); + + for (i = children->nelts; i--; ) + { + const char *name = APR_ARRAY_IDX(children, i, const char *); + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnrsnsnnnnnnnnnnsn", + wc_id, + svn_relpath_join(local_relpath, name, + iterpool), + op_depth, + local_relpath, + revision, + "incomplete", /* 8, presence */ + "unknown", /* 10, kind */ + /* 21, moved_to */ + svn_hash_gets(moved_to_relpaths, name))); + if (repos_id != INVALID_REPOS_ID) + { + SVN_ERR(svn_sqlite__bind_int64(stmt, 5, repos_id)); + SVN_ERR(svn_sqlite__bind_text(stmt, 6, + svn_relpath_join(repos_path, name, + iterpool))); + } + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Insert the working row represented by (insert_working_baton_t *) BATON. */ +static svn_error_t * +insert_working_node(const insert_working_baton_t *piwb, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + const char *parent_relpath; + const char *moved_to_relpath = NULL; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(piwb->op_depth > 0); + + /* We cannot insert a WORKING_NODE row at the wcroot. */ + SVN_ERR_ASSERT(*local_relpath != '\0'); + parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool); + + /* Preserve existing moved-to information for this relpath, + * which might exist in case we're replacing an existing base-deleted + * node. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_MOVED_TO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + piwb->op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + moved_to_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnntstrisn" + "nnnn" /* properties translated_size last_mod_time dav_cache */ + "sns", /* symlink_target, file_external, moved_to */ + wcroot->wc_id, local_relpath, + piwb->op_depth, + parent_relpath, + presence_map, piwb->presence, + (piwb->kind == svn_node_dir) + ? svn_token__to_word(depth_map, piwb->depth) : NULL, + kind_map, piwb->kind, + piwb->changed_rev, + piwb->changed_date, + piwb->changed_author, + /* Note: incomplete nodes may have a NULL target. */ + (piwb->kind == svn_node_symlink) + ? piwb->target : NULL, + moved_to_relpath)); + + if (piwb->moved_here) + { + SVN_ERR(svn_sqlite__bind_int(stmt, 8, TRUE)); + } + + if (piwb->kind == svn_node_file) + { + SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, piwb->checksum, + scratch_pool)); + } + + if (piwb->original_repos_relpath != NULL) + { + SVN_ERR(svn_sqlite__bind_int64(stmt, 5, piwb->original_repos_id)); + SVN_ERR(svn_sqlite__bind_text(stmt, 6, piwb->original_repos_relpath)); + SVN_ERR(svn_sqlite__bind_revnum(stmt, 7, piwb->original_revnum)); + } + + /* Set properties. Must be null if presence not normal or incomplete. */ + assert(piwb->presence == svn_wc__db_status_normal + || piwb->presence == svn_wc__db_status_incomplete + || piwb->props == NULL); + SVN_ERR(svn_sqlite__bind_properties(stmt, 15, piwb->props, scratch_pool)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + + /* Insert incomplete children, if specified. + The children are part of the same op and so have the same op_depth. + (The only time we'd want a different depth is during a recursive + simple add, but we never insert children here during a simple add.) */ + if (piwb->kind == svn_node_dir && piwb->children) + SVN_ERR(insert_incomplete_children(wcroot->sdb, wcroot->wc_id, + local_relpath, + INVALID_REPOS_ID /* inherit repos_id */, + NULL /* inherit repos_path */, + piwb->original_revnum, + piwb->children, + piwb->op_depth, + scratch_pool)); + + if (piwb->update_actual_props) + { + /* Cast away const, to allow calling property helpers */ + apr_hash_t *base_props = (apr_hash_t *)piwb->props; + apr_hash_t *new_actual_props = (apr_hash_t *)piwb->new_actual_props; + + if (base_props != NULL + && new_actual_props != NULL + && (apr_hash_count(base_props) == apr_hash_count(new_actual_props))) + { + apr_array_header_t *diffs; + + SVN_ERR(svn_prop_diffs(&diffs, new_actual_props, base_props, + scratch_pool)); + + if (diffs->nelts == 0) + new_actual_props = NULL; + } + + SVN_ERR(set_actual_props(wcroot->wc_id, local_relpath, new_actual_props, + wcroot->sdb, scratch_pool)); + } + + if (piwb->kind == svn_node_dir) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_EMPTY)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + if (piwb->not_present_op_depth > 0 + && piwb->not_present_op_depth < piwb->op_depth) + { + /* And also insert a not-present node to tell the commit processing that + a child of the parent node was not copied. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_NODE)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtnt", + wcroot->wc_id, local_relpath, + piwb->not_present_op_depth, parent_relpath, + piwb->original_repos_id, + piwb->original_repos_relpath, + piwb->original_revnum, + presence_map, svn_wc__db_status_not_present, + /* NULL */ + kind_map, piwb->kind)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + SVN_ERR(add_work_items(wcroot->sdb, piwb->work_items, scratch_pool)); + if (piwb->conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + piwb->conflict, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* Each name is allocated in RESULT_POOL and stored into CHILDREN as a key + pointed to the same name. */ +static svn_error_t * +add_children_to_hash(apr_hash_t *children, + int stmt_idx, + svn_sqlite__db_t *sdb, + apr_int64_t wc_id, + const char *parent_relpath, + apr_pool_t *result_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, stmt_idx)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, parent_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *name = svn_relpath_basename(child_relpath, result_pool); + + svn_hash_sets(children, name, name); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + return svn_sqlite__reset(stmt); +} + + +/* Set *CHILDREN to a new array of the (const char *) basenames of the + immediate children, whatever their status, of the working node at + LOCAL_RELPATH. */ +static svn_error_t * +gather_children2(const apr_array_header_t **children, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *names_hash = apr_hash_make(scratch_pool); + apr_array_header_t *names_array; + + /* All of the names get allocated in RESULT_POOL. It + appears to be faster to use the hash to remove duplicates than to + use DISTINCT in the SQL query. */ + SVN_ERR(add_children_to_hash(names_hash, STMT_SELECT_WORKING_CHILDREN, + wcroot->sdb, wcroot->wc_id, + local_relpath, result_pool)); + + SVN_ERR(svn_hash_keys(&names_array, names_hash, result_pool)); + *children = names_array; + return SVN_NO_ERROR; +} + +/* Return in *CHILDREN all of the children of the directory LOCAL_RELPATH, + of any status, in all op-depths in the NODES table. */ +static svn_error_t * +gather_children(const apr_array_header_t **children, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *names_hash = apr_hash_make(scratch_pool); + apr_array_header_t *names_array; + + /* All of the names get allocated in RESULT_POOL. It + appears to be faster to use the hash to remove duplicates than to + use DISTINCT in the SQL query. */ + SVN_ERR(add_children_to_hash(names_hash, STMT_SELECT_NODE_CHILDREN, + wcroot->sdb, wcroot->wc_id, + local_relpath, result_pool)); + + SVN_ERR(svn_hash_keys(&names_array, names_hash, result_pool)); + *children = names_array; + return SVN_NO_ERROR; +} + + +/* Set *CHILDREN to a new array of (const char *) names of the children of + the repository directory corresponding to WCROOT:LOCAL_RELPATH:OP_DEPTH - + that is, only the children that are at the same op-depth as their parent. */ +static svn_error_t * +gather_repo_children(const apr_array_header_t **children, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *result + = apr_array_make(result_pool, 0, sizeof(const char *)); + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_OP_DEPTH_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + + /* Allocate the name in RESULT_POOL so we won't have to copy it. */ + APR_ARRAY_PUSH(result, const char *) + = svn_relpath_basename(child_relpath, result_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + *children = result; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_get_children_op_depth(apr_hash_t **children, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + *children = apr_hash_make(result_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_OP_DEPTH_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + svn_node_kind_t *child_kind = apr_palloc(result_pool, sizeof(svn_node_kind_t)); + + *child_kind = svn_sqlite__column_token(stmt, 1, kind_map); + svn_hash_sets(*children, + svn_relpath_basename(child_relpath, result_pool), + child_kind); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + + +/* Return TRUE if CHILD_ABSPATH is an immediate child of PARENT_ABSPATH. + * Else, return FALSE. */ +static svn_boolean_t +is_immediate_child_path(const char *parent_abspath, const char *child_abspath) +{ + const char *local_relpath = svn_dirent_skip_ancestor(parent_abspath, + child_abspath); + + /* To be an immediate child local_relpath should have one (not empty) + component */ + return local_relpath && *local_relpath && !strchr(local_relpath, '/'); +} + + +/* Remove the access baton for LOCAL_ABSPATH from ACCESS_CACHE. */ +static void +remove_from_access_cache(apr_hash_t *access_cache, + const char *local_abspath) +{ + svn_wc_adm_access_t *adm_access; + + adm_access = svn_hash_gets(access_cache, local_abspath); + if (adm_access) + svn_wc__adm_access_set_entries(adm_access, NULL); +} + + +/* Flush the access baton for LOCAL_ABSPATH, and any of its children up to + * the specified DEPTH, from the access baton cache in WCROOT. + * Also flush the access baton for the parent of LOCAL_ABSPATH.I + * + * This function must be called when the access baton cache goes stale, + * i.e. data about LOCAL_ABSPATH will need to be read again from disk. + * + * Use SCRATCH_POOL for temporary allocations. */ +static svn_error_t * +flush_entries(svn_wc__db_wcroot_t *wcroot, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + const char *parent_abspath; + + if (apr_hash_count(wcroot->access_cache) == 0) + return SVN_NO_ERROR; + + remove_from_access_cache(wcroot->access_cache, local_abspath); + + if (depth > svn_depth_empty) + { + apr_hash_index_t *hi; + + /* Flush access batons of children within the specified depth. */ + for (hi = apr_hash_first(scratch_pool, wcroot->access_cache); + hi; + hi = apr_hash_next(hi)) + { + const char *item_abspath = svn__apr_hash_index_key(hi); + + if ((depth == svn_depth_files || depth == svn_depth_immediates) && + is_immediate_child_path(local_abspath, item_abspath)) + { + remove_from_access_cache(wcroot->access_cache, item_abspath); + } + else if (depth == svn_depth_infinity && + svn_dirent_is_ancestor(local_abspath, item_abspath)) + { + remove_from_access_cache(wcroot->access_cache, item_abspath); + } + } + } + + /* We're going to be overly aggressive here and just flush the parent + without doing much checking. This may hurt performance for + legacy API consumers, but that's not our problem. :) */ + parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + remove_from_access_cache(wcroot->access_cache, parent_abspath); + + return SVN_NO_ERROR; +} + + +/* Add a single WORK_ITEM into the given SDB's WORK_QUEUE table. This does + not perform its work within a transaction, assuming the caller will + manage that. */ +static svn_error_t * +add_single_work_item(svn_sqlite__db_t *sdb, + const svn_skel_t *work_item, + apr_pool_t *scratch_pool) +{ + svn_stringbuf_t *serialized; + svn_sqlite__stmt_t *stmt; + + serialized = svn_skel__unparse(work_item, scratch_pool); + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_WORK_ITEM)); + SVN_ERR(svn_sqlite__bind_blob(stmt, 1, serialized->data, serialized->len)); + return svn_error_trace(svn_sqlite__insert(NULL, stmt)); +} + + +/* Add work item(s) to the given SDB. Also see add_single_work_item(). This + SKEL is usually passed to the various wc_db operation functions. It may + be NULL, indicating no additional work items are needed, it may be a + single work item, or it may be a list of work items. */ +static svn_error_t * +add_work_items(svn_sqlite__db_t *sdb, + const svn_skel_t *skel, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + + /* Maybe there are no work items to insert. */ + if (skel == NULL) + return SVN_NO_ERROR; + + /* Should have a list. */ + SVN_ERR_ASSERT(!skel->is_atom); + + /* Is the list a single work item? Or a list of work items? */ + if (SVN_WC__SINGLE_WORK_ITEM(skel)) + return svn_error_trace(add_single_work_item(sdb, skel, scratch_pool)); + + /* SKEL is a list-of-lists, aka list of work items. */ + + iterpool = svn_pool_create(scratch_pool); + for (skel = skel->children; skel; skel = skel->next) + { + svn_pool_clear(iterpool); + + SVN_ERR(add_single_work_item(sdb, skel, iterpool)); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Determine whether the node exists for a given WCROOT and LOCAL_RELPATH. */ +static svn_error_t * +does_node_exist(svn_boolean_t *exists, + const svn_wc__db_wcroot_t *wcroot, + const char *local_relpath) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DOES_NODE_EXIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(exists, stmt)); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +/* Helper for create_db(). Initializes our wc.db schema. + */ +static svn_error_t * +init_db(/* output values */ + apr_int64_t *repos_id, + apr_int64_t *wc_id, + /* input values */ + svn_sqlite__db_t *db, + const char *repos_root_url, + const char *repos_uuid, + const char *root_node_repos_relpath, + svn_revnum_t root_node_revision, + svn_depth_t root_node_depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + /* Create the database's schema. */ + SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_SCHEMA)); + SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_NODES)); + SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_NODES_TRIGGERS)); + SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_EXTERNALS)); + + /* Insert the repository. */ + SVN_ERR(create_repos_id(repos_id, repos_root_url, repos_uuid, + db, scratch_pool)); + + /* Insert the wcroot. */ + /* ### Right now, this just assumes wc metadata is being stored locally. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_WCROOT)); + SVN_ERR(svn_sqlite__insert(wc_id, stmt)); + + if (root_node_repos_relpath) + { + svn_wc__db_status_t status = svn_wc__db_status_normal; + + if (root_node_revision > 0) + status = svn_wc__db_status_incomplete; /* Will be filled by update */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtst", + *wc_id, /* 1 */ + "", /* 2 */ + 0, /* op_depth is 0 for base */ + NULL, /* 4 */ + *repos_id, + root_node_repos_relpath, + root_node_revision, + presence_map, status, /* 8 */ + svn_token__to_word(depth_map, + root_node_depth), + kind_map, svn_node_dir /* 10 */)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + } + + return SVN_NO_ERROR; +} + +/* Create an sqlite database at DIR_ABSPATH/SDB_FNAME and insert + records for REPOS_ID (using REPOS_ROOT_URL and REPOS_UUID) into + REPOSITORY and for WC_ID into WCROOT. Return the DB connection + in *SDB. + + If ROOT_NODE_REPOS_RELPATH is not NULL, insert a BASE node at + the working copy root with repository relpath ROOT_NODE_REPOS_RELPATH, + revision ROOT_NODE_REVISION and depth ROOT_NODE_DEPTH. + */ +static svn_error_t * +create_db(svn_sqlite__db_t **sdb, + apr_int64_t *repos_id, + apr_int64_t *wc_id, + const char *dir_abspath, + const char *repos_root_url, + const char *repos_uuid, + const char *sdb_fname, + const char *root_node_repos_relpath, + svn_revnum_t root_node_revision, + svn_depth_t root_node_depth, + svn_boolean_t exclusive, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_wc__db_util_open_db(sdb, dir_abspath, sdb_fname, + svn_sqlite__mode_rwcreate, exclusive, + NULL /* my_statements */, + result_pool, scratch_pool)); + + SVN_SQLITE__WITH_LOCK(init_db(repos_id, wc_id, + *sdb, repos_root_url, repos_uuid, + root_node_repos_relpath, root_node_revision, + root_node_depth, scratch_pool), + *sdb); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_init(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t initial_rev, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__db_t *sdb; + apr_int64_t repos_id; + apr_int64_t wc_id; + svn_wc__db_wcroot_t *wcroot; + svn_boolean_t sqlite_exclusive = FALSE; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(repos_relpath != NULL); + SVN_ERR_ASSERT(depth == svn_depth_empty + || depth == svn_depth_files + || depth == svn_depth_immediates + || depth == svn_depth_infinity); + + /* ### REPOS_ROOT_URL and REPOS_UUID may be NULL. ... more doc: tbd */ + + SVN_ERR(svn_config_get_bool((svn_config_t *)db->config, &sqlite_exclusive, + SVN_CONFIG_SECTION_WORKING_COPY, + SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE, + FALSE)); + + /* Create the SDB and insert the basic rows. */ + SVN_ERR(create_db(&sdb, &repos_id, &wc_id, local_abspath, repos_root_url, + repos_uuid, SDB_FILE, + repos_relpath, initial_rev, depth, sqlite_exclusive, + db->state_pool, scratch_pool)); + + /* Create the WCROOT for this directory. */ + SVN_ERR(svn_wc__db_pdh_create_wcroot(&wcroot, + apr_pstrdup(db->state_pool, local_abspath), + sdb, wc_id, FORMAT_FROM_SDB, + FALSE /* auto-upgrade */, + FALSE /* enforce_empty_wq */, + db->state_pool, scratch_pool)); + + /* The WCROOT is complete. Stash it into DB. */ + svn_hash_sets(db->dir_data, wcroot->abspath, wcroot); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_to_relpath(const char **local_relpath, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &relpath, db, + wri_abspath, result_pool, scratch_pool)); + + /* This function is indirectly called from the upgrade code, so we + can't verify the wcroot here. Just check that it is not NULL */ + CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool); + + if (svn_dirent_is_ancestor(wcroot->abspath, local_abspath)) + { + *local_relpath = apr_pstrdup(result_pool, + svn_dirent_skip_ancestor(wcroot->abspath, + local_abspath)); + } + else + /* Probably moving from $TMP. Should we allow this? */ + *local_relpath = apr_pstrdup(result_pool, local_abspath); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_from_relpath(const char **local_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *unused_relpath; +#if 0 + SVN_ERR_ASSERT(svn_relpath_is_canonical(local_relpath)); +#endif + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &unused_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + + /* This function is indirectly called from the upgrade code, so we + can't verify the wcroot here. Just check that it is not NULL */ + CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool); + + + *local_abspath = svn_dirent_join(wcroot->abspath, + local_relpath, + result_pool); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_get_wcroot(const char **wcroot_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *unused_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &unused_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + + /* Can't use VERIFY_USABLE_WCROOT, as this should be usable to detect + where call upgrade */ + CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool); + + *wcroot_abspath = apr_pstrdup(result_pool, wcroot->abspath); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_add_directory(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const apr_array_header_t *children, + svn_depth_t depth, + apr_hash_t *dav_cache, + const svn_skel_t *conflict, + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_base_baton_t ibb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(repos_relpath != NULL); + SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool)); + SVN_ERR_ASSERT(repos_uuid != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); + SVN_ERR_ASSERT(props != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev)); +#if 0 + SVN_ERR_ASSERT(children != NULL); +#endif + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + + blank_ibb(&ibb); + + /* Calculate repos_id in insert_base_node() to avoid extra transaction */ + ibb.repos_root_url = repos_root_url; + ibb.repos_uuid = repos_uuid; + + ibb.status = svn_wc__db_status_normal; + ibb.kind = svn_node_dir; + ibb.repos_relpath = repos_relpath; + ibb.revision = revision; + + ibb.iprops = new_iprops; + ibb.props = props; + ibb.changed_rev = changed_rev; + ibb.changed_date = changed_date; + ibb.changed_author = changed_author; + + ibb.children = children; + ibb.depth = depth; + + ibb.dav_cache = dav_cache; + ibb.conflict = conflict; + ibb.work_items = work_items; + + if (update_actual_props) + { + ibb.update_actual_props = TRUE; + ibb.new_actual_props = new_actual_props; + } + + /* Insert the directory and all its children transactionally. + + Note: old children can stick around, even if they are no longer present + in this directory's revision. */ + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_base_add_incomplete_directory(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_depth_t depth, + svn_boolean_t insert_base_deleted, + svn_boolean_t delete_working, + svn_skel_t *conflict, + svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + struct insert_base_baton_t ibb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); + SVN_ERR_ASSERT(repos_relpath && repos_root_url && repos_uuid); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + + VERIFY_USABLE_WCROOT(wcroot); + + blank_ibb(&ibb); + + /* Calculate repos_id in insert_base_node() to avoid extra transaction */ + ibb.repos_root_url = repos_root_url; + ibb.repos_uuid = repos_uuid; + + ibb.status = svn_wc__db_status_incomplete; + ibb.kind = svn_node_dir; + ibb.repos_relpath = repos_relpath; + ibb.revision = revision; + ibb.depth = depth; + ibb.insert_base_deleted = insert_base_deleted; + ibb.delete_working = delete_working; + + ibb.conflict = conflict; + ibb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_add_file(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const svn_checksum_t *checksum, + apr_hash_t *dav_cache, + svn_boolean_t delete_working, + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, + svn_boolean_t keep_recorded_info, + svn_boolean_t insert_base_deleted, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_base_baton_t ibb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(repos_relpath != NULL); + SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool)); + SVN_ERR_ASSERT(repos_uuid != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); + SVN_ERR_ASSERT(props != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev)); + SVN_ERR_ASSERT(checksum != NULL); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + + blank_ibb(&ibb); + + /* Calculate repos_id in insert_base_node() to avoid extra transaction */ + ibb.repos_root_url = repos_root_url; + ibb.repos_uuid = repos_uuid; + + ibb.status = svn_wc__db_status_normal; + ibb.kind = svn_node_file; + ibb.repos_relpath = repos_relpath; + ibb.revision = revision; + + ibb.props = props; + ibb.changed_rev = changed_rev; + ibb.changed_date = changed_date; + ibb.changed_author = changed_author; + + ibb.checksum = checksum; + + ibb.dav_cache = dav_cache; + ibb.iprops = new_iprops; + + if (update_actual_props) + { + ibb.update_actual_props = TRUE; + ibb.new_actual_props = new_actual_props; + } + + ibb.keep_recorded_info = keep_recorded_info; + ibb.insert_base_deleted = insert_base_deleted; + ibb.delete_working = delete_working; + + ibb.conflict = conflict; + ibb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); + + /* If this used to be a directory we should remove children so pass + * depth infinity. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_add_symlink(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const char *target, + apr_hash_t *dav_cache, + svn_boolean_t delete_working, + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, + svn_boolean_t keep_recorded_info, + svn_boolean_t insert_base_deleted, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_base_baton_t ibb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(repos_relpath != NULL); + SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool)); + SVN_ERR_ASSERT(repos_uuid != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); + SVN_ERR_ASSERT(props != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev)); + SVN_ERR_ASSERT(target != NULL); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + blank_ibb(&ibb); + + /* Calculate repos_id in insert_base_node() to avoid extra transaction */ + ibb.repos_root_url = repos_root_url; + ibb.repos_uuid = repos_uuid; + + ibb.status = svn_wc__db_status_normal; + ibb.kind = svn_node_symlink; + ibb.repos_relpath = repos_relpath; + ibb.revision = revision; + + ibb.props = props; + ibb.changed_rev = changed_rev; + ibb.changed_date = changed_date; + ibb.changed_author = changed_author; + + ibb.target = target; + + ibb.dav_cache = dav_cache; + ibb.iprops = new_iprops; + + if (update_actual_props) + { + ibb.update_actual_props = TRUE; + ibb.new_actual_props = new_actual_props; + } + + ibb.keep_recorded_info = keep_recorded_info; + ibb.insert_base_deleted = insert_base_deleted; + ibb.delete_working = delete_working; + + ibb.conflict = conflict; + ibb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); + + /* If this used to be a directory we should remove children so pass + * depth infinity. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + return SVN_NO_ERROR; +} + + +static svn_error_t * +add_excluded_or_not_present_node(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_node_kind_t kind, + svn_wc__db_status_t status, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_base_baton_t ibb; + const char *dir_abspath, *name; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(repos_relpath != NULL); + SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool)); + SVN_ERR_ASSERT(repos_uuid != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); + SVN_ERR_ASSERT(status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_not_present); + + /* These absent presence nodes are only useful below a parent node that is + present. To avoid problems with working copies obstructing the child + we calculate the wcroot and local_relpath of the parent and then add + our own relpath. */ + + svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + local_relpath = svn_relpath_join(local_relpath, name, scratch_pool); + + blank_ibb(&ibb); + + /* Calculate repos_id in insert_base_node() to avoid extra transaction */ + ibb.repos_root_url = repos_root_url; + ibb.repos_uuid = repos_uuid; + + ibb.status = status; + ibb.kind = kind; + ibb.repos_relpath = repos_relpath; + ibb.revision = revision; + + /* Depending upon KIND, any of these might get used. */ + ibb.children = NULL; + ibb.depth = svn_depth_unknown; + ibb.checksum = NULL; + ibb.target = NULL; + + ibb.conflict = conflict; + ibb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); + + /* If this used to be a directory we should remove children so pass + * depth infinity. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_add_excluded_node(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_node_kind_t kind, + svn_wc__db_status_t status, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded); + + return add_excluded_or_not_present_node( + db, local_abspath, repos_relpath, repos_root_url, repos_uuid, revision, + kind, status, conflict, work_items, scratch_pool); +} + + +svn_error_t * +svn_wc__db_base_add_not_present_node(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_node_kind_t kind, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + return add_excluded_or_not_present_node( + db, local_abspath, repos_relpath, repos_root_url, repos_uuid, revision, + kind, svn_wc__db_status_not_present, conflict, work_items, scratch_pool); +} + +/* Recursively clear moved-here information at the copy-half of the move + * which moved the node at SRC_RELPATH away. This transforms the move into + * a simple copy. */ +static svn_error_t * +clear_moved_here(const char *src_relpath, + svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + const char *dst_relpath; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_MOVED_TO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + src_relpath, relpath_depth(src_relpath))); + SVN_ERR(svn_sqlite__step_row(stmt)); + dst_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_HERE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + dst_relpath, relpath_depth(dst_relpath))); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_base_remove(). + */ +static svn_error_t * +db_base_remove(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, /* For checking conflicts */ + svn_boolean_t keep_as_working, + svn_boolean_t queue_deletes, + svn_revnum_t not_present_revision, + svn_skel_t *conflict, + svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_wc__db_status_t status; + apr_int64_t repos_id; + const char *repos_relpath; + svn_node_kind_t kind; + svn_boolean_t keep_working; + + SVN_ERR(svn_wc__db_base_get_info_internal(&status, &kind, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (status == svn_wc__db_status_normal + && keep_as_working) + { + SVN_ERR(svn_wc__db_op_make_copy(db, + svn_dirent_join(wcroot->abspath, + local_relpath, + scratch_pool), + NULL, NULL, + scratch_pool)); + keep_working = TRUE; + } + else + { + /* Check if there is already a working node */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&keep_working, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + } + + /* Step 1: Create workqueue operations to remove files and dirs in the + local-wc */ + if (!keep_working + && queue_deletes + && (status == svn_wc__db_status_normal + || status == svn_wc__db_status_incomplete)) + { + svn_skel_t *work_item; + const char *local_abspath; + + local_abspath = svn_dirent_join(wcroot->abspath, local_relpath, + scratch_pool); + if (kind == svn_node_dir) + { + apr_pool_t *iterpool; + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_BASE_PRESENT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + const char *node_relpath = svn_sqlite__column_text(stmt, 0, NULL); + svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 1, + kind_map); + const char *node_abspath; + svn_error_t *err; + + svn_pool_clear(iterpool); + + node_abspath = svn_dirent_join(wcroot->abspath, node_relpath, + iterpool); + + if (node_kind == svn_node_dir) + err = svn_wc__wq_build_dir_remove(&work_item, + db, wcroot->abspath, + node_abspath, FALSE, + iterpool, iterpool); + else + err = svn_wc__wq_build_file_remove(&work_item, + db, + wcroot->abspath, + node_abspath, + iterpool, iterpool); + + if (!err) + err = add_work_items(wcroot->sdb, work_item, iterpool); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_wc__wq_build_dir_remove(&work_item, + db, wcroot->abspath, + local_abspath, FALSE, + scratch_pool, iterpool)); + svn_pool_destroy(iterpool); + } + else + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, + db, wcroot->abspath, + local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(add_work_items(wcroot->sdb, work_item, scratch_pool)); + } + + /* Step 2: Delete ACTUAL nodes */ + if (! keep_working) + { + /* There won't be a record in NODE left for this node, so we want + to remove *all* ACTUAL nodes, including ACTUAL ONLY. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + else if (! keep_as_working) + { + /* Delete only the ACTUAL nodes that apply to a delete of a BASE node */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + /* Else: Everything has been turned into a copy, so we want to keep all + ACTUAL_NODE records */ + + /* Step 3: Delete WORKING nodes */ + if (conflict) + { + apr_pool_t *iterpool; + + /* + * When deleting a conflicted node, moves of any moved-outside children + * of the node must be broken. Else, the destination will still be marked + * moved-here after the move source disappears from the working copy. + * + * ### FIXME: It would be nicer to have the conflict resolver + * break the move instead. It might also be a good idea to + * flag a tree conflict on each moved-away child. But doing so + * might introduce actual-only nodes without direct parents, + * and we're not yet sure if other existing code is prepared + * to handle such nodes. To be revisited post-1.8. + */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_OUTSIDE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, + relpath_depth(local_relpath))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + const char *child_relpath; + svn_error_t *err; + + svn_pool_clear(iterpool); + child_relpath = svn_sqlite__column_text(stmt, 0, iterpool); + err = clear_moved_here(child_relpath, wcroot, iterpool); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + SVN_ERR(svn_sqlite__reset(stmt)); + } + if (keep_working) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WORKING_BASE_DELETE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + else + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WORKING_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Step 4: Delete the BASE node descendants */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_BASE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* Step 5: handle the BASE node itself */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_BASE_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_wc__db_retract_parent_delete(wcroot, local_relpath, 0, + scratch_pool)); + + /* Step 6: Delete actual node if we don't keep working */ + if (! keep_working) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + if (SVN_IS_VALID_REVNUM(not_present_revision)) + { + struct insert_base_baton_t ibb; + blank_ibb(&ibb); + + ibb.repos_id = repos_id; + ibb.status = svn_wc__db_status_not_present; + ibb.kind = kind; + ibb.repos_relpath = repos_relpath; + ibb.revision = not_present_revision; + + /* Depending upon KIND, any of these might get used. */ + ibb.children = NULL; + ibb.depth = svn_depth_unknown; + ibb.checksum = NULL; + ibb.target = NULL; + + SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool)); + } + + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + if (conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_remove(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t keep_as_working, + svn_boolean_t queue_deletes, + svn_revnum_t not_present_revision, + svn_skel_t *conflict, + svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(db_base_remove(wcroot, local_relpath, + db, keep_as_working, queue_deletes, + not_present_revision, + conflict, work_items, scratch_pool), + wcroot); + + /* If this used to be a directory we should remove children so pass + * depth infinity. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_get_info_internal(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + svn_wc__db_lock_t **lock, + svn_boolean_t *had_props, + apr_hash_t **props, + svn_boolean_t *update_root, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_error_t *err = SVN_NO_ERROR; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + lock ? STMT_SELECT_BASE_NODE_WITH_LOCK + : STMT_SELECT_BASE_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + svn_wc__db_status_t node_status = svn_sqlite__column_token(stmt, 2, + presence_map); + svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 3, kind_map); + + if (kind) + { + *kind = node_kind; + } + if (status) + { + *status = node_status; + } + repos_location_from_columns(repos_id, revision, repos_relpath, + stmt, 0, 4, 1, result_pool); + SVN_ERR_ASSERT(!repos_id || *repos_id != INVALID_REPOS_ID); + SVN_ERR_ASSERT(!repos_relpath || *repos_relpath); + if (lock) + { + *lock = lock_from_columns(stmt, 15, 16, 17, 18, result_pool); + } + if (changed_rev) + { + *changed_rev = svn_sqlite__column_revnum(stmt, 7); + } + if (changed_date) + { + *changed_date = svn_sqlite__column_int64(stmt, 8); + } + if (changed_author) + { + /* Result may be NULL. */ + *changed_author = svn_sqlite__column_text(stmt, 9, result_pool); + } + if (depth) + { + if (node_kind != svn_node_dir) + { + *depth = svn_depth_unknown; + } + else + { + *depth = svn_sqlite__column_token_null(stmt, 10, depth_map, + svn_depth_unknown); + } + } + if (checksum) + { + if (node_kind != svn_node_file) + { + *checksum = NULL; + } + else + { + err = svn_sqlite__column_checksum(checksum, stmt, 5, + result_pool); + if (err != NULL) + err = svn_error_createf( + err->apr_err, err, + _("The node '%s' has a corrupt checksum value."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + } + if (target) + { + if (node_kind != svn_node_symlink) + *target = NULL; + else + *target = svn_sqlite__column_text(stmt, 11, result_pool); + } + if (had_props) + { + *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 13); + } + if (props) + { + if (node_status == svn_wc__db_status_normal + || node_status == svn_wc__db_status_incomplete) + { + SVN_ERR(svn_sqlite__column_properties(props, stmt, 13, + result_pool, scratch_pool)); + if (*props == NULL) + *props = apr_hash_make(result_pool); + } + else + { + assert(svn_sqlite__column_is_null(stmt, 13)); + *props = NULL; + } + } + if (update_root) + { + /* It's an update root iff it's a file external. */ + *update_root = svn_sqlite__column_boolean(stmt, 14); + } + } + else + { + err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + + /* Note: given the composition, no need to wrap for tracing. */ + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); +} + + +svn_error_t * +svn_wc__db_base_get_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + svn_wc__db_lock_t **lock, + svn_boolean_t *had_props, + apr_hash_t **props, + svn_boolean_t *update_root, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + apr_int64_t repos_id; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_wc__db_base_get_info_internal(status, kind, revision, + repos_relpath, &repos_id, + changed_rev, changed_date, + changed_author, depth, + checksum, target, lock, + had_props, props, update_root, + wcroot, local_relpath, + result_pool, scratch_pool)); + SVN_ERR_ASSERT(repos_id != INVALID_REPOS_ID); + SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, + wcroot->sdb, repos_id, result_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_base_get_children_info(apr_hash_t **nodes, + svn_wc__db_t *db, + const char *dir_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + *nodes = apr_hash_make(result_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_BASE_CHILDREN_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + struct svn_wc__db_base_info_t *info; + svn_error_t *err; + apr_int64_t repos_id; + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *name = svn_relpath_basename(child_relpath, result_pool); + + info = apr_pcalloc(result_pool, sizeof(*info)); + + repos_id = svn_sqlite__column_int64(stmt, 1); + info->repos_relpath = svn_sqlite__column_text(stmt, 2, result_pool); + info->status = svn_sqlite__column_token(stmt, 3, presence_map); + info->kind = svn_sqlite__column_token(stmt, 4, kind_map); + info->revnum = svn_sqlite__column_revnum(stmt, 5); + + info->depth = svn_sqlite__column_token_null(stmt, 6, depth_map, + svn_depth_unknown); + + info->update_root = svn_sqlite__column_boolean(stmt, 7); + + info->lock = lock_from_columns(stmt, 8, 9, 10, 11, result_pool); + + err = svn_wc__db_fetch_repos_info(&info->repos_root_url, NULL, + wcroot->sdb, repos_id, result_pool); + + if (err) + return svn_error_trace( + svn_error_compose_create(err, + svn_sqlite__reset(stmt))); + + + svn_hash_sets(*nodes, name, info); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_get_props(apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t presence; + + SVN_ERR(svn_wc__db_base_get_info(&presence, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, props, NULL, + db, local_abspath, + result_pool, scratch_pool)); + if (presence != svn_wc__db_status_normal + && presence != svn_wc__db_status_incomplete) + { + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("The node '%s' has a BASE status that" + " has no properties."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_get_children(const apr_array_header_t **children, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + return gather_repo_children(children, wcroot, local_relpath, 0, + result_pool, scratch_pool); +} + + +svn_error_t * +svn_wc__db_base_set_dav_cache(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int affected_rows; + + SVN_ERR(get_statement_for_path(&stmt, db, local_abspath, + STMT_UPDATE_BASE_NODE_DAV_CACHE, + scratch_pool)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, scratch_pool)); + + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + if (affected_rows != 1) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_get_dav_cache(apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(get_statement_for_path(&stmt, db, local_abspath, + STMT_SELECT_BASE_DAV_CACHE, scratch_pool)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + SVN_ERR(svn_sqlite__column_properties(props, stmt, 0, result_pool, + scratch_pool)); + return svn_error_trace(svn_sqlite__reset(stmt)); +} + + +svn_error_t * +svn_wc__db_base_clear_dav_cache_recursive(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_depth_get_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + svn_boolean_t *had_props, + apr_hash_t **props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_error_t *err = SVN_NO_ERROR; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_DEPTH_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wcroot->wc_id, local_relpath, op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + svn_wc__db_status_t node_status = svn_sqlite__column_token(stmt, 2, + presence_map); + svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 3, kind_map); + + if (kind) + { + *kind = node_kind; + } + if (status) + { + *status = node_status; + + if (op_depth > 0) + SVN_ERR(convert_to_working_status(status, *status)); + } + repos_location_from_columns(repos_id, revision, repos_relpath, + stmt, 0, 4, 1, result_pool); + + if (changed_rev) + { + *changed_rev = svn_sqlite__column_revnum(stmt, 7); + } + if (changed_date) + { + *changed_date = svn_sqlite__column_int64(stmt, 8); + } + if (changed_author) + { + /* Result may be NULL. */ + *changed_author = svn_sqlite__column_text(stmt, 9, result_pool); + } + if (depth) + { + if (node_kind != svn_node_dir) + { + *depth = svn_depth_unknown; + } + else + { + *depth = svn_sqlite__column_token_null(stmt, 10, depth_map, + svn_depth_unknown); + } + } + if (checksum) + { + if (node_kind != svn_node_file) + { + *checksum = NULL; + } + else + { + err = svn_sqlite__column_checksum(checksum, stmt, 5, + result_pool); + if (err != NULL) + err = svn_error_createf( + err->apr_err, err, + _("The node '%s' has a corrupt checksum value."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + } + if (target) + { + if (node_kind != svn_node_symlink) + *target = NULL; + else + *target = svn_sqlite__column_text(stmt, 11, result_pool); + } + if (had_props) + { + *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 13); + } + if (props) + { + if (node_status == svn_wc__db_status_normal + || node_status == svn_wc__db_status_incomplete) + { + SVN_ERR(svn_sqlite__column_properties(props, stmt, 13, + result_pool, scratch_pool)); + if (*props == NULL) + *props = apr_hash_make(result_pool); + } + else + { + assert(svn_sqlite__column_is_null(stmt, 13)); + *props = NULL; + } + } + } + else + { + err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + + /* Note: given the composition, no need to wrap for tracing. */ + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); +} + + +/* Baton for passing args to with_triggers(). */ +struct with_triggers_baton_t { + int create_trigger; + int drop_trigger; + svn_wc__db_txn_callback_t cb_func; + void *cb_baton; +}; + +/* Helper for creating SQLite triggers, running the main transaction + callback, and then dropping the triggers. It guarantees that the + triggers will not survive the transaction. This could be used for + any general prefix/postscript statements where the postscript + *must* be executed if the transaction completes. + + Implements svn_wc__db_txn_callback_t. */ +static svn_error_t * +with_triggers(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + struct with_triggers_baton_t *b = baton; + svn_error_t *err1; + svn_error_t *err2; + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, b->create_trigger)); + + err1 = b->cb_func(b->cb_baton, wcroot, local_relpath, scratch_pool); + + err2 = svn_sqlite__exec_statements(wcroot->sdb, b->drop_trigger); + + return svn_error_trace(svn_error_compose_create(err1, err2)); +} + + +/* Prototype for the "work callback" used by with_finalization(). */ +typedef svn_error_t * (*work_callback_t)( + void *baton, + svn_wc__db_wcroot_t *wcroot, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); + +/* Utility function to provide several features, with a guaranteed + finalization (ie. to drop temporary tables). + + 1) for WCROOT and LOCAL_RELPATH, run TXN_CB(TXN_BATON) within a + sqlite transaction + 2) if (1) is successful and a NOTIFY_FUNC is provided, then run + the "work" step: WORK_CB(WORK_BATON). + 3) execute FINALIZE_STMT_IDX no matter what errors may be thrown + from the above two steps. + + CANCEL_FUNC, CANCEL_BATON, NOTIFY_FUNC and NOTIFY_BATON are their + typical values. These are passed to the work callback, which typically + provides notification about the work done by TXN_CB. */ +static svn_error_t * +with_finalization(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_txn_callback_t txn_cb, + void *txn_baton, + work_callback_t work_cb, + void *work_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + int finalize_stmt_idx, + apr_pool_t *scratch_pool) +{ + svn_error_t *err1; + svn_error_t *err2; + + err1 = svn_wc__db_with_txn(wcroot, local_relpath, txn_cb, txn_baton, + scratch_pool); + + if (err1 == NULL && notify_func != NULL) + { + err2 = work_cb(work_baton, wcroot, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool); + err1 = svn_error_compose_create(err1, err2); + } + + err2 = svn_sqlite__exec_statements(wcroot->sdb, finalize_stmt_idx); + + return svn_error_trace(svn_error_compose_create(err1, err2)); +} + + +/* Initialize the baton with appropriate "blank" values. This allows the + insertion function to leave certain columns null. */ +static void +blank_ieb(insert_external_baton_t *ieb) +{ + memset(ieb, 0, sizeof(*ieb)); + ieb->revision = SVN_INVALID_REVNUM; + ieb->changed_rev = SVN_INVALID_REVNUM; + ieb->repos_id = INVALID_REPOS_ID; + + ieb->recorded_peg_revision = SVN_INVALID_REVNUM; + ieb->recorded_revision = SVN_INVALID_REVNUM; +} + +/* Insert the externals row represented by (insert_external_baton_t *) BATON. + * + * Implements svn_wc__db_txn_callback_t. */ +static svn_error_t * +insert_external_node(const insert_external_baton_t *ieb, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_error_t *err; + svn_boolean_t update_root; + apr_int64_t repos_id; + svn_sqlite__stmt_t *stmt; + + if (ieb->repos_id != INVALID_REPOS_ID) + repos_id = ieb->repos_id; + else + SVN_ERR(create_repos_id(&repos_id, ieb->repos_root_url, ieb->repos_uuid, + wcroot->sdb, scratch_pool)); + + /* And there must be no existing BASE node or it must be a file external */ + err = svn_wc__db_base_get_info_internal(&status, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &update_root, + wcroot, local_relpath, + 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 (status == svn_wc__db_status_normal && !update_root) + return svn_error_create(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, NULL); + + if (ieb->kind == svn_node_file + || ieb->kind == svn_node_symlink) + { + struct insert_base_baton_t ibb; + + blank_ibb(&ibb); + + ibb.status = svn_wc__db_status_normal; + ibb.kind = ieb->kind; + + ibb.repos_id = repos_id; + ibb.repos_relpath = ieb->repos_relpath; + ibb.revision = ieb->revision; + + ibb.props = ieb->props; + ibb.iprops = ieb->iprops; + ibb.changed_rev = ieb->changed_rev; + ibb.changed_date = ieb->changed_date; + ibb.changed_author = ieb->changed_author; + + ibb.dav_cache = ieb->dav_cache; + + ibb.checksum = ieb->checksum; + ibb.target = ieb->target; + + ibb.conflict = ieb->conflict; + + ibb.update_actual_props = ieb->update_actual_props; + ibb.new_actual_props = ieb->new_actual_props; + + ibb.keep_recorded_info = ieb->keep_recorded_info; + + ibb.work_items = ieb->work_items; + + ibb.file_external = TRUE; + + SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool)); + } + else + SVN_ERR(add_work_items(wcroot->sdb, ieb->work_items, scratch_pool)); + + /* The externals table only support presence normal and excluded */ + SVN_ERR_ASSERT(ieb->presence == svn_wc__db_status_normal + || ieb->presence == svn_wc__db_status_excluded); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_EXTERNAL)); + + SVN_ERR(svn_sqlite__bindf(stmt, "issttsis", + wcroot->wc_id, + local_relpath, + svn_relpath_dirname(local_relpath, + scratch_pool), + presence_map, ieb->presence, + kind_map, ieb->kind, + ieb->record_ancestor_relpath, + repos_id, + ieb->recorded_repos_relpath)); + + if (SVN_IS_VALID_REVNUM(ieb->recorded_peg_revision)) + SVN_ERR(svn_sqlite__bind_revnum(stmt, 9, ieb->recorded_peg_revision)); + + if (SVN_IS_VALID_REVNUM(ieb->recorded_revision)) + SVN_ERR(svn_sqlite__bind_revnum(stmt, 10, ieb->recorded_revision)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_external_add_file(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + + const apr_hash_t *props, + apr_array_header_t *iprops, + + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + + const svn_checksum_t *checksum, + + const apr_hash_t *dav_cache, + + const char *record_ancestor_abspath, + const char *recorded_repos_relpath, + svn_revnum_t recorded_peg_revision, + svn_revnum_t recorded_revision, + + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + + svn_boolean_t keep_recorded_info, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_external_baton_t ieb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (! wri_abspath) + wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, + record_ancestor_abspath)); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath)); + + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + + blank_ieb(&ieb); + + ieb.kind = svn_node_file; + ieb.presence = svn_wc__db_status_normal; + + ieb.repos_root_url = repos_root_url; + ieb.repos_uuid = repos_uuid; + + ieb.repos_relpath = repos_relpath; + ieb.revision = revision; + + ieb.props = props; + ieb.iprops = iprops; + + ieb.changed_rev = changed_rev; + ieb.changed_date = changed_date; + ieb.changed_author = changed_author; + + ieb.checksum = checksum; + + ieb.dav_cache = dav_cache; + + ieb.record_ancestor_relpath = svn_dirent_skip_ancestor( + wcroot->abspath, + record_ancestor_abspath); + ieb.recorded_repos_relpath = recorded_repos_relpath; + ieb.recorded_peg_revision = recorded_peg_revision; + ieb.recorded_revision = recorded_revision; + + ieb.update_actual_props = update_actual_props; + ieb.new_actual_props = new_actual_props; + + ieb.keep_recorded_info = keep_recorded_info; + + ieb.conflict = conflict; + ieb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_external_node(&ieb, wcroot, local_relpath, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_external_add_symlink(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const char *target, + const apr_hash_t *dav_cache, + const char *record_ancestor_abspath, + const char *recorded_repos_relpath, + svn_revnum_t recorded_peg_revision, + svn_revnum_t recorded_revision, + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + svn_boolean_t keep_recorded_info, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_external_baton_t ieb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (! wri_abspath) + wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, + record_ancestor_abspath)); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath)); + + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + + blank_ieb(&ieb); + + ieb.kind = svn_node_symlink; + ieb.presence = svn_wc__db_status_normal; + + ieb.repos_root_url = repos_root_url; + ieb.repos_uuid = repos_uuid; + + ieb.repos_relpath = repos_relpath; + ieb.revision = revision; + + ieb.props = props; + + ieb.changed_rev = changed_rev; + ieb.changed_date = changed_date; + ieb.changed_author = changed_author; + + ieb.target = target; + + ieb.dav_cache = dav_cache; + + ieb.record_ancestor_relpath = svn_dirent_skip_ancestor( + wcroot->abspath, + record_ancestor_abspath); + ieb.recorded_repos_relpath = recorded_repos_relpath; + ieb.recorded_peg_revision = recorded_peg_revision; + ieb.recorded_revision = recorded_revision; + + ieb.update_actual_props = update_actual_props; + ieb.new_actual_props = new_actual_props; + + ieb.keep_recorded_info = keep_recorded_info; + + ieb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_external_node(&ieb, wcroot, local_relpath, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_external_add_dir(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *repos_root_url, + const char *repos_uuid, + const char *record_ancestor_abspath, + const char *recorded_repos_relpath, + svn_revnum_t recorded_peg_revision, + svn_revnum_t recorded_revision, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_external_baton_t ieb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (! wri_abspath) + wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, + record_ancestor_abspath)); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath)); + + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + + blank_ieb(&ieb); + + ieb.kind = svn_node_dir; + ieb.presence = svn_wc__db_status_normal; + + ieb.repos_root_url = repos_root_url; + ieb.repos_uuid = repos_uuid; + + ieb.record_ancestor_relpath = svn_dirent_skip_ancestor( + wcroot->abspath, + record_ancestor_abspath); + ieb.recorded_repos_relpath = recorded_repos_relpath; + ieb.recorded_peg_revision = recorded_peg_revision; + ieb.recorded_revision = recorded_revision; + + ieb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_external_node(&ieb, wcroot, local_relpath, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_external_remove(). */ +static svn_error_t * +db_external_remove(const svn_skel_t *work_items, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_EXTERNAL)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + + /* ### What about actual? */ + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_external_remove(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (! wri_abspath) + wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath)); + + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + + SVN_WC__DB_WITH_TXN(db_external_remove(work_items, wcroot, local_relpath, + scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_external_read(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + const char **definining_abspath, + const char **repos_root_url, + const char **repos_uuid, + const char **recorded_repos_relpath, + svn_revnum_t *recorded_peg_revision, + svn_revnum_t *recorded_revision, + 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_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_info; + svn_error_t *err = NULL; + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (! wri_abspath) + wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath)); + + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_EXTERNAL_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_info, stmt)); + + if (have_info) + { + if (status) + *status = svn_sqlite__column_token(stmt, 0, presence_map); + + if (kind) + *kind = svn_sqlite__column_token(stmt, 1, kind_map); + + if (definining_abspath) + { + const char *record_relpath = svn_sqlite__column_text(stmt, 2, NULL); + + *definining_abspath = svn_dirent_join(wcroot->abspath, + record_relpath, result_pool); + } + + if (repos_root_url || repos_uuid) + { + apr_int64_t repos_id; + + repos_id = svn_sqlite__column_int64(stmt, 3); + + err = svn_error_compose_create( + err, + svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, + wcroot->sdb, repos_id, + result_pool)); + } + + if (recorded_repos_relpath) + *recorded_repos_relpath = svn_sqlite__column_text(stmt, 4, + result_pool); + + if (recorded_peg_revision) + *recorded_peg_revision = svn_sqlite__column_revnum(stmt, 5); + + if (recorded_revision) + *recorded_revision = svn_sqlite__column_revnum(stmt, 6); + } + else + { + err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' is not an external."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + return svn_error_trace( + svn_error_compose_create(err, svn_sqlite__reset(stmt))); +} + +svn_error_t * +svn_wc__db_committable_externals_below(apr_array_header_t **externals, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t immediates_only, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + svn_sqlite__stmt_t *stmt; + const char *local_relpath; + svn_boolean_t have_row; + svn_wc__committable_external_info_t *info; + svn_node_kind_t db_kind; + apr_array_header_t *result = NULL; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + immediates_only + ? STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW + : STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + result = apr_array_make(result_pool, 0, + sizeof(svn_wc__committable_external_info_t *)); + + while (have_row) + { + info = apr_palloc(result_pool, sizeof(*info)); + + local_relpath = svn_sqlite__column_text(stmt, 0, NULL); + info->local_abspath = svn_dirent_join(wcroot->abspath, local_relpath, + result_pool); + + db_kind = svn_sqlite__column_token(stmt, 1, kind_map); + SVN_ERR_ASSERT(db_kind == svn_node_file || db_kind == svn_node_dir); + info->kind = db_kind; + + info->repos_relpath = svn_sqlite__column_text(stmt, 2, result_pool); + info->repos_root_url = svn_sqlite__column_text(stmt, 3, result_pool); + + APR_ARRAY_PUSH(result, svn_wc__committable_external_info_t *) = info; + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + *externals = result; + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_wc__db_externals_defined_below(apr_hash_t **externals, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + svn_sqlite__stmt_t *stmt; + const char *local_relpath; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_EXTERNALS_DEFINED)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + *externals = apr_hash_make(result_pool); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + const char *def_local_relpath; + + local_relpath = svn_sqlite__column_text(stmt, 0, NULL); + def_local_relpath = svn_sqlite__column_text(stmt, 1, NULL); + + svn_hash_sets(*externals, + svn_dirent_join(wcroot->abspath, local_relpath, + result_pool), + svn_dirent_join(wcroot->abspath, def_local_relpath, + result_pool)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_wc__db_externals_gather_definitions(apr_hash_t **externals, + apr_hash_t **depths, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + svn_sqlite__stmt_t *stmt; + const char *local_relpath; + svn_boolean_t have_row; + svn_error_t *err = NULL; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, iterpool)); + VERIFY_USABLE_WCROOT(wcroot); + + *externals = apr_hash_make(result_pool); + if (depths != NULL) + *depths = apr_hash_make(result_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_EXTERNAL_PROPERTIES)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + apr_hash_t *node_props; + const char *external_value; + + svn_pool_clear(iterpool); + err = svn_sqlite__column_properties(&node_props, stmt, 0, iterpool, + iterpool); + + if (err) + break; + + external_value = svn_prop_get_value(node_props, SVN_PROP_EXTERNALS); + + if (external_value) + { + const char *node_abspath; + const char *node_relpath = svn_sqlite__column_text(stmt, 1, NULL); + + node_abspath = svn_dirent_join(wcroot->abspath, node_relpath, + result_pool); + + svn_hash_sets(*externals, node_abspath, + apr_pstrdup(result_pool, external_value)); + + if (depths) + { + svn_depth_t depth + = svn_sqlite__column_token_null(stmt, 2, depth_map, + svn_depth_unknown); + + svn_hash_sets(*depths, node_abspath, + /* Use static string */ + svn_token__to_word(depth_map, depth)); + } + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + svn_pool_destroy(iterpool); + + return svn_error_trace(svn_error_compose_create(err, + svn_sqlite__reset(stmt))); +} + +/* Copy the ACTUAL data for SRC_RELPATH and tweak it to refer to DST_RELPATH. + The new ACTUAL data won't have any conflicts. */ +static svn_error_t * +copy_actual(svn_wc__db_wcroot_t *src_wcroot, + const char *src_relpath, + svn_wc__db_wcroot_t *dst_wcroot, + const char *dst_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", src_wcroot->wc_id, src_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + apr_size_t props_size; + const char *changelist; + const char *properties; + + /* Skipping conflict data... */ + changelist = svn_sqlite__column_text(stmt, 0, scratch_pool); + /* No need to parse the properties when simply copying. */ + properties = svn_sqlite__column_blob(stmt, 1, &props_size, scratch_pool); + + if (changelist || properties) + { + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb, + STMT_INSERT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "issbs", + dst_wcroot->wc_id, dst_relpath, + svn_relpath_dirname(dst_relpath, scratch_pool), + properties, props_size, changelist)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +/* Helper for svn_wc__db_op_copy to handle copying from one db to + another */ +static svn_error_t * +cross_db_copy(svn_wc__db_wcroot_t *src_wcroot, + const char *src_relpath, + svn_wc__db_wcroot_t *dst_wcroot, + const char *dst_relpath, + svn_wc__db_status_t dst_status, + int dst_op_depth, + int dst_np_op_depth, + svn_node_kind_t kind, + const apr_array_header_t *children, + apr_int64_t copyfrom_id, + const char *copyfrom_relpath, + svn_revnum_t copyfrom_rev, + apr_pool_t *scratch_pool) +{ + insert_working_baton_t iwb; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + const svn_checksum_t *checksum; + apr_hash_t *props; + svn_depth_t depth; + + SVN_ERR_ASSERT(kind == svn_node_file + || kind == svn_node_dir + ); + + SVN_ERR(read_info(NULL, NULL, NULL, NULL, NULL, + &changed_rev, &changed_date, &changed_author, &depth, + &checksum, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + src_wcroot, src_relpath, scratch_pool, scratch_pool)); + + SVN_ERR(db_read_pristine_props(&props, src_wcroot, src_relpath, FALSE, + scratch_pool, scratch_pool)); + + blank_iwb(&iwb); + iwb.presence = dst_status; + iwb.kind = kind; + + iwb.props = props; + iwb.changed_rev = changed_rev; + iwb.changed_date = changed_date; + iwb.changed_author = changed_author; + iwb.original_repos_id = copyfrom_id; + iwb.original_repos_relpath = copyfrom_relpath; + iwb.original_revnum = copyfrom_rev; + iwb.moved_here = FALSE; + + iwb.op_depth = dst_op_depth; + + iwb.checksum = checksum; + iwb.children = children; + iwb.depth = depth; + + iwb.not_present_op_depth = dst_np_op_depth; + + SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath, scratch_pool)); + + SVN_ERR(copy_actual(src_wcroot, src_relpath, + dst_wcroot, dst_relpath, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Helper for scan_deletion_txn. Extracts the moved-to information, if + any, from STMT. Sets *SCAN to FALSE if moved-to was available. */ +static svn_error_t * +get_moved_to(const char **moved_to_relpath_p, + const char **moved_to_op_root_relpath_p, + svn_boolean_t *scan, + svn_sqlite__stmt_t *stmt, + const char *current_relpath, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *moved_to_relpath = svn_sqlite__column_text(stmt, 3, NULL); + + if (moved_to_relpath) + { + const char *moved_to_op_root_relpath = moved_to_relpath; + + if (strcmp(current_relpath, local_relpath)) + { + /* LOCAL_RELPATH is a child inside the move op-root. */ + const char *moved_child_relpath; + + /* The CURRENT_RELPATH is the op_root of the delete-half of + * the move. LOCAL_RELPATH is a child that was moved along. + * Compute the child's new location within the move target. */ + moved_child_relpath = svn_relpath_skip_ancestor(current_relpath, + local_relpath); + SVN_ERR_ASSERT(moved_child_relpath && + strlen(moved_child_relpath) > 0); + moved_to_relpath = svn_relpath_join(moved_to_op_root_relpath, + moved_child_relpath, + result_pool); + } + + if (moved_to_op_root_relpath && moved_to_op_root_relpath_p) + *moved_to_op_root_relpath_p + = apr_pstrdup(result_pool, moved_to_op_root_relpath); + + if (moved_to_relpath && moved_to_relpath_p) + *moved_to_relpath_p + = apr_pstrdup(result_pool, moved_to_relpath); + + *scan = FALSE; + } + + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_scan_deletion(). + */ +static svn_error_t * +scan_deletion_txn(const char **base_del_relpath, + const char **moved_to_relpath, + const char **work_del_relpath, + const char **moved_to_op_root_relpath, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *current_relpath = local_relpath; + svn_sqlite__stmt_t *stmt; + svn_wc__db_status_t work_presence; + svn_boolean_t have_row, scan, have_base; + int op_depth; + + /* Initialize all the OUT parameters. */ + if (base_del_relpath != NULL) + *base_del_relpath = NULL; + if (moved_to_relpath != NULL) + *moved_to_relpath = NULL; + if (work_del_relpath != NULL) + *work_del_relpath = NULL; + if (moved_to_op_root_relpath != NULL) + *moved_to_op_root_relpath = NULL; + + /* If looking for moved-to info then we need to scan every path + until we find it. If not looking for moved-to we only need to + check op-roots and parents of op-roots. */ + scan = (moved_to_op_root_relpath || moved_to_relpath); + + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + scan ? STMT_SELECT_DELETION_INFO_SCAN + : STMT_SELECT_DELETION_INFO)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, current_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + work_presence = svn_sqlite__column_token(stmt, 1, presence_map); + have_base = !svn_sqlite__column_is_null(stmt, 0); + if (work_presence != svn_wc__db_status_not_present + && work_presence != svn_wc__db_status_base_deleted) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + svn_sqlite__reset(stmt), + _("Expected node '%s' to be deleted."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + op_depth = svn_sqlite__column_int(stmt, 2); + + /* Special case: LOCAL_RELPATH not-present within a WORKING tree, we + treat this as an op-root. At commit time we need to explicitly + delete such nodes otherwise they will be present in the + repository copy. */ + if (work_presence == svn_wc__db_status_not_present + && work_del_relpath && !*work_del_relpath) + { + *work_del_relpath = apr_pstrdup(result_pool, current_relpath); + + if (!scan && !base_del_relpath) + { + /* We have all we need, exit early */ + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + } + + + while (TRUE) + { + svn_error_t *err; + const char *parent_relpath; + int current_depth = relpath_depth(current_relpath); + + /* Step CURRENT_RELPATH to op-root */ + + while (TRUE) + { + if (scan) + { + err = get_moved_to(moved_to_relpath, moved_to_op_root_relpath, + &scan, stmt, current_relpath, + wcroot, local_relpath, + result_pool, scratch_pool); + if (err || (!scan + && !base_del_relpath + && !work_del_relpath)) + { + /* We have all we need (or an error occurred) */ + SVN_ERR(svn_sqlite__reset(stmt)); + return svn_error_trace(err); + } + } + + if (current_depth <= op_depth) + break; + + current_relpath = svn_relpath_dirname(current_relpath, scratch_pool); + --current_depth; + + if (scan || current_depth == op_depth) + { + SVN_ERR(svn_sqlite__reset(stmt)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + current_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR_ASSERT(have_row); + have_base = !svn_sqlite__column_is_null(stmt, 0); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Now CURRENT_RELPATH is an op-root, have a look at the parent. */ + + SVN_ERR_ASSERT(current_relpath[0] != '\0'); /* Catch invalid data */ + parent_relpath = svn_relpath_dirname(current_relpath, scratch_pool); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, parent_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + { + /* No row means no WORKING node which mean we just fell off + the WORKING tree, so CURRENT_RELPATH is the op-root + closest to the wc root. */ + if (have_base && base_del_relpath) + *base_del_relpath = apr_pstrdup(result_pool, current_relpath); + break; + } + + /* Still in the WORKING tree so the first time we get here + CURRENT_RELPATH is a delete op-root in the WORKING tree. */ + if (work_del_relpath && !*work_del_relpath) + { + *work_del_relpath = apr_pstrdup(result_pool, current_relpath); + + if (!scan && !base_del_relpath) + break; /* We have all we need */ + } + + current_relpath = parent_relpath; + op_depth = svn_sqlite__column_int(stmt, 2); + have_base = !svn_sqlite__column_is_null(stmt, 0); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_scan_deletion(const char **base_del_abspath, + const char **moved_to_abspath, + const char **work_del_abspath, + const char **moved_to_op_root_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *base_del_relpath, *moved_to_relpath, *work_del_relpath; + const char *moved_to_op_root_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + scan_deletion_txn(&base_del_relpath, &moved_to_relpath, + &work_del_relpath, &moved_to_op_root_relpath, + wcroot, local_relpath, result_pool, scratch_pool), + wcroot); + + if (base_del_abspath) + { + *base_del_abspath = (base_del_relpath + ? svn_dirent_join(wcroot->abspath, + base_del_relpath, result_pool) + : NULL); + } + if (moved_to_abspath) + { + *moved_to_abspath = (moved_to_relpath + ? svn_dirent_join(wcroot->abspath, + moved_to_relpath, result_pool) + : NULL); + } + if (work_del_abspath) + { + *work_del_abspath = (work_del_relpath + ? svn_dirent_join(wcroot->abspath, + work_del_relpath, result_pool) + : NULL); + } + if (moved_to_op_root_abspath) + { + *moved_to_op_root_abspath = (moved_to_op_root_relpath + ? svn_dirent_join(wcroot->abspath, + moved_to_op_root_relpath, + result_pool) + : NULL); + } + + return SVN_NO_ERROR; +} + + +/* Set *COPYFROM_ID, *COPYFROM_RELPATH, *COPYFROM_REV to the values + appropriate for the copy. Also return *STATUS, *KIND and *HAVE_WORK, *OP_ROOT + since they are available. This is a helper for + svn_wc__db_op_copy. */ +static svn_error_t * +get_info_for_copy(apr_int64_t *copyfrom_id, + const char **copyfrom_relpath, + svn_revnum_t *copyfrom_rev, + svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_boolean_t *op_root, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *repos_relpath; + svn_revnum_t revision; + svn_wc__db_status_t node_status; + apr_int64_t repos_id; + svn_boolean_t is_op_root; + + SVN_ERR(read_info(&node_status, kind, &revision, &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, copyfrom_relpath, + copyfrom_id, copyfrom_rev, NULL, NULL, NULL, NULL, + NULL, &is_op_root, NULL, NULL, + NULL /* have_base */, + NULL /* have_more_work */, + NULL /* have_work */, + wcroot, local_relpath, result_pool, scratch_pool)); + + if (op_root) + *op_root = is_op_root; + + if (node_status == svn_wc__db_status_excluded) + { + /* The parent cannot be excluded, so look at the parent and then + adjust the relpath */ + const char *parent_relpath, *base_name; + + svn_dirent_split(&parent_relpath, &base_name, local_relpath, + scratch_pool); + SVN_ERR(get_info_for_copy(copyfrom_id, copyfrom_relpath, copyfrom_rev, + NULL, NULL, NULL, + wcroot, parent_relpath, + scratch_pool, scratch_pool)); + if (*copyfrom_relpath) + *copyfrom_relpath = svn_relpath_join(*copyfrom_relpath, base_name, + result_pool); + } + else if (node_status == svn_wc__db_status_added) + { + SVN_ERR(scan_addition(&node_status, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, wcroot, local_relpath, + scratch_pool, scratch_pool)); + } + else if (node_status == svn_wc__db_status_deleted && is_op_root) + { + const char *base_del_relpath, *work_del_relpath; + + SVN_ERR(scan_deletion_txn(&base_del_relpath, NULL, + &work_del_relpath, + NULL, wcroot, local_relpath, + scratch_pool, scratch_pool)); + if (work_del_relpath) + { + const char *op_root_relpath; + const char *parent_del_relpath = svn_relpath_dirname(work_del_relpath, + scratch_pool); + + /* Similar to, but not the same as, the _scan_addition and + _join above. Can we use get_copyfrom here? */ + SVN_ERR(scan_addition(NULL, &op_root_relpath, + NULL, NULL, /* repos_* */ + copyfrom_relpath, copyfrom_id, copyfrom_rev, + NULL, NULL, NULL, wcroot, parent_del_relpath, + scratch_pool, scratch_pool)); + *copyfrom_relpath + = svn_relpath_join(*copyfrom_relpath, + svn_relpath_skip_ancestor(op_root_relpath, + local_relpath), + result_pool); + } + else if (base_del_relpath) + { + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, copyfrom_rev, + copyfrom_relpath, + copyfrom_id, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wcroot, local_relpath, + result_pool, + scratch_pool)); + } + else + SVN_ERR_MALFUNCTION(); + } + else if (node_status == svn_wc__db_status_deleted) + { + /* Keep original_* from read_info() to allow seeing the difference + between base-deleted and not present */ + } + else + { + *copyfrom_relpath = repos_relpath; + *copyfrom_rev = revision; + *copyfrom_id = repos_id; + } + + if (status) + *status = node_status; + + return SVN_NO_ERROR; +} + + +/* Set *OP_DEPTH to the highest op depth of WCROOT:LOCAL_RELPATH. */ +static svn_error_t * +op_depth_of(int *op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR_ASSERT(have_row); + *op_depth = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + + +/* Determine at which OP_DEPTH a copy of COPYFROM_REPOS_ID, COPYFROM_RELPATH at + revision COPYFROM_REVISION should be inserted as LOCAL_RELPATH. Do this + by checking if this would be a direct child of a copy of its parent + directory. If it is then set *OP_DEPTH to the op_depth of its parent. + + If the node is not a direct copy at the same revision of the parent + *NP_OP_DEPTH will be set to the op_depth of the parent when a not-present + node should be inserted at this op_depth. This will be the case when the + parent already defined an incomplete child with the same name. Otherwise + *NP_OP_DEPTH will be set to -1. + + If the parent node is not the parent of the to be copied node, then + *OP_DEPTH will be set to the proper op_depth for a new operation root. + + Set *PARENT_OP_DEPTH to the op_depth of the parent. + + */ +static svn_error_t * +op_depth_for_copy(int *op_depth, + int *np_op_depth, + int *parent_op_depth, + apr_int64_t copyfrom_repos_id, + const char *copyfrom_relpath, + svn_revnum_t copyfrom_revision, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + const char *parent_relpath, *name; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int incomplete_op_depth = -1; + int min_op_depth = 1; /* Never touch BASE */ + + *op_depth = relpath_depth(local_relpath); + *np_op_depth = -1; + + svn_relpath_split(&parent_relpath, &name, local_relpath, scratch_pool); + *parent_op_depth = relpath_depth(parent_relpath); + + if (!copyfrom_relpath) + return SVN_NO_ERROR; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + svn_wc__db_status_t status = svn_sqlite__column_token(stmt, 1, + presence_map); + + min_op_depth = svn_sqlite__column_int(stmt, 0); + if (status == svn_wc__db_status_incomplete) + incomplete_op_depth = min_op_depth; + } + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, parent_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + svn_wc__db_status_t presence = svn_sqlite__column_token(stmt, 1, + presence_map); + + *parent_op_depth = svn_sqlite__column_int(stmt, 0); + if (*parent_op_depth < min_op_depth) + { + /* We want to create a copy; not overwrite the lower layers */ + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + + /* You can only add children below a node that exists. + In WORKING that must be status added, which is represented + as presence normal */ + SVN_ERR_ASSERT(presence == svn_wc__db_status_normal); + + if ((incomplete_op_depth < 0) + || (incomplete_op_depth == *parent_op_depth)) + { + apr_int64_t parent_copyfrom_repos_id + = svn_sqlite__column_int64(stmt, 10); + const char *parent_copyfrom_relpath + = svn_sqlite__column_text(stmt, 11, NULL); + svn_revnum_t parent_copyfrom_revision + = svn_sqlite__column_revnum(stmt, 12); + + if (parent_copyfrom_repos_id == copyfrom_repos_id) + { + if (copyfrom_revision == parent_copyfrom_revision + && !strcmp(copyfrom_relpath, + svn_relpath_join(parent_copyfrom_relpath, name, + scratch_pool))) + *op_depth = *parent_op_depth; + else if (incomplete_op_depth > 0) + *np_op_depth = incomplete_op_depth; + } + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + + +/* Like svn_wc__db_op_copy(), but with WCROOT+LOCAL_RELPATH + * instead of DB+LOCAL_ABSPATH. A non-zero MOVE_OP_DEPTH implies that the + * copy operation is part of a move, and indicates the op-depth of the + * move destination op-root. */ +static svn_error_t * +db_op_copy(svn_wc__db_wcroot_t *src_wcroot, + const char *src_relpath, + svn_wc__db_wcroot_t *dst_wcroot, + const char *dst_relpath, + const svn_skel_t *work_items, + int move_op_depth, + apr_pool_t *scratch_pool) +{ + const char *copyfrom_relpath; + svn_revnum_t copyfrom_rev; + svn_wc__db_status_t status; + svn_wc__db_status_t dst_presence; + svn_boolean_t op_root; + apr_int64_t copyfrom_id; + int dst_op_depth; + int dst_np_op_depth; + int dst_parent_op_depth; + svn_node_kind_t kind; + const apr_array_header_t *children; + + SVN_ERR(get_info_for_copy(©from_id, ©from_relpath, ©from_rev, + &status, &kind, &op_root, src_wcroot, + src_relpath, scratch_pool, scratch_pool)); + + SVN_ERR(op_depth_for_copy(&dst_op_depth, &dst_np_op_depth, + &dst_parent_op_depth, + copyfrom_id, copyfrom_relpath, copyfrom_rev, + dst_wcroot, dst_relpath, scratch_pool)); + + SVN_ERR_ASSERT(kind == svn_node_file || kind == svn_node_dir); + + /* ### New status, not finished, see notes/wc-ng/copying */ + switch (status) + { + case svn_wc__db_status_normal: + case svn_wc__db_status_added: + case svn_wc__db_status_moved_here: + case svn_wc__db_status_copied: + dst_presence = svn_wc__db_status_normal; + break; + case svn_wc__db_status_deleted: + if (op_root) + { + /* If the lower layer is already shadowcopied we can skip adding + a not present node. */ + svn_error_t *err; + svn_wc__db_status_t dst_status; + + err = read_info(&dst_status, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + dst_wcroot, dst_relpath, scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + svn_error_clear(err); + else + return svn_error_trace(err); + } + else if (dst_status == svn_wc__db_status_deleted) + { + /* Node is already deleted; skip the NODES work, but do + install wq items if requested */ + SVN_ERR(add_work_items(dst_wcroot->sdb, work_items, + scratch_pool)); + return SVN_NO_ERROR; + } + } + else + { + /* This node is either a not-present node (which should be copied), or + a base-delete of some lower layer (which shouldn't). + Subversion <= 1.7 always added a not-present node here, which is + safe (as it postpones the hard work until commit time and then we + ask the repository), but it breaks some move scenarios. + */ + + if (! copyfrom_relpath) + { + SVN_ERR(add_work_items(dst_wcroot->sdb, work_items, + scratch_pool)); + return SVN_NO_ERROR; + } + + /* Fall through. Install not present node */ + } + case svn_wc__db_status_not_present: + case svn_wc__db_status_excluded: + /* These presence values should not create a new op depth */ + if (dst_np_op_depth > 0) + { + dst_op_depth = dst_np_op_depth; + dst_np_op_depth = -1; + } + if (status == svn_wc__db_status_excluded) + dst_presence = svn_wc__db_status_excluded; + else + dst_presence = svn_wc__db_status_not_present; + break; + case svn_wc__db_status_server_excluded: + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot copy '%s' excluded by server"), + path_for_error_message(src_wcroot, + src_relpath, + scratch_pool)); + default: + /* Perhaps we should allow incomplete to incomplete? We can't + avoid incomplete working nodes as one step in copying a + directory is to add incomplete children. */ + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot handle status of '%s'"), + path_for_error_message(src_wcroot, + src_relpath, + scratch_pool)); + } + + if (kind == svn_node_dir) + { + int src_op_depth; + + SVN_ERR(op_depth_of(&src_op_depth, src_wcroot, src_relpath)); + SVN_ERR(gather_repo_children(&children, src_wcroot, src_relpath, + src_op_depth, scratch_pool, scratch_pool)); + } + else + children = NULL; + + if (src_wcroot == dst_wcroot) + { + svn_sqlite__stmt_t *stmt; + const char *dst_parent_relpath = svn_relpath_dirname(dst_relpath, + scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, + STMT_INSERT_WORKING_NODE_COPY_FROM)); + + SVN_ERR(svn_sqlite__bindf(stmt, "issdst", + src_wcroot->wc_id, src_relpath, + dst_relpath, + dst_op_depth, + dst_parent_relpath, + presence_map, dst_presence)); + + if (move_op_depth > 0) + { + if (relpath_depth(dst_relpath) == move_op_depth) + { + /* We're moving the root of the move operation. + * + * When an added node or the op-root of a copy is moved, + * there is no 'moved-from' corresponding to the moved-here + * node. So the net effect is the same as copy+delete. + * Perform a normal copy operation in these cases. */ + if (!(status == svn_wc__db_status_added || + (status == svn_wc__db_status_copied && op_root))) + SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1)); + } + else + { + svn_sqlite__stmt_t *info_stmt; + svn_boolean_t have_row; + + /* We're moving a child along with the root of the move. + * + * Set moved-here depending on dst_parent, propagating the + * above decision to moved-along children at the same op_depth. + * We can't use scan_addition() to detect moved-here because + * the delete-half of the move might not yet exist. */ + SVN_ERR(svn_sqlite__get_statement(&info_stmt, dst_wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(info_stmt, "is", dst_wcroot->wc_id, + dst_parent_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, info_stmt)); + SVN_ERR_ASSERT(have_row); + if (svn_sqlite__column_boolean(info_stmt, 15) && + dst_op_depth == dst_parent_op_depth) + { + SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1)); + SVN_ERR(svn_sqlite__reset(info_stmt)); + } + else + { + SVN_ERR(svn_sqlite__reset(info_stmt)); + + /* If the child has been moved into the tree we're moving, + * keep its moved-here bit set. */ + SVN_ERR(svn_sqlite__get_statement(&info_stmt, + dst_wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(info_stmt, "is", + dst_wcroot->wc_id, src_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, info_stmt)); + SVN_ERR_ASSERT(have_row); + if (svn_sqlite__column_boolean(info_stmt, 15)) + SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1)); + SVN_ERR(svn_sqlite__reset(info_stmt)); + } + } + } + + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* ### Copying changelist is OK for a move but what about a copy? */ + SVN_ERR(copy_actual(src_wcroot, src_relpath, + dst_wcroot, dst_relpath, scratch_pool)); + + if (dst_np_op_depth > 0) + { + /* We introduce a not-present node at the parent's op_depth to + properly start a new op-depth at our own op_depth. This marks + us as an op_root for commit and allows reverting just this + operation */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb, + STMT_INSERT_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtnt", + src_wcroot->wc_id, dst_relpath, + dst_np_op_depth, dst_parent_relpath, + copyfrom_id, copyfrom_relpath, + copyfrom_rev, + presence_map, + svn_wc__db_status_not_present, + /* NULL */ + kind_map, kind)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + /* Insert incomplete children, if relevant. + The children are part of the same op and so have the same op_depth. + (The only time we'd want a different depth is during a recursive + simple add, but we never insert children here during a simple add.) */ + if (kind == svn_node_dir + && dst_presence == svn_wc__db_status_normal) + SVN_ERR(insert_incomplete_children( + dst_wcroot->sdb, + dst_wcroot->wc_id, + dst_relpath, + copyfrom_id, + copyfrom_relpath, + copyfrom_rev, + children, + dst_op_depth, + scratch_pool)); + } + else + { + SVN_ERR(cross_db_copy(src_wcroot, src_relpath, dst_wcroot, + dst_relpath, dst_presence, dst_op_depth, + dst_np_op_depth, kind, + children, copyfrom_id, copyfrom_relpath, + copyfrom_rev, scratch_pool)); + } + + SVN_ERR(add_work_items(dst_wcroot->sdb, work_items, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Baton for passing args to op_copy_txn(). */ +struct op_copy_baton +{ + svn_wc__db_wcroot_t *src_wcroot; + const char *src_relpath; + + svn_wc__db_wcroot_t *dst_wcroot; + const char *dst_relpath; + + const svn_skel_t *work_items; + + svn_boolean_t is_move; + const char *dst_op_root_relpath; +}; + +/* Helper for svn_wc__db_op_copy(). + * + * Implements svn_sqlite__transaction_callback_t. */ +static svn_error_t * +op_copy_txn(void * baton, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + struct op_copy_baton *ocb = baton; + int move_op_depth; + + if (sdb != ocb->dst_wcroot->sdb) + { + /* Source and destination databases differ; so also start a lock + in the destination database, by calling ourself in a lock. */ + + return svn_error_trace( + svn_sqlite__with_lock(ocb->dst_wcroot->sdb, + op_copy_txn, ocb, scratch_pool)); + } + + /* From this point we can assume a lock in the src and dst databases */ + + if (ocb->is_move) + move_op_depth = relpath_depth(ocb->dst_op_root_relpath); + else + move_op_depth = 0; + + SVN_ERR(db_op_copy(ocb->src_wcroot, ocb->src_relpath, + ocb->dst_wcroot, ocb->dst_relpath, + ocb->work_items, move_op_depth, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_copy(svn_wc__db_t *db, + const char *src_abspath, + const char *dst_abspath, + const char *dst_op_root_abspath, + svn_boolean_t is_move, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + struct op_copy_baton ocb = {0}; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_op_root_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.src_wcroot, + &ocb.src_relpath, db, + src_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(ocb.src_wcroot); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.dst_wcroot, + &ocb.dst_relpath, + db, dst_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(ocb.dst_wcroot); + + ocb.work_items = work_items; + ocb.is_move = is_move; + ocb.dst_op_root_relpath = svn_dirent_skip_ancestor(ocb.dst_wcroot->abspath, + dst_op_root_abspath); + + /* Call with the sdb in src_wcroot. It might call itself again to + also obtain a lock in dst_wcroot */ + SVN_ERR(svn_sqlite__with_lock(ocb.src_wcroot->sdb, op_copy_txn, &ocb, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* The txn body of svn_wc__db_op_handle_move_back */ +static svn_error_t * +handle_move_back(svn_boolean_t *moved_back, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const char *moved_from_relpath, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_wc__db_status_t status; + svn_boolean_t op_root; + svn_boolean_t have_more_work; + int from_op_depth = 0; + svn_boolean_t have_row; + svn_boolean_t different = FALSE; + + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + + SVN_ERR(svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &op_root, NULL, NULL, NULL, + &have_more_work, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (status != svn_wc__db_status_added || !op_root) + return SVN_NO_ERROR; + + /* We have two cases here: BASE-move-back and WORKING-move-back */ + if (have_more_work) + SVN_ERR(op_depth_of(&from_op_depth, wcroot, + svn_relpath_dirname(local_relpath, scratch_pool))); + else + from_op_depth = 0; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_BACK)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isdd", wcroot->wc_id, + local_relpath, + from_op_depth, + relpath_depth(local_relpath))); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + SVN_ERR_ASSERT(have_row); /* We checked that the node is an op-root */ + + { + svn_boolean_t moved_here = svn_sqlite__column_boolean(stmt, 9); + const char *moved_to = svn_sqlite__column_text(stmt, 10, NULL); + + if (!moved_here + || !moved_to + || strcmp(moved_to, moved_from_relpath)) + { + different = TRUE; + have_row = FALSE; + } + } + + while (have_row) + { + svn_wc__db_status_t upper_status; + svn_wc__db_status_t lower_status; + + upper_status = svn_sqlite__column_token(stmt, 1, presence_map); + + if (svn_sqlite__column_is_null(stmt, 5)) + { + /* No lower layer replaced. */ + if (upper_status != svn_wc__db_status_not_present) + { + different = TRUE; + break; + } + continue; + } + + lower_status = svn_sqlite__column_token(stmt, 5, presence_map); + + if (upper_status != lower_status) + { + different = TRUE; + break; + } + + if (upper_status == svn_wc__db_status_not_present + || upper_status == svn_wc__db_status_excluded) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + continue; /* Nothing to check */ + } + else if (upper_status != svn_wc__db_status_normal) + { + /* Not a normal move. Mixed revision move? */ + different = TRUE; + break; + } + + { + const char *upper_repos_relpath; + const char *lower_repos_relpath; + + upper_repos_relpath = svn_sqlite__column_text(stmt, 3, NULL); + lower_repos_relpath = svn_sqlite__column_text(stmt, 7, NULL); + + if (! upper_repos_relpath + || strcmp(upper_repos_relpath, lower_repos_relpath)) + { + different = TRUE; + break; + } + } + + { + svn_revnum_t upper_rev; + svn_revnum_t lower_rev; + + upper_rev = svn_sqlite__column_revnum(stmt, 4); + lower_rev = svn_sqlite__column_revnum(stmt, 8); + + if (upper_rev != lower_rev) + { + different = TRUE; + break; + } + } + + { + apr_int64_t upper_repos_id; + apr_int64_t lower_repos_id; + + upper_repos_id = svn_sqlite__column_int64(stmt, 2); + lower_repos_id = svn_sqlite__column_int64(stmt, 6); + + if (upper_repos_id != lower_repos_id) + { + different = TRUE; + break; + } + } + + /* Check moved_here? */ + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + if (! different) + { + /* Ok, we can now safely remove this complete move, because we + determined that it 100% matches the layer below it. */ + + /* ### We could copy the recorded timestamps from the higher to the + lower layer in an attempt to improve status performance, but + generally these values should be the same anyway as it was + a no-op move. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_MOVED_BACK)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, + relpath_depth(local_relpath))); + + SVN_ERR(svn_sqlite__step_done(stmt)); + + if (moved_back) + *moved_back = TRUE; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_handle_move_back(svn_boolean_t *moved_back, + svn_wc__db_t *db, + const char *local_abspath, + const char *moved_from_abspath, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *moved_from_relpath; + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (moved_back) + *moved_back = FALSE; + + moved_from_relpath = svn_dirent_skip_ancestor(wcroot->abspath, + moved_from_abspath); + + if (! local_relpath[0] + || !moved_from_relpath) + { + /* WC-Roots can't be moved */ + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + return SVN_NO_ERROR; + } + + SVN_WC__DB_WITH_TXN(handle_move_back(moved_back, wcroot, local_relpath, + moved_from_relpath, work_items, + scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* The recursive implementation of svn_wc__db_op_copy_shadowed_layer. + * + * A non-zero MOVE_OP_DEPTH implies that the copy operation is part of + * a move, and indicates the op-depth of the move destination op-root. */ +static svn_error_t * +db_op_copy_shadowed_layer(svn_wc__db_wcroot_t *src_wcroot, + const char *src_relpath, + int src_op_depth, + svn_wc__db_wcroot_t *dst_wcroot, + const char *dst_relpath, + int dst_op_depth, + int del_op_depth, + apr_int64_t repos_id, + const char *repos_relpath, + svn_revnum_t revision, + int move_op_depth, + apr_pool_t *scratch_pool) +{ + const apr_array_header_t *children; + apr_pool_t *iterpool; + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_revnum_t node_revision; + const char *node_repos_relpath; + apr_int64_t node_repos_id; + svn_sqlite__stmt_t *stmt; + svn_wc__db_status_t dst_presence; + int i; + + { + svn_error_t *err; + err = svn_wc__db_depth_get_info(&status, &kind, &node_revision, + &node_repos_relpath, &node_repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, + src_wcroot, src_relpath, src_op_depth, + 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); + return SVN_NO_ERROR; /* There is no shadowed node at src_op_depth */ + } + } + + if (src_op_depth == 0) + { + /* If the node is switched or has a different revision then its parent + we shouldn't copy it. (We can't as we would have to insert it at + an unshadowed depth) */ + if (status == svn_wc__db_status_not_present + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_server_excluded + || node_revision != revision + || node_repos_id != repos_id + || strcmp(node_repos_relpath, repos_relpath)) + { + /* Add a not-present node in the destination wcroot */ + struct insert_working_baton_t iwb; + const char *repos_root_url; + const char *repos_uuid; + + SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, &repos_uuid, + src_wcroot->sdb, node_repos_id, + scratch_pool)); + + SVN_ERR(create_repos_id(&node_repos_id, repos_root_url, repos_uuid, + dst_wcroot->sdb, scratch_pool)); + + blank_iwb(&iwb); + + iwb.op_depth = dst_op_depth; + if (status != svn_wc__db_status_excluded) + iwb.presence = svn_wc__db_status_not_present; + else + iwb.presence = svn_wc__db_status_excluded; + + iwb.kind = kind; + + iwb.original_repos_id = node_repos_id; + iwb.original_revnum = node_revision; + iwb.original_repos_relpath = node_repos_relpath; + + SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath, + scratch_pool)); + + return SVN_NO_ERROR; + } + } + + iterpool = svn_pool_create(scratch_pool); + + switch (status) + { + case svn_wc__db_status_normal: + case svn_wc__db_status_added: + case svn_wc__db_status_moved_here: + case svn_wc__db_status_copied: + dst_presence = svn_wc__db_status_normal; + break; + case svn_wc__db_status_deleted: + case svn_wc__db_status_not_present: + dst_presence = svn_wc__db_status_not_present; + break; + case svn_wc__db_status_excluded: + dst_presence = svn_wc__db_status_excluded; + break; + case svn_wc__db_status_server_excluded: + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot copy '%s' excluded by server"), + path_for_error_message(src_wcroot, + src_relpath, + scratch_pool)); + default: + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot handle status of '%s'"), + path_for_error_message(src_wcroot, + src_relpath, + scratch_pool)); + } + + if (dst_presence == svn_wc__db_status_normal + && src_wcroot == dst_wcroot) /* ### Remove limitation */ + { + SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, + STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH)); + + SVN_ERR(svn_sqlite__bindf(stmt, "issdstd", + src_wcroot->wc_id, src_relpath, + dst_relpath, + dst_op_depth, + svn_relpath_dirname(dst_relpath, iterpool), + presence_map, dst_presence, + src_op_depth)); + + /* moved_here */ + if (dst_op_depth == move_op_depth) + SVN_ERR(svn_sqlite__bind_int(stmt, 8, TRUE)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + + { + /* And mark it deleted to allow proper shadowing */ + struct insert_working_baton_t iwb; + + blank_iwb(&iwb); + + iwb.op_depth = del_op_depth; + iwb.presence = svn_wc__db_status_base_deleted; + + iwb.kind = kind; + + SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath, + scratch_pool)); + } + } + else + { + struct insert_working_baton_t iwb; + if (dst_presence == svn_wc__db_status_normal) /* Fallback for multi-db */ + dst_presence = svn_wc__db_status_not_present; + + /* And mark it deleted to allow proper shadowing */ + + blank_iwb(&iwb); + + iwb.op_depth = dst_op_depth; + iwb.presence = dst_presence; + iwb.kind = kind; + + SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath, + scratch_pool)); + } + + SVN_ERR(gather_repo_children(&children, src_wcroot, src_relpath, + src_op_depth, scratch_pool, iterpool)); + + for (i = 0; i < children->nelts; i++) + { + const char *name = APR_ARRAY_IDX(children, i, const char *); + const char *child_src_relpath; + const char *child_dst_relpath; + const char *child_repos_relpath = NULL; + + svn_pool_clear(iterpool); + child_src_relpath = svn_relpath_join(src_relpath, name, iterpool); + child_dst_relpath = svn_relpath_join(dst_relpath, name, iterpool); + + if (repos_relpath) + child_repos_relpath = svn_relpath_join(repos_relpath, name, iterpool); + + SVN_ERR(db_op_copy_shadowed_layer( + src_wcroot, child_src_relpath, src_op_depth, + dst_wcroot, child_dst_relpath, dst_op_depth, + del_op_depth, + repos_id, child_repos_relpath, revision, + move_op_depth, scratch_pool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Helper for svn_wc__db_op_copy_shadowed_layer(). + * + * Implements svn_sqlite__transaction_callback_t. */ +static svn_error_t * +op_copy_shadowed_layer_txn(void *baton, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + struct op_copy_baton *ocb = baton; + const char *src_parent_relpath; + const char *dst_parent_relpath; + int src_op_depth; + int dst_op_depth; + int del_op_depth; + const char *repos_relpath = NULL; + apr_int64_t repos_id = INVALID_REPOS_ID; + svn_revnum_t revision = SVN_INVALID_REVNUM; + + if (sdb != ocb->dst_wcroot->sdb) + { + /* Source and destination databases differ; so also start a lock + in the destination database, by calling ourself in a lock. */ + + return svn_error_trace( + svn_sqlite__with_lock(ocb->dst_wcroot->sdb, + op_copy_shadowed_layer_txn, + ocb, scratch_pool)); + } + + /* From this point we can assume a lock in the src and dst databases */ + + + /* src_relpath and dst_relpath can't be wcroot as we need their parents */ + SVN_ERR_ASSERT(*ocb->src_relpath && *ocb->dst_relpath); + + src_parent_relpath = svn_relpath_dirname(ocb->src_relpath, scratch_pool); + dst_parent_relpath = svn_relpath_dirname(ocb->dst_relpath, scratch_pool); + + /* src_parent must be status normal or added; get its op-depth */ + SVN_ERR(op_depth_of(&src_op_depth, ocb->src_wcroot, src_parent_relpath)); + + /* dst_parent must be status added; get its op-depth */ + SVN_ERR(op_depth_of(&dst_op_depth, ocb->dst_wcroot, dst_parent_relpath)); + + del_op_depth = relpath_depth(ocb->dst_relpath); + + /* Get some information from the parent */ + SVN_ERR(svn_wc__db_depth_get_info(NULL, NULL, &revision, &repos_relpath, + &repos_id, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + ocb->src_wcroot, + src_parent_relpath, src_op_depth, + scratch_pool, scratch_pool)); + + if (repos_relpath == NULL) + { + /* The node is a local addition and has no shadowed information */ + return SVN_NO_ERROR; + } + + /* And calculate the child repos relpath */ + repos_relpath = svn_relpath_join(repos_relpath, + svn_relpath_basename(ocb->src_relpath, + NULL), + scratch_pool); + + SVN_ERR(db_op_copy_shadowed_layer( + ocb->src_wcroot, ocb->src_relpath, src_op_depth, + ocb->dst_wcroot, ocb->dst_relpath, dst_op_depth, + del_op_depth, + repos_id, repos_relpath, revision, + (ocb->is_move ? dst_op_depth : 0), + scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_copy_shadowed_layer(svn_wc__db_t *db, + const char *src_abspath, + const char *dst_abspath, + svn_boolean_t is_move, + apr_pool_t *scratch_pool) +{ + struct op_copy_baton ocb = {0}; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.src_wcroot, + &ocb.src_relpath, db, + src_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(ocb.src_wcroot); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.dst_wcroot, + &ocb.dst_relpath, + db, dst_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(ocb.dst_wcroot); + + ocb.is_move = is_move; + ocb.dst_op_root_relpath = NULL; /* not used by op_copy_shadowed_layer_txn */ + + ocb.work_items = NULL; + + /* Call with the sdb in src_wcroot. It might call itself again to + also obtain a lock in dst_wcroot */ + SVN_ERR(svn_sqlite__with_lock(ocb.src_wcroot->sdb, + op_copy_shadowed_layer_txn, + &ocb, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* If there are any server-excluded base nodes then the copy must fail + as it's not possible to commit such a copy. + Return an error if there are any server-excluded nodes. */ +static svn_error_t * +catch_copy_of_server_excluded(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + const char *server_excluded_relpath; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_HAS_SERVER_EXCLUDED_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + server_excluded_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + return svn_error_createf(SVN_ERR_AUTHZ_UNREADABLE, NULL, + _("Cannot copy '%s' excluded by server"), + path_for_error_message(wcroot, + server_excluded_relpath, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_copy_dir(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const char *original_repos_relpath, + const char *original_root_url, + const char *original_uuid, + svn_revnum_t original_revision, + const apr_array_header_t *children, + svn_boolean_t is_move, + svn_depth_t depth, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_working_baton_t iwb; + int parent_op_depth; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(props != NULL); + /* ### any assertions for CHANGED_* ? */ + /* ### any assertions for ORIGINAL_* ? */ +#if 0 + SVN_ERR_ASSERT(children != NULL); +#endif + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + blank_iwb(&iwb); + + iwb.presence = svn_wc__db_status_normal; + iwb.kind = svn_node_dir; + + iwb.props = props; + iwb.changed_rev = changed_rev; + iwb.changed_date = changed_date; + iwb.changed_author = changed_author; + + if (original_root_url != NULL) + { + SVN_ERR(create_repos_id(&iwb.original_repos_id, + original_root_url, original_uuid, + wcroot->sdb, scratch_pool)); + iwb.original_repos_relpath = original_repos_relpath; + iwb.original_revnum = original_revision; + } + + /* ### Should we do this inside the transaction? */ + SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth, + &parent_op_depth, iwb.original_repos_id, + original_repos_relpath, original_revision, + wcroot, local_relpath, scratch_pool)); + + iwb.children = children; + iwb.depth = depth; + iwb.moved_here = is_move && (parent_op_depth == 0 || + iwb.op_depth == parent_op_depth); + + iwb.work_items = work_items; + iwb.conflict = conflict; + + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); + SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_copy_file(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const char *original_repos_relpath, + const char *original_root_url, + const char *original_uuid, + svn_revnum_t original_revision, + const svn_checksum_t *checksum, + svn_boolean_t update_actual_props, + const apr_hash_t *new_actual_props, + svn_boolean_t is_move, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_working_baton_t iwb; + int parent_op_depth; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(props != NULL); + /* ### any assertions for CHANGED_* ? */ + SVN_ERR_ASSERT((! original_repos_relpath && ! original_root_url + && ! original_uuid && ! checksum + && original_revision == SVN_INVALID_REVNUM) + || (original_repos_relpath && original_root_url + && original_uuid && checksum + && original_revision != SVN_INVALID_REVNUM)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + blank_iwb(&iwb); + + iwb.presence = svn_wc__db_status_normal; + iwb.kind = svn_node_file; + + iwb.props = props; + iwb.changed_rev = changed_rev; + iwb.changed_date = changed_date; + iwb.changed_author = changed_author; + + if (original_root_url != NULL) + { + SVN_ERR(create_repos_id(&iwb.original_repos_id, + original_root_url, original_uuid, + wcroot->sdb, scratch_pool)); + iwb.original_repos_relpath = original_repos_relpath; + iwb.original_revnum = original_revision; + } + + /* ### Should we do this inside the transaction? */ + SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth, + &parent_op_depth, iwb.original_repos_id, + original_repos_relpath, original_revision, + wcroot, local_relpath, scratch_pool)); + + iwb.checksum = checksum; + iwb.moved_here = is_move && (parent_op_depth == 0 || + iwb.op_depth == parent_op_depth); + + if (update_actual_props) + { + iwb.update_actual_props = update_actual_props; + iwb.new_actual_props = new_actual_props; + } + + iwb.work_items = work_items; + iwb.conflict = conflict; + + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_copy_symlink(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const char *original_repos_relpath, + const char *original_root_url, + const char *original_uuid, + svn_revnum_t original_revision, + const char *target, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_working_baton_t iwb; + int parent_op_depth; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(props != NULL); + /* ### any assertions for CHANGED_* ? */ + /* ### any assertions for ORIGINAL_* ? */ + SVN_ERR_ASSERT(target != NULL); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + blank_iwb(&iwb); + + iwb.presence = svn_wc__db_status_normal; + iwb.kind = svn_node_symlink; + + iwb.props = props; + iwb.changed_rev = changed_rev; + iwb.changed_date = changed_date; + iwb.changed_author = changed_author; + iwb.moved_here = FALSE; + + if (original_root_url != NULL) + { + SVN_ERR(create_repos_id(&iwb.original_repos_id, + original_root_url, original_uuid, + wcroot->sdb, scratch_pool)); + iwb.original_repos_relpath = original_repos_relpath; + iwb.original_revnum = original_revision; + } + + /* ### Should we do this inside the transaction? */ + SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth, + &parent_op_depth, iwb.original_repos_id, + original_repos_relpath, original_revision, + wcroot, local_relpath, scratch_pool)); + + iwb.target = target; + + iwb.work_items = work_items; + iwb.conflict = conflict; + + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_add_directory(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *dir_abspath; + const char *name; + insert_working_baton_t iwb; + + /* Resolve wcroot via parent directory to avoid obstruction handling */ + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + blank_iwb(&iwb); + + local_relpath = svn_relpath_join(local_relpath, name, scratch_pool); + iwb.presence = svn_wc__db_status_normal; + iwb.kind = svn_node_dir; + iwb.op_depth = relpath_depth(local_relpath); + if (props && apr_hash_count((apr_hash_t *)props)) + { + iwb.update_actual_props = TRUE; + iwb.new_actual_props = props; + } + + iwb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); + /* Use depth infinity to make sure we have no invalid cached information + * about children of this dir. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_add_file(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_working_baton_t iwb; + const char *dir_abspath; + const char *name; + + /* Resolve wcroot via parent directory to avoid obstruction handling */ + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + blank_iwb(&iwb); + + local_relpath = svn_relpath_join(local_relpath, name, scratch_pool); + iwb.presence = svn_wc__db_status_normal; + iwb.kind = svn_node_file; + iwb.op_depth = relpath_depth(local_relpath); + if (props && apr_hash_count((apr_hash_t *)props)) + { + iwb.update_actual_props = TRUE; + iwb.new_actual_props = props; + } + + iwb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_add_symlink(svn_wc__db_t *db, + const char *local_abspath, + const char *target, + const apr_hash_t *props, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_working_baton_t iwb; + const char *dir_abspath; + const char *name; + + /* Resolve wcroot via parent directory to avoid obstruction handling */ + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(target != NULL); + + svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + dir_abspath, scratch_pool, scratch_pool)); + + VERIFY_USABLE_WCROOT(wcroot); + + blank_iwb(&iwb); + + local_relpath = svn_relpath_join(local_relpath, name, scratch_pool); + iwb.presence = svn_wc__db_status_normal; + iwb.kind = svn_node_symlink; + iwb.op_depth = relpath_depth(local_relpath); + if (props && apr_hash_count((apr_hash_t *)props)) + { + iwb.update_actual_props = TRUE; + iwb.new_actual_props = props; + } + + iwb.target = target; + + iwb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Record RECORDED_SIZE and RECORDED_TIME into top layer in NODES */ +static svn_error_t * +db_record_fileinfo(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_int64_t recorded_size, + apr_int64_t recorded_time, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int affected_rows; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_NODE_FILEINFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isii", wcroot->wc_id, local_relpath, + recorded_size, recorded_time)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + SVN_ERR_ASSERT(affected_rows == 1); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_global_record_fileinfo(svn_wc__db_t *db, + const char *local_abspath, + svn_filesize_t recorded_size, + apr_time_t recorded_time, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(db_record_fileinfo(wcroot, local_relpath, + recorded_size, recorded_time, scratch_pool)); + + /* We *totally* monkeyed the entries. Toss 'em. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* Set the ACTUAL_NODE properties column for (WC_ID, LOCAL_RELPATH) to + * PROPS. + * + * Note: PROPS=NULL means the actual props are the same as the pristine + * props; to indicate no properties when the pristine has some props, + * PROPS must be an empty hash. */ +static svn_error_t * +set_actual_props(apr_int64_t wc_id, + const char *local_relpath, + apr_hash_t *props, + svn_sqlite__db_t *db, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int affected_rows; + + SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_UPDATE_ACTUAL_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, scratch_pool)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + if (affected_rows == 1 || !props) + return SVN_NO_ERROR; /* We are done */ + + /* We have to insert a row in ACTUAL */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_ACTUAL_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); + if (*local_relpath != '\0') + SVN_ERR(svn_sqlite__bind_text(stmt, 3, + svn_relpath_dirname(local_relpath, + scratch_pool))); + SVN_ERR(svn_sqlite__bind_properties(stmt, 4, props, scratch_pool)); + return svn_error_trace(svn_sqlite__step_done(stmt)); +} + + +/* The body of svn_wc__db_op_set_props(). + + Set the 'properties' column in the 'ACTUAL_NODE' table to BATON->props. + Create an entry in the ACTUAL table for the node if it does not yet + have one. + To specify no properties, BATON->props must be an empty hash, not NULL. + BATON is of type 'struct set_props_baton_t'. +*/ +static svn_error_t * +set_props_txn(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_hash_t *props, + svn_boolean_t clear_recorded_info, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + apr_hash_t *pristine_props; + + /* Check if the props are modified. If no changes, then wipe out the + ACTUAL props. PRISTINE_PROPS==NULL means that any + ACTUAL props are okay as provided, so go ahead and set them. */ + SVN_ERR(db_read_pristine_props(&pristine_props, wcroot, local_relpath, FALSE, + scratch_pool, scratch_pool)); + if (props && pristine_props) + { + apr_array_header_t *prop_diffs; + + SVN_ERR(svn_prop_diffs(&prop_diffs, props, pristine_props, + scratch_pool)); + if (prop_diffs->nelts == 0) + props = NULL; + } + + SVN_ERR(set_actual_props(wcroot->wc_id, local_relpath, + props, wcroot->sdb, scratch_pool)); + + if (clear_recorded_info) + { + SVN_ERR(db_record_fileinfo(wcroot, local_relpath, + SVN_INVALID_FILESIZE, 0, + scratch_pool)); + } + + /* And finally. */ + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + if (conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_set_props(svn_wc__db_t *db, + const char *local_abspath, + apr_hash_t *props, + svn_boolean_t clear_recorded_info, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(set_props_txn(wcroot, local_relpath, props, + clear_recorded_info, conflict, work_items, + scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_modified(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + NOT_IMPLEMENTED(); +} + +/* */ +static svn_error_t * +populate_targets_tree(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_depth_t depth, + const apr_array_header_t *changelist_filter, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int affected_rows = 0; + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_TARGETS_LIST)); + + if (changelist_filter && changelist_filter->nelts > 0) + { + /* Iterate over the changelists, adding the nodes which match. + Common case: we only have one changelist, so this only + happens once. */ + int i; + int stmt_idx; + + switch (depth) + { + case svn_depth_empty: + stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST; + break; + + case svn_depth_files: + stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES; + break; + + case svn_depth_immediates: + stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES; + break; + + case svn_depth_infinity: + stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY; + break; + + default: + /* We don't know how to handle unknown or exclude. */ + SVN_ERR_MALFUNCTION(); + break; + } + + for (i = 0; i < changelist_filter->nelts; i++) + { + int sub_affected; + const char *changelist = APR_ARRAY_IDX(changelist_filter, i, + const char *); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_TARGET_WITH_CHANGELIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, + local_relpath, changelist)); + SVN_ERR(svn_sqlite__update(&sub_affected, stmt)); + + /* If the root is matched by the changelist, we don't have to match + the children. As that tells us the root is a file */ + if (!sub_affected && depth > svn_depth_empty) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, + local_relpath, changelist)); + SVN_ERR(svn_sqlite__update(&sub_affected, stmt)); + } + + affected_rows += sub_affected; + } + } + else /* No changelist filtering */ + { + int stmt_idx; + int sub_affected; + + switch (depth) + { + case svn_depth_empty: + stmt_idx = STMT_INSERT_TARGET; + break; + + case svn_depth_files: + stmt_idx = STMT_INSERT_TARGET_DEPTH_FILES; + break; + + case svn_depth_immediates: + stmt_idx = STMT_INSERT_TARGET_DEPTH_IMMEDIATES; + break; + + case svn_depth_infinity: + stmt_idx = STMT_INSERT_TARGET_DEPTH_INFINITY; + break; + + default: + /* We don't know how to handle unknown or exclude. */ + SVN_ERR_MALFUNCTION(); + break; + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_TARGET)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__update(&sub_affected, stmt)); + affected_rows += sub_affected; + + if (depth > svn_depth_empty) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__update(&sub_affected, stmt)); + affected_rows += sub_affected; + } + } + + /* Does the target exist? */ + if (affected_rows == 0) + { + svn_boolean_t exists; + SVN_ERR(does_node_exist(&exists, wcroot, local_relpath)); + + if (!exists) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +#if 0 +static svn_error_t * +dump_targets(svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_TARGETS)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *target = svn_sqlite__column_text(stmt, 0, NULL); + SVN_DBG(("Target: '%s'\n", target)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} +#endif + + +struct set_changelist_baton_t +{ + const char *new_changelist; + const apr_array_header_t *changelist_filter; + svn_depth_t depth; +}; + + +/* The main part of svn_wc__db_op_set_changelist(). + * + * Implements svn_wc__db_txn_callback_t. */ +static svn_error_t * +set_changelist_txn(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + struct set_changelist_baton_t *scb = baton; + svn_sqlite__stmt_t *stmt; + + SVN_ERR(populate_targets_tree(wcroot, local_relpath, scb->depth, + scb->changelist_filter, scratch_pool)); + + /* Ensure we have actual nodes for our targets. */ + if (scb->new_changelist) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_ACTUAL_EMPTIES)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Now create our notification table. */ + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_CHANGELIST_LIST)); + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_CHANGELIST_TRIGGER)); + + /* Update our changelists. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_ACTUAL_CHANGELISTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, + scb->new_changelist)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + if (scb->new_changelist) + { + /* We have to notify that we skipped directories, so do that now. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_MARK_SKIPPED_CHANGELIST_DIRS)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, + scb->new_changelist)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* We may have left empty ACTUAL nodes, so remove them. This is only a + potential problem if we removed changelists. */ + if (!scb->new_changelist) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_EMPTIES)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + return SVN_NO_ERROR; +} + + +/* Send notifications for svn_wc__db_op_set_changelist(). + * + * Implements work_callback_t. */ +static svn_error_t * +do_changelist_notify(void *baton, + svn_wc__db_wcroot_t *wcroot, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_CHANGELIST_LIST)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + /* ### wc_id is column 0. use it one day... */ + const char *notify_relpath = svn_sqlite__column_text(stmt, 1, NULL); + svn_wc_notify_action_t action = svn_sqlite__column_int(stmt, 2); + svn_wc_notify_t *notify; + const char *notify_abspath; + + svn_pool_clear(iterpool); + + if (cancel_func) + { + svn_error_t *err = cancel_func(cancel_baton); + + if (err) + return svn_error_trace(svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + } + + notify_abspath = svn_dirent_join(wcroot->abspath, notify_relpath, + iterpool); + notify = svn_wc_create_notify(notify_abspath, action, iterpool); + notify->changelist_name = svn_sqlite__column_text(stmt, 3, NULL); + notify_func(notify_baton, notify, iterpool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + + +svn_error_t * +svn_wc__db_op_set_changelist(svn_wc__db_t *db, + const char *local_abspath, + const char *new_changelist, + const apr_array_header_t *changelist_filter, + svn_depth_t depth, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + struct set_changelist_baton_t scb; + + scb.new_changelist = new_changelist; + scb.changelist_filter = changelist_filter; + scb.depth = depth; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* Flush the entries before we do the work. Even if no work is performed, + the flush isn't a problem. */ + SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool)); + + /* Perform the set-changelist operation (transactionally), perform any + notifications necessary, and then clean out our temporary tables. */ + return svn_error_trace(with_finalization(wcroot, local_relpath, + set_changelist_txn, &scb, + do_changelist_notify, NULL, + cancel_func, cancel_baton, + notify_func, notify_baton, + STMT_FINALIZE_CHANGELIST, + scratch_pool)); +} + +/* Implementation of svn_wc__db_op_mark_conflict() */ +svn_error_t * +svn_wc__db_mark_conflict_internal(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const svn_skel_t *conflict_skel, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t got_row; + svn_boolean_t is_complete; + + SVN_ERR(svn_wc__conflict_skel_is_complete(&is_complete, conflict_skel)); + SVN_ERR_ASSERT(is_complete); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (got_row) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_ACTUAL_CONFLICT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + } + else + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_ACTUAL_CONFLICT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + if (*local_relpath != '\0') + SVN_ERR(svn_sqlite__bind_text(stmt, 4, + svn_relpath_dirname(local_relpath, + scratch_pool))); + } + + { + svn_stringbuf_t *sb = svn_skel__unparse(conflict_skel, scratch_pool); + + SVN_ERR(svn_sqlite__bind_blob(stmt, 3, sb->data, sb->len)); + } + + SVN_ERR(svn_sqlite__update(NULL, stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_mark_conflict(svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflict_skel, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict_skel, scratch_pool)); + + /* ### Should be handled in the same transaction as setting the conflict */ + if (work_items) + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; + +} + +/* The body of svn_wc__db_op_mark_resolved(). + */ +static svn_error_t * +db_op_mark_resolved(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + svn_boolean_t resolved_text, + svn_boolean_t resolved_props, + svn_boolean_t resolved_tree, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int total_affected_rows = 0; + svn_boolean_t resolved_all; + apr_size_t conflict_len; + const void *conflict_data; + svn_skel_t *conflicts; + + /* Check if we have a conflict in ACTUAL */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (! have_row) + { + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (have_row) + return SVN_NO_ERROR; + + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + conflict_data = svn_sqlite__column_blob(stmt, 2, &conflict_len, + scratch_pool); + conflicts = svn_skel__parse(conflict_data, conflict_len, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_wc__conflict_skel_resolve(&resolved_all, conflicts, + db, wcroot->abspath, + resolved_text, + resolved_props ? "" : NULL, + resolved_tree, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_ACTUAL_CONFLICT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + if (! resolved_all) + { + svn_stringbuf_t *sb = svn_skel__unparse(conflicts, scratch_pool); + + SVN_ERR(svn_sqlite__bind_blob(stmt, 3, sb->data, sb->len)); + } + + SVN_ERR(svn_sqlite__update(&total_affected_rows, stmt)); + + /* Now, remove the actual node if it doesn't have any more useful + information. We only need to do this if we've remove data ourselves. */ + if (total_affected_rows > 0) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_EMPTY)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_mark_resolved(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t resolved_text, + svn_boolean_t resolved_props, + svn_boolean_t resolved_tree, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + db_op_mark_resolved(wcroot, local_relpath, db, + resolved_text, resolved_props, resolved_tree, + work_items, scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Clear moved-to information at the delete-half of the move which + * moved LOCAL_RELPATH here. This transforms the move into a simple delete. */ +static svn_error_t * +clear_moved_to(const char *local_relpath, + svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + const char *moved_from_relpath; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_FROM_RELPATH)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + { + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + + moved_from_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_TO_RELPATH)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + moved_from_relpath, + relpath_depth(moved_from_relpath))); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +/* One of the two alternative bodies of svn_wc__db_op_revert(). + * + * Implements svn_wc__db_txn_callback_t. */ +static svn_error_t * +op_revert_txn(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = baton; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int op_depth; + svn_boolean_t moved_here; + int affected_rows; + const char *moved_to; + + /* ### Similar structure to op_revert_recursive_txn, should they be + combined? */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + { + SVN_ERR(svn_sqlite__reset(stmt)); + + /* There was no NODE row, so attempt to delete an ACTUAL_NODE row. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + if (affected_rows) + { + /* Can't do non-recursive actual-only revert if actual-only + children exist. Raise an error to cancel the transaction. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_ACTUAL_HAS_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + return svn_error_createf(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, + _("Can't revert '%s' without" + " reverting children"), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + return SVN_NO_ERROR; + } + + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + op_depth = svn_sqlite__column_int(stmt, 0); + moved_here = svn_sqlite__column_boolean(stmt, 15); + moved_to = svn_sqlite__column_text(stmt, 17, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (moved_to) + { + SVN_ERR(svn_wc__db_resolve_break_moved_away_internal(wcroot, + local_relpath, + scratch_pool)); + } + else + { + svn_skel_t *conflict; + + SVN_ERR(svn_wc__db_read_conflict_internal(&conflict, wcroot, + local_relpath, + scratch_pool, scratch_pool)); + if (conflict) + { + svn_wc_operation_t operation; + svn_boolean_t tree_conflicted; + + SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, NULL, + &tree_conflicted, + db, wcroot->abspath, + conflict, + scratch_pool, scratch_pool)); + if (tree_conflicted + && (operation == svn_wc_operation_update + || operation == svn_wc_operation_switch)) + { + svn_wc_conflict_reason_t reason; + svn_wc_conflict_action_t action; + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, + NULL, + db, wcroot->abspath, + conflict, + scratch_pool, + scratch_pool)); + + if (reason == svn_wc_conflict_reason_deleted) + SVN_ERR(svn_wc__db_resolve_delete_raise_moved_away( + db, svn_dirent_join(wcroot->abspath, local_relpath, + scratch_pool), + NULL, NULL /* ### How do we notify this? */, + scratch_pool)); + } + } + } + + if (op_depth > 0 && op_depth == relpath_depth(local_relpath)) + { + /* Can't do non-recursive revert if children exist */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_GE_OP_DEPTH_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + return svn_error_createf(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, + _("Can't revert '%s' without" + " reverting children"), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + + /* Rewrite the op-depth of all deleted children making the + direct children into roots of deletes. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* ### This removes the lock, but what about the access baton? */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WC_LOCK_ORPHAN)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* If this node was moved-here, clear moved-to at the move source. */ + if (moved_here) + SVN_ERR(clear_moved_to(local_relpath, wcroot, scratch_pool)); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + if (!affected_rows) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + } + + return SVN_NO_ERROR; +} + + +/* One of the two alternative bodies of svn_wc__db_op_revert(). + * + * Implements svn_wc__db_txn_callback_t. */ +static svn_error_t * +op_revert_recursive_txn(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int op_depth; + int select_op_depth; + svn_boolean_t moved_here; + int affected_rows; + apr_pool_t *iterpool; + + /* ### Similar structure to op_revert_txn, should they be + combined? */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + { + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + if (affected_rows) + return SVN_NO_ERROR; /* actual-only revert */ + + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + op_depth = svn_sqlite__column_int(stmt, 0); + moved_here = svn_sqlite__column_boolean(stmt, 15); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (op_depth > 0 && op_depth != relpath_depth(local_relpath)) + return svn_error_createf(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, + _("Can't revert '%s' without" + " reverting parent"), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + + /* Remove moved-here from move destinations outside the tree. */ + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, STMT_SELECT_MOVED_OUTSIDE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *move_src_relpath = svn_sqlite__column_text(stmt, 0, NULL); + svn_error_t *err; + + err = svn_wc__db_resolve_break_moved_away_internal(wcroot, + move_src_relpath, + scratch_pool); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Don't delete BASE nodes */ + select_op_depth = op_depth ? op_depth : 1; + + /* Reverting any non wc-root node */ + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, select_op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* ### This removes the locks, but what about the access batons? */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_HERE_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + const char *moved_here_child_relpath; + svn_error_t *err; + + svn_pool_clear(iterpool); + + moved_here_child_relpath = svn_sqlite__column_text(stmt, 0, iterpool); + err = clear_moved_to(moved_here_child_relpath, wcroot, iterpool); + if (err) + return svn_error_trace(svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + svn_pool_destroy(iterpool); + + /* Clear potential moved-to pointing at the target node itself. */ + if (op_depth > 0 && op_depth == relpath_depth(local_relpath) + && moved_here) + SVN_ERR(clear_moved_to(local_relpath, wcroot, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_revert(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + struct with_triggers_baton_t wtb = { STMT_CREATE_REVERT_LIST, + STMT_DROP_REVERT_LIST_TRIGGERS, + NULL, NULL}; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + switch (depth) + { + case svn_depth_empty: + wtb.cb_func = op_revert_txn; + wtb.cb_baton = db; + break; + case svn_depth_infinity: + wtb.cb_func = op_revert_recursive_txn; + break; + default: + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Unsupported depth for revert of '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(with_triggers(&wtb, wcroot, local_relpath, scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_revert_list_read(). + */ +static svn_error_t * +revert_list_read(svn_boolean_t *reverted, + const apr_array_header_t **marker_paths, + svn_boolean_t *copied_here, + svn_node_kind_t *kind, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + *reverted = FALSE; + *marker_paths = NULL; + *copied_here = FALSE; + *kind = svn_node_unknown; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_REVERT_LIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + svn_boolean_t is_actual = svn_sqlite__column_boolean(stmt, 0); + svn_boolean_t another_row = FALSE; + + if (is_actual) + { + apr_size_t conflict_len; + const void *conflict_data; + + conflict_data = svn_sqlite__column_blob(stmt, 5, &conflict_len, + scratch_pool); + if (conflict_data) + { + svn_skel_t *conflicts = svn_skel__parse(conflict_data, + conflict_len, + scratch_pool); + + SVN_ERR(svn_wc__conflict_read_markers(marker_paths, + db, wcroot->abspath, + conflicts, + result_pool, + scratch_pool)); + } + + if (!svn_sqlite__column_is_null(stmt, 1)) /* notify */ + *reverted = TRUE; + + SVN_ERR(svn_sqlite__step(&another_row, stmt)); + } + + if (!is_actual || another_row) + { + *reverted = TRUE; + if (!svn_sqlite__column_is_null(stmt, 4)) /* repos_id */ + { + int op_depth = svn_sqlite__column_int(stmt, 3); + *copied_here = (op_depth == relpath_depth(local_relpath)); + } + *kind = svn_sqlite__column_token(stmt, 2, kind_map); + } + + } + SVN_ERR(svn_sqlite__reset(stmt)); + + if (have_row) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_REVERT_LIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_revert_list_read(svn_boolean_t *reverted, + const apr_array_header_t **marker_files, + svn_boolean_t *copied_here, + svn_node_kind_t *kind, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + revert_list_read(reverted, marker_files, copied_here, kind, + wcroot, local_relpath, db, + result_pool, scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_revert_list_read_copied_children(). + */ +static svn_error_t * +revert_list_read_copied_children(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const apr_array_header_t **children_p, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_array_header_t *children; + + children = + apr_array_make(result_pool, 0, + sizeof(svn_wc__db_revert_list_copied_child_info_t *)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_REVERT_LIST_COPIED_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "sd", + local_relpath, relpath_depth(local_relpath))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + svn_wc__db_revert_list_copied_child_info_t *child_info; + const char *child_relpath; + + child_info = apr_palloc(result_pool, sizeof(*child_info)); + + child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + child_info->abspath = svn_dirent_join(wcroot->abspath, child_relpath, + result_pool); + child_info->kind = svn_sqlite__column_token(stmt, 1, kind_map); + APR_ARRAY_PUSH( + children, + svn_wc__db_revert_list_copied_child_info_t *) = child_info; + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + *children_p = children; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_revert_list_read_copied_children(const apr_array_header_t **children, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + revert_list_read_copied_children(wcroot, local_relpath, children, + result_pool, scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_revert_list_notify(svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, scratch_pool, iterpool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_REVERT_LIST_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + return svn_error_trace(svn_sqlite__reset(stmt)); /* optimise for no row */ + while (have_row) + { + const char *notify_relpath = svn_sqlite__column_text(stmt, 0, NULL); + + svn_pool_clear(iterpool); + + notify_func(notify_baton, + svn_wc_create_notify(svn_dirent_join(wcroot->abspath, + notify_relpath, + iterpool), + svn_wc_notify_revert, + iterpool), + iterpool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_REVERT_LIST_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_revert_list_done(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_DROP_REVERT_LIST)); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_op_remove_node(). + */ +static svn_error_t * +remove_node_txn(svn_boolean_t *left_changes, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + svn_boolean_t destroy_wc, + svn_boolean_t destroy_changes, + svn_revnum_t not_present_rev, + svn_wc__db_status_t not_present_status, + svn_node_kind_t not_present_kind, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + apr_int64_t repos_id; + const char *repos_relpath; + + /* Note that unlike many similar functions it is a valid scenario for this + function to be called on a wcroot! */ + + /* db set when destroying wc */ + SVN_ERR_ASSERT(!destroy_wc || db != NULL); + + if (left_changes) + *left_changes = FALSE; + + /* Need info for not_present node? */ + if (SVN_IS_VALID_REVNUM(not_present_rev)) + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (destroy_wc + && (!destroy_changes || *local_relpath == '\0')) + { + svn_boolean_t have_row; + apr_pool_t *iterpool; + svn_error_t *err = NULL; + + /* Install WQ items for deleting the unmodified files and all dirs */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_PRESENT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + + while (have_row) + { + const char *child_relpath; + const char *child_abspath; + svn_node_kind_t child_kind; + svn_boolean_t have_checksum; + svn_filesize_t recorded_size; + apr_int64_t recorded_time; + const svn_io_dirent2_t *dirent; + svn_boolean_t modified_p = TRUE; + svn_skel_t *work_item = NULL; + + svn_pool_clear(iterpool); + + child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + child_kind = svn_sqlite__column_token(stmt, 1, kind_map); + + child_abspath = svn_dirent_join(wcroot->abspath, child_relpath, + iterpool); + + if (child_kind == svn_node_file) + { + have_checksum = !svn_sqlite__column_is_null(stmt, 2); + recorded_size = get_recorded_size(stmt, 3); + recorded_time = svn_sqlite__column_int64(stmt, 4); + } + + if (cancel_func) + err = cancel_func(cancel_baton); + + if (err) + break; + + err = svn_io_stat_dirent2(&dirent, child_abspath, FALSE, TRUE, + iterpool, iterpool); + + if (err) + break; + + if (destroy_changes + || dirent->kind != svn_node_file + || child_kind != svn_node_file) + { + /* Not interested in keeping changes */ + modified_p = FALSE; + } + else if (child_kind == svn_node_file + && dirent->kind == svn_node_file + && dirent->filesize == recorded_size + && dirent->mtime == recorded_time) + { + modified_p = FALSE; /* File matches recorded state */ + } + else if (have_checksum) + err = svn_wc__internal_file_modified_p(&modified_p, + db, child_abspath, + FALSE, iterpool); + + if (err) + break; + + if (modified_p) + { + if (left_changes) + *left_changes = TRUE; + } + else if (child_kind == svn_node_dir) + { + err = svn_wc__wq_build_dir_remove(&work_item, + db, wcroot->abspath, + child_abspath, FALSE, + iterpool, iterpool); + } + else /* svn_node_file || svn_node_symlink */ + { + err = svn_wc__wq_build_file_remove(&work_item, + db, wcroot->abspath, + child_abspath, + iterpool, iterpool); + } + + if (err) + break; + + if (work_item) + { + err = add_work_items(wcroot->sdb, work_item, iterpool); + if (err) + break; + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + } + + if (destroy_wc && *local_relpath != '\0') + { + /* Create work item for destroying the root */ + svn_wc__db_status_t status; + svn_node_kind_t kind; + SVN_ERR(read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (status == svn_wc__db_status_normal + || status == svn_wc__db_status_added + || status == svn_wc__db_status_incomplete) + { + svn_skel_t *work_item = NULL; + const char *local_abspath = svn_dirent_join(wcroot->abspath, + local_relpath, + scratch_pool); + + if (kind == svn_node_dir) + { + SVN_ERR(svn_wc__wq_build_dir_remove(&work_item, + db, wcroot->abspath, + local_abspath, + destroy_changes + /* recursive */, + scratch_pool, scratch_pool)); + } + else + { + svn_boolean_t modified_p = FALSE; + + if (!destroy_changes) + { + SVN_ERR(svn_wc__internal_file_modified_p(&modified_p, + db, local_abspath, + FALSE, + scratch_pool)); + } + + if (!modified_p) + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, + db, wcroot->abspath, + local_abspath, + scratch_pool, + scratch_pool)); + else + { + if (left_changes) + *left_changes = TRUE; + } + } + + SVN_ERR(add_work_items(wcroot->sdb, work_item, scratch_pool)); + } + } + + /* Remove all nodes below local_relpath */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_NODE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* Delete the root NODE when this is not the working copy root */ + if (local_relpath[0] != '\0') + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE_RECURSIVE)); + + /* Delete all actual nodes at or below local_relpath */ + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* Should we leave a not-present node? */ + if (SVN_IS_VALID_REVNUM(not_present_rev)) + { + insert_base_baton_t ibb; + blank_ibb(&ibb); + + ibb.repos_id = repos_id; + + SVN_ERR_ASSERT(not_present_status == svn_wc__db_status_not_present + || not_present_status == svn_wc__db_status_excluded); + + ibb.status = not_present_status; + ibb.kind = not_present_kind; + + ibb.repos_relpath = repos_relpath; + ibb.revision = not_present_rev; + + SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool)); + } + + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + if (conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_remove_node(svn_boolean_t *left_changes, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t destroy_wc, + svn_boolean_t destroy_changes, + svn_revnum_t not_present_revision, + svn_wc__db_status_t not_present_status, + svn_node_kind_t not_present_kind, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(remove_node_txn(left_changes, + wcroot, local_relpath, db, + destroy_wc, destroy_changes, + not_present_revision, not_present_status, + not_present_kind, conflict, work_items, + cancel_func, cancel_baton, scratch_pool), + wcroot); + + /* Flush everything below this node in all ways */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_op_set_base_depth(). + */ +static svn_error_t * +db_op_set_base_depth(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int affected_rows; + + /* Flush any entries before we start monkeying the database. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_NODE_BASE_DEPTH)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, + svn_token__to_word(depth_map, depth))); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + if (affected_rows == 0) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + "The node '%s' is not a committed directory", + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_set_base_depth(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(depth >= svn_depth_empty && depth <= svn_depth_infinity); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* ### We set depth on working and base to match entry behavior. + Maybe these should be separated later? */ + SVN_WC__DB_WITH_TXN(db_op_set_base_depth(wcroot, local_relpath, depth, + scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +info_below_working(svn_boolean_t *have_base, + svn_boolean_t *have_work, + svn_wc__db_status_t *status, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int below_op_depth, /* < 0 is ignored */ + apr_pool_t *scratch_pool); + + +/* Convert STATUS, the raw status obtained from the presence map, to + the status appropriate for a working (op_depth > 0) node and return + it in *WORKING_STATUS. */ +static svn_error_t * +convert_to_working_status(svn_wc__db_status_t *working_status, + svn_wc__db_status_t status) +{ + svn_wc__db_status_t work_status = status; + + SVN_ERR_ASSERT(work_status == svn_wc__db_status_normal + || work_status == svn_wc__db_status_not_present + || work_status == svn_wc__db_status_base_deleted + || work_status == svn_wc__db_status_incomplete + || work_status == svn_wc__db_status_excluded); + + if (work_status == svn_wc__db_status_excluded) + { + *working_status = svn_wc__db_status_excluded; + } + else if (work_status == svn_wc__db_status_not_present + || work_status == svn_wc__db_status_base_deleted) + { + /* The caller should scan upwards to detect whether this + deletion has occurred because this node has been moved + away, or it is a regular deletion. Also note that the + deletion could be of the BASE tree, or a child of + something that has been copied/moved here. */ + + *working_status = svn_wc__db_status_deleted; + } + else /* normal or incomplete */ + { + /* The caller should scan upwards to detect whether this + addition has occurred because of a simple addition, + a copy, or is the destination of a move. */ + *working_status = svn_wc__db_status_added; + } + + return SVN_NO_ERROR; +} + + +/* Return the status of the node, if any, below the "working" node (or + below BELOW_OP_DEPTH if >= 0). + Set *HAVE_BASE or *HAVE_WORK to indicate if a base node or lower + working node is present, and *STATUS to the status of the first + layer below the selected node. */ +static svn_error_t * +info_below_working(svn_boolean_t *have_base, + svn_boolean_t *have_work, + svn_wc__db_status_t *status, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int below_op_depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + *have_base = *have_work = FALSE; + *status = svn_wc__db_status_normal; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (below_op_depth >= 0) + { + while (have_row && + (svn_sqlite__column_int(stmt, 0) > below_op_depth)) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + } + if (have_row) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + *status = svn_sqlite__column_token(stmt, 3, presence_map); + + while (have_row) + { + int op_depth = svn_sqlite__column_int(stmt, 0); + + if (op_depth > 0) + *have_work = TRUE; + else + *have_base = TRUE; + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + if (*have_work) + SVN_ERR(convert_to_working_status(status, *status)); + + return SVN_NO_ERROR; +} + +/* Helper function for op_delete_txn */ +static svn_error_t * +delete_update_movedto(svn_wc__db_wcroot_t *wcroot, + const char *child_moved_from_relpath, + int op_depth, + const char *new_moved_to_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int affected; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_MOVED_TO_RELPATH)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isds", + wcroot->wc_id, + child_moved_from_relpath, + op_depth, + new_moved_to_relpath)); + SVN_ERR(svn_sqlite__update(&affected, stmt)); + assert(affected == 1); + + return SVN_NO_ERROR; +} + + +struct op_delete_baton_t { + const char *moved_to_relpath; /* NULL if delete is not part of a move */ + svn_skel_t *conflict; + svn_skel_t *work_items; + svn_boolean_t delete_dir_externals; + svn_boolean_t notify; +}; + +/* This structure is used while rewriting move information for nodes. + * + * The most simple case of rewriting move information happens when + * a moved-away subtree is moved again: mv A B; mv B C + * The second move requires rewriting moved-to info at or within A. + * + * Another example is a move of a subtree which had nodes moved into it: + * mv A B/F; mv B G + * This requires rewriting such that A/F is marked has having moved to G/F. + * + * Another case is where a node becomes a nested moved node. + * A nested move happens when a subtree child is moved before or after + * the subtree itself is moved. For example: + * mv A/F A/G; mv A B + * In this case, the move A/F -> A/G is rewritten to B/F -> B/G. + * Note that the following sequence results in the same DB state: + * mv A B; mv B/F B/G + * We do not care about the order the moves were performed in. + * For details, see http://wiki.apache.org/subversion/MultiLayerMoves + */ +struct moved_node_t { + /* The source of the move. */ + const char *local_relpath; + + /* The move destination. */ + const char *moved_to_relpath; + + /* The op-depth of the deleted node at the source of the move. */ + int op_depth; +}; + +static svn_error_t * +delete_node(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + struct op_delete_baton_t *b = baton; + svn_wc__db_status_t status; + svn_boolean_t have_row, op_root; + svn_boolean_t add_work = FALSE; + svn_sqlite__stmt_t *stmt; + int select_depth; /* Depth of what is to be deleted */ + svn_boolean_t refetch_depth = FALSE; + svn_node_kind_t kind; + apr_array_header_t *moved_nodes = NULL; + int delete_depth = relpath_depth(local_relpath); + + SVN_ERR(read_info(&status, + &kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &op_root, NULL, NULL, + NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (status == svn_wc__db_status_deleted + || status == svn_wc__db_status_not_present) + return SVN_NO_ERROR; + + /* Don't copy BASE directories with server excluded nodes */ + if (status == svn_wc__db_status_normal && kind == svn_node_dir) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_HAS_SERVER_EXCLUDED_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + const char *absent_path = svn_sqlite__column_text(stmt, 0, + scratch_pool); + + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + svn_sqlite__reset(stmt), + _("Cannot delete '%s' as '%s' is excluded by server"), + path_for_error_message(wcroot, local_relpath, + scratch_pool), + path_for_error_message(wcroot, absent_path, + scratch_pool)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + } + else if (status == svn_wc__db_status_server_excluded) + { + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot delete '%s' as it is excluded by server"), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + else if (status == svn_wc__db_status_excluded) + { + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot delete '%s' as it is excluded"), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + + if (b->moved_to_relpath) + { + const char *moved_from_relpath = NULL; + struct moved_node_t *moved_node; + int move_op_depth; + + moved_nodes = apr_array_make(scratch_pool, 1, + sizeof(struct moved_node_t *)); + + /* The node is being moved-away. + * Figure out if the node was moved-here before, or whether this + * is the first time the node is moved. */ + if (status == svn_wc__db_status_added) + SVN_ERR(scan_addition(&status, NULL, NULL, NULL, NULL, NULL, NULL, + &moved_from_relpath, + NULL, + &move_op_depth, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (op_root && moved_from_relpath) + { + const char *part = svn_relpath_skip_ancestor(local_relpath, + moved_from_relpath); + + /* Existing move-root is moved to another location */ + moved_node = apr_palloc(scratch_pool, sizeof(struct moved_node_t)); + if (!part) + moved_node->local_relpath = moved_from_relpath; + else + moved_node->local_relpath = svn_relpath_join(b->moved_to_relpath, + part, scratch_pool); + moved_node->op_depth = move_op_depth; + moved_node->moved_to_relpath = b->moved_to_relpath; + + APR_ARRAY_PUSH(moved_nodes, const struct moved_node_t *) = moved_node; + } + else if (!op_root && (status == svn_wc__db_status_normal + || status == svn_wc__db_status_copied + || status == svn_wc__db_status_moved_here)) + { + /* The node is becoming a move-root for the first time, + * possibly because of a nested move operation. */ + moved_node = apr_palloc(scratch_pool, sizeof(struct moved_node_t)); + moved_node->local_relpath = local_relpath; + moved_node->op_depth = delete_depth; + moved_node->moved_to_relpath = b->moved_to_relpath; + + APR_ARRAY_PUSH(moved_nodes, const struct moved_node_t *) = moved_node; + } + /* Else: We can't track history of local additions and/or of things we are + about to delete. */ + + /* And update all moved_to values still pointing to this location */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_MOVED_TO_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, + local_relpath, + b->moved_to_relpath)); + SVN_ERR(svn_sqlite__update(NULL, stmt)); + } + else + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_TO_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__update(NULL, stmt)); + } + + /* Find children that were moved out of the subtree rooted at this node. + * We'll need to update their op-depth columns because their deletion + * is now implied by the deletion of their parent (i.e. this node). */ + { + apr_pool_t *iterpool; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_FOR_DELETE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + struct moved_node_t *mn; + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *mv_to_relpath = svn_sqlite__column_text(stmt, 1, NULL); + int child_op_depth = svn_sqlite__column_int(stmt, 2); + svn_boolean_t fixup = FALSE; + + if (!b->moved_to_relpath + && ! svn_relpath_skip_ancestor(local_relpath, mv_to_relpath)) + { + /* Update the op-depth of an moved node below this tree */ + fixup = TRUE; + child_op_depth = delete_depth; + } + else if (b->moved_to_relpath + && delete_depth == child_op_depth) + { + /* Update the op-depth of a tree shadowed by this tree */ + fixup = TRUE; + child_op_depth = delete_depth; + } + else if (b->moved_to_relpath + && child_op_depth >= delete_depth + && !svn_relpath_skip_ancestor(local_relpath, mv_to_relpath)) + { + /* Update the move destination of something that is now moved + away further */ + + child_relpath = svn_relpath_skip_ancestor(local_relpath, child_relpath); + + if (child_relpath) + { + child_relpath = svn_relpath_join(b->moved_to_relpath, child_relpath, scratch_pool); + + if (child_op_depth > delete_depth + && svn_relpath_skip_ancestor(local_relpath, child_relpath)) + child_op_depth = delete_depth; + else + child_op_depth = relpath_depth(child_relpath); + + fixup = TRUE; + } + } + + if (fixup) + { + mn = apr_pcalloc(scratch_pool, sizeof(struct moved_node_t)); + + mn->local_relpath = apr_pstrdup(scratch_pool, child_relpath); + mn->moved_to_relpath = apr_pstrdup(scratch_pool, mv_to_relpath); + mn->op_depth = child_op_depth; + + if (!moved_nodes) + moved_nodes = apr_array_make(scratch_pool, 1, + sizeof(struct moved_node_t *)); + APR_ARRAY_PUSH(moved_nodes, struct moved_node_t *) = mn; + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + SVN_ERR(svn_sqlite__reset(stmt)); + } + + if (op_root) + { + svn_boolean_t below_base; + svn_boolean_t below_work; + svn_wc__db_status_t below_status; + + /* Use STMT_SELECT_NODE_INFO directly instead of read_info plus + info_below_working */ + SVN_ERR(info_below_working(&below_base, &below_work, &below_status, + wcroot, local_relpath, -1, scratch_pool)); + if ((below_base || below_work) + && below_status != svn_wc__db_status_not_present + && below_status != svn_wc__db_status_deleted) + { + add_work = TRUE; + refetch_depth = TRUE; + } + + select_depth = relpath_depth(local_relpath); + } + else + { + add_work = TRUE; + if (status != svn_wc__db_status_normal) + SVN_ERR(op_depth_of(&select_depth, wcroot, local_relpath)); + else + select_depth = 0; /* Deleting BASE node */ + } + + /* ### Put actual-only nodes into the list? */ + if (b->notify) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_DELETE_LIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wcroot->wc_id, local_relpath, select_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wcroot->wc_id, local_relpath, delete_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + if (refetch_depth) + SVN_ERR(op_depth_of(&select_depth, wcroot, local_relpath)); + + /* Delete ACTUAL_NODE rows, but leave those that have changelist + and a NODES row. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + if (add_work) + { + /* Delete the node at LOCAL_RELPATH, and possibly mark it as moved. */ + + /* Delete the node and possible descendants. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_DELETE_FROM_NODE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdd", + wcroot->wc_id, local_relpath, + select_depth, delete_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + if (moved_nodes) + { + int i; + + for (i = 0; i < moved_nodes->nelts; ++i) + { + const struct moved_node_t *moved_node + = APR_ARRAY_IDX(moved_nodes, i, void *); + + SVN_ERR(delete_update_movedto(wcroot, + moved_node->local_relpath, + moved_node->op_depth, + moved_node->moved_to_relpath, + scratch_pool)); + } + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_FILE_EXTERNALS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + b->delete_dir_externals + ? STMT_DELETE_EXTERNAL_REGISTATIONS + : STMT_DELETE_FILE_EXTERNAL_REGISTATIONS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(add_work_items(wcroot->sdb, b->work_items, scratch_pool)); + if (b->conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + b->conflict, scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +op_delete_txn(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_CREATE_DELETE_LIST)); + SVN_ERR(delete_node(baton, wcroot, local_relpath, scratch_pool)); + return SVN_NO_ERROR; +} + + +struct op_delete_many_baton_t { + apr_array_header_t *rel_targets; + svn_boolean_t delete_dir_externals; + const svn_skel_t *work_items; +} op_delete_many_baton_t; + +static svn_error_t * +op_delete_many_txn(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + struct op_delete_many_baton_t *odmb = baton; + struct op_delete_baton_t odb; + int i; + apr_pool_t *iterpool; + + odb.moved_to_relpath = NULL; + odb.conflict = NULL; + odb.work_items = NULL; + odb.delete_dir_externals = odmb->delete_dir_externals; + odb.notify = TRUE; + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_CREATE_DELETE_LIST)); + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < odmb->rel_targets->nelts; i++) + { + const char *target_relpath = APR_ARRAY_IDX(odmb->rel_targets, i, + const char *); + + + svn_pool_clear(iterpool); + SVN_ERR(delete_node(&odb, wcroot, target_relpath, iterpool)); + } + svn_pool_destroy(iterpool); + + SVN_ERR(add_work_items(wcroot->sdb, odmb->work_items, scratch_pool)); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +do_delete_notify(void *baton, + svn_wc__db_wcroot_t *wcroot, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_DELETE_LIST)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + const char *notify_relpath; + const char *notify_abspath; + + svn_pool_clear(iterpool); + + notify_relpath = svn_sqlite__column_text(stmt, 0, NULL); + notify_abspath = svn_dirent_join(wcroot->abspath, + notify_relpath, + iterpool); + + notify_func(notify_baton, + svn_wc_create_notify(notify_abspath, + svn_wc_notify_delete, + iterpool), + iterpool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + + SVN_ERR(svn_sqlite__reset(stmt)); + + /* We only allow cancellation after notification for all deleted nodes + * has happened. The nodes are already deleted so we should notify for + * all of them. */ + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_delete(svn_wc__db_t *db, + const char *local_abspath, + const char *moved_to_abspath, + svn_boolean_t delete_dir_externals, + svn_skel_t *conflict, + svn_skel_t *work_items, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + svn_wc__db_wcroot_t *moved_to_wcroot; + const char *local_relpath; + const char *moved_to_relpath; + struct op_delete_baton_t odb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (moved_to_abspath) + { + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&moved_to_wcroot, + &moved_to_relpath, + db, moved_to_abspath, + scratch_pool, + scratch_pool)); + VERIFY_USABLE_WCROOT(moved_to_wcroot); + + if (strcmp(wcroot->abspath, moved_to_wcroot->abspath) != 0) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Cannot move '%s' to '%s' because they " + "are not in the same working copy"), + svn_dirent_local_style(local_abspath, + scratch_pool), + svn_dirent_local_style(moved_to_abspath, + scratch_pool)); + } + else + moved_to_relpath = NULL; + + odb.moved_to_relpath = moved_to_relpath; + odb.conflict = conflict; + odb.work_items = work_items; + odb.delete_dir_externals = delete_dir_externals; + + if (notify_func) + { + /* Perform the deletion operation (transactionally), perform any + notifications necessary, and then clean out our temporary tables. */ + odb.notify = TRUE; + SVN_ERR(with_finalization(wcroot, local_relpath, + op_delete_txn, &odb, + do_delete_notify, NULL, + cancel_func, cancel_baton, + notify_func, notify_baton, + STMT_FINALIZE_DELETE, + scratch_pool)); + } + else + { + /* Avoid the trigger work */ + odb.notify = FALSE; + SVN_WC__DB_WITH_TXN( + delete_node(&odb, wcroot, local_relpath, scratch_pool), + wcroot); + } + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_delete_many(svn_wc__db_t *db, + apr_array_header_t *targets, + svn_boolean_t delete_dir_externals, + const svn_skel_t *work_items, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + struct op_delete_many_baton_t odmb; + int i; + apr_pool_t *iterpool; + + odmb.rel_targets = apr_array_make(scratch_pool, targets->nelts, + sizeof(const char *)); + odmb.work_items = work_items; + odmb.delete_dir_externals = delete_dir_externals; + iterpool = svn_pool_create(scratch_pool); + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, + APR_ARRAY_IDX(targets, 0, + const char *), + scratch_pool, iterpool)); + VERIFY_USABLE_WCROOT(wcroot); + for (i = 0; i < targets->nelts; i++) + { + const char *local_abspath = APR_ARRAY_IDX(targets, i, const char*); + svn_wc__db_wcroot_t *target_wcroot; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&target_wcroot, + &local_relpath, db, + APR_ARRAY_IDX(targets, i, + const char *), + scratch_pool, iterpool)); + VERIFY_USABLE_WCROOT(target_wcroot); + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* Assert that all targets are within the same working copy. */ + SVN_ERR_ASSERT(wcroot->wc_id == target_wcroot->wc_id); + + APR_ARRAY_PUSH(odmb.rel_targets, const char *) = local_relpath; + SVN_ERR(flush_entries(target_wcroot, local_abspath, svn_depth_infinity, + iterpool)); + + } + svn_pool_destroy(iterpool); + + /* Perform the deletion operation (transactionally), perform any + notifications necessary, and then clean out our temporary tables. */ + return svn_error_trace(with_finalization(wcroot, wcroot->abspath, + op_delete_many_txn, &odmb, + do_delete_notify, NULL, + cancel_func, cancel_baton, + notify_func, notify_baton, + STMT_FINALIZE_DELETE, + scratch_pool)); +} + + +/* Like svn_wc__db_read_info(), but taking WCROOT+LOCAL_RELPATH instead of + DB+LOCAL_ABSPATH, and outputting repos ids instead of URL+UUID. */ +static svn_error_t * +read_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + const char **original_repos_relpath, + apr_int64_t *original_repos_id, + svn_revnum_t *original_revision, + svn_wc__db_lock_t **lock, + svn_filesize_t *recorded_size, + apr_time_t *recorded_time, + const char **changelist, + svn_boolean_t *conflicted, + svn_boolean_t *op_root, + svn_boolean_t *had_props, + svn_boolean_t *props_mod, + svn_boolean_t *have_base, + svn_boolean_t *have_more_work, + svn_boolean_t *have_work, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt_info; + svn_sqlite__stmt_t *stmt_act; + svn_boolean_t have_info; + svn_boolean_t have_act; + svn_error_t *err = NULL; + + /* Obtain the most likely to exist record first, to make sure we don't + have to obtain the SQLite read-lock multiple times */ + SVN_ERR(svn_sqlite__get_statement(&stmt_info, wcroot->sdb, + lock ? STMT_SELECT_NODE_INFO_WITH_LOCK + : STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt_info, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_info, stmt_info)); + + if (changelist || conflicted || props_mod) + { + SVN_ERR(svn_sqlite__get_statement(&stmt_act, wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt_act, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_act, stmt_act)); + } + else + { + have_act = FALSE; + stmt_act = NULL; + } + + if (have_info) + { + int op_depth; + svn_node_kind_t node_kind; + + op_depth = svn_sqlite__column_int(stmt_info, 0); + node_kind = svn_sqlite__column_token(stmt_info, 4, kind_map); + + if (status) + { + *status = svn_sqlite__column_token(stmt_info, 3, presence_map); + + if (op_depth != 0) /* WORKING */ + err = svn_error_compose_create(err, + convert_to_working_status(status, + *status)); + } + if (kind) + { + *kind = node_kind; + } + if (op_depth != 0) + { + if (repos_id) + *repos_id = INVALID_REPOS_ID; + if (revision) + *revision = SVN_INVALID_REVNUM; + if (repos_relpath) + /* Our path is implied by our parent somewhere up the tree. + With the NULL value and status, the caller will know to + search up the tree for the base of our path. */ + *repos_relpath = NULL; + } + else + { + /* Fetch repository information. If we have a + WORKING_NODE (and have been added), then the repository + we're being added to will be dependent upon a parent. The + caller can scan upwards to locate the repository. */ + repos_location_from_columns(repos_id, revision, repos_relpath, + stmt_info, 1, 5, 2, result_pool); + } + if (changed_rev) + { + *changed_rev = svn_sqlite__column_revnum(stmt_info, 8); + } + if (changed_date) + { + *changed_date = svn_sqlite__column_int64(stmt_info, 9); + } + if (changed_author) + { + *changed_author = svn_sqlite__column_text(stmt_info, 10, + result_pool); + } + if (recorded_time) + { + *recorded_time = svn_sqlite__column_int64(stmt_info, 13); + } + if (depth) + { + if (node_kind != svn_node_dir) + { + *depth = svn_depth_unknown; + } + else + { + *depth = svn_sqlite__column_token_null(stmt_info, 11, depth_map, + svn_depth_unknown); + } + } + if (checksum) + { + if (node_kind != svn_node_file) + { + *checksum = NULL; + } + else + { + + err = svn_error_compose_create( + err, svn_sqlite__column_checksum(checksum, stmt_info, 6, + result_pool)); + } + } + if (recorded_size) + { + *recorded_size = get_recorded_size(stmt_info, 7); + } + if (target) + { + if (node_kind != svn_node_symlink) + *target = NULL; + else + *target = svn_sqlite__column_text(stmt_info, 12, result_pool); + } + if (changelist) + { + if (have_act) + *changelist = svn_sqlite__column_text(stmt_act, 0, result_pool); + else + *changelist = NULL; + } + if (op_depth == 0) + { + if (original_repos_id) + *original_repos_id = INVALID_REPOS_ID; + if (original_revision) + *original_revision = SVN_INVALID_REVNUM; + if (original_repos_relpath) + *original_repos_relpath = NULL; + } + else + { + repos_location_from_columns(original_repos_id, + original_revision, + original_repos_relpath, + stmt_info, 1, 5, 2, result_pool); + } + if (props_mod) + { + *props_mod = have_act && !svn_sqlite__column_is_null(stmt_act, 1); + } + if (had_props) + { + *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt_info, 14); + } + if (conflicted) + { + if (have_act) + { + *conflicted = + !svn_sqlite__column_is_null(stmt_act, 2); /* conflict_data */ + } + else + *conflicted = FALSE; + } + + if (lock) + { + if (op_depth != 0) + *lock = NULL; + else + *lock = lock_from_columns(stmt_info, 17, 18, 19, 20, result_pool); + } + + if (have_work) + *have_work = (op_depth != 0); + + if (op_root) + { + *op_root = ((op_depth > 0) + && (op_depth == relpath_depth(local_relpath))); + } + + if (have_base || have_more_work) + { + if (have_more_work) + *have_more_work = FALSE; + + while (!err && op_depth != 0) + { + err = svn_sqlite__step(&have_info, stmt_info); + + if (err || !have_info) + break; + + op_depth = svn_sqlite__column_int(stmt_info, 0); + + if (have_more_work) + { + if (op_depth > 0) + *have_more_work = TRUE; + + if (!have_base) + break; + } + } + + if (have_base) + *have_base = (op_depth == 0); + } + } + else if (have_act) + { + /* A row in ACTUAL_NODE should never exist without a corresponding + node in BASE_NODE and/or WORKING_NODE unless it flags a tree conflict. */ + if (svn_sqlite__column_is_null(stmt_act, 2)) /* conflict_data */ + err = svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Corrupt data for '%s'"), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + /* ### What should we return? Should we have a separate + function for reading actual-only nodes? */ + + /* As a safety measure, until we decide if we want to use + read_info for actual-only nodes, make sure the caller asked + for the conflict status. */ + SVN_ERR_ASSERT(conflicted); + + if (status) + *status = svn_wc__db_status_normal; /* What! No it's not! */ + if (kind) + *kind = svn_node_unknown; + if (revision) + *revision = SVN_INVALID_REVNUM; + if (repos_relpath) + *repos_relpath = NULL; + if (repos_id) + *repos_id = INVALID_REPOS_ID; + if (changed_rev) + *changed_rev = SVN_INVALID_REVNUM; + if (changed_date) + *changed_date = 0; + if (depth) + *depth = svn_depth_unknown; + if (checksum) + *checksum = NULL; + if (target) + *target = NULL; + if (original_repos_relpath) + *original_repos_relpath = NULL; + if (original_repos_id) + *original_repos_id = INVALID_REPOS_ID; + if (original_revision) + *original_revision = SVN_INVALID_REVNUM; + if (lock) + *lock = NULL; + if (recorded_size) + *recorded_size = 0; + if (recorded_time) + *recorded_time = 0; + if (changelist) + *changelist = svn_sqlite__column_text(stmt_act, 0, result_pool); + if (op_root) + *op_root = FALSE; + if (had_props) + *had_props = FALSE; + if (props_mod) + *props_mod = FALSE; + if (conflicted) + *conflicted = TRUE; + if (have_base) + *have_base = FALSE; + if (have_more_work) + *have_more_work = FALSE; + if (have_work) + *have_work = FALSE; + } + else + { + err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + + if (stmt_act != NULL) + err = svn_error_compose_create(err, svn_sqlite__reset(stmt_act)); + + if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + err = svn_error_quick_wrap(err, + apr_psprintf(scratch_pool, + "Error reading node '%s'", + local_relpath)); + + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt_info))); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_read_info_internal(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + const char **original_repos_relpath, + apr_int64_t *original_repos_id, + svn_revnum_t *original_revision, + svn_wc__db_lock_t **lock, + svn_filesize_t *recorded_size, + apr_time_t *recorded_time, + const char **changelist, + svn_boolean_t *conflicted, + svn_boolean_t *op_root, + svn_boolean_t *had_props, + svn_boolean_t *props_mod, + svn_boolean_t *have_base, + svn_boolean_t *have_more_work, + svn_boolean_t *have_work, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + read_info(status, kind, revision, repos_relpath, repos_id, + changed_rev, changed_date, changed_author, + depth, checksum, target, original_repos_relpath, + original_repos_id, original_revision, lock, + recorded_size, recorded_time, changelist, conflicted, + op_root, had_props, props_mod, + have_base, have_more_work, have_work, + wcroot, local_relpath, result_pool, scratch_pool)); +} + + +svn_error_t * +svn_wc__db_read_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + const char **original_repos_relpath, + const char **original_root_url, + const char **original_uuid, + svn_revnum_t *original_revision, + svn_wc__db_lock_t **lock, + svn_filesize_t *recorded_size, + apr_time_t *recorded_time, + const char **changelist, + svn_boolean_t *conflicted, + svn_boolean_t *op_root, + svn_boolean_t *have_props, + svn_boolean_t *props_mod, + svn_boolean_t *have_base, + svn_boolean_t *have_more_work, + svn_boolean_t *have_work, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + apr_int64_t repos_id, original_repos_id; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(read_info(status, kind, revision, repos_relpath, &repos_id, + changed_rev, changed_date, changed_author, + depth, checksum, target, original_repos_relpath, + &original_repos_id, original_revision, lock, + recorded_size, recorded_time, changelist, conflicted, + op_root, have_props, props_mod, + have_base, have_more_work, have_work, + wcroot, local_relpath, result_pool, scratch_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, + wcroot->sdb, repos_id, result_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(original_root_url, original_uuid, + wcroot->sdb, original_repos_id, + result_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +is_wclocked(svn_boolean_t *locked, + svn_wc__db_wcroot_t *wcroot, + const char *dir_relpath, + apr_pool_t *scratch_pool); + +/* What we really want to store about a node. This relies on the + offset of svn_wc__db_info_t being zero. */ +struct read_children_info_item_t +{ + struct svn_wc__db_info_t info; + int op_depth; + int nr_layers; +}; + +static svn_error_t * +read_children_info(svn_wc__db_wcroot_t *wcroot, + const char *dir_relpath, + apr_hash_t *conflicts, + apr_hash_t *nodes, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + const char *repos_root_url = NULL; + const char *repos_uuid = NULL; + apr_int64_t last_repos_id = INVALID_REPOS_ID; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_CHILDREN_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, dir_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + /* CHILD item points to what we have about the node. We only provide + CHILD->item to our caller. */ + struct read_children_info_item_t *child_item; + const char *child_relpath = svn_sqlite__column_text(stmt, 19, NULL); + const char *name = svn_relpath_basename(child_relpath, NULL); + svn_error_t *err; + int op_depth; + svn_boolean_t new_child; + + child_item = svn_hash_gets(nodes, name); + if (child_item) + new_child = FALSE; + else + { + child_item = apr_pcalloc(result_pool, sizeof(*child_item)); + new_child = TRUE; + } + + op_depth = svn_sqlite__column_int(stmt, 0); + + /* Do we have new or better information? */ + if (new_child || op_depth > child_item->op_depth) + { + struct svn_wc__db_info_t *child = &child_item->info; + child_item->op_depth = op_depth; + + child->kind = svn_sqlite__column_token(stmt, 4, kind_map); + + child->status = svn_sqlite__column_token(stmt, 3, presence_map); + if (op_depth != 0) + { + if (child->status == svn_wc__db_status_incomplete) + child->incomplete = TRUE; + err = convert_to_working_status(&child->status, child->status); + if (err) + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + } + + if (op_depth != 0) + child->revnum = SVN_INVALID_REVNUM; + else + child->revnum = svn_sqlite__column_revnum(stmt, 5); + + if (op_depth != 0) + child->repos_relpath = NULL; + else + child->repos_relpath = svn_sqlite__column_text(stmt, 2, + result_pool); + + if (op_depth != 0 || svn_sqlite__column_is_null(stmt, 1)) + { + child->repos_root_url = NULL; + child->repos_uuid = NULL; + } + else + { + const char *last_repos_root_url = NULL; + + apr_int64_t repos_id = svn_sqlite__column_int64(stmt, 1); + if (!repos_root_url || + (last_repos_id != INVALID_REPOS_ID && + repos_id != last_repos_id)) + { + last_repos_root_url = repos_root_url; + err = svn_wc__db_fetch_repos_info(&repos_root_url, + &repos_uuid, + wcroot->sdb, repos_id, + result_pool); + if (err) + SVN_ERR(svn_error_compose_create(err, + svn_sqlite__reset(stmt))); + } + + if (last_repos_id == INVALID_REPOS_ID) + last_repos_id = repos_id; + + /* Assume working copy is all one repos_id so that a + single cached value is sufficient. */ + if (repos_id != last_repos_id) + { + err= svn_error_createf( + SVN_ERR_WC_DB_ERROR, NULL, + _("The node '%s' comes from unexpected repository " + "'%s', expected '%s'; if this node is a file " + "external using the correct URL in the external " + "definition can fix the problem, see issue #4087"), + child_relpath, repos_root_url, last_repos_root_url); + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + } + child->repos_root_url = repos_root_url; + child->repos_uuid = repos_uuid; + } + + child->changed_rev = svn_sqlite__column_revnum(stmt, 8); + + child->changed_date = svn_sqlite__column_int64(stmt, 9); + + child->changed_author = svn_sqlite__column_text(stmt, 10, + result_pool); + + if (child->kind != svn_node_dir) + child->depth = svn_depth_unknown; + else + { + child->depth = svn_sqlite__column_token_null(stmt, 11, depth_map, + svn_depth_unknown); + if (new_child) + SVN_ERR(is_wclocked(&child->locked, wcroot, child_relpath, + scratch_pool)); + } + + child->recorded_time = svn_sqlite__column_int64(stmt, 13); + child->recorded_size = get_recorded_size(stmt, 7); + child->has_checksum = !svn_sqlite__column_is_null(stmt, 6); + child->copied = op_depth > 0 && !svn_sqlite__column_is_null(stmt, 2); + child->had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 14); +#ifdef HAVE_SYMLINK + if (child->had_props) + { + apr_hash_t *properties; + err = svn_sqlite__column_properties(&properties, stmt, 14, + scratch_pool, scratch_pool); + if (err) + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + + child->special = (child->had_props + && svn_hash_gets(properties, SVN_PROP_SPECIAL)); + } +#endif + if (op_depth == 0) + child->op_root = FALSE; + else + child->op_root = (op_depth == relpath_depth(child_relpath)); + + svn_hash_sets(nodes, apr_pstrdup(result_pool, name), child); + } + + if (op_depth == 0) + { + child_item->info.have_base = TRUE; + + /* Get the lock info, available only at op_depth 0. */ + child_item->info.lock = lock_from_columns(stmt, 15, 16, 17, 18, + result_pool); + + /* FILE_EXTERNAL flag only on op_depth 0. */ + child_item->info.file_external = svn_sqlite__column_boolean(stmt, + 22); + } + else + { + const char *moved_to_relpath; + + child_item->nr_layers++; + child_item->info.have_more_work = (child_item->nr_layers > 1); + + /* Moved-to can only exist at op_depth > 0. */ + /* ### Should we really do this for every layer where op_depth > 0 + in undefined order? */ + moved_to_relpath = svn_sqlite__column_text(stmt, 21, NULL); + if (moved_to_relpath) + child_item->info.moved_to_abspath = + svn_dirent_join(wcroot->abspath, moved_to_relpath, result_pool); + + /* Moved-here can only exist at op_depth > 0. */ + /* ### Should we really do this for every layer where op_depth > 0 + in undefined order? */ + child_item->info.moved_here = svn_sqlite__column_boolean(stmt, 20); + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_CHILDREN_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, dir_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + struct read_children_info_item_t *child_item; + struct svn_wc__db_info_t *child; + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *name = svn_relpath_basename(child_relpath, NULL); + + child_item = svn_hash_gets(nodes, name); + if (!child_item) + { + child_item = apr_pcalloc(result_pool, sizeof(*child_item)); + child_item->info.status = svn_wc__db_status_not_present; + } + + child = &child_item->info; + + child->changelist = svn_sqlite__column_text(stmt, 1, result_pool); + + child->props_mod = !svn_sqlite__column_is_null(stmt, 2); +#ifdef HAVE_SYMLINK + if (child->props_mod) + { + svn_error_t *err; + apr_hash_t *properties; + + err = svn_sqlite__column_properties(&properties, stmt, 2, + scratch_pool, scratch_pool); + if (err) + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + child->special = (NULL != svn_hash_gets(properties, + SVN_PROP_SPECIAL)); + } +#endif + + child->conflicted = !svn_sqlite__column_is_null(stmt, 3); /* conflict */ + + if (child->conflicted) + svn_hash_sets(conflicts, apr_pstrdup(result_pool, name), ""); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_children_info(apr_hash_t **nodes, + apr_hash_t **conflicts, + svn_wc__db_t *db, + const char *dir_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *dir_relpath; + + *conflicts = apr_hash_make(result_pool); + *nodes = apr_hash_make(result_pool); + SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &dir_relpath, db, + dir_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + read_children_info(wcroot, dir_relpath, *conflicts, *nodes, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_pristine_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, /* dirs only */ + const svn_checksum_t **checksum, /* files only */ + const char **target, /* symlinks only */ + svn_boolean_t *had_props, + apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_error_t *err = NULL; + int op_depth; + svn_wc__db_status_t raw_status; + svn_node_kind_t node_kind; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* Obtain the most likely to exist record first, to make sure we don't + have to obtain the SQLite read-lock multiple times */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + op_depth = svn_sqlite__column_int(stmt, 0); + raw_status = svn_sqlite__column_token(stmt, 3, presence_map); + + if (op_depth > 0 && raw_status == svn_wc__db_status_base_deleted) + { + SVN_ERR(svn_sqlite__step_row(stmt)); + + op_depth = svn_sqlite__column_int(stmt, 0); + raw_status = svn_sqlite__column_token(stmt, 3, presence_map); + } + + node_kind = svn_sqlite__column_token(stmt, 4, kind_map); + + if (status) + { + if (op_depth > 0) + { + err = svn_error_compose_create(err, + convert_to_working_status( + status, + raw_status)); + } + else + *status = raw_status; + } + if (kind) + { + *kind = node_kind; + } + if (changed_rev) + { + *changed_rev = svn_sqlite__column_revnum(stmt, 8); + } + if (changed_date) + { + *changed_date = svn_sqlite__column_int64(stmt, 9); + } + if (changed_author) + { + *changed_author = svn_sqlite__column_text(stmt, 10, + result_pool); + } + if (depth) + { + if (node_kind != svn_node_dir) + { + *depth = svn_depth_unknown; + } + else + { + *depth = svn_sqlite__column_token_null(stmt, 11, depth_map, + svn_depth_unknown); + } + } + if (checksum) + { + if (node_kind != svn_node_file) + { + *checksum = NULL; + } + else + { + svn_error_t *err2; + err2 = svn_sqlite__column_checksum(checksum, stmt, 6, result_pool); + + if (err2 != NULL) + { + if (err) + err = svn_error_compose_create( + err, + svn_error_createf( + err->apr_err, err2, + _("The node '%s' has a corrupt checksum value."), + path_for_error_message(wcroot, local_relpath, + scratch_pool))); + else + err = err2; + } + } + } + if (target) + { + if (node_kind != svn_node_symlink) + *target = NULL; + else + *target = svn_sqlite__column_text(stmt, 12, result_pool); + } + if (had_props) + { + *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 14); + } + if (props) + { + if (raw_status == svn_wc__db_status_normal + || raw_status == svn_wc__db_status_incomplete) + { + SVN_ERR(svn_sqlite__column_properties(props, stmt, 14, + result_pool, scratch_pool)); + if (*props == NULL) + *props = apr_hash_make(result_pool); + } + else + { + assert(svn_sqlite__column_is_null(stmt, 14)); + *props = NULL; + } + } + + return svn_error_trace( + svn_error_compose_create(err, + svn_sqlite__reset(stmt))); +} + +svn_error_t * +svn_wc__db_read_children_walker_info(apr_hash_t **nodes, + svn_wc__db_t *db, + const char *dir_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *dir_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &dir_relpath, db, + dir_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_CHILDREN_WALKER_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, dir_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + *nodes = apr_hash_make(result_pool); + while (have_row) + { + struct svn_wc__db_walker_info_t *child; + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *name = svn_relpath_basename(child_relpath, NULL); + int op_depth = svn_sqlite__column_int(stmt, 1); + svn_error_t *err; + + child = apr_palloc(result_pool, sizeof(*child)); + child->status = svn_sqlite__column_token(stmt, 2, presence_map); + if (op_depth > 0) + { + err = convert_to_working_status(&child->status, child->status); + if (err) + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + } + child->kind = svn_sqlite__column_token(stmt, 3, kind_map); + svn_hash_sets(*nodes, apr_pstrdup(result_pool, name), child); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_node_install_info(const char **wcroot_abspath, + const svn_checksum_t **sha1_checksum, + apr_hash_t **pristine_props, + apr_time_t *changed_date, + 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_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_error_t *err = NULL; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (!wri_abspath) + wri_abspath = local_abspath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (local_abspath != wri_abspath + && strcmp(local_abspath, wri_abspath)) + { + if (!svn_dirent_is_ancestor(wcroot->abspath, local_abspath)) + return svn_error_createf( + SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' is not in working copy '%s'"), + svn_dirent_local_style(local_abspath, scratch_pool), + svn_dirent_local_style(wcroot->abspath, scratch_pool)); + + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + } + + if (wcroot_abspath != NULL) + *wcroot_abspath = apr_pstrdup(result_pool, wcroot->abspath); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + if (!err && sha1_checksum) + err = svn_sqlite__column_checksum(sha1_checksum, stmt, 6, result_pool); + + if (!err && pristine_props) + { + err = svn_sqlite__column_properties(pristine_props, stmt, 14, + result_pool, scratch_pool); + /* Null means no props (assuming presence normal or incomplete). */ + if (*pristine_props == NULL) + *pristine_props = apr_hash_make(result_pool); + } + + if (changed_date) + *changed_date = svn_sqlite__column_int64(stmt, 9); + } + else + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' is not installable"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + + return SVN_NO_ERROR; +} + + + +/* The body of svn_wc__db_read_url(). + */ +static svn_error_t * +read_url_txn(const char **url, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + const char *repos_relpath; + const char *repos_root_url; + apr_int64_t repos_id; + svn_boolean_t have_base; + + SVN_ERR(read_info(&status, NULL, NULL, &repos_relpath, &repos_id, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &have_base, NULL, NULL, + wcroot, local_relpath, scratch_pool, scratch_pool)); + + if (repos_relpath == NULL) + { + if (status == svn_wc__db_status_added) + { + SVN_ERR(scan_addition(NULL, NULL, &repos_relpath, &repos_id, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + } + else if (status == svn_wc__db_status_deleted) + { + const char *base_del_relpath; + const char *work_del_relpath; + + SVN_ERR(scan_deletion_txn(&base_del_relpath, NULL, + &work_del_relpath, + NULL, wcroot, + local_relpath, + scratch_pool, + scratch_pool)); + + if (base_del_relpath) + { + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, + &repos_id, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wcroot, + base_del_relpath, + scratch_pool, + scratch_pool)); + + repos_relpath = svn_relpath_join( + repos_relpath, + svn_dirent_skip_ancestor(base_del_relpath, + local_relpath), + scratch_pool); + } + else + { + /* The parent of the WORKING delete, must be an addition */ + const char *work_relpath = NULL; + + /* work_del_relpath should not be NULL. However, we have + * observed instances where that assumption was not met. + * Bail out in that case instead of crashing with a segfault. + */ + SVN_ERR_ASSERT(work_del_relpath != NULL); + work_relpath = svn_relpath_dirname(work_del_relpath, + scratch_pool); + + SVN_ERR(scan_addition(NULL, NULL, &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, work_relpath, + scratch_pool, scratch_pool)); + + repos_relpath = svn_relpath_join( + repos_relpath, + svn_dirent_skip_ancestor(work_relpath, + local_relpath), + scratch_pool); + } + } + else if (status == svn_wc__db_status_excluded) + { + const char *parent_relpath; + const char *name; + const char *url2; + + /* Set 'url' to the *full URL* of the parent WC dir, + * and 'name' to the *single path component* that is the + * basename of this WC directory, so that joining them will result + * in the correct full URL. */ + svn_relpath_split(&parent_relpath, &name, local_relpath, + scratch_pool); + SVN_ERR(read_url_txn(&url2, wcroot, parent_relpath, + scratch_pool, scratch_pool)); + + *url = svn_path_url_add_component2(url2, name, result_pool); + + return SVN_NO_ERROR; + } + else + { + /* All working statee are explicitly handled and all base statee + have a repos_relpath */ + SVN_ERR_MALFUNCTION(); + } + } + + SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, NULL, wcroot->sdb, + repos_id, scratch_pool)); + + SVN_ERR_ASSERT(repos_root_url != NULL && repos_relpath != NULL); + *url = svn_path_url_add_component2(repos_root_url, repos_relpath, + result_pool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_read_url(const char **url, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(read_url_txn(url, wcroot, local_relpath, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + + +/* Call RECEIVER_FUNC, passing RECEIVER_BATON, an absolute path, and + a hash table mapping <tt>char *</tt> names onto svn_string_t * + values for any properties of immediate or recursive child nodes of + LOCAL_ABSPATH, the actual query being determined by STMT_IDX. + If FILES_ONLY is true, only report properties for file child nodes. + Check for cancellation between calls of RECEIVER_FUNC. +*/ +typedef struct cache_props_baton_t +{ + svn_depth_t depth; + svn_boolean_t pristine; + const apr_array_header_t *changelists; + svn_cancel_func_t cancel_func; + void *cancel_baton; +} cache_props_baton_t; + + +static svn_error_t * +cache_props_recursive(void *cb_baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + cache_props_baton_t *baton = cb_baton; + svn_sqlite__stmt_t *stmt; + int stmt_idx; + + SVN_ERR(populate_targets_tree(wcroot, local_relpath, baton->depth, + baton->changelists, scratch_pool)); + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_TARGET_PROP_CACHE)); + + if (baton->pristine) + stmt_idx = STMT_CACHE_TARGET_PRISTINE_PROPS; + else + stmt_idx = STMT_CACHE_TARGET_PROPS; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx)); + SVN_ERR(svn_sqlite__bind_int64(stmt, 1, wcroot->wc_id)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_read_props_streamily(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t pristine, + const apr_array_header_t *changelists, + svn_wc__proplist_receiver_t receiver_func, + void *receiver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + cache_props_baton_t baton; + svn_boolean_t have_row; + apr_pool_t *iterpool; + svn_error_t *err = NULL; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(receiver_func); + SVN_ERR_ASSERT((depth == svn_depth_files) || + (depth == svn_depth_immediates) || + (depth == svn_depth_infinity)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + baton.depth = depth; + baton.pristine = pristine; + baton.changelists = changelists; + baton.cancel_func = cancel_func; + baton.cancel_baton = cancel_baton; + + SVN_ERR(with_finalization(wcroot, local_relpath, + cache_props_recursive, &baton, + NULL, NULL, + cancel_func, cancel_baton, + NULL, NULL, + STMT_DROP_TARGETS_LIST, + scratch_pool)); + + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ALL_TARGET_PROP_CACHE)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (!err && have_row) + { + apr_hash_t *props; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__column_properties(&props, stmt, 1, iterpool, + iterpool)); + + /* see if someone wants to cancel this operation. */ + if (cancel_func) + err = cancel_func(cancel_baton); + + if (!err && props && apr_hash_count(props) != 0) + { + const char *child_relpath; + const char *child_abspath; + + child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + child_abspath = svn_dirent_join(wcroot->abspath, + child_relpath, iterpool); + + err = receiver_func(receiver_baton, child_abspath, props, iterpool); + } + + err = svn_error_compose_create(err, svn_sqlite__step(&have_row, stmt)); + } + + err = svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + svn_pool_destroy(iterpool); + + SVN_ERR(svn_error_compose_create( + err, + svn_sqlite__exec_statements(wcroot->sdb, + STMT_DROP_TARGET_PROP_CACHE))); + return SVN_NO_ERROR; +} + + +/* Helper for svn_wc__db_read_props(). + */ +static svn_error_t * +db_read_props(apr_hash_t **props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_error_t *err = NULL; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row && !svn_sqlite__column_is_null(stmt, 0)) + { + err = svn_sqlite__column_properties(props, stmt, 0, + result_pool, scratch_pool); + } + else + have_row = FALSE; + + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + + if (have_row) + return SVN_NO_ERROR; + + /* No local changes. Return the pristine props for this node. */ + SVN_ERR(db_read_pristine_props(props, wcroot, local_relpath, FALSE, + result_pool, scratch_pool)); + if (*props == NULL) + { + /* Pristine properties are not defined for this node. + ### we need to determine whether this node is in a state that + ### allows for ACTUAL properties (ie. not deleted). for now, + ### just say all nodes, no matter the state, have at least an + ### empty set of props. */ + *props = apr_hash_make(result_pool); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_read_props(apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(db_read_props(props, wcroot, local_relpath, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +db_read_pristine_props(apr_hash_t **props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_boolean_t deleted_ok, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_wc__db_status_t presence; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_NODE_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + + /* Examine the presence: */ + presence = svn_sqlite__column_token(stmt, 1, presence_map); + + /* For "base-deleted", it is obvious the pristine props are located + below the current node. Fetch the NODE from the next record. */ + if (presence == svn_wc__db_status_base_deleted && deleted_ok) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + SVN_ERR_ASSERT(have_row); + + presence = svn_sqlite__column_token(stmt, 1, presence_map); + } + + /* normal or copied: Fetch properties (during update we want + properties for incomplete as well) */ + if (presence == svn_wc__db_status_normal + || presence == svn_wc__db_status_incomplete) + { + svn_error_t *err; + + err = svn_sqlite__column_properties(props, stmt, 0, result_pool, + scratch_pool); + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + + if (!*props) + *props = apr_hash_make(result_pool); + + return SVN_NO_ERROR; + } + + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + svn_sqlite__reset(stmt), + _("The node '%s' has a status that" + " has no properties."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); +} + + +svn_error_t * +svn_wc__db_read_pristine_props(apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(db_read_pristine_props(props, wcroot, local_relpath, TRUE, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_prop_retrieve_recursive(apr_hash_t **values, + svn_wc__db_t *db, + const char *local_abspath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_CURRENT_PROPS_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + *values = apr_hash_make(result_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + apr_hash_t *node_props; + svn_string_t *value; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__column_properties(&node_props, stmt, 0, + iterpool, iterpool)); + + value = (node_props + ? svn_hash_gets(node_props, propname) + : NULL); + + if (value) + { + svn_hash_sets(*values, + svn_dirent_join(wcroot->abspath, + svn_sqlite__column_text(stmt, 1, NULL), + result_pool), + svn_string_dup(value, result_pool)); + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + svn_pool_destroy(iterpool); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +/* The body of svn_wc__db_read_cached_iprops(). */ +static svn_error_t * +db_read_cached_iprops(apr_array_header_t **iprops, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_IPROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + + SVN_ERR(svn_sqlite__column_iprops(iprops, stmt, 0, + result_pool, scratch_pool)); + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_cached_iprops(apr_array_header_t **iprops, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* Don't use with_txn yet, as we perform just a single transaction */ + SVN_ERR(db_read_cached_iprops(iprops, wcroot, local_relpath, + result_pool, scratch_pool)); + + if (!*iprops) + { + *iprops = apr_array_make(result_pool, 0, + sizeof(svn_prop_inherited_item_t *)); + } + + return SVN_NO_ERROR; +} + +/* Remove all prop name value pairs from PROP_HASH where the property + name is not PROPNAME. */ +static void +filter_unwanted_props(apr_hash_t *prop_hash, + const char * propname, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, prop_hash); + hi; + hi = apr_hash_next(hi)) + { + const char *ipropname = svn__apr_hash_index_key(hi); + + if (strcmp(ipropname, propname) != 0) + svn_hash_sets(prop_hash, ipropname, NULL); + } + return; +} + +/* Get the changed properties as stored in the ACTUAL table */ +static svn_error_t * +db_get_changed_props(apr_hash_t **actual_props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row && !svn_sqlite__column_is_null(stmt, 0)) + SVN_ERR(svn_sqlite__column_properties(actual_props, stmt, 0, + result_pool, scratch_pool)); + else + *actual_props = NULL; /* Cached when we read that record */ + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +/* The body of svn_wc__db_read_inherited_props(). */ +static svn_error_t * +db_read_inherited_props(apr_array_header_t **inherited_props, + apr_hash_t **actual_props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i; + apr_array_header_t *cached_iprops = NULL; + apr_array_header_t *iprops; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + svn_sqlite__stmt_t *stmt; + const char *relpath; + const char *expected_parent_repos_relpath = NULL; + const char *parent_relpath; + + iprops = apr_array_make(result_pool, 1, + sizeof(svn_prop_inherited_item_t *)); + *inherited_props = iprops; + + if (actual_props) + *actual_props = NULL; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + + relpath = local_relpath; + + /* Walk up to the root of the WC looking for inherited properties. When we + reach the WC root also check for cached inherited properties. */ + for (relpath = local_relpath; relpath; relpath = parent_relpath) + { + svn_boolean_t have_row; + int op_depth; + svn_wc__db_status_t status; + apr_hash_t *node_props; + + parent_relpath = relpath[0] ? svn_relpath_dirname(relpath, scratch_pool) + : NULL; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + return svn_error_createf( + SVN_ERR_WC_PATH_NOT_FOUND, svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, relpath, + scratch_pool)); + + op_depth = svn_sqlite__column_int(stmt, 0); + + status = svn_sqlite__column_token(stmt, 3, presence_map); + + if (status != svn_wc__db_status_normal + && status != svn_wc__db_status_incomplete) + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, svn_sqlite__reset(stmt), + _("The node '%s' has a status that has no properties."), + path_for_error_message(wcroot, relpath, + scratch_pool)); + + if (op_depth > 0) + { + /* WORKING node. Nothing to check */ + } + else if (expected_parent_repos_relpath) + { + const char *repos_relpath = svn_sqlite__column_text(stmt, 2, NULL); + + if (strcmp(expected_parent_repos_relpath, repos_relpath) != 0) + { + /* The child of this node has a different parent than this node + (It is "switched"), so we can stop here. Note that switched + with the same parent is not interesting for us here. */ + SVN_ERR(svn_sqlite__reset(stmt)); + break; + } + + expected_parent_repos_relpath = + svn_relpath_dirname(expected_parent_repos_relpath, scratch_pool); + } + else + { + const char *repos_relpath = svn_sqlite__column_text(stmt, 2, NULL); + + expected_parent_repos_relpath = + svn_relpath_dirname(repos_relpath, scratch_pool); + } + + if (op_depth == 0 + && !svn_sqlite__column_is_null(stmt, 16)) + { + /* The node contains a cache. No reason to look further */ + SVN_ERR(svn_sqlite__column_iprops(&cached_iprops, stmt, 16, + result_pool, iterpool)); + + parent_relpath = NULL; /* Stop after this */ + } + + SVN_ERR(svn_sqlite__column_properties(&node_props, stmt, 14, + iterpool, iterpool)); + + SVN_ERR(svn_sqlite__reset(stmt)); + + /* If PARENT_ABSPATH is a parent of LOCAL_ABSPATH, then LOCAL_ABSPATH + can inherit properties from it. */ + if (relpath != local_relpath) + { + apr_hash_t *changed_props; + + SVN_ERR(db_get_changed_props(&changed_props, wcroot, relpath, + result_pool, iterpool)); + + if (changed_props) + node_props = changed_props; + else if (node_props) + node_props = svn_prop_hash_dup(node_props, result_pool); + + if (node_props && apr_hash_count(node_props)) + { + /* If we only want PROPNAME filter out any other properties. */ + if (propname) + filter_unwanted_props(node_props, propname, iterpool); + + if (apr_hash_count(node_props)) + { + svn_prop_inherited_item_t *iprop_elt = + apr_pcalloc(result_pool, + sizeof(svn_prop_inherited_item_t)); + iprop_elt->path_or_url = svn_dirent_join(wcroot->abspath, + relpath, + result_pool); + + iprop_elt->prop_hash = node_props; + /* Build the output array in depth-first order. */ + svn_sort__array_insert(&iprop_elt, iprops, 0); + } + } + } + else if (actual_props) + { + apr_hash_t *changed_props; + + SVN_ERR(db_get_changed_props(&changed_props, wcroot, relpath, + result_pool, iterpool)); + + if (changed_props) + *actual_props = changed_props; + else if (node_props) + *actual_props = svn_prop_hash_dup(node_props, result_pool); + } + } + + if (cached_iprops) + { + for (i = cached_iprops->nelts - 1; i >= 0; i--) + { + svn_prop_inherited_item_t *cached_iprop = + APR_ARRAY_IDX(cached_iprops, i, svn_prop_inherited_item_t *); + + /* An empty property hash in the iprops cache means there are no + inherited properties. */ + if (apr_hash_count(cached_iprop->prop_hash) == 0) + continue; + + if (propname) + filter_unwanted_props(cached_iprop->prop_hash, propname, + scratch_pool); + + /* If we didn't filter everything then keep this iprop. */ + if (apr_hash_count(cached_iprop->prop_hash)) + svn_sort__array_insert(&cached_iprop, iprops, 0); + } + } + + if (actual_props && !*actual_props) + *actual_props = apr_hash_make(result_pool); + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_inherited_props(apr_array_header_t **iprops, + apr_hash_t **actual_props, + svn_wc__db_t *db, + const char *local_abspath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(db_read_inherited_props(iprops, actual_props, + wcroot, local_relpath, propname, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_get_children_with_cached_iprops(). + */ +static svn_error_t * +get_children_with_cached_iprops(apr_hash_t **iprop_paths, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + *iprop_paths = apr_hash_make(result_pool); + + /* First check if LOCAL_RELPATH itself has iprops */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_IPROPS_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + const char *relpath_with_cache = svn_sqlite__column_text(stmt, 0, + NULL); + const char *abspath_with_cache = svn_dirent_join(wcroot->abspath, + relpath_with_cache, + result_pool); + svn_hash_sets(*iprop_paths, abspath_with_cache, + svn_sqlite__column_text(stmt, 1, result_pool)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + if (depth == svn_depth_empty) + return SVN_NO_ERROR; + + /* Now fetch information for children or all descendants */ + if (depth == svn_depth_files + || depth == svn_depth_immediates) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_IPROPS_CHILDREN)); + } + else /* Default to svn_depth_infinity. */ + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_IPROPS_RECURSIVE)); + } + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + const char *relpath_with_cache = svn_sqlite__column_text(stmt, 0, + NULL); + const char *abspath_with_cache = svn_dirent_join(wcroot->abspath, + relpath_with_cache, + result_pool); + svn_hash_sets(*iprop_paths, abspath_with_cache, + svn_sqlite__column_text(stmt, 1, result_pool)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + /* For depth files we should filter non files */ + if (depth == svn_depth_files) + { + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + for (hi = apr_hash_first(scratch_pool, *iprop_paths); + hi; + hi = apr_hash_next(hi)) + { + const char *child_abspath = svn__apr_hash_index_key(hi); + const char *child_relpath; + svn_node_kind_t child_kind; + + svn_pool_clear(iterpool); + + child_relpath = svn_dirent_is_child(local_relpath, child_abspath, + NULL); + + if (! child_relpath) + { + continue; /* local_relpath itself */ + } + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &child_kind, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wcroot, child_relpath, + scratch_pool, + scratch_pool)); + + /* Filter if not a file */ + if (child_kind != svn_node_file) + { + svn_hash_sets(*iprop_paths, child_abspath, NULL); + } + } + + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_get_children_with_cached_iprops(apr_hash_t **iprop_paths, + svn_depth_t depth, + const char *local_abspath, + svn_wc__db_t *db, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, + scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + get_children_with_cached_iprops(iprop_paths, wcroot, local_relpath, + depth, result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_children_of_working_node(const apr_array_header_t **children, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + return gather_children2(children, wcroot, local_relpath, + result_pool, scratch_pool); +} + +/* Helper for svn_wc__db_node_check_replace(). + */ +static svn_error_t * +check_replace_txn(svn_boolean_t *is_replace_root_p, + svn_boolean_t *base_replace_p, + svn_boolean_t *is_replace_p, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_boolean_t is_replace = FALSE; + int replaced_op_depth; + svn_wc__db_status_t replaced_status; + + /* Our caller initialized the output values to FALSE */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + { + svn_wc__db_status_t status; + + status = svn_sqlite__column_token(stmt, 3, presence_map); + + if (status != svn_wc__db_status_normal) + return svn_error_trace(svn_sqlite__reset(stmt)); + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + return svn_error_trace(svn_sqlite__reset(stmt)); + + replaced_status = svn_sqlite__column_token(stmt, 3, presence_map); + + /* If the layer below the add describes a not present or a deleted node, + this is not a replacement. Deleted can only occur if an ancestor is + the delete root. */ + if (replaced_status != svn_wc__db_status_not_present + && replaced_status != svn_wc__db_status_excluded + && replaced_status != svn_wc__db_status_server_excluded + && replaced_status != svn_wc__db_status_base_deleted) + { + is_replace = TRUE; + if (is_replace_p) + *is_replace_p = TRUE; + } + + replaced_op_depth = svn_sqlite__column_int(stmt, 0); + + if (base_replace_p) + { + int op_depth = svn_sqlite__column_int(stmt, 0); + + while (op_depth != 0 && have_row) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + op_depth = svn_sqlite__column_int(stmt, 0); + } + + if (have_row && op_depth == 0) + { + svn_wc__db_status_t base_status; + + base_status = svn_sqlite__column_token(stmt, 3, presence_map); + + *base_replace_p = (base_status != svn_wc__db_status_not_present); + } + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + if (!is_replace_root_p || !is_replace) + return SVN_NO_ERROR; + + if (replaced_status != svn_wc__db_status_base_deleted) + { + int parent_op_depth; + + /* Check the current op-depth of the parent to see if we are a replacement + root */ + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + svn_relpath_dirname(local_relpath, + scratch_pool))); + + SVN_ERR(svn_sqlite__step_row(stmt)); /* Parent must exist as 'normal' */ + + parent_op_depth = svn_sqlite__column_int(stmt, 0); + + if (parent_op_depth >= replaced_op_depth) + { + /* Did we replace inside our directory? */ + + *is_replace_root_p = (parent_op_depth == replaced_op_depth); + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + parent_op_depth = svn_sqlite__column_int(stmt, 0); + + SVN_ERR(svn_sqlite__reset(stmt)); + + if (!have_row) + *is_replace_root_p = TRUE; /* Parent is no replacement */ + else if (parent_op_depth < replaced_op_depth) + *is_replace_root_p = TRUE; /* Parent replaces a lower layer */ + /*else // No replacement root */ + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_node_check_replace(svn_boolean_t *is_replace_root, + svn_boolean_t *base_replace, + svn_boolean_t *is_replace, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (is_replace_root) + *is_replace_root = FALSE; + if (base_replace) + *base_replace = FALSE; + if (is_replace) + *is_replace = FALSE; + + if (local_relpath[0] == '\0') + return SVN_NO_ERROR; /* Working copy root can't be replaced */ + + SVN_WC__DB_WITH_TXN( + check_replace_txn(is_replace_root, base_replace, is_replace, + wcroot, local_relpath, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_children(const apr_array_header_t **children, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + return gather_children(children, wcroot, local_relpath, + result_pool, scratch_pool); +} + + +/* */ +static svn_error_t * +relocate_txn(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_boolean_t have_base_node, + apr_int64_t old_repos_id, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + apr_int64_t new_repos_id; + + /* This function affects all the children of the given local_relpath, + but the way that it does this is through the repos inheritance mechanism. + So, we only need to rewrite the repos_id of the given local_relpath, + as well as any children with a non-null repos_id, as well as various + repos_id fields in the locks and working_node tables. + */ + + /* Get the repos_id for the new repository. */ + SVN_ERR(create_repos_id(&new_repos_id, repos_root_url, repos_uuid, + wcroot->sdb, scratch_pool)); + + /* Set the (base and working) repos_ids and clear the dav_caches */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_RECURSIVE_UPDATE_NODE_REPO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isii", wcroot->wc_id, local_relpath, + old_repos_id, new_repos_id)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + if (have_base_node) + { + /* Update any locks for the root or its children. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_LOCK_REPOS_ID)); + SVN_ERR(svn_sqlite__bindf(stmt, "ii", old_repos_id, new_repos_id)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_global_relocate(svn_wc__db_t *db, + const char *local_dir_abspath, + const char *repos_root_url, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *local_dir_relpath; + svn_wc__db_status_t status; + const char *repos_uuid; + svn_boolean_t have_base_node; + apr_int64_t old_repos_id; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath)); + /* ### assert that we were passed a directory? */ + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_dir_relpath, + db, local_dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + local_relpath = local_dir_relpath; + + SVN_ERR(read_info(&status, + NULL, NULL, NULL, &old_repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, + &have_base_node, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (status == svn_wc__db_status_excluded) + { + /* The parent cannot be excluded, so look at the parent and then + adjust the relpath */ + const char *parent_relpath = svn_relpath_dirname(local_dir_relpath, + scratch_pool); + SVN_ERR(read_info(&status, + NULL, NULL, NULL, &old_repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + wcroot, parent_relpath, + scratch_pool, scratch_pool)); + local_dir_relpath = parent_relpath; + } + + if (old_repos_id == INVALID_REPOS_ID) + { + /* Do we need to support relocating something that is + added/deleted/excluded without relocating the parent? If not + then perhaps relpath, root_url and uuid should be passed down + to the children so that they don't have to scan? */ + + if (status == svn_wc__db_status_deleted) + { + const char *work_del_relpath; + + SVN_ERR(scan_deletion_txn(NULL, NULL, + &work_del_relpath, NULL, + wcroot, local_dir_relpath, + scratch_pool, + scratch_pool)); + if (work_del_relpath) + { + /* Deleted within a copy/move */ + + /* The parent of the delete is added. */ + status = svn_wc__db_status_added; + local_dir_relpath = svn_relpath_dirname(work_del_relpath, + scratch_pool); + } + } + + if (status == svn_wc__db_status_added) + { + SVN_ERR(scan_addition(NULL, NULL, NULL, &old_repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, local_dir_relpath, + scratch_pool, scratch_pool)); + } + else + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, NULL, + &old_repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_dir_relpath, + scratch_pool, scratch_pool)); + } + + SVN_ERR(svn_wc__db_fetch_repos_info(NULL, &repos_uuid, wcroot->sdb, + old_repos_id, scratch_pool)); + SVN_ERR_ASSERT(repos_uuid); + + SVN_WC__DB_WITH_TXN( + relocate_txn(wcroot, local_relpath, repos_root_url, repos_uuid, + have_base_node, old_repos_id, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + + +/* Set *REPOS_ID and *REPOS_RELPATH to the BASE repository location of + (WCROOT, LOCAL_RELPATH), directly if its BASE row exists or implied from + its parent's BASE row if not. In the latter case, error if the parent + BASE row does not exist. */ +static svn_error_t * +determine_repos_info(apr_int64_t *repos_id, + const char **repos_relpath, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + const char *repos_parent_relpath; + const char *local_parent_relpath, *name; + + /* ### is it faster to fetch fewer columns? */ + + /* Prefer the current node's repository information. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_BASE_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 0)); + SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 1)); + + *repos_id = svn_sqlite__column_int64(stmt, 0); + *repos_relpath = svn_sqlite__column_text(stmt, 1, result_pool); + + return svn_error_trace(svn_sqlite__reset(stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + /* This was a child node within this wcroot. We want to look at the + BASE node of the directory. */ + svn_relpath_split(&local_parent_relpath, &name, local_relpath, scratch_pool); + + /* The REPOS_ID will be the same (### until we support mixed-repos) */ + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_parent_relpath, repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_parent_relpath, + scratch_pool, scratch_pool)); + + *repos_relpath = svn_relpath_join(repos_parent_relpath, name, result_pool); + + return SVN_NO_ERROR; +} + +/* Helper for svn_wc__db_global_commit() + + Makes local_relpath and all its descendants at the same op-depth represent + the copy origin repos_id:repos_relpath@revision. + + This code is only valid to fix-up a move from an old location, to a new + location during a commit. + + Assumptions: + * local_relpath is not the working copy root (can't be moved) + * repos_relpath is not the repository root (can't be moved) + */ +static svn_error_t * +moved_descendant_commit(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_int64_t repos_id, + const char *repos_relpath, + svn_revnum_t revision, + apr_pool_t *scratch_pool) +{ + apr_hash_t *children; + apr_pool_t *iterpool; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_hash_index_t *hi; + + SVN_ERR_ASSERT(*local_relpath != '\0' + && *repos_relpath != '\0'); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, + op_depth)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (! have_row) + return svn_error_trace(svn_sqlite__reset(stmt)); + + children = apr_hash_make(scratch_pool); + + /* First, obtain all moved children */ + /* To keep error handling simple, first cache them in a hashtable */ + while (have_row) + { + const char *src_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + const char *to_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool); + + svn_hash_sets(children, src_relpath, to_relpath); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Then update them */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_COMMIT_UPDATE_ORIGIN)); + + iterpool = svn_pool_create(scratch_pool); + for (hi = apr_hash_first(scratch_pool, children); hi; hi = apr_hash_next(hi)) + { + const char *src_relpath = svn__apr_hash_index_key(hi); + const char *to_relpath = svn__apr_hash_index_val(hi); + const char *new_repos_relpath; + int to_op_depth = relpath_depth(to_relpath); + int affected; + + svn_pool_clear(iterpool); + + SVN_ERR_ASSERT(to_op_depth > 0); + + new_repos_relpath = svn_relpath_join( + repos_relpath, + svn_relpath_skip_ancestor(local_relpath, + src_relpath), + iterpool); + + SVN_ERR(svn_sqlite__bindf(stmt, "isdisr", wcroot->wc_id, + to_relpath, + to_op_depth, + repos_id, + new_repos_relpath, + revision)); + SVN_ERR(svn_sqlite__update(&affected, stmt)); + +#ifdef SVN_DEBUG + /* Enable in release code? + Broken moves are not fatal yet, but this assertion would break + committing them */ + SVN_ERR_ASSERT(affected >= 1); /* If this fails there is no move dest */ +#endif + + SVN_ERR(moved_descendant_commit(wcroot, to_relpath, to_op_depth, + repos_id, new_repos_relpath, revision, + iterpool)); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +/* Helper for svn_wc__db_global_commit() + + Moves all nodes below LOCAL_RELPATH from op-depth OP_DEPTH to op-depth 0 + (BASE), setting their presence to 'not-present' if their presence wasn't + 'normal'. + + Makes all nodes below LOCAL_RELPATH represent the descendants of repository + location repos_id:repos_relpath@revision. + + Assumptions: + * local_relpath is not the working copy root (can't be replaced) + * repos_relpath is not the repository root (can't be replaced) + */ +static svn_error_t * +descendant_commit(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_int64_t repos_id, + const char *repos_relpath, + svn_revnum_t revision, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR_ASSERT(*local_relpath != '\0' + && *repos_relpath != '\0'); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_COMMIT_DESCENDANTS_TO_BASE)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isdisr", wcroot->wc_id, + local_relpath, + op_depth, + repos_id, + repos_relpath, + revision)); + + SVN_ERR(svn_sqlite__update(NULL, stmt)); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_global_commit(). + */ +static svn_error_t * +commit_node(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_revnum_t new_revision, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const svn_checksum_t *new_checksum, + const apr_array_header_t *new_children, + apr_hash_t *new_dav_cache, + svn_boolean_t keep_changelist, + svn_boolean_t no_unlock, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt_info; + svn_sqlite__stmt_t *stmt_act; + svn_boolean_t have_act; + svn_string_t prop_blob = { 0 }; + svn_string_t inherited_prop_blob = { 0 }; + const char *changelist = NULL; + const char *parent_relpath; + svn_wc__db_status_t new_presence; + svn_node_kind_t new_kind; + const char *new_depth_str = NULL; + svn_sqlite__stmt_t *stmt; + apr_int64_t repos_id; + const char *repos_relpath; + int op_depth; + svn_wc__db_status_t old_presence; + + /* If we are adding a file or directory, then we need to get + repository information from the parent node since "this node" does + not have a BASE). + + For existing nodes, we should retain the (potentially-switched) + repository information. */ + SVN_ERR(determine_repos_info(&repos_id, &repos_relpath, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + /* ### is it better to select only the data needed? */ + SVN_ERR(svn_sqlite__get_statement(&stmt_info, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt_info, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_row(stmt_info)); + + SVN_ERR(svn_sqlite__get_statement(&stmt_act, wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt_act, "is", + wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_act, stmt_act)); + + /* There should be something to commit! */ + + op_depth = svn_sqlite__column_int(stmt_info, 0); + + /* Figure out the new node's kind. It will be whatever is in WORKING_NODE, + or there will be a BASE_NODE that has it. */ + new_kind = svn_sqlite__column_token(stmt_info, 4, kind_map); + + /* What will the new depth be? */ + if (new_kind == svn_node_dir) + new_depth_str = svn_sqlite__column_text(stmt_info, 11, scratch_pool); + + /* Check that the repository information is not being changed. */ + if (op_depth == 0) + { + SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt_info, 1)); + SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt_info, 2)); + + /* A commit cannot change these values. */ + SVN_ERR_ASSERT(repos_id == svn_sqlite__column_int64(stmt_info, 1)); + SVN_ERR_ASSERT(strcmp(repos_relpath, + svn_sqlite__column_text(stmt_info, 2, NULL)) == 0); + } + + /* Find the appropriate new properties -- ACTUAL overrides any properties + in WORKING that arrived as part of a copy/move. + + Note: we'll keep them as a big blob of data, rather than + deserialize/serialize them. */ + if (have_act) + prop_blob.data = svn_sqlite__column_blob(stmt_act, 1, &prop_blob.len, + scratch_pool); + if (prop_blob.data == NULL) + prop_blob.data = svn_sqlite__column_blob(stmt_info, 14, &prop_blob.len, + scratch_pool); + + inherited_prop_blob.data = svn_sqlite__column_blob(stmt_info, 16, + &inherited_prop_blob.len, + scratch_pool); + + if (keep_changelist && have_act) + changelist = svn_sqlite__column_text(stmt_act, 0, scratch_pool); + + old_presence = svn_sqlite__column_token(stmt_info, 3, presence_map); + + /* ### other stuff? */ + + SVN_ERR(svn_sqlite__reset(stmt_info)); + SVN_ERR(svn_sqlite__reset(stmt_act)); + + if (op_depth > 0) + { + int affected_rows; + + /* This removes all layers of this node and at the same time determines + if we need to remove shadowed layers below our descendants. */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ALL_LAYERS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + if (affected_rows > 1) + { + /* We commit a shadowing operation + + 1) Remove all shadowed nodes + 2) And remove all nodes that have a base-deleted as lowest layer, + because 1) removed that layer */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_SHADOWED_RECURSIVE)); + + SVN_ERR(svn_sqlite__bindf(stmt, + "isd", + wcroot->wc_id, + local_relpath, + op_depth)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Note that while these two calls look so similar that they might + be integrated, they really affect a different op-depth and + completely different nodes (via a different recursion pattern). */ + + /* Collapse descendants of the current op_depth in layer 0 */ + SVN_ERR(descendant_commit(wcroot, local_relpath, op_depth, + repos_id, repos_relpath, new_revision, + scratch_pool)); + + /* And make the recorded local moves represent moves of the node we just + committed. */ + SVN_ERR(moved_descendant_commit(wcroot, local_relpath, 0, + repos_id, repos_relpath, new_revision, + scratch_pool)); + + /* This node is no longer modified, so no node was moved here */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_TO_FROM_DEST)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Update or add the BASE_NODE row with all the new information. */ + + if (*local_relpath == '\0') + parent_relpath = NULL; + else + parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool); + + /* Preserve any incomplete status */ + new_presence = (old_presence == svn_wc__db_status_incomplete + ? svn_wc__db_status_incomplete + : svn_wc__db_status_normal); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_APPLY_CHANGES_TO_BASE_NODE)); + /* symlink_target not yet used */ + SVN_ERR(svn_sqlite__bindf(stmt, "issisrtstrisnbn", + wcroot->wc_id, local_relpath, + parent_relpath, + repos_id, + repos_relpath, + new_revision, + presence_map, new_presence, + new_depth_str, + kind_map, new_kind, + changed_rev, + changed_date, + changed_author, + prop_blob.data, prop_blob.len)); + + SVN_ERR(svn_sqlite__bind_checksum(stmt, 13, new_checksum, + scratch_pool)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 15, new_dav_cache, + scratch_pool)); + if (inherited_prop_blob.data != NULL) + { + SVN_ERR(svn_sqlite__bind_blob(stmt, 17, inherited_prop_blob.data, + inherited_prop_blob.len)); + } + + SVN_ERR(svn_sqlite__step_done(stmt)); + + if (have_act) + { + if (keep_changelist && changelist != NULL) + { + /* The user told us to keep the changelist. Replace the row in + ACTUAL_NODE with the basic keys and the changelist. */ + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + STMT_RESET_ACTUAL_WITH_CHANGELIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "isss", + wcroot->wc_id, local_relpath, + svn_relpath_dirname(local_relpath, + scratch_pool), + changelist)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + else + { + /* Toss the ACTUAL_NODE row. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + } + + if (new_kind == svn_node_dir) + { + /* When committing a directory, we should have its new children. */ + /* ### one day. just not today. */ +#if 0 + SVN_ERR_ASSERT(new_children != NULL); +#endif + + /* ### process the children */ + } + + if (!no_unlock) + { + svn_sqlite__stmt_t *lock_stmt; + + SVN_ERR(svn_sqlite__get_statement(&lock_stmt, wcroot->sdb, + STMT_DELETE_LOCK)); + SVN_ERR(svn_sqlite__bindf(lock_stmt, "is", repos_id, repos_relpath)); + SVN_ERR(svn_sqlite__step_done(lock_stmt)); + } + + /* Install any work items into the queue, as part of this transaction. */ + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_global_commit(svn_wc__db_t *db, + const char *local_abspath, + svn_revnum_t new_revision, + svn_revnum_t changed_revision, + apr_time_t changed_date, + const char *changed_author, + const svn_checksum_t *new_checksum, + const apr_array_header_t *new_children, + apr_hash_t *new_dav_cache, + svn_boolean_t keep_changelist, + svn_boolean_t no_unlock, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + svn_wc__db_wcroot_t *wcroot; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_revision)); + SVN_ERR_ASSERT(new_checksum == NULL || new_children == NULL); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + commit_node(wcroot, local_relpath, + new_revision, changed_revision, changed_date, changed_author, + new_checksum, new_children, new_dav_cache, keep_changelist, + no_unlock, work_items, scratch_pool), + wcroot); + + /* We *totally* monkeyed the entries. Toss 'em. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_global_update(svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t new_kind, + const char *new_repos_relpath, + svn_revnum_t new_revision, + const apr_hash_t *new_props, + svn_revnum_t new_changed_rev, + apr_time_t new_changed_date, + const char *new_changed_author, + const apr_array_header_t *new_children, + const svn_checksum_t *new_checksum, + const char *new_target, + const apr_hash_t *new_dav_cache, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + NOT_IMPLEMENTED(); + +#if 0 + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + /* ### allow NULL for NEW_REPOS_RELPATH to indicate "no change"? */ + SVN_ERR_ASSERT(svn_relpath_is_canonical(new_repos_relpath)); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_revision)); + SVN_ERR_ASSERT(new_props != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_changed_rev)); + SVN_ERR_ASSERT((new_children != NULL + && new_checksum == NULL + && new_target == NULL) + || (new_children == NULL + && new_checksum != NULL + && new_target == NULL) + || (new_children == NULL + && new_checksum == NULL + && new_target != NULL)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + update_node(wcroot, local_relpath, + new_repos_relpath, new_revision, new_props, + new_changed_rev, new_changed_date, new_changed_author, + new_children, new_checksum, new_target, + conflict, work_items, scratch_pool), + wcroot); + + /* We *totally* monkeyed the entries. Toss 'em. */ + SVN_ERR(flush_entries(wcroot, local_abspath, scratch_pool)); + + return SVN_NO_ERROR; +#endif +} + +/* Sets a base nodes revision, repository relative path, and/or inherited + propertis. If LOCAL_ABSPATH's rev (REV) is valid, set its revision. If + SET_REPOS_RELPATH is TRUE set its repository relative path to REPOS_RELPATH + (and make sure its REPOS_ID is still valid). If IPROPS is not NULL set its + inherited properties to IPROPS, if IPROPS is NULL then clear any the iprops + cache for the base node. + */ +static svn_error_t * +db_op_set_rev_repos_relpath_iprops(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_array_header_t *iprops, + svn_revnum_t rev, + svn_boolean_t set_repos_relpath, + const char *repos_relpath, + apr_int64_t repos_id, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(flush_entries(wcroot, + svn_dirent_join(wcroot->abspath, local_relpath, + scratch_pool), + svn_depth_empty, scratch_pool)); + + + if (SVN_IS_VALID_REVNUM(rev)) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_BASE_REVISION)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isr", wcroot->wc_id, local_relpath, + rev)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + if (set_repos_relpath) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_BASE_REPOS)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isis", wcroot->wc_id, local_relpath, + repos_id, repos_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Set or clear iprops. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_IPROP)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__bind_iprops(stmt, 3, iprops, scratch_pool)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +/* The main body of bump_revisions_post_update(). + * + * Tweak the information for LOCAL_RELPATH in WCROOT. If NEW_REPOS_RELPATH is + * non-NULL update the entry to the new url specified by NEW_REPOS_RELPATH, + * NEW_REPOS_ID. If NEW_REV is valid, make this the node's working revision. + * + * If WCROOT_IPROPS is not NULL it is a hash mapping const char * absolute + * working copy paths to depth-first ordered arrays of + * svn_prop_inherited_item_t * structures. If the absolute path equivalent + * of LOCAL_RELPATH exists in WCROOT_IPROPS, then set the hashed value as the + * node's inherited properties. + * + * Unless S_ROOT is TRUE the tweaks might cause the node for LOCAL_ABSPATH to + * be removed from the WC; if IS_ROOT is TRUE this will not happen. + */ +static svn_error_t * +bump_node_revision(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_int64_t new_repos_id, + const char *new_repos_relpath, + svn_revnum_t new_rev, + svn_depth_t depth, + apr_hash_t *exclude_relpaths, + apr_hash_t *wcroot_iprops, + svn_boolean_t is_root, + svn_boolean_t skip_when_dir, + svn_wc__db_t *db, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + const apr_array_header_t *children; + int i; + svn_wc__db_status_t status; + svn_node_kind_t db_kind; + svn_revnum_t revision; + const char *repos_relpath; + apr_int64_t repos_id; + svn_boolean_t set_repos_relpath = FALSE; + svn_boolean_t update_root; + svn_depth_t depth_below_here = depth; + apr_array_header_t *iprops = NULL; + + /* Skip an excluded path and its descendants. */ + if (svn_hash_gets(exclude_relpaths, local_relpath)) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__db_base_get_info_internal(&status, &db_kind, &revision, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &update_root, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + /* Skip file externals */ + if (update_root + && db_kind == svn_node_file + && !is_root) + return SVN_NO_ERROR; + + if (skip_when_dir && db_kind == svn_node_dir) + return SVN_NO_ERROR; + + /* If the node is still marked 'not-present', then the server did not + re-add it. So it's really gone in this revision, thus we remove the node. + + If the node is still marked 'server-excluded' and yet is not the same + revision as new_rev, then the server did not re-add it, nor + re-server-exclude it, so we can remove the node. */ + if (!is_root + && (status == svn_wc__db_status_not_present + || (status == svn_wc__db_status_server_excluded && + revision != new_rev))) + { + return svn_error_trace(db_base_remove(wcroot, local_relpath, + db, FALSE, FALSE, + SVN_INVALID_REVNUM, + NULL, NULL, scratch_pool)); + } + + if (new_repos_relpath != NULL && strcmp(repos_relpath, new_repos_relpath)) + set_repos_relpath = TRUE; + + if (wcroot_iprops) + iprops = svn_hash_gets(wcroot_iprops, + svn_dirent_join(wcroot->abspath, local_relpath, + scratch_pool)); + + if (iprops + || set_repos_relpath + || (SVN_IS_VALID_REVNUM(new_rev) && new_rev != revision)) + { + SVN_ERR(db_op_set_rev_repos_relpath_iprops(wcroot, local_relpath, + iprops, new_rev, + set_repos_relpath, + new_repos_relpath, + new_repos_id, + scratch_pool)); + } + + /* Early out */ + if (depth <= svn_depth_empty + || db_kind != svn_node_dir + || status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_not_present) + return SVN_NO_ERROR; + + /* And now recurse over the children */ + + depth_below_here = depth; + + if (depth == svn_depth_immediates || depth == svn_depth_files) + depth_below_here = svn_depth_empty; + + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(gather_repo_children(&children, wcroot, local_relpath, 0, + scratch_pool, iterpool)); + for (i = 0; i < children->nelts; i++) + { + const char *child_basename = APR_ARRAY_IDX(children, i, const char *); + const char *child_local_relpath; + const char *child_repos_relpath = NULL; + + svn_pool_clear(iterpool); + + /* Derive the new URL for the current (child) entry */ + if (new_repos_relpath) + child_repos_relpath = svn_relpath_join(new_repos_relpath, + child_basename, iterpool); + + child_local_relpath = svn_relpath_join(local_relpath, child_basename, + iterpool); + + SVN_ERR(bump_node_revision(wcroot, child_local_relpath, new_repos_id, + child_repos_relpath, new_rev, + depth_below_here, + exclude_relpaths, wcroot_iprops, + FALSE /* is_root */, + (depth < svn_depth_immediates), db, + iterpool)); + } + + /* Cleanup */ + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Helper for svn_wc__db_op_bump_revisions_post_update(). + */ +static svn_error_t * +bump_revisions_post_update(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + svn_depth_t depth, + const char *new_repos_relpath, + const char *new_repos_root_url, + const char *new_repos_uuid, + svn_revnum_t new_revision, + apr_hash_t *exclude_relpaths, + apr_hash_t *wcroot_iprops, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_error_t *err; + apr_int64_t new_repos_id = INVALID_REPOS_ID; + + err = svn_wc__db_base_get_info_internal(&status, &kind, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool); + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + switch (status) + { + case svn_wc__db_status_excluded: + case svn_wc__db_status_server_excluded: + case svn_wc__db_status_not_present: + return SVN_NO_ERROR; + + /* Explicitly ignore other statii */ + default: + break; + } + + if (new_repos_root_url != NULL) + SVN_ERR(create_repos_id(&new_repos_id, new_repos_root_url, + new_repos_uuid, + wcroot->sdb, scratch_pool)); + + SVN_ERR(bump_node_revision(wcroot, local_relpath, new_repos_id, + new_repos_relpath, new_revision, + depth, exclude_relpaths, + wcroot_iprops, + TRUE /* is_root */, FALSE, db, + scratch_pool)); + + SVN_ERR(svn_wc__db_bump_moved_away(wcroot, local_relpath, depth, db, + scratch_pool)); + + SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, SVN_INVALID_REVNUM, + SVN_INVALID_REVNUM, notify_func, + notify_baton, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_bump_revisions_post_update(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + const char *new_repos_relpath, + const char *new_repos_root_url, + const char *new_repos_uuid, + svn_revnum_t new_revision, + apr_hash_t *exclude_relpaths, + apr_hash_t *wcroot_iprops, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + svn_wc__db_wcroot_t *wcroot; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + + VERIFY_USABLE_WCROOT(wcroot); + + if (svn_hash_gets(exclude_relpaths, local_relpath)) + return SVN_NO_ERROR; + + if (depth == svn_depth_unknown) + depth = svn_depth_infinity; + + SVN_WC__DB_WITH_TXN( + bump_revisions_post_update(wcroot, local_relpath, db, + depth, new_repos_relpath, new_repos_root_url, + new_repos_uuid, new_revision, + exclude_relpaths, wcroot_iprops, + notify_func, notify_baton, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_lock_add(). + */ +static svn_error_t * +lock_add_txn(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const svn_wc__db_lock_t *lock, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + const char *repos_relpath; + apr_int64_t repos_id; + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_LOCK)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", + repos_id, repos_relpath, lock->token)); + + if (lock->owner != NULL) + SVN_ERR(svn_sqlite__bind_text(stmt, 4, lock->owner)); + + if (lock->comment != NULL) + SVN_ERR(svn_sqlite__bind_text(stmt, 5, lock->comment)); + + if (lock->date != 0) + SVN_ERR(svn_sqlite__bind_int64(stmt, 6, lock->date)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_lock_add(svn_wc__db_t *db, + const char *local_abspath, + const svn_wc__db_lock_t *lock, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(lock != NULL); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + lock_add_txn(wcroot, local_relpath, lock, scratch_pool), + wcroot); + + /* There may be some entries, and the lock info is now out of date. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_lock_remove(). + */ +static svn_error_t * +lock_remove_txn(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + const char *repos_relpath; + apr_int64_t repos_id; + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_LOCK)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", repos_id, repos_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_lock_remove(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + lock_remove_txn(wcroot, local_relpath, scratch_pool), + wcroot); + + /* There may be some entries, and the lock info is now out of date. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_scan_base_repos(const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + apr_int64_t repos_id; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + result_pool, scratch_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, wcroot->sdb, + repos_id, result_pool)); + + return SVN_NO_ERROR; +} + + +/* A helper for scan_addition(). + * Compute moved-from information for the node at LOCAL_RELPATH which + * has been determined as having been moved-here. + * If MOVED_FROM_RELPATH is not NULL, set *MOVED_FROM_RELPATH to the + * path of the move-source node in *MOVED_FROM_RELPATH. + * If DELETE_OP_ROOT_RELPATH is not NULL, set *DELETE_OP_ROOT_RELPATH + * to the path of the op-root of the delete-half of the move. + * If moved-from information cannot be derived, set both *MOVED_FROM_RELPATH + * and *DELETE_OP_ROOT_RELPATH to NULL, and return a "copied" status. + * COPY_OPT_ROOT_RELPATH is the relpath of the op-root of the copied-half + * of the move. */ +static svn_error_t * +get_moved_from_info(const char **moved_from_relpath, + const char **moved_from_op_root_relpath, + const char *moved_to_op_root_relpath, + int *op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + /* Run a query to get the moved-from path from the DB. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_FROM_RELPATH)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, moved_to_op_root_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + /* The move was only recorded at the copy-half, possibly because + * the move operation was interrupted mid-way between the copy + * and the delete. Treat this node as a normal copy. */ + if (moved_from_relpath) + *moved_from_relpath = NULL; + if (moved_from_op_root_relpath) + *moved_from_op_root_relpath = NULL; + + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + + if (op_depth) + *op_depth = svn_sqlite__column_int(stmt, 1); + + if (moved_from_relpath || moved_from_op_root_relpath) + { + const char *db_delete_op_root_relpath; + + /* The moved-from path from the DB is the relpath of + * the op_root of the delete-half of the move. */ + db_delete_op_root_relpath = svn_sqlite__column_text(stmt, 0, + result_pool); + if (moved_from_op_root_relpath) + *moved_from_op_root_relpath = db_delete_op_root_relpath; + + if (moved_from_relpath) + { + if (strcmp(moved_to_op_root_relpath, local_relpath) == 0) + { + /* LOCAL_RELPATH is the op_root of the copied-half of the + * move, so the correct MOVED_FROM_ABSPATH is the op-root + * of the delete-half. */ + *moved_from_relpath = db_delete_op_root_relpath; + } + else + { + const char *child_relpath; + + /* LOCAL_RELPATH is a child that was copied along with the + * op_root of the copied-half of the move. Construct the + * corresponding path beneath the op_root of the delete-half. */ + + /* Grab the child path relative to the op_root of the move + * destination. */ + child_relpath = svn_relpath_skip_ancestor( + moved_to_op_root_relpath, local_relpath); + + SVN_ERR_ASSERT(child_relpath && strlen(child_relpath) > 0); + + /* This join is valid because LOCAL_RELPATH has not been moved + * within the copied-half of the move yet -- else, it would + * be its own op_root. */ + *moved_from_relpath = svn_relpath_join(db_delete_op_root_relpath, + child_relpath, + result_pool); + } + } + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +/* The body of scan_addition(). + */ +static svn_error_t * +scan_addition_txn(svn_wc__db_status_t *status, + const char **op_root_relpath_p, + const char **repos_relpath, + apr_int64_t *repos_id, + const char **original_repos_relpath, + apr_int64_t *original_repos_id, + svn_revnum_t *original_revision, + const char **moved_from_relpath, + const char **moved_from_op_root_relpath, + int *moved_from_op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *op_root_relpath; + const char *build_relpath = ""; + + /* Initialize most of the OUT parameters. Generally, we'll only be filling + in a subset of these, so it is easier to init all up front. Note that + the STATUS parameter will be initialized once we read the status of + the specified node. */ + if (op_root_relpath_p) + *op_root_relpath_p = NULL; + if (original_repos_relpath) + *original_repos_relpath = NULL; + if (original_repos_id) + *original_repos_id = INVALID_REPOS_ID; + if (original_revision) + *original_revision = SVN_INVALID_REVNUM; + if (moved_from_relpath) + *moved_from_relpath = NULL; + if (moved_from_op_root_relpath) + *moved_from_op_root_relpath = NULL; + if (moved_from_op_depth) + *moved_from_op_depth = 0; + + { + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_wc__db_status_t presence; + int op_depth; + const char *repos_prefix_path = ""; + int i; + + /* ### is it faster to fetch fewer columns? */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + /* Reset statement before returning */ + SVN_ERR(svn_sqlite__reset(stmt)); + + /* ### maybe we should return a usage error instead? */ + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + presence = svn_sqlite__column_token(stmt, 1, presence_map); + + /* The starting node should exist normally. */ + op_depth = svn_sqlite__column_int(stmt, 0); + if (op_depth == 0 || (presence != svn_wc__db_status_normal + && presence != svn_wc__db_status_incomplete)) + /* reset the statement as part of the error generation process */ + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + svn_sqlite__reset(stmt), + _("Expected node '%s' to be added."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + + if (original_revision) + *original_revision = svn_sqlite__column_revnum(stmt, 12); + + /* Provide the default status; we'll override as appropriate. */ + if (status) + { + if (presence == svn_wc__db_status_normal) + *status = svn_wc__db_status_added; + else + *status = svn_wc__db_status_incomplete; + } + + + /* Calculate the op root local path components */ + op_root_relpath = local_relpath; + + for (i = relpath_depth(local_relpath); i > op_depth; --i) + { + /* Calculate the path of the operation root */ + repos_prefix_path = + svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL), + repos_prefix_path, + scratch_pool); + op_root_relpath = svn_relpath_dirname(op_root_relpath, scratch_pool); + } + + if (op_root_relpath_p) + *op_root_relpath_p = apr_pstrdup(result_pool, op_root_relpath); + + /* ### This if-statement is quite redundant. + * ### We're checking all these values again within the body anyway. + * ### The body should be broken up appropriately and move into the + * ### outer scope. */ + if (original_repos_relpath + || original_repos_id + || (original_revision + && *original_revision == SVN_INVALID_REVNUM) + || status + || moved_from_relpath || moved_from_op_root_relpath) + { + if (local_relpath != op_root_relpath) + /* requery to get the add/copy root */ + { + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, op_root_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + /* Reset statement before returning */ + SVN_ERR(svn_sqlite__reset(stmt)); + + /* ### maybe we should return a usage error instead? */ + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + op_root_relpath, + scratch_pool)); + } + + if (original_revision + && *original_revision == SVN_INVALID_REVNUM) + *original_revision = svn_sqlite__column_revnum(stmt, 12); + } + + if (original_repos_relpath) + *original_repos_relpath = svn_sqlite__column_text(stmt, 11, + result_pool); + + if (!svn_sqlite__column_is_null(stmt, 10) + && (status + || original_repos_id + || moved_from_relpath || moved_from_op_root_relpath)) + /* If column 10 (original_repos_id) is NULL, + this is a plain add, not a copy or a move */ + { + svn_boolean_t moved_here; + if (original_repos_id) + *original_repos_id = svn_sqlite__column_int64(stmt, 10); + + moved_here = svn_sqlite__column_boolean(stmt, 13 /* moved_here */); + if (status) + *status = moved_here ? svn_wc__db_status_moved_here + : svn_wc__db_status_copied; + + if (moved_here + && (moved_from_relpath || moved_from_op_root_relpath)) + { + svn_error_t *err; + + err = get_moved_from_info(moved_from_relpath, + moved_from_op_root_relpath, + op_root_relpath, + moved_from_op_depth, + wcroot, local_relpath, + result_pool, + scratch_pool); + + if (err) + return svn_error_compose_create( + err, svn_sqlite__reset(stmt)); + } + } + } + + + /* ### This loop here is to skip up to the first node which is a BASE node, + because base_get_info() doesn't accommodate the scenario that + we're looking at here; we found the true op_root, which may be inside + further changed trees. */ + if (repos_relpath || repos_id) + { + const char *base_relpath; + + while (TRUE) + { + + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Pointing at op_depth, look at the parent */ + repos_prefix_path = + svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL), + repos_prefix_path, + scratch_pool); + op_root_relpath = svn_relpath_dirname(op_root_relpath, scratch_pool); + + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, op_root_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (! have_row) + break; + + op_depth = svn_sqlite__column_int(stmt, 0); + + /* Skip to op_depth */ + for (i = relpath_depth(op_root_relpath); i > op_depth; i--) + { + /* Calculate the path of the operation root */ + repos_prefix_path = + svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL), + repos_prefix_path, + scratch_pool); + op_root_relpath = + svn_relpath_dirname(op_root_relpath, scratch_pool); + } + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + build_relpath = repos_prefix_path; + + /* If we're here, then we have an added/copied/moved (start) node, and + CURRENT_ABSPATH now points to a BASE node. Figure out the repository + information for the current node, and use that to compute the start + node's repository information. */ + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &base_relpath, repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, op_root_relpath, + scratch_pool, scratch_pool)); + + if (repos_relpath) + *repos_relpath = svn_relpath_join(base_relpath, build_relpath, + result_pool); + } + else + SVN_ERR(svn_sqlite__reset(stmt)); + } + /* Postconditions */ +#ifdef SVN_DEBUG + if (status) + { + SVN_ERR_ASSERT(*status == svn_wc__db_status_added + || *status == svn_wc__db_status_copied + || *status == svn_wc__db_status_incomplete + || *status == svn_wc__db_status_moved_here); + if (*status == svn_wc__db_status_added) + { + SVN_ERR_ASSERT(!original_repos_relpath + || *original_repos_relpath == NULL); + SVN_ERR_ASSERT(!original_revision + || *original_revision == SVN_INVALID_REVNUM); + SVN_ERR_ASSERT(!original_repos_id + || *original_repos_id == INVALID_REPOS_ID); + } + /* An upgrade with a missing directory can leave INCOMPLETE working + op-roots. See upgrade_tests.py 29: upgrade with missing replaced dir + */ + else if (*status != svn_wc__db_status_incomplete) + { + SVN_ERR_ASSERT(!original_repos_relpath + || *original_repos_relpath != NULL); + SVN_ERR_ASSERT(!original_revision + || *original_revision != SVN_INVALID_REVNUM); + SVN_ERR_ASSERT(!original_repos_id + || *original_repos_id != INVALID_REPOS_ID); + } + } + SVN_ERR_ASSERT(!op_root_relpath_p || *op_root_relpath_p != NULL); +#endif + + return SVN_NO_ERROR; +} + + +/* Like svn_wc__db_scan_addition(), but with WCROOT+LOCAL_RELPATH instead of + DB+LOCAL_ABSPATH. + + The output value of *ORIGINAL_REPOS_ID will be INVALID_REPOS_ID if there + is no 'copy-from' repository. */ +static svn_error_t * +scan_addition(svn_wc__db_status_t *status, + const char **op_root_relpath, + const char **repos_relpath, + apr_int64_t *repos_id, + const char **original_repos_relpath, + apr_int64_t *original_repos_id, + svn_revnum_t *original_revision, + const char **moved_from_relpath, + const char **moved_from_op_root_relpath, + int *moved_from_op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_WC__DB_WITH_TXN( + scan_addition_txn(status, op_root_relpath, repos_relpath, repos_id, + original_repos_relpath, original_repos_id, + original_revision, moved_from_relpath, + moved_from_op_root_relpath, moved_from_op_depth, + wcroot, local_relpath, result_pool, scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_scan_addition(svn_wc__db_status_t *status, + const char **op_root_abspath, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + const char **original_repos_relpath, + const char **original_root_url, + const char **original_uuid, + svn_revnum_t *original_revision, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *op_root_relpath = NULL; + apr_int64_t repos_id = INVALID_REPOS_ID; + apr_int64_t original_repos_id = INVALID_REPOS_ID; + apr_int64_t *repos_id_p + = (repos_root_url || repos_uuid) ? &repos_id : NULL; + apr_int64_t *original_repos_id_p + = (original_root_url || original_uuid) ? &original_repos_id : NULL; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(scan_addition(status, + op_root_abspath + ? &op_root_relpath + : NULL, + repos_relpath, repos_id_p, + original_repos_relpath, original_repos_id_p, + original_revision, + NULL, NULL, NULL, + wcroot, local_relpath, result_pool, scratch_pool)); + + if (op_root_abspath) + *op_root_abspath = svn_dirent_join(wcroot->abspath, op_root_relpath, + result_pool); + /* REPOS_ID must be valid if requested; ORIGINAL_REPOS_ID need not be. */ + SVN_ERR_ASSERT(repos_id_p == NULL || repos_id != INVALID_REPOS_ID); + + SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, wcroot->sdb, + repos_id, result_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(original_root_url, original_uuid, + wcroot->sdb, original_repos_id, + result_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_scan_moved(const char **moved_from_abspath, + const char **op_root_abspath, + const char **op_root_moved_from_abspath, + const char **moved_from_delete_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_wc__db_status_t status; + const char *op_root_relpath = NULL; + const char *moved_from_relpath = NULL; + const char *moved_from_op_root_relpath = NULL; + int moved_from_op_depth = -1; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(scan_addition(&status, + op_root_abspath + ? &op_root_relpath + : NULL, + NULL, NULL, + NULL, NULL, NULL, + moved_from_abspath + ? &moved_from_relpath + : NULL, + (op_root_moved_from_abspath + || moved_from_delete_abspath) + ? &moved_from_op_root_relpath + : NULL, + moved_from_delete_abspath + ? &moved_from_op_depth + : NULL, + wcroot, local_relpath, scratch_pool, scratch_pool)); + + if (status != svn_wc__db_status_moved_here || !moved_from_relpath) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Path '%s' was not moved here"), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + if (op_root_abspath) + *op_root_abspath = svn_dirent_join(wcroot->abspath, op_root_relpath, + result_pool); + + if (moved_from_abspath) + *moved_from_abspath = svn_dirent_join(wcroot->abspath, moved_from_relpath, + result_pool); + + if (op_root_moved_from_abspath) + *op_root_moved_from_abspath = svn_dirent_join(wcroot->abspath, + moved_from_op_root_relpath, + result_pool); + + /* The deleted node is either where we moved from, or one of its ancestors */ + if (moved_from_delete_abspath) + { + const char *tmp = moved_from_op_root_relpath; + + SVN_ERR_ASSERT(moved_from_op_depth >= 0); + + while (relpath_depth(tmp) > moved_from_op_depth) + tmp = svn_relpath_dirname(tmp, scratch_pool); + + *moved_from_delete_abspath = svn_dirent_join(wcroot->abspath, tmp, + scratch_pool); + } + + return SVN_NO_ERROR; +} + +/* ### + */ +static svn_error_t * +follow_moved_to(apr_array_header_t **moved_tos, + int op_depth, + const char *repos_path, + svn_revnum_t revision, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int working_op_depth; + const char *ancestor_relpath, *node_moved_to = NULL; + int i; + + SVN_ERR_ASSERT((!op_depth && !repos_path) || (op_depth && repos_path)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_OP_DEPTH_MOVED_TO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + working_op_depth = svn_sqlite__column_int(stmt, 0); + node_moved_to = svn_sqlite__column_text(stmt, 1, result_pool); + if (!repos_path) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row || svn_sqlite__column_revnum(stmt, 0)) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The base node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + repos_path = svn_sqlite__column_text(stmt, 2, scratch_pool); + revision = svn_sqlite__column_revnum(stmt, 3); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + if (node_moved_to) + { + svn_boolean_t have_row2; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_HERE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, node_moved_to, + relpath_depth(node_moved_to))); + SVN_ERR(svn_sqlite__step(&have_row2, stmt)); + if (!have_row2 || !svn_sqlite__column_int(stmt, 0) + || revision != svn_sqlite__column_revnum(stmt, 3) + || strcmp(repos_path, svn_sqlite__column_text(stmt, 2, NULL))) + node_moved_to = NULL; + SVN_ERR(svn_sqlite__reset(stmt)); + } + + if (node_moved_to) + { + struct svn_wc__db_moved_to_t *moved_to; + + moved_to = apr_palloc(result_pool, sizeof(*moved_to)); + moved_to->op_depth = working_op_depth; + moved_to->local_relpath = node_moved_to; + APR_ARRAY_PUSH(*moved_tos, struct svn_wc__db_moved_to_t *) = moved_to; + } + + /* A working row with moved_to, or no working row, and we are done. */ + if (node_moved_to || !have_row) + return SVN_NO_ERROR; + + /* Need to handle being moved via an ancestor. */ + ancestor_relpath = local_relpath; + for (i = relpath_depth(local_relpath); i > working_op_depth; --i) + { + const char *ancestor_moved_to; + + ancestor_relpath = svn_relpath_dirname(ancestor_relpath, scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_TO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, ancestor_relpath, + working_op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR_ASSERT(have_row); + ancestor_moved_to = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + if (ancestor_moved_to) + { + node_moved_to + = svn_relpath_join(ancestor_moved_to, + svn_relpath_skip_ancestor(ancestor_relpath, + local_relpath), + result_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_HERE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, node_moved_to, + relpath_depth(ancestor_moved_to))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + ancestor_moved_to = NULL; + else if (!svn_sqlite__column_int(stmt, 0)) + { + svn_wc__db_status_t presence + = svn_sqlite__column_token(stmt, 1, presence_map); + if (presence != svn_wc__db_status_not_present) + ancestor_moved_to = NULL; + else + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row && !svn_sqlite__column_int(stmt, 0)) + ancestor_moved_to = NULL; + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + if (!ancestor_moved_to) + break; + /* verify repos_path points back? */ + } + if (ancestor_moved_to) + { + struct svn_wc__db_moved_to_t *moved_to; + + moved_to = apr_palloc(result_pool, sizeof(*moved_to)); + moved_to->op_depth = working_op_depth; + moved_to->local_relpath = node_moved_to; + APR_ARRAY_PUSH(*moved_tos, struct svn_wc__db_moved_to_t *) = moved_to; + + SVN_ERR(follow_moved_to(moved_tos, relpath_depth(ancestor_moved_to), + repos_path, revision, wcroot, node_moved_to, + result_pool, scratch_pool)); + break; + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_follow_moved_to(apr_array_header_t **moved_tos, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + *moved_tos = apr_array_make(result_pool, 0, + sizeof(struct svn_wc__db_moved_to_t *)); + + /* ### Wrap in a transaction */ + SVN_ERR(follow_moved_to(moved_tos, 0, NULL, SVN_INVALID_REVNUM, + wcroot, local_relpath, + result_pool, scratch_pool)); + + /* ### Convert moved_to to abspath */ + + return SVN_NO_ERROR; +} + +/* Extract the moved-to information for LOCAL_RELPATH at OP-DEPTH by + examining the lowest working node above OP_DEPTH. The output paths + are NULL if there is no move, otherwise: + + *MOVE_DST_RELPATH: the moved-to destination of LOCAL_RELPATH. + + *MOVE_DST_OP_ROOT_RELPATH: the moved-to destination of the root of + the move of LOCAL_RELPATH. This may be equal to *MOVE_DST_RELPATH + if LOCAL_RELPATH is the root of the move. + + *MOVE_SRC_ROOT_RELPATH: the root of the move source. For moves + inside a delete this will be different from *MOVE_SRC_OP_ROOT_RELPATH. + + *MOVE_SRC_OP_ROOT_RELPATH: the root of the source layer that + contains the move. For moves inside deletes this is the root of + the delete, for other moves this is the root of the move. + + Given a path A/B/C with A/B moved to X then for A/B/C + + MOVE_DST_RELPATH is X/C + MOVE_DST_OP_ROOT_RELPATH is X + MOVE_SRC_ROOT_RELPATH is A/B + MOVE_SRC_OP_ROOT_RELPATH is A/B + + If A is then deleted the MOVE_DST_RELPATH, MOVE_DST_OP_ROOT_RELPATH + and MOVE_SRC_ROOT_RELPATH remain the same but MOVE_SRC_OP_ROOT_RELPATH + changes to A. + + ### Think about combining with scan_deletion? Also with + ### scan_addition to get moved-to for replaces? Do we need to + ### return the op-root of the move source, i.e. A/B in the example + ### above? */ +svn_error_t * +svn_wc__db_op_depth_moved_to(const char **move_dst_relpath, + const char **move_dst_op_root_relpath, + const char **move_src_root_relpath, + const char **move_src_op_root_relpath, + int op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int delete_op_depth; + const char *relpath = local_relpath; + + *move_dst_relpath = *move_dst_op_root_relpath = NULL; + *move_src_root_relpath = *move_src_op_root_relpath = NULL; + + do + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_LOWEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, relpath, op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + delete_op_depth = svn_sqlite__column_int(stmt, 0); + *move_dst_op_root_relpath = svn_sqlite__column_text(stmt, 3, + result_pool); + if (*move_dst_op_root_relpath) + *move_src_root_relpath = apr_pstrdup(result_pool, relpath); + } + SVN_ERR(svn_sqlite__reset(stmt)); + if (!*move_dst_op_root_relpath) + relpath = svn_relpath_dirname(relpath, scratch_pool); + } + while (!*move_dst_op_root_relpath + && have_row && delete_op_depth <= relpath_depth(relpath)); + + if (*move_dst_op_root_relpath) + { + *move_dst_relpath + = svn_relpath_join(*move_dst_op_root_relpath, + svn_relpath_skip_ancestor(relpath, local_relpath), + result_pool); + while (delete_op_depth < relpath_depth(relpath)) + relpath = svn_relpath_dirname(relpath, scratch_pool); + *move_src_op_root_relpath = apr_pstrdup(result_pool, relpath); + } + + return SVN_NO_ERROR; +} + +/* Public (within libsvn_wc) absolute path version of + svn_wc__db_op_depth_moved_to with the op-depth hard-coded to + BASE. */ +svn_error_t * +svn_wc__db_base_moved_to(const char **move_dst_abspath, + const char **move_dst_op_root_abspath, + const char **move_src_root_abspath, + const char **move_src_op_root_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *move_dst_relpath, *move_dst_op_root_relpath; + const char *move_src_root_relpath, *move_src_op_root_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(svn_wc__db_op_depth_moved_to(&move_dst_relpath, + &move_dst_op_root_relpath, + &move_src_root_relpath, + &move_src_op_root_relpath, + 0 /* BASE op-depth */, + wcroot, local_relpath, + scratch_pool, scratch_pool), + wcroot); + + if (move_dst_abspath) + *move_dst_abspath + = move_dst_relpath + ? svn_dirent_join(wcroot->abspath, move_dst_relpath, result_pool) + : NULL; + + if (move_dst_op_root_abspath) + *move_dst_op_root_abspath + = move_dst_op_root_relpath + ? svn_dirent_join(wcroot->abspath, move_dst_op_root_relpath, result_pool) + : NULL; + + if (move_src_root_abspath) + *move_src_root_abspath + = move_src_root_relpath + ? svn_dirent_join(wcroot->abspath, move_src_root_relpath, result_pool) + : NULL; + + if (move_src_op_root_abspath) + *move_src_op_root_abspath + = move_src_op_root_relpath + ? svn_dirent_join(wcroot->abspath, move_src_op_root_relpath, result_pool) + : NULL; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_upgrade_begin(svn_sqlite__db_t **sdb, + apr_int64_t *repos_id, + apr_int64_t *wc_id, + svn_wc__db_t *wc_db, + const char *dir_abspath, + const char *repos_root_url, + const char *repos_uuid, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + + /* Upgrade is inherently exclusive so specify exclusive locking. */ + SVN_ERR(create_db(sdb, repos_id, wc_id, dir_abspath, + repos_root_url, repos_uuid, + SDB_FILE, + NULL, SVN_INVALID_REVNUM, svn_depth_unknown, + TRUE /* exclusive */, + wc_db->state_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_pdh_create_wcroot(&wcroot, + apr_pstrdup(wc_db->state_pool, + dir_abspath), + *sdb, *wc_id, FORMAT_FROM_SDB, + FALSE /* auto-upgrade */, + FALSE /* enforce_empty_wq */, + wc_db->state_pool, scratch_pool)); + + /* The WCROOT is complete. Stash it into DB. */ + svn_hash_sets(wc_db->dir_data, wcroot->abspath, wcroot); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_upgrade_apply_dav_cache(svn_sqlite__db_t *sdb, + const char *dir_relpath, + apr_hash_t *cache_values, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_int64_t wc_id; + apr_hash_index_t *hi; + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_wc__db_util_fetch_wc_id(&wc_id, sdb, iterpool)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPDATE_BASE_NODE_DAV_CACHE)); + + /* Iterate over all the wcprops, writing each one to the wc_db. */ + for (hi = apr_hash_first(scratch_pool, cache_values); + hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + apr_hash_t *props = svn__apr_hash_index_val(hi); + const char *local_relpath; + + svn_pool_clear(iterpool); + + local_relpath = svn_relpath_join(dir_relpath, name, iterpool); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, iterpool)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_upgrade_apply_props(svn_sqlite__db_t *sdb, + const char *dir_abspath, + const char *local_relpath, + apr_hash_t *base_props, + apr_hash_t *revert_props, + apr_hash_t *working_props, + int original_format, + apr_int64_t wc_id, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int top_op_depth = -1; + int below_op_depth = -1; + svn_wc__db_status_t top_presence; + svn_wc__db_status_t below_presence; + int affected_rows; + + /* ### working_props: use set_props_txn. + ### if working_props == NULL, then skip. what if they equal the + ### pristine props? we should probably do the compare here. + ### + ### base props go into WORKING_NODE if avail, otherwise BASE. + ### + ### revert only goes into BASE. (and WORKING better be there!) + + Prior to 1.4.0 (ORIGINAL_FORMAT < 8), REVERT_PROPS did not exist. If a + file was deleted, then a copy (potentially with props) was disallowed + and could not replace the deletion. An addition *could* be performed, + but that would never bring its own props. + + 1.4.0 through 1.4.5 created the concept of REVERT_PROPS, but had a + bug in svn_wc_add_repos_file2() whereby a copy-with-props did NOT + construct a REVERT_PROPS if the target had no props. Thus, reverting + the delete/copy would see no REVERT_PROPS to restore, leaving the + props from the copy source intact, and appearing as if they are (now) + the base props for the previously-deleted file. (wc corruption) + + 1.4.6 ensured that an empty REVERT_PROPS would be established at all + times. See issue 2530, and r861670 as starting points. + + We will use ORIGINAL_FORMAT and SVN_WC__NO_REVERT_FILES to determine + the handling of our inputs, relative to the state of this node. + */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + top_op_depth = svn_sqlite__column_int(stmt, 0); + top_presence = svn_sqlite__column_token(stmt, 3, presence_map); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + below_op_depth = svn_sqlite__column_int(stmt, 0); + below_presence = svn_sqlite__column_token(stmt, 3, presence_map); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Detect the buggy scenario described above. We cannot upgrade this + working copy if we have no idea where BASE_PROPS should go. */ + if (original_format > SVN_WC__NO_REVERT_FILES + && revert_props == NULL + && top_op_depth != -1 + && top_presence == svn_wc__db_status_normal + && below_op_depth != -1 + && below_presence != svn_wc__db_status_not_present) + { + /* There should be REVERT_PROPS, so it appears that we just ran into + the described bug. Sigh. */ + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("The properties of '%s' are in an " + "indeterminate state and cannot be " + "upgraded. See issue #2530."), + svn_dirent_local_style( + svn_dirent_join(dir_abspath, local_relpath, + scratch_pool), scratch_pool)); + } + + /* Need at least one row, or two rows if there are revert props */ + if (top_op_depth == -1 + || (below_op_depth == -1 && revert_props)) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Insufficient NODES rows for '%s'"), + svn_dirent_local_style( + svn_dirent_join(dir_abspath, local_relpath, + scratch_pool), scratch_pool)); + + /* one row, base props only: upper row gets base props + two rows, base props only: lower row gets base props + two rows, revert props only: lower row gets revert props + two rows, base and revert props: upper row gets base, lower gets revert */ + + + if (revert_props || below_op_depth == -1) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPDATE_NODE_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wc_id, local_relpath, top_op_depth)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 4, base_props, scratch_pool)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + SVN_ERR_ASSERT(affected_rows == 1); + } + + if (below_op_depth != -1) + { + apr_hash_t *props = revert_props ? revert_props : base_props; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPDATE_NODE_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wc_id, local_relpath, below_op_depth)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 4, props, scratch_pool)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + SVN_ERR_ASSERT(affected_rows == 1); + } + + /* If there are WORKING_PROPS, then they always go into ACTUAL_NODE. */ + if (working_props != NULL + && base_props != NULL) + { + apr_array_header_t *diffs; + + SVN_ERR(svn_prop_diffs(&diffs, working_props, base_props, scratch_pool)); + + if (diffs->nelts == 0) + working_props = NULL; /* No differences */ + } + + if (working_props != NULL) + { + SVN_ERR(set_actual_props(wc_id, local_relpath, working_props, + sdb, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_upgrade_insert_external(svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + const char *parent_abspath, + const char *def_local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t def_peg_revision, + svn_revnum_t def_revision, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *def_local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_int64_t repos_id; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* We know only of DEF_LOCAL_ABSPATH that it definitely belongs to "this" + * WC, i.e. where the svn:externals prop is set. The external target path + * itself may be "hidden behind" other working copies. */ + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &def_local_relpath, + db, def_local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_REPOSITORY)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", repos_root_url)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + repos_id = svn_sqlite__column_int64(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (!have_row) + { + /* Need to set up a new repository row. */ + SVN_ERR(create_repos_id(&repos_id, repos_root_url, repos_uuid, + wcroot->sdb, scratch_pool)); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_EXTERNAL)); + + /* wc_id, local_relpath, parent_relpath, presence, kind, def_local_relpath, + * repos_id, def_repos_relpath, def_operational_revision, def_revision */ + SVN_ERR(svn_sqlite__bindf(stmt, "issstsis", + wcroot->wc_id, + svn_dirent_skip_ancestor(wcroot->abspath, + local_abspath), + svn_dirent_skip_ancestor(wcroot->abspath, + parent_abspath), + "normal", + kind_map, kind, + def_local_relpath, + repos_id, + repos_relpath)); + + if (SVN_IS_VALID_REVNUM(def_peg_revision)) + SVN_ERR(svn_sqlite__bind_revnum(stmt, 9, def_peg_revision)); + + if (SVN_IS_VALID_REVNUM(def_revision)) + SVN_ERR(svn_sqlite__bind_revnum(stmt, 10, def_revision)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_upgrade_get_repos_id(apr_int64_t *repos_id, + svn_sqlite__db_t *sdb, + const char *repos_root_url, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_REPOSITORY)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", repos_root_url)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + return svn_error_createf(SVN_ERR_WC_DB_ERROR, svn_sqlite__reset(stmt), + _("Repository '%s' not found in the database"), + repos_root_url); + + *repos_id = svn_sqlite__column_int64(stmt, 0); + return svn_error_trace(svn_sqlite__reset(stmt)); +} + + +svn_error_t * +svn_wc__db_wq_add(svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *work_item, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + + /* Quick exit, if there are no work items to queue up. */ + if (work_item == NULL) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* Add the work item(s) to the WORK_QUEUE. */ + return svn_error_trace(add_work_items(wcroot->sdb, work_item, + scratch_pool)); +} + +/* The body of svn_wc__db_wq_fetch_next(). + */ +static svn_error_t * +wq_fetch_next(apr_uint64_t *id, + svn_skel_t **work_item, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_uint64_t completed_id, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + if (completed_id != 0) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WORK_ITEM)); + SVN_ERR(svn_sqlite__bind_int64(stmt, 1, completed_id)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORK_ITEM)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + *id = 0; + *work_item = NULL; + } + else + { + apr_size_t len; + const void *val; + + *id = svn_sqlite__column_int64(stmt, 0); + + val = svn_sqlite__column_blob(stmt, 1, &len, result_pool); + + *work_item = svn_skel__parse(val, len, result_pool); + } + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_wc__db_wq_fetch_next(apr_uint64_t *id, + svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + apr_uint64_t completed_id, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(id != NULL); + SVN_ERR_ASSERT(work_item != NULL); + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + wq_fetch_next(id, work_item, + wcroot, local_relpath, completed_id, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +/* Records timestamp and date for one or more files in wcroot */ +static svn_error_t * +wq_record(svn_wc__db_wcroot_t *wcroot, + apr_hash_t *record_map, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + for (hi = apr_hash_first(scratch_pool, record_map); hi; + hi = apr_hash_next(hi)) + { + const char *local_abspath = svn__apr_hash_index_key(hi); + const svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi); + const char *local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, + local_abspath); + + svn_pool_clear(iterpool); + + if (! local_relpath) + continue; + + SVN_ERR(db_record_fileinfo(wcroot, local_relpath, + dirent->filesize, dirent->mtime, + iterpool)); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_wq_record_and_fetch_next(apr_uint64_t *id, + svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + apr_uint64_t completed_id, + apr_hash_t *record_map, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(id != NULL); + SVN_ERR_ASSERT(work_item != NULL); + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + svn_error_compose_create( + wq_fetch_next(id, work_item, + wcroot, local_relpath, completed_id, + result_pool, scratch_pool), + wq_record(wcroot, record_map, scratch_pool)), + wcroot); + + return SVN_NO_ERROR; +} + + + +/* ### temporary API. remove before release. */ +svn_error_t * +svn_wc__db_temp_get_format(int *format, + svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_error_t *err; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath)); + /* ### assert that we were passed a directory? */ + + err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_dir_abspath, scratch_pool, scratch_pool); + + /* If we hit an error examining this directory, then declare this + directory to not be a working copy. */ + if (err) + { + if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) + return svn_error_trace(err); + svn_error_clear(err); + + /* Remap the returned error. */ + *format = 0; + return svn_error_createf(SVN_ERR_WC_MISSING, NULL, + _("'%s' is not a working copy"), + svn_dirent_local_style(local_dir_abspath, + scratch_pool)); + } + + SVN_ERR_ASSERT(wcroot != NULL); + SVN_ERR_ASSERT(wcroot->format >= 1); + + *format = wcroot->format; + + return SVN_NO_ERROR; +} + +/* ### temporary API. remove before release. */ +svn_wc_adm_access_t * +svn_wc__db_temp_get_access(svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + svn_wc__db_wcroot_t *wcroot; + svn_error_t *err; + + SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_dir_abspath)); + + /* ### we really need to assert that we were passed a directory. sometimes + ### adm_retrieve_internal is asked about a file, and then it asks us + ### for an access baton for it. we should definitely return NULL, but + ### ideally: the caller would never ask us about a non-directory. */ + + err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_dir_abspath, scratch_pool, scratch_pool); + if (err) + { + svn_error_clear(err); + return NULL; + } + + if (!wcroot) + return NULL; + + return svn_hash_gets(wcroot->access_cache, local_dir_abspath); +} + + +/* ### temporary API. remove before release. */ +void +svn_wc__db_temp_set_access(svn_wc__db_t *db, + const char *local_dir_abspath, + svn_wc_adm_access_t *adm_access, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + svn_wc__db_wcroot_t *wcroot; + svn_error_t *err; + + SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_dir_abspath)); + /* ### assert that we were passed a directory? */ + + err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_dir_abspath, scratch_pool, scratch_pool); + if (err) + { + /* We don't even have a wcroot, so just bail. */ + svn_error_clear(err); + return; + } + + /* Better not override something already there. */ + SVN_ERR_ASSERT_NO_RETURN( + svn_hash_gets(wcroot->access_cache, local_dir_abspath) == NULL + ); + svn_hash_sets(wcroot->access_cache, local_dir_abspath, adm_access); +} + + +/* ### temporary API. remove before release. */ +svn_error_t * +svn_wc__db_temp_close_access(svn_wc__db_t *db, + const char *local_dir_abspath, + svn_wc_adm_access_t *adm_access, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + svn_wc__db_wcroot_t *wcroot; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath)); + /* ### assert that we were passed a directory? */ + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_dir_abspath, scratch_pool, scratch_pool)); + svn_hash_sets(wcroot->access_cache, local_dir_abspath, NULL); + + return SVN_NO_ERROR; +} + + +/* ### temporary API. remove before release. */ +void +svn_wc__db_temp_clear_access(svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + svn_wc__db_wcroot_t *wcroot; + svn_error_t *err; + + SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_dir_abspath)); + /* ### assert that we were passed a directory? */ + + err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_dir_abspath, scratch_pool, scratch_pool); + if (err) + { + svn_error_clear(err); + return; + } + + svn_hash_sets(wcroot->access_cache, local_dir_abspath, NULL); +} + + +apr_hash_t * +svn_wc__db_temp_get_all_access(svn_wc__db_t *db, + apr_pool_t *result_pool) +{ + apr_hash_t *result = apr_hash_make(result_pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(result_pool, db->dir_data); + hi; + hi = apr_hash_next(hi)) + { + const svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi); + + /* This is highly redundant, 'cause the same WCROOT will appear many + times in dir_data. */ + result = apr_hash_overlay(result_pool, result, wcroot->access_cache); + } + + return result; +} + + +svn_error_t * +svn_wc__db_temp_borrow_sdb(svn_sqlite__db_t **sdb, + svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + *sdb = wcroot->sdb; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_read_conflict_victims(const apr_array_header_t **victims, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_array_header_t *new_victims; + + /* The parent should be a working copy directory. */ + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* ### This will be much easier once we have all conflicts in one + field of actual*/ + + /* Look for text, tree and property conflicts in ACTUAL */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_CONFLICT_VICTIMS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + new_victims = apr_array_make(result_pool, 0, sizeof(const char *)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + + APR_ARRAY_PUSH(new_victims, const char *) = + svn_relpath_basename(child_relpath, result_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + *victims = new_victims; + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_get_conflict_marker_files(). + */ +static svn_error_t * +get_conflict_marker_files(apr_hash_t **marker_files_p, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_hash_t *marker_files = apr_hash_make(result_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row && !svn_sqlite__column_is_null(stmt, 2)) + { + apr_size_t len; + const void *data = svn_sqlite__column_blob(stmt, 2, &len, NULL); + svn_skel_t *conflicts; + const apr_array_header_t *markers; + int i; + + conflicts = svn_skel__parse(data, len, scratch_pool); + + /* ### ADD markers to *marker_files */ + SVN_ERR(svn_wc__conflict_read_markers(&markers, db, wcroot->abspath, + conflicts, + result_pool, scratch_pool)); + + for (i = 0; markers && (i < markers->nelts); i++) + { + const char *marker_abspath = APR_ARRAY_IDX(markers, i, const char*); + + svn_hash_sets(marker_files, marker_abspath, ""); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_CONFLICT_VICTIMS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + apr_size_t len; + const void *data = svn_sqlite__column_blob(stmt, 1, &len, NULL); + + const apr_array_header_t *markers; + int i; + + if (data) + { + svn_skel_t *conflicts; + conflicts = svn_skel__parse(data, len, scratch_pool); + + SVN_ERR(svn_wc__conflict_read_markers(&markers, db, wcroot->abspath, + conflicts, + result_pool, scratch_pool)); + + for (i = 0; markers && (i < markers->nelts); i++) + { + const char *marker_abspath = APR_ARRAY_IDX(markers, i, const char*); + + svn_hash_sets(marker_files, marker_abspath, ""); + } + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + if (apr_hash_count(marker_files)) + *marker_files_p = marker_files; + else + *marker_files_p = NULL; + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_wc__db_get_conflict_marker_files(apr_hash_t **marker_files, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + /* The parent should be a working copy directory. */ + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + get_conflict_marker_files(marker_files, wcroot, local_relpath, db, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_read_conflict(svn_skel_t **conflict, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + /* The parent should be a working copy directory. */ + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + return svn_error_trace(svn_wc__db_read_conflict_internal(conflict, wcroot, + local_relpath, + result_pool, + scratch_pool)); +} + +svn_error_t * +svn_wc__db_read_conflict_internal(svn_skel_t **conflict, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + /* Check if we have a conflict in ACTUAL */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (! have_row) + { + /* Do this while stmt is still open to avoid closing the sqlite + transaction and then reopening. */ + svn_sqlite__stmt_t *stmt_node; + svn_error_t *err; + + err = svn_sqlite__get_statement(&stmt_node, wcroot->sdb, + STMT_SELECT_NODE_INFO); + + if (err) + stmt_node = NULL; + else + err = svn_sqlite__bindf(stmt_node, "is", wcroot->wc_id, + local_relpath); + + if (!err) + err = svn_sqlite__step(&have_row, stmt_node); + + if (stmt_node) + err = svn_error_compose_create(err, + svn_sqlite__reset(stmt_node)); + + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + + if (have_row) + { + *conflict = NULL; + return SVN_NO_ERROR; + } + + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + { + apr_size_t cfl_len; + const void *cfl_data; + + /* svn_skel__parse doesn't copy data, so store in result_pool */ + cfl_data = svn_sqlite__column_blob(stmt, 2, &cfl_len, result_pool); + + if (cfl_data) + *conflict = svn_skel__parse(cfl_data, cfl_len, result_pool); + else + *conflict = NULL; + + return svn_error_trace(svn_sqlite__reset(stmt)); + } +} + + +svn_error_t * +svn_wc__db_read_kind(svn_node_kind_t *kind, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t allow_missing, + svn_boolean_t show_deleted, + svn_boolean_t show_hidden, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt_info; + svn_boolean_t have_info; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt_info, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt_info, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_info, stmt_info)); + + if (!have_info) + { + if (allow_missing) + { + *kind = svn_node_unknown; + SVN_ERR(svn_sqlite__reset(stmt_info)); + return SVN_NO_ERROR; + } + else + { + SVN_ERR(svn_sqlite__reset(stmt_info)); + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + } + + if (!(show_deleted && show_hidden)) + { + int op_depth = svn_sqlite__column_int(stmt_info, 0); + svn_boolean_t report_none = FALSE; + svn_wc__db_status_t status = svn_sqlite__column_token(stmt_info, 3, + presence_map); + + if (op_depth > 0) + SVN_ERR(convert_to_working_status(&status, status)); + + switch (status) + { + case svn_wc__db_status_not_present: + if (! (show_hidden && show_deleted)) + report_none = TRUE; + break; + case svn_wc__db_status_excluded: + case svn_wc__db_status_server_excluded: + if (! show_hidden) + report_none = TRUE; + break; + case svn_wc__db_status_deleted: + if (! show_deleted) + report_none = TRUE; + break; + default: + break; + } + + if (report_none) + { + *kind = svn_node_none; + return svn_error_trace(svn_sqlite__reset(stmt_info)); + } + } + + *kind = svn_sqlite__column_token(stmt_info, 4, kind_map); + + return svn_error_trace(svn_sqlite__reset(stmt_info)); +} + + +svn_error_t * +svn_wc__db_node_hidden(svn_boolean_t *hidden, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_wc__db_status_t status; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(read_info(&status, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + *hidden = (status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_not_present + || status == svn_wc__db_status_excluded); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_is_wcroot(svn_boolean_t *is_wcroot, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (*local_relpath != '\0') + { + *is_wcroot = FALSE; /* Node is a file, or has a parent directory within + the same wcroot */ + return SVN_NO_ERROR; + } + + *is_wcroot = TRUE; + + return SVN_NO_ERROR; +} + +/* Find a node's kind and whether it is switched, putting the outputs in + * *IS_SWITCHED and *KIND. Either of the outputs may be NULL if not wanted. + */ +static svn_error_t * +db_is_switched(svn_boolean_t *is_switched, + svn_node_kind_t *kind, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + apr_int64_t repos_id; + const char *repos_relpath; + const char *name; + const char *parent_local_relpath; + apr_int64_t parent_repos_id; + const char *parent_repos_relpath; + + SVN_ERR_ASSERT(*local_relpath != '\0'); /* Handled in wrapper */ + + SVN_ERR(read_info(&status, kind, NULL, &repos_relpath, &repos_id, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, scratch_pool, scratch_pool)); + + if (status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_not_present) + { + return svn_error_createf( + SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + else if (! repos_relpath) + { + /* Node is shadowed; easy out */ + if (is_switched) + *is_switched = FALSE; + + return SVN_NO_ERROR; + } + + if (! is_switched) + return SVN_NO_ERROR; + + svn_relpath_split(&parent_local_relpath, &name, local_relpath, scratch_pool); + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &parent_repos_relpath, + &parent_repos_id, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, + wcroot, parent_local_relpath, + scratch_pool, scratch_pool)); + + if (repos_id != parent_repos_id) + *is_switched = TRUE; + else + { + const char *expected_relpath; + + expected_relpath = svn_relpath_join(parent_repos_relpath, name, + scratch_pool); + + *is_switched = (strcmp(expected_relpath, repos_relpath) != 0); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_is_switched(svn_boolean_t *is_wcroot, + svn_boolean_t *is_switched, + svn_node_kind_t *kind, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (is_switched) + *is_switched = FALSE; + + if (*local_relpath == '\0') + { + /* Easy out */ + if (is_wcroot) + *is_wcroot = TRUE; + + if (kind) + *kind = svn_node_dir; + return SVN_NO_ERROR; + } + + if (is_wcroot) + *is_wcroot = FALSE; + + if (! is_switched && ! kind) + return SVN_NO_ERROR; + + SVN_WC__DB_WITH_TXN( + db_is_switched(is_switched, kind, wcroot, local_relpath, scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_temp_wcroot_tempdir(const char **temp_dir_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(temp_dir_abspath != NULL); + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + *temp_dir_abspath = svn_dirent_join_many(result_pool, + wcroot->abspath, + svn_wc_get_adm_dir(scratch_pool), + WCROOT_TEMPDIR_RELPATH, + NULL); + return SVN_NO_ERROR; +} + + +/* Helper for wclock_obtain_cb() to steal an existing lock */ +static svn_error_t * +wclock_steal(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DELETE_WC_LOCK)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_wclock_obtain(). + */ +static svn_error_t * +wclock_obtain_cb(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int levels_to_lock, + svn_boolean_t steal_lock, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_error_t *err; + const char *lock_relpath; + int max_depth; + int lock_depth; + svn_boolean_t got_row; + + svn_wc__db_wclock_t lock; + + /* Upgrade locks the root before the node exists. Apart from that + the root node always exists so we will just skip the check. + + ### Perhaps the lock for upgrade should be created when the db is + created? 1.6 used to lock .svn on creation. */ + if (local_relpath[0]) + { + svn_boolean_t exists; + + SVN_ERR(does_node_exist(&exists, wcroot, local_relpath)); + if (!exists) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + /* Check if there are nodes locked below the new lock root */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_FIND_WC_LOCK)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + lock_depth = relpath_depth(local_relpath); + max_depth = lock_depth + levels_to_lock; + + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + + while (got_row) + { + svn_boolean_t own_lock; + + lock_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + + /* If we are not locking with depth infinity, check if this lock + voids our lock request */ + if (levels_to_lock >= 0 + && relpath_depth(lock_relpath) > max_depth) + { + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + continue; + } + + /* Check if we are the lock owner, because we should be able to + extend our lock. */ + err = wclock_owns_lock(&own_lock, wcroot, lock_relpath, + TRUE, scratch_pool); + + if (err) + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + + if (!own_lock && !steal_lock) + { + SVN_ERR(svn_sqlite__reset(stmt)); + err = svn_error_createf(SVN_ERR_WC_LOCKED, NULL, + _("'%s' is already locked."), + path_for_error_message(wcroot, + lock_relpath, + scratch_pool)); + return svn_error_createf(SVN_ERR_WC_LOCKED, err, + _("Working copy '%s' locked."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + else if (!own_lock) + { + err = wclock_steal(wcroot, lock_relpath, scratch_pool); + + if (err) + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + } + + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + if (steal_lock) + SVN_ERR(wclock_steal(wcroot, local_relpath, scratch_pool)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_WC_LOCK)); + lock_relpath = local_relpath; + + while (TRUE) + { + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, lock_relpath)); + + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + + if (got_row) + { + int levels = svn_sqlite__column_int(stmt, 0); + if (levels >= 0) + levels += relpath_depth(lock_relpath); + + SVN_ERR(svn_sqlite__reset(stmt)); + + if (levels == -1 || levels >= lock_depth) + { + + err = svn_error_createf( + SVN_ERR_WC_LOCKED, NULL, + _("'%s' is already locked."), + svn_dirent_local_style( + svn_dirent_join(wcroot->abspath, + lock_relpath, + scratch_pool), + scratch_pool)); + return svn_error_createf( + SVN_ERR_WC_LOCKED, err, + _("Working copy '%s' locked."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + break; /* There can't be interesting locks on higher nodes */ + } + else + SVN_ERR(svn_sqlite__reset(stmt)); + + if (!*lock_relpath) + break; + + lock_relpath = svn_relpath_dirname(lock_relpath, scratch_pool); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_WC_LOCK)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + levels_to_lock)); + err = svn_sqlite__insert(NULL, stmt); + if (err) + return svn_error_createf(SVN_ERR_WC_LOCKED, err, + _("Working copy '%s' locked"), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + + /* And finally store that we obtained the lock */ + lock.local_relpath = apr_pstrdup(wcroot->owned_locks->pool, local_relpath); + lock.levels = levels_to_lock; + APR_ARRAY_PUSH(wcroot->owned_locks, svn_wc__db_wclock_t) = lock; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_wclock_obtain(svn_wc__db_t *db, + const char *local_abspath, + int levels_to_lock, + svn_boolean_t steal_lock, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(levels_to_lock >= -1); + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (!steal_lock) + { + int i; + int depth = relpath_depth(local_relpath); + + for (i = 0; i < wcroot->owned_locks->nelts; i++) + { + svn_wc__db_wclock_t* lock = &APR_ARRAY_IDX(wcroot->owned_locks, + i, svn_wc__db_wclock_t); + + if (svn_relpath_skip_ancestor(lock->local_relpath, local_relpath) + && (lock->levels == -1 + || (lock->levels + relpath_depth(lock->local_relpath)) + >= depth)) + { + return svn_error_createf( + SVN_ERR_WC_LOCKED, NULL, + _("'%s' is already locked via '%s'."), + svn_dirent_local_style(local_abspath, scratch_pool), + path_for_error_message(wcroot, lock->local_relpath, + scratch_pool)); + } + } + } + + SVN_WC__DB_WITH_TXN( + wclock_obtain_cb(wcroot, local_relpath, levels_to_lock, steal_lock, + scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_wclock_find_root() and svn_wc__db_wclocked(). */ +static svn_error_t * +find_wclock(const char **lock_relpath, + svn_wc__db_wcroot_t *wcroot, + const char *dir_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int dir_depth = relpath_depth(dir_relpath); + const char *first_relpath; + + /* Check for locks on all directories that might be ancestors. + As our new apis only use recursive locks the number of locks stored + in the DB will be very low */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ANCESTOR_WCLOCKS)); + + /* Get the top level relpath to reduce the worst case number of results + to the number of directories below this node plus two. + (1: the node itself and 2: the wcroot). */ + first_relpath = strchr(dir_relpath, '/'); + + if (first_relpath != NULL) + first_relpath = apr_pstrndup(scratch_pool, dir_relpath, + first_relpath - dir_relpath); + else + first_relpath = dir_relpath; + + SVN_ERR(svn_sqlite__bindf(stmt, "iss", + wcroot->wc_id, + dir_relpath, + first_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + const char *relpath = svn_sqlite__column_text(stmt, 0, NULL); + + if (svn_relpath_skip_ancestor(relpath, dir_relpath)) + { + int locked_levels = svn_sqlite__column_int(stmt, 1); + int row_depth = relpath_depth(relpath); + + if (locked_levels == -1 + || locked_levels + row_depth >= dir_depth) + { + *lock_relpath = apr_pstrdup(result_pool, relpath); + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + *lock_relpath = NULL; + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +static svn_error_t * +is_wclocked(svn_boolean_t *locked, + svn_wc__db_wcroot_t *wcroot, + const char *dir_relpath, + apr_pool_t *scratch_pool) +{ + const char *lock_relpath; + + SVN_ERR(find_wclock(&lock_relpath, wcroot, dir_relpath, + scratch_pool, scratch_pool)); + *locked = (lock_relpath != NULL); + return SVN_NO_ERROR; +} + + +svn_error_t* +svn_wc__db_wclock_find_root(const char **lock_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *lock_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + find_wclock(&lock_relpath, wcroot, local_relpath, + scratch_pool, scratch_pool), + wcroot); + + if (!lock_relpath) + *lock_abspath = NULL; + else + SVN_ERR(svn_wc__db_from_relpath(lock_abspath, db, wcroot->abspath, + lock_relpath, result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_wclocked(svn_boolean_t *locked, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + is_wclocked(locked, wcroot, local_relpath, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_wclock_release(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + int i; + apr_array_header_t *owned_locks; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + + VERIFY_USABLE_WCROOT(wcroot); + + /* First check and remove the owns-lock information as failure in + removing the db record implies that we have to steal the lock later. */ + owned_locks = wcroot->owned_locks; + for (i = 0; i < owned_locks->nelts; i++) + { + svn_wc__db_wclock_t *lock = &APR_ARRAY_IDX(owned_locks, i, + svn_wc__db_wclock_t); + + if (strcmp(lock->local_relpath, local_relpath) == 0) + break; + } + + if (i >= owned_locks->nelts) + return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL, + _("Working copy not locked at '%s'."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + if (i < owned_locks->nelts) + { + owned_locks->nelts--; + + /* Move the last item in the array to the deleted place */ + if (owned_locks->nelts > 0) + APR_ARRAY_IDX(owned_locks, i, svn_wc__db_wclock_t) = + APR_ARRAY_IDX(owned_locks, owned_locks->nelts, svn_wc__db_wclock_t); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WC_LOCK)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + + +/* Like svn_wc__db_wclock_owns_lock() but taking WCROOT+LOCAL_RELPATH instead + of DB+LOCAL_ABSPATH. */ +static svn_error_t * +wclock_owns_lock(svn_boolean_t *own_lock, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_boolean_t exact, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *owned_locks; + int lock_level; + int i; + + *own_lock = FALSE; + owned_locks = wcroot->owned_locks; + lock_level = relpath_depth(local_relpath); + + if (exact) + { + for (i = 0; i < owned_locks->nelts; i++) + { + svn_wc__db_wclock_t *lock = &APR_ARRAY_IDX(owned_locks, i, + svn_wc__db_wclock_t); + + if (strcmp(lock->local_relpath, local_relpath) == 0) + { + *own_lock = TRUE; + return SVN_NO_ERROR; + } + } + } + else + { + for (i = 0; i < owned_locks->nelts; i++) + { + svn_wc__db_wclock_t *lock = &APR_ARRAY_IDX(owned_locks, i, + svn_wc__db_wclock_t); + + if (svn_relpath_skip_ancestor(lock->local_relpath, local_relpath) + && (lock->levels == -1 + || ((relpath_depth(lock->local_relpath) + lock->levels) + >= lock_level))) + { + *own_lock = TRUE; + return SVN_NO_ERROR; + } + } + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_wclock_owns_lock(svn_boolean_t *own_lock, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t exact, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + + if (!wcroot) + return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(wclock_owns_lock(own_lock, wcroot, local_relpath, exact, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_temp_op_end_directory_update(). + */ +static svn_error_t * +end_directory_update(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_wc__db_status_t base_status; + + SVN_ERR(svn_wc__db_base_get_info_internal(&base_status, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (base_status == svn_wc__db_status_normal) + return SVN_NO_ERROR; + + SVN_ERR_ASSERT(base_status == svn_wc__db_status_incomplete); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_NODE_BASE_PRESENCE)); + SVN_ERR(svn_sqlite__bindf(stmt, "ist", wcroot->wc_id, local_relpath, + presence_map, svn_wc__db_status_normal)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_temp_op_end_directory_update(svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + end_directory_update(wcroot, local_relpath, scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_dir_abspath, svn_depth_empty, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_temp_op_start_directory_update(). + */ +static svn_error_t * +start_directory_update_txn(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const char *new_repos_relpath, + svn_revnum_t new_rev, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + /* Note: In the majority of calls, the repos_relpath is unchanged. */ + /* ### TODO: Maybe check if we can make repos_relpath NULL. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH)); + + SVN_ERR(svn_sqlite__bindf(stmt, "istrs", + wcroot->wc_id, + local_relpath, + presence_map, svn_wc__db_status_incomplete, + new_rev, + new_repos_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; + +} + +svn_error_t * +svn_wc__db_temp_op_start_directory_update(svn_wc__db_t *db, + const char *local_abspath, + const char *new_repos_relpath, + svn_revnum_t new_rev, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_rev)); + SVN_ERR_ASSERT(svn_relpath_is_canonical(new_repos_relpath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + start_directory_update_txn(wcroot, local_relpath, + new_repos_relpath, new_rev, scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_temp_op_make_copy(). This is + used by the update editor when deleting a base node tree would be a + tree-conflict because there are changes to subtrees. This function + inserts a copy of the base node tree below any existing working + subtrees. Given a tree: + + 0 1 2 3 + / normal - + A normal - + A/B normal - normal + A/B/C normal - base-del normal + A/F normal - normal + A/F/G normal - normal + A/F/H normal - base-deleted normal + A/F/E normal - not-present + A/X normal - + A/X/Y incomplete - + + This function adds layers to A and some of its descendants in an attempt + to make the working copy look like as if it were a copy of the BASE nodes. + + 0 1 2 3 + / normal - + A normal norm + A/B normal norm norm + A/B/C normal norm base-del normal + A/F normal norm norm + A/F/G normal norm norm + A/F/H normal norm not-pres + A/F/E normal norm base-del + A/X normal norm + A/X/Y incomplete incomplete + */ +static svn_error_t * +make_copy_txn(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + const svn_skel_t *conflicts, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_boolean_t add_working_base_deleted = FALSE; + svn_boolean_t remove_working = FALSE; + const apr_array_header_t *children; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_LOWEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, 0)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + svn_wc__db_status_t working_status; + int working_op_depth; + + working_status = svn_sqlite__column_token(stmt, 1, presence_map); + working_op_depth = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR_ASSERT(working_status == svn_wc__db_status_normal + || working_status == svn_wc__db_status_base_deleted + || working_status == svn_wc__db_status_not_present + || working_status == svn_wc__db_status_incomplete); + + /* Only change nodes in the layers where we are creating the copy. + Deletes in higher layers will just apply to the copy */ + if (working_op_depth <= op_depth) + { + add_working_base_deleted = TRUE; + + if (working_status == svn_wc__db_status_base_deleted) + remove_working = TRUE; + } + } + else + SVN_ERR(svn_sqlite__reset(stmt)); + + if (remove_working) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_LOWEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + if (add_working_base_deleted) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_DELETE_FROM_BASE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + else + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_WORKING_NODE_FROM_BASE_COPY)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Get the BASE children, as WORKING children don't need modifications */ + SVN_ERR(gather_repo_children(&children, wcroot, local_relpath, + 0, scratch_pool, iterpool)); + + for (i = 0; i < children->nelts; i++) + { + const char *name = APR_ARRAY_IDX(children, i, const char *); + const char *copy_relpath; + + svn_pool_clear(iterpool); + + copy_relpath = svn_relpath_join(local_relpath, name, iterpool); + + SVN_ERR(make_copy_txn(wcroot, copy_relpath, op_depth, NULL, NULL, + iterpool)); + } + + SVN_ERR(flush_entries(wcroot, svn_dirent_join(wcroot->abspath, local_relpath, + iterpool), + svn_depth_empty, iterpool)); + + if (conflicts) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflicts, iterpool)); + + SVN_ERR(add_work_items(wcroot->sdb, work_items, iterpool)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_make_copy(svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflicts, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* The update editor is supposed to call this function when there is + no working node for LOCAL_ABSPATH. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Modification of '%s' already exists"), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + + /* We don't allow copies to contain server-excluded nodes; + the update editor is going to have to bail out. */ + SVN_ERR(catch_copy_of_server_excluded(wcroot, local_relpath, scratch_pool)); + + SVN_WC__DB_WITH_TXN( + make_copy_txn(wcroot, local_relpath, + relpath_depth(local_relpath), conflicts, work_items, + scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_info_below_working(svn_boolean_t *have_base, + svn_boolean_t *have_work, + svn_wc__db_status_t *status, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + SVN_ERR(info_below_working(have_base, have_work, status, + wcroot, local_relpath, -1, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_get_not_present_descendants(const apr_array_header_t **descendants, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NOT_PRESENT_DESCENDANTS)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wcroot->wc_id, + local_relpath, + relpath_depth(local_relpath))); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + apr_array_header_t *paths; + + paths = apr_array_make(result_pool, 4, sizeof(const char*)); + while (have_row) + { + const char *found_relpath = svn_sqlite__column_text(stmt, 0, NULL); + + APR_ARRAY_PUSH(paths, const char *) + = apr_pstrdup(result_pool, svn_relpath_skip_ancestor( + local_relpath, found_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + *descendants = paths; + } + else + *descendants = apr_array_make(result_pool, 0, sizeof(const char*)); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + + +/* Like svn_wc__db_min_max_revisions(), + * but accepts a WCROOT/LOCAL_RELPATH pair. */ +static svn_error_t * +get_min_max_revisions(svn_revnum_t *min_revision, + svn_revnum_t *max_revision, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_boolean_t committed, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_revnum_t min_rev, max_rev; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MIN_MAX_REVISIONS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_row(stmt)); + + if (committed) + { + min_rev = svn_sqlite__column_revnum(stmt, 2); + max_rev = svn_sqlite__column_revnum(stmt, 3); + } + else + { + min_rev = svn_sqlite__column_revnum(stmt, 0); + max_rev = svn_sqlite__column_revnum(stmt, 1); + } + + /* The statement returns exactly one row. */ + SVN_ERR(svn_sqlite__reset(stmt)); + + if (min_revision) + *min_revision = min_rev; + if (max_revision) + *max_revision = max_rev; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_min_max_revisions(svn_revnum_t *min_revision, + svn_revnum_t *max_revision, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t committed, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + return svn_error_trace(get_min_max_revisions(min_revision, max_revision, + wcroot, local_relpath, + committed, scratch_pool)); +} + + +/* Set *IS_SPARSE_CHECKOUT TRUE if LOCAL_RELPATH or any of the nodes + * within LOCAL_RELPATH is sparse, FALSE otherwise. */ +static svn_error_t * +is_sparse_checkout_internal(svn_boolean_t *is_sparse_checkout, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_HAS_SPARSE_NODES)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, + local_relpath)); + /* If this query returns a row, the working copy is sparse. */ + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + *is_sparse_checkout = have_row; + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + + +/* Like svn_wc__db_has_switched_subtrees(), + * but accepts a WCROOT/LOCAL_RELPATH pair. */ +static svn_error_t * +has_switched_subtrees(svn_boolean_t *is_switched, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const char *trail_url, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_int64_t repos_id; + const char *repos_relpath; + + /* Optional argument handling for caller */ + if (!is_switched) + return SVN_NO_ERROR; + + *is_switched = FALSE; + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + /* First do the cheap check where we only need info on the origin itself */ + if (trail_url != NULL) + { + const char *repos_root_url; + const char *url; + apr_size_t len1, len2; + + /* If the trailing part of the URL of the working copy directory + does not match the given trailing URL then the whole working + copy is switched. */ + + SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, NULL, wcroot->sdb, + repos_id, scratch_pool)); + url = svn_path_url_add_component2(repos_root_url, repos_relpath, + scratch_pool); + + len1 = strlen(trail_url); + len2 = strlen(url); + if ((len1 > len2) || strcmp(url + len2 - len1, trail_url)) + { + *is_switched = TRUE; + return SVN_NO_ERROR; + } + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_HAS_SWITCHED)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, repos_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + *is_switched = TRUE; + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_has_switched_subtrees(svn_boolean_t *is_switched, + svn_wc__db_t *db, + const char *local_abspath, + const char *trail_url, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + return svn_error_trace(has_switched_subtrees(is_switched, wcroot, + local_relpath, trail_url, + scratch_pool)); +} + +svn_error_t * +svn_wc__db_get_excluded_subtrees(apr_hash_t **excluded_subtrees, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ALL_EXCLUDED_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + *excluded_subtrees = apr_hash_make(result_pool); + else + *excluded_subtrees = NULL; + + while (have_row) + { + const char *abs_path = + svn_dirent_join(wcroot->abspath, + svn_sqlite__column_text(stmt, 0, NULL), + result_pool); + svn_hash_sets(*excluded_subtrees, abs_path, abs_path); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; +} + +/* Like svn_wc__db_has_local_mods(), + * but accepts a WCROOT/LOCAL_RELPATH pair. + * ### This needs a DB as well as a WCROOT/RELPATH pair... */ +static svn_error_t * +has_local_mods(svn_boolean_t *is_modified, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + /* Check for additions or deletions. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SUBTREE_HAS_TREE_MODIFICATIONS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + /* If this query returns a row, the working copy is modified. */ + SVN_ERR(svn_sqlite__step(is_modified, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + if (! *is_modified) + { + /* Check for property modifications. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SUBTREE_HAS_PROP_MODIFICATIONS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + /* If this query returns a row, the working copy is modified. */ + SVN_ERR(svn_sqlite__step(is_modified, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + } + + if (! *is_modified) + { + apr_pool_t *iterpool = NULL; + svn_boolean_t have_row; + + /* Check for text modifications. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_BASE_FILES_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + const char *node_abspath; + svn_filesize_t recorded_size; + apr_time_t recorded_time; + svn_boolean_t skip_check = FALSE; + svn_error_t *err; + + if (cancel_func) + { + err = cancel_func(cancel_baton); + if (err) + return svn_error_trace(svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + } + + svn_pool_clear(iterpool); + + node_abspath = svn_dirent_join(wcroot->abspath, + svn_sqlite__column_text(stmt, 0, + iterpool), + iterpool); + + recorded_size = get_recorded_size(stmt, 1); + recorded_time = svn_sqlite__column_int64(stmt, 2); + + if (recorded_size != SVN_INVALID_FILESIZE + && recorded_time != 0) + { + const svn_io_dirent2_t *dirent; + + err = svn_io_stat_dirent2(&dirent, node_abspath, FALSE, TRUE, + iterpool, iterpool); + if (err) + return svn_error_trace(svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + + if (dirent->kind != svn_node_file) + { + *is_modified = TRUE; /* Missing or obstruction */ + break; + } + else if (dirent->filesize == recorded_size + && dirent->mtime == recorded_time) + { + /* The file is not modified */ + skip_check = TRUE; + } + } + + if (! skip_check) + { + err = svn_wc__internal_file_modified_p(is_modified, + db, node_abspath, + FALSE, iterpool); + + if (err) + return svn_error_trace(svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + + if (*is_modified) + break; + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + if (iterpool) + svn_pool_destroy(iterpool); + + SVN_ERR(svn_sqlite__reset(stmt)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_has_local_mods(svn_boolean_t *is_modified, + svn_wc__db_t *db, + const char *local_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + return svn_error_trace(has_local_mods(is_modified, wcroot, local_relpath, + db, cancel_func, cancel_baton, + scratch_pool)); +} + + +/* The body of svn_wc__db_revision_status(). + */ +static svn_error_t * +revision_status_txn(svn_revnum_t *min_revision, + svn_revnum_t *max_revision, + svn_boolean_t *is_sparse_checkout, + svn_boolean_t *is_modified, + svn_boolean_t *is_switched, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + const char *trail_url, + svn_boolean_t committed, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_boolean_t exists; + + SVN_ERR(does_node_exist(&exists, wcroot, local_relpath)); + + if (!exists) + { + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + + /* Determine mixed-revisionness. */ + SVN_ERR(get_min_max_revisions(min_revision, max_revision, wcroot, + local_relpath, committed, scratch_pool)); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + /* Determine sparseness. */ + SVN_ERR(is_sparse_checkout_internal(is_sparse_checkout, wcroot, + local_relpath, scratch_pool)); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + /* Check for switched nodes. */ + { + err = has_switched_subtrees(is_switched, wcroot, local_relpath, + trail_url, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); /* No Base node, but no fatal error */ + *is_switched = FALSE; + } + } + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + /* Check for local mods. */ + SVN_ERR(has_local_mods(is_modified, wcroot, local_relpath, db, + cancel_func, cancel_baton, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_revision_status(svn_revnum_t *min_revision, + svn_revnum_t *max_revision, + svn_boolean_t *is_sparse_checkout, + svn_boolean_t *is_modified, + svn_boolean_t *is_switched, + svn_wc__db_t *db, + const char *local_abspath, + const char *trail_url, + svn_boolean_t committed, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + revision_status_txn(min_revision, max_revision, + is_sparse_checkout, is_modified, is_switched, + wcroot, local_relpath, db, + trail_url, committed, cancel_func, cancel_baton, + scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_get_lock_tokens_recursive(apr_hash_t **lock_tokens, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_int64_t last_repos_id = INVALID_REPOS_ID; + const char *last_repos_root_url = NULL; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + *lock_tokens = apr_hash_make(result_pool); + + /* Fetch all the lock tokens in and under LOCAL_RELPATH. */ + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + apr_int64_t child_repos_id = svn_sqlite__column_int64(stmt, 0); + const char *child_relpath = svn_sqlite__column_text(stmt, 1, NULL); + const char *lock_token = svn_sqlite__column_text(stmt, 2, result_pool); + + if (child_repos_id != last_repos_id) + { + svn_error_t *err = svn_wc__db_fetch_repos_info(&last_repos_root_url, + NULL, wcroot->sdb, + child_repos_id, + scratch_pool); + + if (err) + { + return svn_error_trace( + svn_error_compose_create(err, + svn_sqlite__reset(stmt))); + } + + last_repos_id = child_repos_id; + } + + SVN_ERR_ASSERT(last_repos_root_url != NULL); + svn_hash_sets(*lock_tokens, + svn_path_url_add_component2(last_repos_root_url, + child_relpath, result_pool), + lock_token); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + return svn_sqlite__reset(stmt); +} + + +/* If EXPRESSION is false, cause the caller to return an SVN_ERR_WC_CORRUPT + * error, showing EXPRESSION and the caller's LOCAL_RELPATH in the message. */ +#define VERIFY(expression) \ + do { \ + if (! (expression)) \ + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, \ + _("database inconsistency at local_relpath='%s' verifying " \ + "expression '%s'"), local_relpath, #expression); \ + } while (0) + + +/* Verify consistency of the metadata concerning WCROOT. This is intended + * for use only during testing and debugging, so is not intended to be + * blazingly fast. + * + * This code is a complement to any verification that we can do in SQLite + * triggers. See, for example, 'wc-checks.sql'. + * + * Some more verification steps we might want to add are: + * + * * on every ACTUAL row (except root): a NODES row exists at its parent path + * * the op-depth root must always exist and every intermediate too + */ +static svn_error_t * +verify_wcroot(svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ALL_NODES)); + SVN_ERR(svn_sqlite__bindf(stmt, "i", wcroot->wc_id)); + while (TRUE) + { + svn_boolean_t have_row; + const char *local_relpath, *parent_relpath; + int op_depth; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + break; + + op_depth = svn_sqlite__column_int(stmt, 0); + local_relpath = svn_sqlite__column_text(stmt, 1, iterpool); + parent_relpath = svn_sqlite__column_text(stmt, 2, iterpool); + + /* Verify parent_relpath is the parent path of local_relpath */ + VERIFY((parent_relpath == NULL) + ? (local_relpath[0] == '\0') + : (strcmp(svn_relpath_dirname(local_relpath, iterpool), + parent_relpath) == 0)); + + /* Verify op_depth <= the tree depth of local_relpath */ + VERIFY(op_depth <= relpath_depth(local_relpath)); + + /* Verify parent_relpath refers to a row that exists */ + /* TODO: Verify there is a suitable parent row - e.g. has op_depth <= + * the child's and a suitable presence */ + if (parent_relpath && svn_sqlite__column_is_null(stmt, 3)) + { + svn_sqlite__stmt_t *stmt2; + svn_boolean_t have_a_parent_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt2, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt2, "is", wcroot->wc_id, + parent_relpath)); + SVN_ERR(svn_sqlite__step(&have_a_parent_row, stmt2)); + VERIFY(have_a_parent_row); + SVN_ERR(svn_sqlite__reset(stmt2)); + } + } + svn_pool_destroy(iterpool); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_wc__db_verify(svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, wri_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(verify_wcroot(wcroot, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_bump_format(int *result_format, + const char *wcroot_abspath, + svn_wc__db_t *db, + apr_pool_t *scratch_pool) +{ + svn_sqlite__db_t *sdb; + svn_error_t *err; + int format; + + /* Do not scan upwards for a working copy root here to prevent accidental + * upgrades of any working copies the WCROOT might be nested in. + * Just try to open a DB at the specified path instead. */ + err = svn_wc__db_util_open_db(&sdb, wcroot_abspath, SDB_FILE, + svn_sqlite__mode_readwrite, + TRUE, /* exclusive */ + NULL, /* my statements */ + scratch_pool, scratch_pool); + if (err) + { + svn_error_t *err2; + apr_hash_t *entries; + + /* Could not open an sdb. Check for an entries file instead. */ + err2 = svn_wc__read_entries_old(&entries, wcroot_abspath, + scratch_pool, scratch_pool); + if (err2 || apr_hash_count(entries) == 0) + return svn_error_createf(SVN_ERR_WC_INVALID_OP_ON_CWD, + svn_error_compose_create(err, err2), + _("Can't upgrade '%s' as it is not a working copy root"), + svn_dirent_local_style(wcroot_abspath, scratch_pool)); + + /* An entries file was found. This is a pre-wc-ng working copy + * so suggest an upgrade. */ + return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, err, + _("Working copy '%s' is too old and must be upgraded to " + "at least format %d, as created by Subversion %s"), + svn_dirent_local_style(wcroot_abspath, scratch_pool), + SVN_WC__WC_NG_VERSION, + svn_wc__version_string_from_format(SVN_WC__WC_NG_VERSION)); + } + + SVN_ERR(svn_sqlite__read_schema_version(&format, sdb, scratch_pool)); + err = svn_wc__upgrade_sdb(result_format, wcroot_abspath, + sdb, format, scratch_pool); + + /* Make sure we return a different error than expected for upgrades from + entries */ + if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) + err = svn_error_create(SVN_ERR_WC_UNSUPPORTED_FORMAT, err, + _("Working copy upgrade failed")); + + err = svn_error_compose_create(err, svn_sqlite__close(sdb)); + + return svn_error_trace(err); +} + +svn_error_t * +svn_wc__db_vacuum(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_VACUUM)); + + return SVN_NO_ERROR; +} |