| /* |
| * 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_private_config.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 "private/svn_sorts_private.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(svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| apr_hash_t *props, |
| 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 * |
| 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_wc__db_wcroot_t *wcroot, |
| 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, wcroot->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); |
| } |
| } |
| |
| /* 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; |
| } |
| |
| |
| /* Extend any delete of the parent of LOCAL_RELPATH to LOCAL_RELPATH. |
| |
| ### What about KIND and OP_DEPTH? KIND ought to be redundant; I'm |
| discussing on dev@ whether we can let that be null for presence |
| == base-deleted. OP_DEPTH is the op-depth of what, and why? |
| It is used to select the lowest working node higher than OP_DEPTH, |
| so, in terms of the API, OP_DEPTH means ...? |
| |
| Given a wc: |
| |
| 0 1 2 3 4 |
| normal |
| A normal |
| A/B normal normal |
| A/B/C not-pres normal |
| A/B/C/D normal |
| |
| That is checkout, delete A/B, copy a replacement A/B, delete copied |
| child A/B/C, add replacement A/B/C, add A/B/C/D. |
| |
| Now an update that adds base nodes for A/B/C, A/B/C/D and A/B/C/D/E |
| must extend the A/B deletion: |
| |
| 0 1 2 3 4 |
| normal |
| A normal |
| A/B normal normal |
| A/B/C normal not-pres normal |
| A/B/C/D normal base-del normal |
| A/B/C/D/E normal base-del |
| |
| When adding a node if the parent has a higher working node then the |
| parent node is deleted (or replaced) and the delete must be extended |
| to cover new node. |
| |
| In the example above A/B/C/D and A/B/C/D/E are the nodes that get |
| the extended delete, A/B/C is already deleted. |
| |
| If ADDED_DELETE is not NULL, set *ADDED_DELETE to TRUE if a new delete |
| was recorded, otherwise to FALSE. |
| */ |
| static svn_error_t * |
| 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 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. |
| |
| This function (like most wcroot functions) assumes that its caller |
| only uses this function within an sqlite transaction if atomic |
| behavior is needed. |
| */ |
| static svn_error_t * |
| 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_boolean_t have_row; |
| int working_depth; |
| svn_wc__db_status_t presence; |
| const char *moved_to; |
| |
| 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, |
| op_depth)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| if (!have_row) |
| return svn_error_trace(svn_sqlite__reset(stmt)); |
| |
| working_depth = svn_sqlite__column_int(stmt, 0); |
| presence = svn_sqlite__column_token(stmt, 1, presence_map); |
| moved_to = svn_sqlite__column_text(stmt, 3, scratch_pool); |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| if (moved_to) |
| { |
| /* Turn the move into a copy to keep the NODES table valid */ |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_CLEAR_MOVED_HERE_RECURSIVE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, |
| moved_to, relpath_depth(moved_to))); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| |
| /* This leaves just the moved_to information on the origin, |
| which we will remove in the next step */ |
| } |
| |
| if (presence == svn_wc__db_status_base_deleted) |
| { |
| /* Nothing left to shadow; remove the base-deleted node */ |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DELETE_NODE)); |
| } |
| else if (moved_to) |
| { |
| /* Clear moved to information, as this node is no longer base-deleted */ |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_CLEAR_MOVED_TO_RELPATH)); |
| } |
| else |
| { |
| /* Nothing to update */ |
| return SVN_NO_ERROR; |
| } |
| |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, |
| working_depth)); |
| |
| return svn_error_trace(svn_sqlite__update(NULL, stmt)); |
| } |
| |
| |
| |
| /* 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; |
| svn_boolean_t present; |
| |
| /* 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)); |
| } |
| |
| present = (pibb->status == svn_wc__db_status_normal |
| || pibb->status == svn_wc__db_status_incomplete); |
| |
| 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 && present) /* 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 && present) ? |
| pibb->target : NULL)); /* 19 */ |
| if (pibb->kind == svn_node_file && present) |
| { |
| 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); |
| if (present) |
| { |
| 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, local_relpath, new_actual_props, |
| 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(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(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_boolean_t present; |
| |
| 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)); |
| |
| present = (piwb->presence == svn_wc__db_status_normal |
| || piwb->presence == svn_wc__db_status_incomplete); |
| |
| 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 && present) |
| ? 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 && present) |
| ? 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 && present) |
| { |
| 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); |
| if (present && piwb->original_repos_relpath) |
| 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, local_relpath, new_actual_props, |
| 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; |
| } |
| |
| |
| /* 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 *parent_relpath, |
| int stmt_idx, |
| int op_depth, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| apr_array_header_t *result; |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| |
| result = apr_array_make(result_pool, 16, sizeof(const char*)); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, parent_relpath)); |
| if (op_depth >= 0) |
| SVN_ERR(svn_sqlite__bind_int(stmt, 3, op_depth)); |
| |
| 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); |
| |
| APR_ARRAY_PUSH(result, const char *) = name; |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| } |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| *children = result; |
| 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 = apr_hash_this_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)); |
| } |
| |
| svn_error_t * |
| svn_wc__db_install_schema_statistics(svn_sqlite__db_t *sdb, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_INSTALL_SCHEMA_STATISTICS)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* 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)); |
| |
| SVN_ERR(svn_wc__db_install_schema_statistics(db, scratch_pool)); |
| |
| /* 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 */ |
| SVN_VA_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_int32_t timeout, |
| 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, |
| timeout, |
| 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; |
| apr_int32_t sqlite_timeout = 0; /* default timeout */ |
| apr_hash_index_t *hi; |
| |
| 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(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, |
| sqlite_timeout, |
| 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 */, |
| db->state_pool, scratch_pool)); |
| |
| /* Any previously cached children may now have a new WCROOT, most likely that |
| of the new WCROOT, but there might be descendant directories that are their |
| own working copy, in which case setting WCROOT to our new WCROOT might |
| actually break things for those. |
| |
| Clearing is the safest thing we can do in this case, as a test would lead |
| to unnecessary probing, while the standard code probes later anyway. So we |
| only lose a bit of memory |
| |
| ### Perhaps we could check wcroot->abspath to detect which case we have |
| where, but currently it is already very hard to trigger this from |
| the short living 'svn' client. (GUI clients like TortoiseSVN are far |
| more likely to get in these cases) |
| */ |
| for (hi = apr_hash_first(scratch_pool, db->dir_data); |
| hi; |
| hi = apr_hash_next(hi)) |
| { |
| const char *abspath = apr_hash_this_key(hi); |
| if (svn_dirent_is_ancestor(wcroot->abspath, abspath)) |
| svn_hash_sets(db->dir_data, abspath, NULL); |
| } |
| |
| /* 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, |
| svn_boolean_t update_actual_props, |
| apr_hash_t *new_actual_props, |
| apr_array_header_t *new_iprops, |
| 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)); |
| #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 a node to MOVED_TO_RELPATH. This transforms this side of the |
| * move into a simple copy. |
| */ |
| static svn_error_t * |
| clear_moved_here(svn_wc__db_wcroot_t *wcroot, |
| const char *moved_to_relpath, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| int affected_rows; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_CLEAR_MOVED_HERE_RECURSIVE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, moved_to_relpath, |
| relpath_depth(moved_to_relpath))); |
| |
| 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' was not found."), |
| path_for_error_message(wcroot, moved_to_relpath, |
| scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc__db_op_break_move_internal(svn_wc__db_wcroot_t *wcroot, |
| const char *src_relpath, |
| int delete_op_depth, |
| const char *dst_relpath, |
| const svn_skel_t *work_items, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| int affected; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_CLEAR_MOVED_TO_RELPATH)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, src_relpath, |
| delete_op_depth)); |
| SVN_ERR(svn_sqlite__update(&affected, stmt)); |
| |
| if (affected != 1) |
| return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, |
| _("Path '%s' is not moved"), |
| path_for_error_message(wcroot, src_relpath, |
| scratch_pool)); |
| |
| SVN_ERR(clear_moved_here(wcroot, dst_relpath, scratch_pool)); |
| |
| SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); |
| 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 mark_not_present, |
| svn_boolean_t mark_excluded, |
| svn_revnum_t marker_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; |
| svn_revnum_t revision; |
| apr_int64_t repos_id; |
| const char *repos_relpath; |
| svn_node_kind_t kind; |
| svn_boolean_t keep_working; |
| int op_depth; |
| svn_node_kind_t wrk_kind; |
| svn_boolean_t no_delete_wc = FALSE; |
| svn_boolean_t file_external; |
| |
| SVN_ERR(svn_wc__db_base_get_info_internal(&status, &kind, &revision, |
| &repos_relpath, &repos_id, |
| NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, |
| &file_external, |
| wcroot, local_relpath, |
| scratch_pool, scratch_pool)); |
| |
| /* Check if there is already a working node */ |
| 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_trace(svn_sqlite__reset(stmt)); /* No BASE */ |
| |
| op_depth = svn_sqlite__column_int(stmt, 0); |
| wrk_kind = svn_sqlite__column_token(stmt, 4, kind_map); |
| |
| if (op_depth > 0 |
| && op_depth == relpath_depth(local_relpath)) |
| { |
| svn_wc__db_status_t presence; |
| presence = svn_sqlite__column_token(stmt, 3, presence_map); |
| |
| if (presence == svn_wc__db_status_base_deleted) |
| { |
| keep_working = FALSE; |
| no_delete_wc = TRUE; |
| } |
| else |
| { |
| keep_working = TRUE; |
| } |
| } |
| else |
| keep_working = FALSE; |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| if (keep_as_working && op_depth == 0) |
| { |
| if (status == svn_wc__db_status_normal |
| || status == svn_wc__db_status_incomplete) |
| { |
| SVN_ERR(svn_wc__db_op_make_copy_internal(wcroot, local_relpath, TRUE, |
| NULL, NULL, |
| scratch_pool)); |
| } |
| keep_working = TRUE; |
| } |
| |
| /* Step 1: Create workqueue operations to remove files and dirs in the |
| local-wc */ |
| if (!keep_working && !no_delete_wc) |
| { |
| svn_skel_t *work_item; |
| const char *local_abspath; |
| |
| local_abspath = svn_dirent_join(wcroot->abspath, local_relpath, |
| scratch_pool); |
| if (wrk_kind == svn_node_dir) |
| { |
| apr_pool_t *iterpool; |
| 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)); |
| |
| 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 (!keep_working) |
| { |
| apr_pool_t *iterpool; |
| |
| /* When deleting everything in working we should break moves from |
| here and to here. |
| */ |
| 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 *moved_to_relpath; |
| svn_error_t *err; |
| |
| svn_pool_clear(iterpool); |
| moved_to_relpath = svn_sqlite__column_text(stmt, 1, iterpool); |
| err = clear_moved_here(wcroot, moved_to_relpath, 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)); |
| } |
| else |
| { |
| /* We are keeping things that are in WORKING, but we should still |
| break moves of things in BASE. (Mixed revisions make it |
| impossible to guarantee that we can keep everything moved) */ |
| |
| apr_pool_t *iterpool; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_SELECT_MOVED_DESCENDANTS_SRC)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, |
| local_relpath, 0)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| iterpool = svn_pool_create(scratch_pool); |
| while (have_row) |
| { |
| int delete_op_depth = svn_sqlite__column_int(stmt, 0); |
| const char *src_relpath; |
| const char *dst_relpath; |
| svn_error_t *err; |
| |
| svn_pool_clear(iterpool); |
| |
| src_relpath = svn_sqlite__column_text(stmt, 1, iterpool); |
| dst_relpath = svn_sqlite__column_text(stmt, 4, iterpool); |
| |
| err = svn_wc__db_op_break_move_internal(wcroot, src_relpath, |
| delete_op_depth, |
| dst_relpath, |
| NULL, |
| 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_RECURSIVE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, 0)); |
| 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)); |
| |
| SVN_ERR(db_retract_parent_delete(wcroot, local_relpath, 0, scratch_pool)); |
| |
| if (mark_not_present || mark_excluded) |
| { |
| struct insert_base_baton_t ibb; |
| svn_boolean_t no_marker = FALSE; |
| |
| if (file_external) |
| { |
| const char *parent_local_relpath; |
| const char *name; |
| svn_error_t *err; |
| |
| /* For file externals we only want to place a not present marker |
| if there is a BASE parent */ |
| |
| svn_relpath_split(&parent_local_relpath, &name, local_relpath, |
| scratch_pool); |
| |
| 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, parent_local_relpath, |
| scratch_pool, scratch_pool); |
| |
| if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) |
| return svn_error_trace(err); |
| else if (err) |
| { |
| svn_error_clear(err); |
| no_marker = TRUE; |
| } |
| else |
| { |
| /* Replace the repos_relpath with something more expected than |
| the unrelated old file external repository relpath, which |
| one day may come from a different repository */ |
| repos_relpath = svn_relpath_join(repos_relpath, name, scratch_pool); |
| } |
| } |
| |
| if (!no_marker) |
| { |
| blank_ibb(&ibb); |
| |
| ibb.repos_id = repos_id; |
| ibb.status = mark_excluded ? svn_wc__db_status_excluded |
| : svn_wc__db_status_not_present; |
| ibb.kind = kind; |
| ibb.repos_relpath = repos_relpath; |
| ibb.revision = SVN_IS_VALID_REVNUM(marker_revision) |
| ? marker_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; |
| |
| 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 mark_not_present, |
| svn_boolean_t mark_excluded, |
| svn_revnum_t marker_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, |
| mark_not_present, mark_excluded, |
| marker_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_WC__DB_WITH_TXN4( |
| 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_wc__db_fetch_repos_info(repos_root_url, repos_uuid, |
| wcroot, repos_id, result_pool), |
| SVN_NO_ERROR, |
| SVN_NO_ERROR, |
| wcroot); |
| SVN_ERR_ASSERT(repos_id != INVALID_REPOS_ID); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* The implementation of svn_wc__db_base_get_children_info */ |
| static svn_error_t * |
| base_get_children_info(apr_hash_t **nodes, |
| svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| svn_boolean_t obtain_locks, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| 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; |
| |
| *nodes = apr_hash_make(result_pool); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| obtain_locks |
| ? STMT_SELECT_BASE_CHILDREN_INFO_LOCK |
| : 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; |
| 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); |
| |
| if (obtain_locks) |
| info->lock = lock_from_columns(stmt, 8, 9, 10, 11, result_pool); |
| |
| if (repos_id != last_repos_id) |
| { |
| svn_error_t *err; |
| |
| err = svn_wc__db_fetch_repos_info(&last_repos_root_url, NULL, |
| wcroot, repos_id, |
| result_pool); |
| |
| if (err) |
| return svn_error_trace( |
| svn_error_compose_create(err, |
| svn_sqlite__reset(stmt))); |
| |
| last_repos_id = repos_id; |
| } |
| |
| info->repos_root_url = last_repos_root_url; |
| |
| 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_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_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); |
| |
| return svn_error_trace(base_get_children_info(nodes, |
| wcroot, |
| local_relpath, |
| TRUE /* obtain_locks */, |
| result_pool, |
| scratch_pool)); |
| } |
| |
| |
| 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 svn_error_trace( |
| gather_children(children, wcroot, local_relpath, |
| STMT_SELECT_OP_DEPTH_CHILDREN, 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_wc__db_wcroot_t *wcroot; |
| const char *local_relpath; |
| svn_sqlite__stmt_t *stmt; |
| int affected_rows; |
| |
| 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_UPDATE_BASE_NODE_DAV_CACHE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->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) |
| 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_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_BASE_DAV_CACHE)); |
| |
| 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, 12); |
| } |
| 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, 12, |
| result_pool, scratch_pool)); |
| if (*props == NULL) |
| *props = apr_hash_make(result_pool); |
| } |
| else |
| { |
| assert(svn_sqlite__column_is_null(stmt, 12)); |
| *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)); |
| } |
| |
| /* A callback which supplies WCROOTs and LOCAL_RELPATHs. */ |
| typedef svn_error_t *(*svn_wc__db_txn_callback_t)(void *baton, |
| svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| apr_pool_t *scratch_pool); |
| |
| /* 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_sqlite__begin_savepoint(wcroot->sdb); |
| if (!err1) |
| { |
| err1 = txn_cb(txn_baton, wcroot, local_relpath, scratch_pool); |
| |
| err1 = svn_sqlite__finish_savepoint(wcroot->sdb, err1); |
| } |
| |
| 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; |
| int affected_rows; |
| |
| 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__update(&affected_rows, stmt)); |
| |
| if (!affected_rows) |
| return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, |
| _("The node '%s' is not an external."), |
| path_for_error_message(wcroot, local_relpath, |
| scratch_pool)); |
| |
| 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, 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)); |
| |
| if (dst_status != svn_wc__db_status_not_present |
| && dst_status != svn_wc__db_status_excluded |
| && dst_status != svn_wc__db_status_server_excluded) |
| { |
| SVN_ERR(db_read_pristine_props(&props, src_wcroot, src_relpath, FALSE, |
| scratch_pool, scratch_pool)); |
| } |
| else |
| props = NULL; |
| |
| 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(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, 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_internal( |
| 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) |
| { |
| return svn_error_trace( |
| scan_deletion(base_del_relpath, moved_to_relpath, work_del_relpath, |
| moved_to_op_root_relpath, |
| wcroot, local_relpath, |
| result_pool, scratch_pool)); |
| } |
| |
| |
| 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(&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 *src_wcroot, |
| const char *local_relpath, |
| svn_wc__db_wcroot_t *dst_wcroot, |
| 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 */, |
| src_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, |
| src_wcroot, parent_relpath, dst_wcroot, |
| 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, src_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(&base_del_relpath, NULL, |
| &work_del_relpath, |
| NULL, src_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, |
| src_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, |
| src_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; |
| |
| if (src_wcroot != dst_wcroot && *copyfrom_relpath) |
| { |
| const char *repos_root_url; |
| const char *repos_uuid; |
| |
| /* Pass the right repos-id for the destination db. We can't just use |
| the id of the source database, as this value can change after |
| relocation (and perhaps also when we start storing multiple |
| working copies in a single db)! */ |
| |
| SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, &repos_uuid, |
| src_wcroot, *copyfrom_id, |
| scratch_pool)); |
| |
| SVN_ERR(create_repos_id(copyfrom_id, repos_root_url, repos_uuid, |
| dst_wcroot->sdb, scratch_pool)); |
| } |
| |
| 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, dst_wcroot, |
| 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_children(&children, src_wcroot, src_relpath, |
| STMT_SELECT_OP_DEPTH_CHILDREN, 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(). */ |
| static svn_error_t * |
| op_copy_txn(svn_wc__db_wcroot_t *wcroot, |
| struct op_copy_baton *ocb, |
| apr_pool_t *scratch_pool) |
| { |
| int move_op_depth; |
| |
| if (wcroot != ocb->dst_wcroot) |
| { |
| /* Source and destination databases differ; so also start a lock |
| in the destination database, by calling ourself in an extra lock. */ |
| |
| SVN_WC__DB_WITH_TXN(op_copy_txn(ocb->dst_wcroot, ocb, scratch_pool), |
| ocb->dst_wcroot); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* 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_WC__DB_WITH_TXN(op_copy_txn(ocb.src_wcroot, &ocb, scratch_pool), |
| ocb.src_wcroot); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Remove unneeded actual nodes for svn_wc__db_op_copy_layer_internal */ |
| static svn_error_t * |
| clear_or_remove_actual(svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| int op_depth, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row, shadowed; |
| svn_boolean_t keep_conflict = 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) |
| { |
| svn_wc__db_status_t presence; |
| |
| shadowed = (svn_sqlite__column_int(stmt, 0) > op_depth); |
| presence = svn_sqlite__column_token(stmt, 3, presence_map); |
| |
| if (shadowed && presence == svn_wc__db_status_base_deleted) |
| { |
| keep_conflict = TRUE; |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| if (have_row) |
| shadowed = (svn_sqlite__column_int(stmt, 0) > op_depth); |
| else |
| shadowed = FALSE; |
| } |
| } |
| else |
| shadowed = FALSE; |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| if (shadowed) |
| return SVN_NO_ERROR; |
| |
| if (keep_conflict) |
| { |
| /* We don't want to accidentally remove delete-delete conflicts */ |
| SVN_ERR(svn_sqlite__get_statement( |
| &stmt, wcroot->sdb, |
| STMT_CLEAR_ACTUAL_NODE_LEAVING_CONFLICT)); |
| 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)); |
| } |
| else |
| { |
| 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)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc__db_op_copy_layer_internal(svn_wc__db_wcroot_t *wcroot, |
| const char *src_op_relpath, |
| int src_op_depth, |
| const char *dst_op_relpath, |
| svn_skel_t *conflict, |
| svn_skel_t *work_items, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt, *stmt2; |
| svn_boolean_t have_row; |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| int dst_op_depth = relpath_depth(dst_op_relpath); |
| svn_boolean_t locked; |
| svn_error_t *err = NULL; |
| |
| SVN_ERR(svn_wc__db_wclock_owns_lock_internal(&locked, wcroot, dst_op_relpath, |
| FALSE, scratch_pool)); |
| |
| if (!locked) |
| return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL, |
| _("No write-lock in '%s'"), |
| path_for_error_message(wcroot, dst_op_relpath, |
| scratch_pool)); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt2, wcroot->sdb, |
| STMT_COPY_NODE_MOVE)); |
| |
| /* Replace entire subtree at one op-depth. */ |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_SELECT_LAYER_FOR_REPLACE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isdsd", wcroot->wc_id, |
| src_op_relpath, src_op_depth, |
| dst_op_relpath, dst_op_depth)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| while (have_row) |
| { |
| const char *src_relpath; |
| const char *dst_relpath; |
| |
| svn_pool_clear(iterpool); |
| |
| src_relpath = svn_sqlite__column_text(stmt, 0, iterpool); |
| dst_relpath = svn_sqlite__column_text(stmt, 2, iterpool); |
| |
| err = svn_sqlite__bindf(stmt2, "isdsds", wcroot->wc_id, |
| src_relpath, src_op_depth, |
| dst_relpath, dst_op_depth, |
| svn_relpath_dirname(dst_relpath, iterpool)); |
| if (!err) |
| err = svn_sqlite__step_done(stmt2); |
| |
| /* stmt2 is reset (never modified or by step_done) */ |
| |
| if (err) |
| break; |
| |
| /* The node can't be deleted where it is added, so extension of |
| an existing shadowing is only interesting 2 levels deep. */ |
| if (relpath_depth(dst_relpath) > (dst_op_depth+1)) |
| { |
| svn_boolean_t exists = !svn_sqlite__column_is_null(stmt, 3); |
| |
| if (exists) |
| { |
| svn_wc__db_status_t presence; |
| |
| presence = svn_sqlite__column_token(stmt, 3, presence_map); |
| |
| if (presence != svn_wc__db_status_normal) |
| exists = FALSE; |
| } |
| |
| if (!exists) |
| { |
| svn_node_kind_t kind = svn_sqlite__column_token(stmt, 1, kind_map); |
| |
| err = db_extend_parent_delete(wcroot, dst_relpath, |
| kind, dst_op_depth, iterpool); |
| |
| if (err) |
| break; |
| } |
| } |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| } |
| |
| SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); |
| |
| /* And now remove the records that are no longer needed */ |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_SELECT_NO_LONGER_MOVED_RV)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isdsd", wcroot->wc_id, |
| dst_op_relpath, dst_op_depth, |
| src_op_relpath, src_op_depth)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| while (have_row) |
| { |
| const char *dst_relpath; |
| svn_wc__db_status_t shadowed_presence; |
| |
| svn_pool_clear(iterpool); |
| |
| dst_relpath = svn_sqlite__column_text(stmt, 0, iterpool); |
| |
| if (!svn_sqlite__column_is_null(stmt, 2)) |
| shadowed_presence = svn_sqlite__column_token(stmt, 2, presence_map); |
| else |
| shadowed_presence = svn_wc__db_status_not_present; |
| |
| if (shadowed_presence != svn_wc__db_status_normal |
| && shadowed_presence != svn_wc__db_status_incomplete) |
| { |
| err = svn_sqlite__get_statement(&stmt2, wcroot->sdb, |
| STMT_DELETE_NODE); |
| } |
| else |
| { |
| err =svn_sqlite__get_statement(&stmt2, wcroot->sdb, |
| STMT_REPLACE_WITH_BASE_DELETED); |
| } |
| |
| if (!err) |
| err = svn_sqlite__bindf(stmt2, "isd", wcroot->wc_id, dst_relpath, |
| dst_op_depth); |
| |
| if (!err) |
| err = svn_sqlite__step_done(stmt2); |
| |
| /* stmt2 is reset (never modified or by step_done) */ |
| if (err) |
| break; |
| |
| /* Delete ACTUAL information about this node that we just deleted */ |
| err = clear_or_remove_actual(wcroot, dst_relpath, dst_op_depth, |
| scratch_pool); |
| |
| if (err) |
| break; |
| |
| /* Retract base-delete for the node itself */ |
| err = db_retract_parent_delete(wcroot, dst_relpath, dst_op_depth, |
| scratch_pool); |
| |
| 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))); |
| |
| SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); |
| |
| if (conflict) |
| SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, dst_op_relpath /* ## */, |
| conflict, 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_WORKING_OP_DEPTH)); |
| |
| 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, 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)); |
| } |
| |
| if (dst_presence == svn_wc__db_status_not_present) |
| { |
| /* Don't create descendants of a not present node! */ |
| |
| /* This code is currently still triggered by copying deleted nodes |
| between separate working copies. See ### comment above. */ |
| |
| svn_pool_destroy(iterpool); |
| return SVN_NO_ERROR; |
| } |
| |
| SVN_ERR(gather_children(&children, src_wcroot, src_relpath, |
| STMT_SELECT_OP_DEPTH_CHILDREN, 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(). */ |
| static svn_error_t * |
| op_copy_shadowed_layer_txn(svn_wc__db_wcroot_t *wcroot, |
| struct op_copy_baton *ocb, |
| apr_pool_t *scratch_pool) |
| { |
| 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 (wcroot != ocb->dst_wcroot) |
| { |
| /* Source and destination databases differ; so also start a lock |
| in the destination database, by calling ourself in an extra lock. */ |
| |
| SVN_WC__DB_WITH_TXN(op_copy_shadowed_layer_txn(ocb->dst_wcroot, ocb, |
| scratch_pool), |
| ocb->dst_wcroot); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* 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_WC__DB_WITH_TXN(op_copy_shadowed_layer_txn(ocb.src_wcroot, &ocb, |
| scratch_pool), |
| ocb.src_wcroot); |
| |
| 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_depth_t depth, |
| 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_* ? */ |
| /* ### 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; |
| |
| 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; |
| |
| iwb.props = props; |
| iwb.changed_rev = changed_rev; |
| iwb.changed_date = changed_date; |
| iwb.changed_author = changed_author; |
| } |
| |
| /* ### 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; |
| |
| 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; |
| |
| iwb.props = props; |
| iwb.changed_rev = changed_rev; |
| iwb.changed_date = changed_date; |
| iwb.changed_author = changed_author; |
| } |
| |
| /* ### 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, |
| 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_* ? */ |
| /* ### 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; |
| |
| |
| 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; |
| |
| iwb.props = props; |
| iwb.changed_rev = changed_rev; |
| iwb.changed_date = changed_date; |
| iwb.changed_author = changed_author; |
| } |
| |
| /* ### 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.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, 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(svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| apr_hash_t *props, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| int affected_rows; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_UPDATE_ACTUAL_PROPS)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->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) |
| { |
| /* Perhaps the entire ACTUAL record is unneeded now? */ |
| if (!props && affected_rows) |
| { |
| 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)); |
| } |
| |
| return SVN_NO_ERROR; /* We are done */ |
| } |
| |
| /* We have to insert a row in ACTUAL */ |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_INSERT_ACTUAL_PROPS)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->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)); |
| } |
| |
| svn_error_t * |
| svn_wc__db_op_set_props_internal(svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| apr_hash_t *props, |
| svn_boolean_t clear_recorded_info, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR(set_actual_props(wcroot, local_relpath, props, scratch_pool)); |
| |
| if (clear_recorded_info) |
| { |
| SVN_ERR(db_record_fileinfo(wcroot, local_relpath, |
| SVN_INVALID_FILESIZE, 0, |
| scratch_pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* 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(svn_wc__db_op_set_props_internal(wcroot, local_relpath, props, |
| clear_recorded_info, 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_FILES)); |
| 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(). |
| */ |
| svn_error_t * |
| svn_wc__db_op_mark_resolved_internal(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); |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); |
| |
| if (!conflict_data) |
| return SVN_NO_ERROR; |
| |
| conflicts = svn_skel__parse(conflict_data, conflict_len, scratch_pool); |
| |
| |
| 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)); |
| } |
| |
| 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( |
| svn_wc__db_op_mark_resolved_internal( |
| 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 |
| * MOVED_TO_RELPATH here. This transforms the delete part of the move into a |
| * normal delete. |
| * |
| * Note that the moved-to location is always an op-root, while this is not the |
| * case for a moved-from location. |
| */ |
| static svn_error_t * |
| clear_moved_to(svn_wc__db_wcroot_t *wcroot, |
| const char *moved_to_relpath, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| const char *moved_from_relpath; |
| int moved_from_op_depth; |
| |
| 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_relpath)); |
| SVN_ERR(svn_sqlite__step_row(stmt)); |
| |
| moved_from_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); |
| moved_from_op_depth = svn_sqlite__column_int(stmt, 1); |
| 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, moved_from_op_depth)); |
| SVN_ERR(svn_sqlite__update(NULL, stmt)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Helper function for op_revert_txn. Raises move tree conflicts on |
| descendants to ensure database stability on a non recursive revert |
| of an ancestor that contains a possible move related tree conflict. |
| */ |
| static svn_error_t * |
| revert_maybe_raise_moved_away(svn_wc__db_wcroot_t * wcroot, |
| svn_wc__db_t *db, |
| const char *local_relpath, |
| int op_depth_below, |
| apr_pool_t *scratch_pool) |
| { |
| svn_skel_t *conflict; |
| svn_wc_operation_t operation; |
| svn_boolean_t tree_conflicted; |
| const apr_array_header_t *locations; |
| svn_wc_conflict_reason_t reason; |
| svn_wc_conflict_action_t action; |
| |
| SVN_ERR(svn_wc__db_read_conflict_internal(&conflict, NULL, NULL, wcroot, |
| local_relpath, |
| scratch_pool, scratch_pool)); |
| |
| if (!conflict) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR(svn_wc__conflict_read_info(&operation, &locations, 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)) |
| { |
| return SVN_NO_ERROR; |
| } |
| |
| 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 |
| || reason == svn_wc_conflict_reason_replaced) |
| { |
| SVN_ERR(svn_wc__db_op_raise_moved_away_internal( |
| wcroot, local_relpath, op_depth_below, db, |
| operation, action, |
| (locations && locations->nelts > 0) |
| ? APR_ARRAY_IDX(locations, 0, |
| const svn_wc_conflict_version_t *) |
| : NULL, |
| (locations && locations->nelts > 1) |
| ? APR_ARRAY_IDX(locations, 1, |
| const svn_wc_conflict_version_t *) |
| : NULL, |
| scratch_pool)); |
| |
| /* Transform the move information into revert information */ |
| SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, |
| STMT_MOVE_NOTIFY_TO_REVERT)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Baton for op_revert_txn and op_revert_recursive_txn */ |
| struct revert_baton_t |
| { |
| svn_wc__db_t *db; |
| svn_boolean_t clear_changelists; |
| }; |
| |
| /* 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) |
| { |
| struct revert_baton_t *rvb = baton; |
| svn_wc__db_t *db = rvb->db; |
| 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; |
| int op_depth_below; |
| |
| /* ### 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__step(&have_row, stmt)); |
| if (have_row) |
| op_depth_below = svn_sqlite__column_int(stmt, 0); |
| else |
| op_depth_below = -1; |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| if (moved_to) |
| { |
| SVN_ERR(svn_wc__db_op_break_move_internal(wcroot, |
| local_relpath, op_depth, |
| moved_to, NULL, scratch_pool)); |
| } |
| |
| if (op_depth > 0 && op_depth == relpath_depth(local_relpath)) |
| { |
| int op_depth_increased; |
| |
| /* 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__update(&op_depth_increased, 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(wcroot, local_relpath, scratch_pool)); |
| |
| /* If the node was moved itself, we don't have interesting moved |
| children (and the move itself was already broken) */ |
| if (op_depth_increased && !moved_to) |
| SVN_ERR(revert_maybe_raise_moved_away(wcroot, db, local_relpath, |
| op_depth_below, scratch_pool)); |
| } |
| |
| if (rvb->clear_changelists) |
| { |
| 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)); |
| } |
| else |
| { |
| 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) |
| { |
| struct revert_baton_t *rvb = baton; |
| 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 *src_relpath = svn_sqlite__column_text(stmt, 0, NULL); |
| const char *dst_relpath = svn_sqlite__column_text(stmt, 1, NULL); |
| int move_op_depth = svn_sqlite__column_int(stmt, 2); |
| svn_error_t *err; |
| |
| err = svn_wc__db_op_break_move_internal(wcroot, |
| src_relpath, move_op_depth, |
| dst_relpath, NULL, 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)); |
| |
| if (rvb->clear_changelists) |
| { |
| 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 |
| { |
| 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(wcroot, moved_here_child_relpath, 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(wcroot, local_relpath, 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, |
| svn_boolean_t clear_changelists, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_wcroot_t *wcroot; |
| const char *local_relpath; |
| struct revert_baton_t rvb; |
| 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)); |
| |
| rvb.db = db; |
| rvb.clear_changelists = clear_changelists; |
| wtb.cb_baton = &rvb; |
| |
| switch (depth) |
| { |
| case svn_depth_empty: |
| wtb.cb_func = op_revert_txn; |
| 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, |
| 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(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_wc_notify_t *notify; |
| |
| svn_pool_clear(iterpool); |
| |
| notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath, |
| notify_relpath, |
| iterpool), |
| svn_wc_notify_revert, |
| iterpool); |
| |
| if (!svn_sqlite__column_is_null(stmt, 1)) |
| notify->kind = svn_sqlite__column_token(stmt, 1, kind_map); |
| else |
| { |
| if (!svn_sqlite__column_is_null(stmt, 3)) |
| notify->kind = svn_sqlite__column_token(stmt, 3, kind_map_none); |
| |
| switch (svn_sqlite__column_int(stmt, 2)) |
| { |
| case 0: |
| continue; |
| case 1: |
| /* standard revert */ |
| break; |
| case 2: |
| notify->action = svn_wc_notify_tree_conflict; |
| break; |
| default: |
| SVN_ERR_MALFUNCTION(); |
| } |
| } |
| |
| notify_func(notify_baton, notify, 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, |
| 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; |
| |
| /* 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; |
| |
| 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_ALL_LAYERS)); |
| 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)); |
| |
| 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, |
| 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, |
| 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)); |
| #ifdef SVN_DEBUG |
| /* Not fatal in release mode. The move recording is broken, |
| but the rest of the working copy can handle this. */ |
| SVN_ERR_ASSERT(affected == 1); |
| #endif |
| |
| 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; |
| |
| /* When >= 1 the op_depth at which local_relpath was moved to its |
| location. Used to find its original location outside the delete */ |
| int moved_from_depth; |
| }; |
| |
| /* Helper function to resolve the original location of local_relpath at OP_DEPTH |
| before it was moved into the tree rooted at ROOT_RELPATH. */ |
| static svn_error_t * |
| resolve_moved_from(const char **moved_from_relpath, |
| int *moved_from_op_depth, |
| svn_wc__db_wcroot_t *wcroot, |
| const char *root_relpath, |
| const char *local_relpath, |
| int op_depth, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *suffix = ""; |
| svn_sqlite__stmt_t *stmt; |
| const char *m_from_relpath; |
| int m_from_op_depth; |
| int m_move_from_depth; |
| svn_boolean_t have_row; |
| |
| while (relpath_depth(local_relpath) > op_depth) |
| { |
| const char *name; |
| svn_relpath_split(&local_relpath, &name, local_relpath, scratch_pool); |
| suffix = svn_relpath_join(suffix, name, scratch_pool); |
| } |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_SELECT_MOVED_FROM_FOR_DELETE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", |
| wcroot->wc_id, local_relpath)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| if (!have_row) |
| { |
| /* assert(have_row); */ |
| *moved_from_relpath = NULL; |
| *moved_from_op_depth = -1; |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| m_from_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); |
| m_from_op_depth = svn_sqlite__column_int(stmt, 1); |
| m_move_from_depth = svn_sqlite__column_int(stmt, 2); |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| if (! svn_relpath_skip_ancestor(root_relpath, m_from_relpath)) |
| { |
| *moved_from_relpath = svn_relpath_join(m_from_relpath, suffix, |
| result_pool); |
| *moved_from_op_depth = m_from_op_depth; /* ### Ok? */ |
| return SVN_NO_ERROR; |
| } |
| else if (!m_move_from_depth) |
| { |
| *moved_from_relpath = NULL; |
| *moved_from_op_depth = -1; |
| return SVN_NO_ERROR; |
| } |
| |
| return svn_error_trace( |
| resolve_moved_from(moved_from_relpath, |
| moved_from_op_depth, |
| wcroot, |
| root_relpath, |
| svn_relpath_join(m_from_relpath, suffix, |
| scratch_pool), |
| m_move_from_depth, |
| result_pool, scratch_pool)); |
| } |
| |
| 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 working_op_depth; /* Depth of what is to be deleted */ |
| int keep_op_depth = 0; /* Depth of what is below what is deleted */ |
| svn_node_kind_t kind; |
| apr_array_header_t *moved_nodes = NULL; |
| int delete_op_depth = relpath_depth(local_relpath); |
| |
| assert(*local_relpath); /* Can't delete wcroot */ |
| |
| 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)); |
| } |
| |
| working_op_depth = svn_sqlite__column_int(stmt, 0); |
| status = svn_sqlite__column_token(stmt, 3, presence_map); |
| kind = svn_sqlite__column_token(stmt, 4, kind_map); |
| |
| if (working_op_depth < delete_op_depth) |
| { |
| op_root = FALSE; |
| add_work = TRUE; |
| keep_op_depth = working_op_depth; |
| } |
| else |
| { |
| op_root = TRUE; |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| if (have_row) |
| { |
| svn_wc__db_status_t below_status; |
| int below_op_depth; |
| |
| below_op_depth = svn_sqlite__column_int(stmt, 0); |
| below_status = svn_sqlite__column_token(stmt, 3, presence_map); |
| |
| if (below_status != svn_wc__db_status_not_present |
| && below_status != svn_wc__db_status_base_deleted) |
| { |
| add_work = TRUE; |
| keep_op_depth = below_op_depth; |
| } |
| else |
| keep_op_depth = 0; |
| } |
| else |
| keep_op_depth = -1; |
| } |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| if (working_op_depth != 0) /* WORKING */ |
| SVN_ERR(convert_to_working_status(&status, status)); |
| |
| 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; |
| moved_node->moved_from_depth = -1; |
| |
| 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_op_depth; |
| moved_node->moved_to_relpath = b->moved_to_relpath; |
| moved_node->moved_from_depth = -1; |
| |
| 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)); |
| } |
| |
| /* 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; |
| int i; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_SELECT_MOVED_FOR_DELETE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, |
| delete_op_depth)); |
| |
| 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); |
| int moved_from_depth = -1; |
| svn_boolean_t fixup = FALSE; |
| |
| if (! b->moved_to_relpath |
| && ! svn_relpath_skip_ancestor(local_relpath, mv_to_relpath)) |
| { |
| /* a NULL moved_here_depth will be reported as 0 */ |
| int moved_here_depth = svn_sqlite__column_int(stmt, 3); |
| |
| /* Plain delete. Fixup move information of descendants that were |
| moved here, or that were moved out */ |
| |
| if (moved_here_depth >= delete_op_depth) |
| { |
| /* The move we recorded here must be moved to the location |
| this node had before it was moved here. |
| |
| This might contain multiple steps when the node was moved |
| in several places within the to be deleted tree */ |
| |
| /* ### TODO: Add logic */ |
| fixup = TRUE; |
| moved_from_depth = moved_here_depth; |
| } |
| else |
| { |
| /* Update the op-depth of an moved away node that was |
| registered as moved by the records that we are about |
| to delete */ |
| fixup = TRUE; |
| child_op_depth = delete_op_depth; |
| } |
| } |
| else if (b->moved_to_relpath) |
| { |
| /* The node is moved to a new location */ |
| |
| if (delete_op_depth == child_op_depth) |
| { |
| /* Update the op-depth of a tree shadowed by this tree */ |
| fixup = TRUE; |
| /*child_op_depth = delete_depth;*/ |
| } |
| else if (child_op_depth >= delete_op_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_op_depth |
| && svn_relpath_skip_ancestor(local_relpath, |
| child_relpath)) |
| child_op_depth = delete_op_depth; |
| else |
| { |
| /* Calculate depth of the shadowing at the new location */ |
| child_op_depth = child_op_depth |
| - relpath_depth(local_relpath) |
| + relpath_depth(b->moved_to_relpath); |
| } |
| |
| fixup = TRUE; |
| } |
| } |
| } |
| |
| if (fixup) |
| { |
| mn = apr_palloc(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; |
| mn->moved_from_depth = moved_from_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_ERR(svn_sqlite__reset(stmt)); |
| |
| for (i = 0; moved_nodes && (i < moved_nodes->nelts); i++) |
| { |
| struct moved_node_t *mn = APR_ARRAY_IDX(moved_nodes, i, |
| struct moved_node_t *); |
| |
| if (mn->moved_from_depth > 0) |
| { |
| svn_pool_clear(iterpool); |
| |
| SVN_ERR(resolve_moved_from(&mn->local_relpath, &mn->op_depth, |
| wcroot, local_relpath, |
| mn->local_relpath, |
| mn->moved_from_depth, |
| scratch_pool, iterpool)); |
| |
| if (!mn->local_relpath) |
| svn_sort__array_delete(moved_nodes, i--, 1); |
| } |
| } |
| |
| svn_pool_destroy(iterpool); |
| } |
| |
| if (!b->moved_to_relpath) |
| { |
| 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)); |
| |
| if (op_root) |
| { |
| 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__update(NULL, stmt)); |
| } |
| } |
| |
| |
| /* ### 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, working_op_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_op_depth)); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| |
| /* 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, |
| keep_op_depth, delete_op_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; |
| }; |
| |
| 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)); |
| } |
| |
| /* Helper function for read_info() to provide better diagnostics than just |
| asserting. |
| |
| ### BH: Yes this code is ugly, and that is why I only introduce it in |
| ### read_info(). But we really need something to determine the root cause |
| ### of this problem to diagnose why TortoiseSVN users were seeing all those |
| ### assertions. |
| |
| Adds an error to the *err chain if invalid values are encountered. In that |
| case the value is set to the first value in the map, assuming that caller |
| will just return the combined error. |
| */ |
| static int |
| column_token_err(svn_error_t **err, |
| svn_sqlite__stmt_t *stmt, |
| int column, |
| const svn_token_map_t *map) |
| { |
| svn_error_t *err2; |
| const char *word = svn_sqlite__column_text(stmt, column, NULL); |
| int value; |
| |
| /* svn_token__from_word_err() handles NULL for us */ |
| err2 = svn_token__from_word_err(&value, map, word); |
| |
| if (err2) |
| { |
| *err = svn_error_compose_create( |
| *err, |
| svn_error_createf( |
| SVN_ERR_WC_CORRUPT, err2, |
| _("Encountered invalid node state in column %d of " |
| "info query to working copy database"), |
| column)); |
| value = map[0].val; |
| } |
| |
| return value; |
| } |
| |
| /* 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 = column_token_err(&err, stmt_info, 4, kind_map); |
| |
| if (status) |
| { |
| *status = column_token_err(&err, 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 if (svn_sqlite__column_is_null(stmt_info, 11)) |
| *depth = svn_depth_unknown; |
| else |
| *depth = column_token_err(&err, stmt_info, 11, depth_map); |
| } |
| 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_wrapf(err, _("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_WC__DB_WITH_TXN4( |
| 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_wc__db_fetch_repos_info(repos_root_url, repos_uuid, |
| wcroot, repos_id, result_pool), |
| svn_wc__db_fetch_repos_info(original_root_url, original_uuid, |
| wcroot, original_repos_id, |
| result_pool), |
| SVN_NO_ERROR, |
| wcroot); |
| |
| 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); |
| |
| /* Helper for read_children_info and single variant */ |
| static svn_error_t * |
| find_conflict_descendants(svn_boolean_t *conflict_exists, |
| svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| |
| /* Only used on files, so certainly not wcroot*/ |
| assert(local_relpath[0] != '\0'); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_FIND_CONFLICT_DESCENDANT)); |
| |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); |
| SVN_ERR(svn_sqlite__step(conflict_exists, stmt)); |
| |
| return svn_error_trace(svn_sqlite__reset(stmt)); |
| } |
| |
| /* 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; |
| svn_boolean_t was_dir; |
| }; |
| |
| /* Implementation of svn_wc__db_read_children_info */ |
| 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, |
| svn_boolean_t base_tree_only, |
| 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; |
| const char *last_repos_root_url = NULL; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| (base_tree_only |
| ? STMT_SELECT_BASE_NODE_CHILDREN_INFO |
| : 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 = (base_tree_only ? NULL : 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) |
| { |
| 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 |
| { |
| 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, 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->has_descendants = TRUE; |
| child_item->was_dir = TRUE; |
| child->depth = svn_sqlite__column_token_null(stmt, 11, depth_map, |
| svn_depth_unknown); |
| if (new_child) |
| { |
| err = is_wclocked(&child->locked, wcroot, child_relpath, |
| scratch_pool); |
| |
| if (err) |
| SVN_ERR(svn_error_compose_create(err, |
| svn_sqlite__reset(stmt))); |
| } |
| } |
| |
| 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)); |
| |
| if (op_depth && child->op_root) |
| child_item->info.moved_here = svn_sqlite__column_boolean(stmt, 20); |
| |
| if (new_child) |
| svn_hash_sets(nodes, apr_pstrdup(result_pool, name), child); |
| } |
| else if (!child_item->was_dir |
| && svn_sqlite__column_token(stmt, 4, kind_map) == svn_node_dir) |
| { |
| child_item->was_dir = TRUE; |
| |
| err = find_conflict_descendants(&child_item->info.has_descendants, |
| wcroot, child_relpath, |
| scratch_pool); |
| if (err) |
| SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); |
| } |
| |
| 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); |
| |
| |
| /* A local_relpath can be moved multiple times at different op |
| depths and it really depends on the caller what is interesting. |
| We provide a simple linked list with the moved_from information */ |
| |
| moved_to_relpath = svn_sqlite__column_text(stmt, 21, NULL); |
| if (moved_to_relpath) |
| { |
| struct svn_wc__db_moved_to_info_t *moved_to; |
| struct svn_wc__db_moved_to_info_t **next; |
| const char *shadow_op_relpath; |
| |
| moved_to = apr_pcalloc(result_pool, sizeof(*moved_to)); |
| moved_to->moved_to_abspath = svn_dirent_join(wcroot->abspath, |
| moved_to_relpath, |
| result_pool); |
| |
| shadow_op_relpath = svn_relpath_prefix(child_relpath, op_depth, |
| scratch_pool); |
| |
| moved_to->shadow_op_root_abspath = |
| svn_dirent_join(wcroot->abspath, shadow_op_relpath, |
| result_pool); |
| |
| next = &child_item->info.moved_to; |
| |
| while (*next && |
| 0 < strcmp((*next)->shadow_op_root_abspath, |
| moved_to->shadow_op_root_abspath)) |
| next = &((*next)->next); |
| |
| moved_to->next = *next; |
| *next = moved_to; |
| } |
| } |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| } |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| if (!base_tree_only) |
| { |
| 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 |
| |
| /* conflict */ |
| child->conflicted = !svn_sqlite__column_is_null(stmt, 3); |
| |
| 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, |
| svn_boolean_t base_tree_only, |
| 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, |
| base_tree_only, result_pool, scratch_pool), |
| wcroot); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Implementation of svn_wc__db_read_single_info. |
| |
| ### This function is very similar to a lot of code inside |
| read_children_info, but that performs some tricks to re-use data between |
| different siblings. |
| |
| ### We read the same few NODES records a few times via different helper |
| functions, so this could be optimized bit, but everything is within |
| a sqlite transaction and all queries are backed by an index, so generally |
| everything (including the used indexes) should be in the sqlite page cache |
| after the first query. |
| */ |
| static svn_error_t * |
| read_single_info(const struct svn_wc__db_info_t **info, |
| svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| svn_boolean_t base_tree_only, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| struct svn_wc__db_info_t *mtb; |
| apr_int64_t repos_id; |
| const svn_checksum_t *checksum; |
| const char *original_repos_relpath; |
| svn_boolean_t have_work; |
| apr_hash_t *properties; |
| |
| mtb = apr_pcalloc(result_pool, sizeof(*mtb)); |
| |
| if (!base_tree_only) |
| SVN_ERR(read_info(&mtb->status, &mtb->kind, &mtb->revnum, |
| &mtb->repos_relpath, &repos_id, &mtb->changed_rev, |
| &mtb->changed_date, &mtb->changed_author, &mtb->depth, |
| &checksum, NULL, &original_repos_relpath, NULL, NULL, |
| &mtb->lock, &mtb->recorded_size, &mtb->recorded_time, |
| &mtb->changelist, &mtb->conflicted, &mtb->op_root, |
| &mtb->had_props, &mtb->props_mod, &mtb->have_base, |
| &mtb->have_more_work, &have_work, |
| wcroot, local_relpath, result_pool, scratch_pool)); |
| else |
| { |
| svn_boolean_t update_root; |
| |
| have_work = FALSE; |
| original_repos_relpath = NULL; |
| |
| SVN_ERR(svn_wc__db_base_get_info_internal( |
| &mtb->status, &mtb->kind, &mtb->revnum, &mtb->repos_relpath, |
| &repos_id, &mtb->changed_rev, &mtb->changed_date, |
| &mtb->changed_author, &mtb->depth, &checksum, NULL, |
| &mtb->lock, &mtb->had_props, &properties, &update_root, |
| wcroot, local_relpath, scratch_pool, scratch_pool)); |
| |
| mtb->have_base = TRUE; |
| mtb->file_external = (update_root && mtb->kind == svn_node_file); |
| } |
| |
| /* Query the same rows in the database again for move information */ |
| if (have_work && (mtb->have_base || mtb->have_more_work)) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_SELECT_MOVED_TO_NODE)); |
| 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_moved_to_info_t *move; |
| int op_depth = svn_sqlite__column_int(stmt, 0); |
| const char *moved_to_relpath = svn_sqlite__column_text(stmt, 1, NULL); |
| const char *cur_relpath; |
| |
| move = apr_pcalloc(result_pool, sizeof(*move)); |
| move->moved_to_abspath = svn_dirent_join(wcroot->abspath, |
| moved_to_relpath, |
| result_pool); |
| |
| cur_relpath = svn_relpath_prefix(local_relpath, op_depth, |
| scratch_pool); |
| |
| move->shadow_op_root_abspath = svn_dirent_join(wcroot->abspath, |
| cur_relpath, |
| result_pool); |
| |
| move->next = mtb->moved_to; |
| mtb->moved_to = move; |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| } |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| } |
| |
| /* Maybe we have to get some shadowed lock from BASE to make our test suite |
| happy... (It might be completely unrelated, but...) |
| This queries the same BASE row again, joined to the lock table */ |
| if (!base_tree_only && mtb->have_base |
| && (have_work || mtb->kind == svn_node_file)) |
| { |
| svn_boolean_t update_root; |
| svn_wc__db_lock_t **lock_arg = NULL; |
| |
| if (have_work) |
| lock_arg = &mtb->lock; |
| |
| SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, |
| NULL, lock_arg, NULL, NULL, |
| &update_root, |
| wcroot, local_relpath, |
| result_pool, scratch_pool)); |
| |
| mtb->file_external = (update_root && mtb->kind == svn_node_file); |
| } |
| |
| if (mtb->status == svn_wc__db_status_added) |
| { |
| svn_wc__db_status_t status; |
| |
| SVN_ERR(scan_addition(&status, NULL, NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, |
| wcroot, local_relpath, |
| result_pool, scratch_pool)); |
| |
| mtb->moved_here = (status == svn_wc__db_status_moved_here); |
| mtb->incomplete = (status == svn_wc__db_status_incomplete); |
| } |
| |
| #ifdef HAVE_SYMLINK |
| if (mtb->kind == svn_node_file |
| && (mtb->had_props || mtb->props_mod |
| || (base_tree_only && properties))) |
| { |
| if (!base_tree_only) |
| { |
| if (mtb->props_mod) |
| SVN_ERR(svn_wc__db_read_props_internal(&properties, |
| wcroot, local_relpath, |
| scratch_pool, scratch_pool)); |
| else |
| SVN_ERR(db_read_pristine_props(&properties, wcroot, local_relpath, |
| TRUE /* deleted_ok */, |
| scratch_pool, scratch_pool)); |
| } |
| |
| mtb->special = (NULL != svn_hash_gets(properties, SVN_PROP_SPECIAL)); |
| } |
| #endif |
| |
| mtb->has_checksum = (checksum != NULL); |
| mtb->copied = (original_repos_relpath != NULL); |
| |
| SVN_ERR(svn_wc__db_fetch_repos_info(&mtb->repos_root_url, &mtb->repos_uuid, |
| wcroot, repos_id, result_pool)); |
| |
| if (!base_tree_only && mtb->kind == svn_node_dir) |
| SVN_ERR(is_wclocked(&mtb->locked, wcroot, local_relpath, scratch_pool)); |
| |
| if (mtb->kind == svn_node_dir) |
| mtb->has_descendants = TRUE; |
| else |
| SVN_ERR(find_conflict_descendants(&mtb->has_descendants, |
| wcroot, local_relpath, scratch_pool)); |
| |
| *info = mtb; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc__db_read_single_info(const struct svn_wc__db_info_t **info, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_boolean_t base_tree_only, |
| 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_single_info(info, wcroot, local_relpath, |
| base_tree_only, |
| 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(const apr_array_header_t **items, |
| 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; |
| apr_array_header_t *nodes; |
| |
| 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_array_make(result_pool, 16, |
| sizeof(struct svn_wc__db_walker_info_t *)); |
| 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, result_pool); |
| int op_depth = svn_sqlite__column_int(stmt, 1); |
| svn_error_t *err; |
| |
| child = apr_palloc(result_pool, sizeof(*child)); |
| child->name = name; |
| 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); |
| |
| APR_ARRAY_PUSH(nodes, struct svn_wc__db_walker_info_t *) = child; |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| } |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| *items = nodes; |
| |
| 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 (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_repos_info(). |
| */ |
| static svn_error_t * |
| db_read_repos_info(svn_revnum_t *revision, |
| const char **repos_relpath, |
| apr_int64_t *repos_id, |
| 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; |
| |
| SVN_ERR(read_info(&status, NULL, revision, 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, result_pool, scratch_pool)); |
| |
| if ((repos_relpath && !*repos_relpath) |
| || (repos_id && *repos_id == INVALID_REPOS_ID)) |
| { |
| 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, |
| result_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(&base_del_relpath, NULL, |
| &work_del_relpath, |
| NULL, wcroot, |
| local_relpath, |
| scratch_pool, |
| scratch_pool)); |
| |
| if (work_del_relpath) |
| { |
| /* 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)); |
| |
| if (repos_relpath) |
| *repos_relpath = svn_relpath_join( |
| *repos_relpath, |
| svn_dirent_skip_ancestor(work_relpath, |
| local_relpath), |
| result_pool); |
| } |
| else if (base_del_relpath) |
| { |
| SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, revision, |
| repos_relpath, |
| repos_id, |
| NULL, NULL, NULL, |
| NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, |
| wcroot, |
| base_del_relpath, |
| scratch_pool, |
| scratch_pool)); |
| |
| if (repos_relpath) |
| *repos_relpath = svn_relpath_join( |
| *repos_relpath, |
| svn_dirent_skip_ancestor(base_del_relpath, |
| local_relpath), |
| result_pool); |
| } |
| else |
| SVN_ERR_MALFUNCTION(); |
| } |
| else if (status == svn_wc__db_status_excluded) |
| { |
| const char *parent_relpath; |
| const char *name; |
| |
| /* A BASE excluded would have had repository information, so |
| we have a working exclude, which must be below an addition */ |
| |
| svn_relpath_split(&parent_relpath, &name, local_relpath, |
| scratch_pool); |
| SVN_ERR(scan_addition(NULL, NULL, repos_relpath, repos_id, NULL, |
| NULL, NULL, NULL, NULL, NULL, |
| wcroot, parent_relpath, |
| scratch_pool, scratch_pool)); |
| |
| if (repos_relpath) |
| *repos_relpath = svn_relpath_join(*repos_relpath, 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(); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_read_repos_info(svn_revnum_t *revision, |
| 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 = INVALID_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_WC__DB_WITH_TXN4(db_read_repos_info(revision, repos_relpath, |
| (repos_root_url || repos_uuid) |
| ? &repos_id : NULL, |
| wcroot, local_relpath, |
| result_pool, scratch_pool), |
| svn_wc__db_fetch_repos_info(repos_root_url, |
| repos_uuid, |
| wcroot, repos_id, |
| result_pool), |
| SVN_NO_ERROR, SVN_NO_ERROR, |
| 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(). |
| */ |
| svn_error_t * |
| svn_wc__db_read_props_internal(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(svn_wc__db_read_props_internal(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)); |
| } |
| |
| /* 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 = apr_hash_this_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(iprops, &iprop_elt, 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(iprops, &cached_iprop, 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 = apr_hash_this_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 svn_error_trace( |
| gather_children(children, wcroot, local_relpath, |
| STMT_SELECT_WORKING_CHILDREN, -1, |
| result_pool, scratch_pool)); |
| } |
| |
| svn_error_t * |
| svn_wc__db_base_read_not_present_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 svn_error_trace( |
| gather_children(children, wcroot, local_relpath, |
| STMT_SELECT_BASE_NOT_PRESENT_CHILDREN, -1, |
| 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, |
| STMT_SELECT_NODE_CHILDREN, -1, |
| result_pool, scratch_pool); |
| } |
| |
| |
| /* Implementation of svn_wc__db_global_relocate */ |
| static svn_error_t * |
| relocate_txn(svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| const char *repos_root_url, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| apr_int64_t new_repos_id; |
| 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; |
| |
| local_dir_relpath = local_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(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, |
| old_repos_id, scratch_pool)); |
| SVN_ERR_ASSERT(repos_uuid); |
| |
| /* 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)); |
| } |
| |
| /* ### TODO: Update urls stored in inherited properties... |
| What about urls in conflicts? |
| # We can probably keep these as they are only used |
| for showing full urls to the user */ |
| |
| 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; |
| |
| 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)); |
| VERIFY_USABLE_WCROOT(wcroot); |
| |
| SVN_WC__DB_WITH_TXN( |
| relocate_txn(wcroot, local_relpath, repos_root_url, scratch_pool), |
| wcroot); |
| |
| SVN_ERR(flush_entries(wcroot, local_dir_abspath, svn_depth_infinity, |
| scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Helper for commit_node() |
| 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_commit_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; |
| int op_depth; |
| |
| /* Prefer the current node's repository information. */ |
| 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); |
| |
| if (op_depth > 0) |
| { |
| svn_wc__db_status_t presence = svn_sqlite__column_token(stmt, 3, |
| presence_map); |
| |
| if (presence == svn_wc__db_status_base_deleted) |
| { |
| SVN_ERR(svn_sqlite__step_row(stmt)); /* There must be a row */ |
| op_depth = svn_sqlite__column_int(stmt, 0); |
| } |
| else |
| { |
| const char *parent_repos_relpath; |
| const char *parent_relpath; |
| const char *name; |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| /* The repository relative path of an add/copy is based on its |
| ancestor, not on the shadowed base layer. |
| |
| As this function is only used from the commit processing we know |
| the parent directory has only a BASE row, so we can just obtain |
| the information directly by recursing (once!) */ |
| |
| svn_relpath_split(&parent_relpath, &name, local_relpath, |
| scratch_pool); |
| |
| SVN_ERR(determine_commit_repos_info(repos_id, &parent_repos_relpath, |
| wcroot, parent_relpath, |
| scratch_pool, scratch_pool)); |
| |
| *repos_relpath = svn_relpath_join(parent_repos_relpath, name, |
| result_pool); |
| return SVN_NO_ERROR; |
| } |
| } |
| |
| |
| SVN_ERR_ASSERT(op_depth == 0); /* And that row must be BASE */ |
| |
| SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 1)); |
| SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 2)); |
| |
| *repos_id = svn_sqlite__column_int64(stmt, 1); |
| *repos_relpath = svn_sqlite__column_text(stmt, 2, result_pool); |
| |
| return svn_error_trace(svn_sqlite__reset(stmt)); |
| } |
| |
| static svn_error_t * |
| moved_descendant_collect(apr_hash_t **map, |
| 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; |
| |
| *map = NULL; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_SELECT_MOVED_DESCENDANTS_SRC)); |
| 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)); |
| |
| /* Find all moved descendants. Key them on target, because that is |
| always unique */ |
| while (have_row) |
| { |
| const char *src_relpath = svn_sqlite__column_text(stmt, 1, result_pool); |
| const char *to_relpath = svn_sqlite__column_text(stmt, 4, result_pool); |
| |
| if (!*map) |
| *map = apr_hash_make(result_pool); |
| |
| svn_hash_sets(*map, to_relpath, src_relpath); |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| } |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| 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, |
| apr_int64_t repos_id, |
| const char *repos_relpath, |
| svn_revnum_t revision, |
| apr_hash_t *children, |
| apr_pool_t *scratch_pool) |
| { |
| apr_pool_t *iterpool; |
| svn_sqlite__stmt_t *stmt; |
| apr_hash_index_t *hi; |
| |
| SVN_ERR_ASSERT(*local_relpath != '\0' |
| && *repos_relpath != '\0'); |
| |
| if (!children) |
| return SVN_NO_ERROR; |
| |
| /* 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 = apr_hash_this_val(hi); |
| const char *to_relpath = apr_hash_this_key(hi); |
| const char *new_repos_relpath; |
| int to_op_depth = relpath_depth(to_relpath); |
| int affected; |
| apr_hash_t *map; |
| |
| 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_collect(&map, wcroot, to_relpath, to_op_depth, |
| iterpool, iterpool)); |
| SVN_ERR(moved_descendant_commit(wcroot, to_relpath, |
| repos_id, new_repos_relpath, revision, |
| map, 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, |
| 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; |
| svn_boolean_t moved_here; |
| |
| /* 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_commit_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. */ |
| old_presence = svn_sqlite__column_token(stmt_info, 3, presence_map); |
| 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); |
| } |
| |
| if (old_presence != svn_wc__db_status_base_deleted) |
| { |
| /* 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); |
| |
| moved_here = svn_sqlite__column_int(stmt_info, 15); |
| } |
| else |
| { |
| moved_here = FALSE; |
| changelist = NULL; |
| } |
| |
| /* ### other stuff? */ |
| |
| SVN_ERR(svn_sqlite__reset(stmt_info)); |
| SVN_ERR(svn_sqlite__reset(stmt_act)); |
| |
| if (op_depth > 0) |
| { |
| int affected_rows; |
| |
| SVN_ERR_ASSERT(op_depth == relpath_depth(local_relpath)); |
| |
| /* First clear the moves that we are going to delete in a bit */ |
| { |
| apr_hash_t *old_moves; |
| apr_hash_index_t *hi; |
| SVN_ERR(moved_descendant_collect(&old_moves, wcroot, local_relpath, 0, |
| scratch_pool, scratch_pool)); |
| |
| if (old_moves) |
| for (hi = apr_hash_first(scratch_pool, old_moves); |
| hi; hi = apr_hash_next(hi)) |
| { |
| SVN_ERR(clear_moved_here(wcroot, apr_hash_this_key(hi), |
| scratch_pool)); |
| } |
| } |
| |
| /* 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_NODE_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). */ |
| |
| if (old_presence != svn_wc__db_status_base_deleted) |
| { |
| /* Collapse descendants of the current op_depth to layer 0, |
| this includes moved-from/to clearing */ |
| SVN_ERR(descendant_commit(wcroot, local_relpath, op_depth, |
| repos_id, repos_relpath, new_revision, |
| scratch_pool)); |
| } |
| |
| if (old_presence != svn_wc__db_status_base_deleted) |
| { |
| apr_hash_t *moves = NULL; |
| |
| SVN_ERR(moved_descendant_collect(&moves, wcroot, local_relpath, 0, |
| scratch_pool, scratch_pool)); |
| |
| /* And make the recorded local moves represent moves of the node we |
| just committed. */ |
| SVN_ERR(moved_descendant_commit(wcroot, local_relpath, |
| repos_id, repos_relpath, new_revision, |
| moves, scratch_pool)); |
| } |
| |
| if (moved_here) |
| { |
| /* 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 */ |
| if (old_presence != svn_wc__db_status_base_deleted) |
| { |
| 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)); |
| } |
| else |
| { |
| struct insert_base_baton_t ibb; |
| blank_ibb(&ibb); |
| |
| ibb.repos_id = repos_id; |
| ibb.status = svn_wc__db_status_not_present; |
| ibb.kind = new_kind; |
| ibb.repos_relpath = repos_relpath; |
| ibb.revision = new_revision; |
| |
| SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool)); |
| |
| keep_changelist = FALSE; /* Nothing there */ |
| } |
| |
| 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 (!no_unlock) |
| { |
| svn_sqlite__stmt_t *lock_stmt; |
| svn_boolean_t op_root = (op_depth > 0 |
| && (relpath_depth(local_relpath) == op_depth)); |
| |
| /* If we are committing an add of a delete, we can assume we own |
| all locks at or below REPOS_RELPATH (or the server would have |
| denied the commit). As we must have passed these to the server |
| we can now safely remove them. |
| */ |
| SVN_ERR(svn_sqlite__get_statement(&lock_stmt, wcroot->sdb, |
| op_root |
| ? STMT_DELETE_LOCK_RECURSIVELY |
| : 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, |
| 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(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_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. |
| * |
| * NODE_STATUS, NODE_KIND, NODE_REVISION and NODE_REPOS_RELPATH represent the |
| * values as stored currently in WCROOT for LOCAL_RELPATH. |
| * |
| * 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, |
| svn_wc__db_status_t node_status, |
| svn_node_kind_t node_kind, |
| svn_revnum_t node_revision, |
| const char *node_repos_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; |
| apr_hash_t *children; |
| apr_hash_index_t *hi; |
| svn_boolean_t set_repos_relpath = FALSE; |
| svn_depth_t depth_below_here = depth; |
| apr_array_header_t *iprops = NULL; |
| |
| if (new_repos_relpath != NULL |
| && strcmp(node_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 != node_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 |
| || node_kind != svn_node_dir |
| || node_status == svn_wc__db_status_server_excluded |
| || node_status == svn_wc__db_status_excluded |
| || node_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(base_get_children_info(&children, wcroot, local_relpath, 0, |
| scratch_pool, iterpool)); |
| for (hi = apr_hash_first(scratch_pool, children); hi; hi = apr_hash_next(hi)) |
| { |
| const char *child_basename = apr_hash_this_key(hi); |
| const struct svn_wc__db_base_info_t *child_info; |
| const char *child_local_relpath; |
| const char *child_repos_relpath = NULL; |
| |
| svn_pool_clear(iterpool); |
| |
| child_info = apr_hash_this_val(hi); |
| |
| if (child_info->update_root && child_info->kind == svn_node_file) |
| continue; /* Skip file externals */ |
| |
| if (depth < svn_depth_immediates && child_info->kind == svn_node_dir) |
| continue; /* Skip directories */ |
| |
| child_local_relpath = svn_relpath_join(local_relpath, child_basename, |
| iterpool); |
| |
| /* Don't touch nodes that can't be touched via the exclude list */ |
| if (svn_hash_gets(exclude_relpaths, child_local_relpath)) |
| continue; |
| |
| /* 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 (child_info->status == svn_wc__db_status_not_present |
| || (child_info->status == svn_wc__db_status_server_excluded && |
| child_info->revnum != new_rev)) |
| { |
| svn_sqlite__stmt_t *stmt; |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_DELETE_BASE_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, child_local_relpath)); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| continue; |
| } |
| |
| /* 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); |
| |
| SVN_ERR(bump_node_revision(wcroot, child_local_relpath, |
| child_info->status, |
| child_info->kind, |
| child_info->revnum, |
| child_info->repos_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_boolean_t empty_update, |
| 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; |
| svn_revnum_t revision; |
| const char *repos_relpath; |
| |
| err = svn_wc__db_base_get_info_internal(&status, &kind, &revision, |
| &repos_relpath, 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, |
| status, kind, revision, repos_relpath, |
| new_repos_id, |
| new_repos_relpath, new_revision, |
| depth, exclude_relpaths, |
| wcroot_iprops, |
| TRUE /* is_root */, FALSE, db, |
| scratch_pool)); |
| |
| /* ### TODO: Use empty_update flag for change knowledge */ |
| 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_boolean_t empty_update, |
| 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, empty_update, |
| 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, |
| svn_skel_t *work_items, |
| 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)); |
| |
| SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_lock_remove(svn_wc__db_t *db, |
| const char *local_abspath, |
| 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( |
| lock_remove_txn(wcroot, local_relpath, work_items, 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; |
| } |
| |
| /* 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; |
| } |
| |
| /* 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_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; |
| |
| /* ### 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 = svn_relpath_prefix(local_relpath, op_depth, |
| scratch_pool); |
| repos_prefix_path = svn_relpath_skip_ancestor(op_root_relpath, |
| local_relpath); |
| |
| 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) |
| { |
| const char *tmp; |
| |
| 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 */ |
| tmp = op_root_relpath; |
| |
| op_root_relpath = svn_relpath_prefix(op_root_relpath, op_depth, |
| scratch_pool); |
| repos_prefix_path = svn_relpath_join( |
| svn_relpath_skip_ancestor(op_root_relpath, tmp), |
| repos_prefix_path, 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; |
| } |
| |
| svn_error_t * |
| svn_wc__db_scan_addition_internal( |
| 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, |
| svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| return svn_error_trace( |
| scan_addition(status, op_root_relpath_p, repos_relpath, repos_id, |
| original_repos_relpath, original_repos_id, |
| original_revision, NULL, NULL, NULL, |
| wcroot, local_relpath, result_pool, scratch_pool)); |
| } |
| |
| 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_WC__DB_WITH_TXN4( |
| 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), |
| svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, wcroot, |
| repos_id, result_pool), |
| svn_wc__db_fetch_repos_info(original_root_url, original_uuid, |
| wcroot, original_repos_id, |
| result_pool), |
| SVN_NO_ERROR, |
| wcroot); |
| |
| 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); |
| |
| 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_WC__DB_WITH_TXN( |
| 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), |
| wcroot); |
| |
| 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 = svn_relpath_prefix(moved_from_op_root_relpath, |
| moved_from_op_depth, scratch_pool); |
| |
| *moved_from_delete_abspath = svn_dirent_join(wcroot->abspath, tmp, |
| scratch_pool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* ### Recursive helper for svn_wc__db_follow_moved_to() |
| */ |
| static svn_error_t * |
| follow_moved_to(svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| int op_depth, |
| apr_array_header_t **moved_tos, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| int shadowing_op_depth; |
| const char *ancestor_relpath; |
| const char *node_moved_to = NULL; |
| int i; |
| |
| /* Obtain the depth of the node directly shadowing local_relpath |
| as it exists at OP_DEPTH, and perhaps moved to info */ |
| 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) |
| { |
| shadowing_op_depth = svn_sqlite__column_int(stmt, 0); |
| node_moved_to = svn_sqlite__column_text(stmt, 1, result_pool); |
| |
| 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 = shadowing_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(svn_sqlite__reset(stmt)); |
| |
| if (!have_row) |
| { |
| /* Node is not shadowed, so not moved */ |
| return SVN_NO_ERROR; |
| } |
| else if (node_moved_to) |
| { |
| /* Moved directly, so we have the final location */ |
| return SVN_NO_ERROR; |
| } |
| /* Need to handle being moved via an ancestor. */ |
| ancestor_relpath = local_relpath; |
| for (i = relpath_depth(local_relpath); i > shadowing_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, |
| shadowing_op_depth)); |
| SVN_ERR(svn_sqlite__step_row(stmt)); |
| |
| ancestor_moved_to = svn_sqlite__column_text(stmt, 0, scratch_pool); |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| if (ancestor_moved_to) |
| { |
| struct svn_wc__db_moved_to_t *moved_to; |
| |
| node_moved_to |
| = svn_relpath_join(ancestor_moved_to, |
| svn_relpath_skip_ancestor(ancestor_relpath, |
| local_relpath), |
| result_pool); |
| |
| moved_to = apr_palloc(result_pool, sizeof(*moved_to)); |
| moved_to->op_depth = shadowing_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(wcroot, node_moved_to, |
| relpath_depth(ancestor_moved_to), |
| moved_tos, 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_WC__DB_WITH_TXN(follow_moved_to(wcroot, local_relpath, 0, moved_tos, |
| result_pool, scratch_pool), |
| wcroot); |
| |
| /* ### Convert moved_to to abspath */ |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc__db_scan_moved_to_internal(const char **move_src_relpath, |
| const char **move_dst_relpath, |
| const char **delete_relpath, |
| 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; |
| int delete_op_depth; |
| const char *relpath = local_relpath; |
| const char *dst_relpath; |
| |
| SVN_ERR_ASSERT(local_relpath[0]); /* Not valid on the WC root */ |
| |
| if (move_src_relpath) |
| *move_src_relpath = NULL; |
| if (move_dst_relpath) |
| *move_dst_relpath = NULL; |
| if (delete_relpath) |
| *delete_relpath = NULL; |
| |
| 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, relpath, op_depth)); |
| |
| 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), |
| _("Node '%s' is not shadowed"), |
| path_for_error_message(wcroot, local_relpath, |
| scratch_pool)); |
| } |
| |
| delete_op_depth = svn_sqlite__column_int(stmt, 0); |
| dst_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool); |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| while (!dst_relpath && have_row) |
| { |
| relpath = svn_relpath_dirname(relpath, scratch_pool); |
| |
| if (relpath_depth(relpath) < delete_op_depth) |
| break; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_SELECT_DEPTH_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, relpath, |
| delete_op_depth)); |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| if (have_row) |
| dst_relpath = svn_sqlite__column_text(stmt, 13, scratch_pool); |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| } |
| |
| if (dst_relpath) |
| { |
| if (move_src_relpath) |
| *move_src_relpath = apr_pstrdup(result_pool, relpath); |
| |
| if (move_dst_relpath) |
| *move_dst_relpath = apr_pstrdup(result_pool, dst_relpath); |
| |
| if (delete_relpath) |
| *delete_relpath = svn_relpath_prefix(local_relpath, delete_op_depth, |
| result_pool); |
| } |
| |
| 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 **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; |
| const char *dst_root_relpath; |
| const char *src_root_relpath, *delete_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_scan_moved_to_internal(&src_root_relpath, |
| &dst_root_relpath, |
| &delete_relpath, |
| wcroot, local_relpath, |
| 0 /* BASE */, |
| scratch_pool, |
| scratch_pool), |
| wcroot); |
| |
| if (move_dst_abspath) |
| *move_dst_abspath = |
| dst_root_relpath |
| ? svn_dirent_join(wcroot->abspath, |
| svn_dirent_join( |
| dst_root_relpath, |
| svn_relpath_skip_ancestor(src_root_relpath, |
| local_relpath), |
| scratch_pool), |
| result_pool) |
| : NULL; |
| |
| if (move_dst_op_root_abspath) |
| *move_dst_op_root_abspath = |
| dst_root_relpath |
| ? svn_dirent_join(wcroot->abspath, dst_root_relpath, result_pool) |
| : NULL; |
| |
| if (move_src_root_abspath) |
| *move_src_root_abspath = |
| src_root_relpath |
| ? svn_dirent_join(wcroot->abspath, src_root_relpath, result_pool) |
| : NULL; |
| |
| if (delete_abspath) |
| *delete_abspath = |
| delete_relpath |
| ? svn_dirent_join(wcroot->abspath, delete_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 */, |
| 0 /* timeout */, |
| 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 */, |
| 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_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_wq_add_internal(svn_wc__db_wcroot_t *wcroot, |
| const svn_skel_t *work_item, |
| apr_pool_t *scratch_pool) |
| { |
| /* Add the work item(s) to the WORK_QUEUE. */ |
| return svn_error_trace(add_work_items(wcroot->sdb, work_item, |
| scratch_pool)); |
| } |
| |
| 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 = apr_hash_this_key(hi); |
| const svn_io_dirent2_t *dirent = apr_hash_this_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 = apr_hash_this_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_node_kind_t *kind, |
| 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; |
| |
| /* 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, kind, props, |
| wcroot, local_relpath, |
| result_pool, |
| scratch_pool)); |
| } |
| |
| svn_error_t * |
| svn_wc__db_read_conflict_internal(svn_skel_t **conflict, |
| svn_node_kind_t *kind, |
| 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; |
| |
| if (kind) |
| *kind = svn_node_none; |
| if (props) |
| *props = NULL; |
| |
| /* 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) |
| { |
| 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; |
| |
| if (props) |
| { |
| svn_error_t *err; |
| |
| err = svn_error_trace(svn_sqlite__column_properties(props, stmt, 1, |
| result_pool, |
| scratch_pool)); |
| |
| if (err) |
| return svn_error_compose_create(err, svn_sqlite__reset(stmt)); |
| } |
| } |
| else |
| *conflict = NULL; |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| if (!have_row || kind || (props && !*props)) |
| { |
| svn_error_t *err = NULL; |
| svn_boolean_t have_info = 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_info, stmt)); |
| |
| if (have_info) |
| { |
| if (kind) |
| { |
| svn_wc__db_status_t status; |
| int op_depth = svn_sqlite__column_int(stmt, 0); |
| |
| status = svn_sqlite__column_token(stmt, 3, presence_map); |
| |
| if (op_depth > 0) |
| err = convert_to_working_status(&status, status); |
| |
| if (!err && (status == svn_wc__db_status_normal |
| || status == svn_wc__db_status_added |
| || status == svn_wc__db_status_deleted |
| || status == svn_wc__db_status_incomplete)) |
| { |
| *kind = svn_sqlite__column_token(stmt, 4, kind_map); |
| } |
| } |
| |
| /* Need props, and no props in ACTUAL? */ |
| if (!err && (props && !*props)) |
| { |
| err = svn_sqlite__column_properties(props, stmt, 14, |
| result_pool, scratch_pool); |
| } |
| } |
| |
| SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); |
| |
| if (!have_row && !have_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)); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| 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_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(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)); |
| } |
| } |
| |
| status = svn_sqlite__column_token(stmt_info, 3, presence_map); |
| |
| if (show_deleted && status == svn_wc__db_status_base_deleted) |
| { |
| /* Let's return the kind of what is really deleted insead of what |
| we have cached in the base-deleted record */ |
| |
| SVN_ERR(svn_sqlite__step(&have_info, stmt_info)); |
| |
| if (!have_info) |
| { |
| /* No lower layer deleted? Database inconsistency! */ |
| *kind = svn_node_none; |
| return svn_error_trace(svn_sqlite__reset(stmt_info)); |
| } |
| } |
| |
| if (!(show_deleted && show_hidden)) |
| { |
| int op_depth = svn_sqlite__column_int(stmt_info, 0); |
| svn_boolean_t report_none = FALSE; |
| |
| 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_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, |
| SVN_VA_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, |
| svn_boolean_t enforce_empty_wq, |
| 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)); |
| } |
| |
| if (enforce_empty_wq) |
| SVN_ERR(svn_wc__db_verify_no_work(wcroot->sdb)); |
| |
| /* 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 = svn_wc__db_wclock_owns_lock_internal(&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, |
| _("Failed to lock working copy '%s'."), |
| 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, |
| db->enforce_empty_wq, 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. */ |
| svn_error_t * |
| svn_wc__db_wclock_owns_lock_internal(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(svn_wc__db_wclock_owns_lock_internal(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; |
| } |
| |
| /* Helper for svn_wc__db_op_make_copy_internal */ |
| static svn_error_t * |
| db_move_moved_to(svn_wc__db_wcroot_t *wcroot, |
| const char *src1_relpath, |
| int src1_op_depth, |
| const char *src2_relpath, |
| int src2_op_depth, |
| const char *dst_relpath, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| int affected_rows; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_UPDATE_MOVED_TO_RELPATH)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, |
| src1_relpath, src1_op_depth)); |
| SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); |
| |
| if (affected_rows == 1) |
| { |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_UPDATE_MOVED_TO_RELPATH)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isds", wcroot->wc_id, |
| src2_relpath, src2_op_depth, |
| dst_relpath)); |
| SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); |
| } |
| if (affected_rows != 1) |
| return svn_error_create(SVN_ERR_WC_PATH_NOT_FOUND, NULL, NULL); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| db_move_moved_to_down_recursive(svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| int new_shadow_layer, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_SELECT_MOVED_DESCENDANTS_SRC)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, |
| new_shadow_layer)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| while (have_row) |
| { |
| int del_op_depth; |
| const char *src_relpath; |
| const char *dst_relpath; |
| svn_error_t *err; |
| |
| svn_pool_clear(iterpool); |
| |
| del_op_depth = svn_sqlite__column_int(stmt, 0); |
| src_relpath = svn_sqlite__column_text(stmt, 1, iterpool); |
| dst_relpath = svn_sqlite__column_text(stmt, 4, iterpool); |
| |
| err = svn_error_trace( |
| db_move_moved_to( |
| wcroot, |
| src_relpath, del_op_depth, |
| src_relpath, new_shadow_layer, |
| dst_relpath, 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)); |
| |
| 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, |
| apr_int64_t last_repos_id, |
| const char *last_repos_relpath, |
| svn_revnum_t last_revision, |
| int last_op_depth, |
| svn_boolean_t shadowed, |
| int root_shadow_depth, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row = FALSE; |
| svn_revnum_t revision; |
| apr_int64_t repos_id; |
| const char *repos_relpath; |
| svn_node_kind_t kind; |
| int op_depth = relpath_depth(local_relpath); |
| |
| if (last_op_depth != op_depth) |
| { |
| 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)); |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| if (have_row) |
| shadowed = TRUE; |
| } |
| |
| SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &kind, &revision, |
| &repos_relpath, &repos_id, NULL, |
| NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, |
| wcroot, local_relpath, |
| scratch_pool, scratch_pool)); |
| |
| if (last_repos_relpath |
| && repos_id == last_repos_id |
| && revision == last_revision) |
| { |
| const char *name = svn_relpath_skip_ancestor(last_repos_relpath, |
| repos_relpath); |
| |
| if (name && strcmp(name, svn_relpath_basename(local_relpath, NULL)) == 0) |
| op_depth = last_op_depth; |
| } |
| |
| /* Can we add a new copy node at the wanted op-depth? */ |
| if (!have_row || op_depth == last_op_depth) |
| { |
| int i; |
| |
| 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)); |
| |
| if (shadowed) |
| SVN_ERR(db_extend_parent_delete(wcroot, local_relpath, kind, |
| op_depth, scratch_pool)); |
| |
| if (kind == svn_node_dir) |
| { |
| const apr_array_header_t *children; |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| |
| SVN_ERR(gather_children(&children, wcroot, local_relpath, |
| STMT_SELECT_OP_DEPTH_CHILDREN, 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, |
| repos_id, repos_relpath, revision, |
| op_depth, shadowed, root_shadow_depth, |
| scratch_pool)); |
| } |
| svn_pool_destroy(iterpool); |
| } |
| } |
| else |
| { |
| /* Auch... we can't make a copy of whatever comes deeper, as this |
| op-depth is already filled by something else. Let's hope |
| the user doesn't mind. |
| |
| Luckily we know that the moves are already moved to the shadowing |
| layer, so we can just remove dangling base-deletes if there are |
| any. |
| */ |
| /* BASE_DELETED may be at op_depth, so let's use last_op_depth! */ |
| SVN_ERR(db_move_moved_to_down_recursive(wcroot, local_relpath, |
| root_shadow_depth, |
| scratch_pool)); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_DELETE_WORKING_BASE_DELETE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, |
| last_op_depth)); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_DELETE_WORKING_BASE_DELETE_RECURSIVE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, |
| last_op_depth)); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| } |
| |
| /* Insert a not-present node to mark that we don't know what exists here. |
| |
| We do this last (after recursing), to allow the move fix-up code to |
| see the original moves. */ |
| if (last_op_depth > 0 && last_op_depth != op_depth) |
| { |
| insert_working_baton_t iwb; |
| |
| blank_iwb(&iwb); |
| iwb.presence = svn_wc__db_status_not_present; |
| iwb.op_depth = last_op_depth; |
| |
| iwb.original_repos_id = repos_id; |
| iwb.original_repos_relpath = repos_relpath; |
| iwb.original_revnum = revision; |
| iwb.kind = kind; |
| |
| SVN_ERR(insert_working_node(&iwb, wcroot, local_relpath, scratch_pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_op_make_copy_internal(svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| svn_boolean_t move_move_info, |
| 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; |
| int op_depth = -1; |
| |
| /* 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)); |
| if (have_row) |
| op_depth = svn_sqlite__column_int(stmt, 0); |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| if (have_row) |
| { |
| if (op_depth == relpath_depth(local_relpath)) |
| 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 have a working layer, but not one at the op-depth of local-relpath, |
| so we can create a copy by just copying the lower layer */ |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_COPY_OP_DEPTH_RECURSIVE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isdd", wcroot->wc_id, local_relpath, |
| op_depth, relpath_depth(local_relpath))); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| } |
| else |
| { |
| int affected_rows; |
| |
| op_depth = relpath_depth(local_relpath); |
| /* 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)); |
| |
| /* Insert a shadowing layer */ |
| SVN_ERR(svn_sqlite__get_statement( |
| &stmt, wcroot->sdb, |
| STMT_INSERT_DELETE_FROM_NODE_RECURSIVE)); |
| |
| /* As we are keeping whatever is below, move the*/ |
| |
| SVN_ERR(svn_sqlite__bindf(stmt, "isdd", |
| wcroot->wc_id, local_relpath, |
| 0, op_depth)); |
| SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); |
| SVN_ERR_ASSERT(affected_rows > 0); |
| |
| if (!move_move_info) |
| SVN_ERR(db_move_moved_to_down_recursive(wcroot, local_relpath, |
| op_depth, scratch_pool)); |
| |
| |
| SVN_ERR(make_copy_txn(wcroot, local_relpath, |
| INVALID_REPOS_ID, NULL, SVN_INVALID_REVNUM, |
| op_depth, FALSE, op_depth, |
| scratch_pool)); |
| } |
| |
| if (conflicts) |
| SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, |
| conflicts, scratch_pool)); |
| |
| SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); |
| |
| 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_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_make_copy_internal(wcroot, local_relpath, FALSE, |
| conflicts, work_items, |
| 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_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, |
| 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_db_mods(), |
| * but accepts a WCROOT/LOCAL_RELPATH pair. |
| * ### This needs a DB as well as a WCROOT/RELPATH pair... */ |
| static svn_error_t * |
| has_db_mods(svn_boolean_t *is_modified, |
| svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| 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 (! *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)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_has_db_mods(svn_boolean_t *is_modified, |
| 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); |
| |
| return svn_error_trace(has_db_mods(is_modified, wcroot, local_relpath, |
| 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, |
| 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)); |
| |
| /* Determine sparseness. */ |
| SVN_ERR(is_sparse_checkout_internal(is_sparse_checkout, wcroot, |
| local_relpath, scratch_pool)); |
| |
| /* 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; |
| } |
| } |
| |
| /* Check for db mods. */ |
| SVN_ERR(has_db_mods(is_modified, wcroot, local_relpath, 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, |
| 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, |
| 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, |
| 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_verify_db_full_internal(svn_wc__db_wcroot_t *wcroot, |
| svn_wc__db_verify_cb_t callback, |
| void *baton, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| svn_error_t *err = NULL; |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_STATIC_VERIFY)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| while (have_row) |
| { |
| const char *local_relpath; |
| int op_depth = svn_sqlite__column_int(stmt, 1); |
| int id = svn_sqlite__column_int(stmt, 2); |
| const char *msg; |
| |
| svn_pool_clear(iterpool); |
| |
| local_relpath = svn_sqlite__column_text(stmt, 0, iterpool); |
| msg = svn_sqlite__column_text(stmt, 3, scratch_pool); |
| |
| err = callback(baton, wcroot->abspath, local_relpath, op_depth, |
| id, msg, iterpool); |
| |
| if (err) |
| break; |
| |
| 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))); |
| } |
| |
| svn_error_t * |
| svn_wc__db_verify_db_full(svn_wc__db_t *db, |
| const char *wri_abspath, |
| svn_wc__db_verify_cb_t callback, |
| void *baton, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_wcroot_t *wcroot; |
| const char *local_relpath; |
| |
| 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); |
| |
| return svn_error_trace( |
| svn_wc__db_verify_db_full_internal(wcroot, callback, baton, |
| scratch_pool)); |
| } |
| |
| svn_error_t * |
| svn_wc__db_bump_format(int *result_format, |
| svn_boolean_t *bumped_format, |
| svn_wc__db_t *db, |
| const char *wcroot_abspath, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__db_t *sdb; |
| svn_error_t *err; |
| int format; |
| |
| if (bumped_format) |
| *bumped_format = FALSE; |
| |
| /* 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 */ |
| 0, /* default timeout */ |
| 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); |
| |
| if (err == SVN_NO_ERROR && bumped_format) |
| *bumped_format = (*result_format > format); |
| |
| /* 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; |
| } |
| |
| /* Item queued with svn_wc__db_commit_queue_add */ |
| typedef struct commit_queue_item_t |
| { |
| const char *local_relpath; |
| svn_boolean_t recurse; /* Use legacy recursion */ |
| svn_boolean_t committed; /* Process the node as committed */ |
| svn_boolean_t remove_lock; /* Remove existing lock on node */ |
| svn_boolean_t remove_changelist; /* Remove changelist on node */ |
| |
| /* The pristine text checksum. NULL if the old value should be kept |
| and for directories */ |
| const svn_checksum_t *new_sha1_checksum; |
| |
| apr_hash_t *new_dav_cache; /* New DAV cache for the node */ |
| } commit_queue_item_t; |
| |
| /* The queue definition for vn_wc__db_create_commit_queue, |
| svn_wc__db_commit_queue_add and finally svn_wc__db_process_commit_queue */ |
| struct svn_wc__db_commit_queue_t |
| { |
| svn_wc__db_wcroot_t *wcroot; /* Wcroot for ITEMS */ |
| apr_array_header_t *items; /* List of commit_queue_item_t* */ |
| svn_boolean_t have_recurse; /* Is one or more item[x]->recurse TRUE? */ |
| }; |
| |
| /* Create a new svn_wc__db_commit_queue_t instance in RESULT_POOL for the |
| working copy specified with WRI_ABSPATH */ |
| svn_error_t * |
| svn_wc__db_create_commit_queue(svn_wc__db_commit_queue_t **queue, |
| 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_wc__db_commit_queue_t *q; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); |
| |
| SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, |
| wri_abspath, result_pool, scratch_pool)); |
| VERIFY_USABLE_WCROOT(wcroot); |
| |
| q = apr_pcalloc(result_pool, sizeof(*q)); |
| |
| SVN_ERR_ASSERT(wcroot->sdb); |
| |
| q->wcroot = wcroot; |
| q->items = apr_array_make(result_pool, 64, |
| sizeof(commit_queue_item_t*)); |
| q->have_recurse = FALSE; |
| |
| *queue = q; |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc__db_commit_queue_add(svn_wc__db_commit_queue_t *queue, |
| const char *local_abspath, |
| svn_boolean_t recurse, |
| svn_boolean_t is_commited, |
| svn_boolean_t remove_lock, |
| svn_boolean_t remove_changelist, |
| const svn_checksum_t *new_sha1_checksum, |
| apr_hash_t *new_dav_cache, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| commit_queue_item_t *cqi; |
| const char *local_relpath; |
| |
| local_relpath = svn_dirent_skip_ancestor(queue->wcroot->abspath, |
| local_abspath); |
| |
| if (! local_relpath) |
| return svn_error_createf( |
| SVN_ERR_WC_PATH_NOT_FOUND, NULL, |
| _("The path '%s' is not in the working copy '%s'"), |
| svn_dirent_local_style(local_abspath, scratch_pool), |
| svn_dirent_local_style(queue->wcroot->abspath, scratch_pool)); |
| |
| cqi = apr_pcalloc(result_pool, sizeof(*cqi)); |
| cqi->local_relpath = local_relpath; |
| cqi->recurse = recurse; |
| cqi->committed = is_commited; |
| cqi->remove_lock = remove_lock; |
| cqi->remove_changelist = remove_changelist; |
| cqi->new_sha1_checksum = new_sha1_checksum; |
| cqi->new_dav_cache = new_dav_cache; |
| |
| queue->have_recurse |= recurse; |
| |
| APR_ARRAY_PUSH(queue->items, commit_queue_item_t *) = cqi; |
| return SVN_NO_ERROR; |
| } |
| |
| /*** Finishing updates and commits. ***/ |
| |
| /* Post process an item that is committed in the repository. Collapse layers into |
| * BASE. Queue work items that will finish a commit of the file or directory |
| * LOCAL_ABSPATH in DB: |
| */ |
| static svn_error_t * |
| process_committed_leaf(svn_wc__db_t *db, |
| svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| svn_boolean_t via_recurse, |
| svn_wc__db_status_t status, |
| svn_node_kind_t kind, |
| svn_boolean_t prop_mods, |
| const svn_checksum_t *old_checksum, |
| svn_revnum_t new_revnum, |
| apr_time_t new_changed_date, |
| const char *new_changed_author, |
| apr_hash_t *new_dav_cache, |
| svn_boolean_t remove_lock, |
| svn_boolean_t remove_changelist, |
| const svn_checksum_t *checksum, |
| apr_pool_t *scratch_pool) |
| { |
| svn_revnum_t new_changed_rev = new_revnum; |
| svn_skel_t *work_item = NULL; |
| |
| { |
| const char *lock_relpath; |
| svn_boolean_t locked; |
| |
| if (kind == svn_node_dir) |
| lock_relpath = local_relpath; |
| else |
| lock_relpath = svn_relpath_dirname(local_relpath, scratch_pool); |
| |
| SVN_ERR(svn_wc__db_wclock_owns_lock_internal(&locked, wcroot, |
| lock_relpath, FALSE, |
| scratch_pool)); |
| |
| if (!locked) |
| return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL, |
| _("No write-lock in '%s'"), |
| path_for_error_message(wcroot, local_relpath, |
| scratch_pool)); |
| |
| SVN_ERR(flush_entries(wcroot, lock_relpath, svn_depth_empty, |
| scratch_pool)); |
| } |
| |
| if (status == svn_wc__db_status_not_present) |
| { |
| /* We are committing the leaf of a copy operation. |
| We leave the not-present marker to allow pulling in excluded |
| children of a copy. |
| |
| The next update will remove the not-present marker. */ |
| |
| return SVN_NO_ERROR; |
| } |
| |
| SVN_ERR_ASSERT(status == svn_wc__db_status_normal |
| || status == svn_wc__db_status_incomplete |
| || status == svn_wc__db_status_added |
| || status == svn_wc__db_status_deleted); |
| |
| if (kind != svn_node_dir |
| && status != svn_wc__db_status_deleted) |
| { |
| /* If we sent a delta (meaning: post-copy modification), |
| then this file will appear in the queue and so we should have |
| its checksum already. */ |
| if (checksum == NULL) |
| { |
| /* It was copied and not modified. We must have a text |
| base for it. And the node should have a checksum. */ |
| SVN_ERR_ASSERT(old_checksum != NULL); |
| |
| checksum = old_checksum; |
| |
| /* Is the node completely unmodified and are we recursing? */ |
| if (via_recurse && !prop_mods) |
| { |
| /* If a copied node itself is not modified, but the op_root of |
| the copy is committed we have to make sure that changed_rev, |
| changed_date and changed_author don't change or the working |
| copy used for committing will show different last modified |
| information then a clean checkout of exactly the same |
| revisions. (Issue #3676) */ |
| |
| SVN_ERR(svn_wc__db_read_info_internal( |
| NULL, NULL, NULL, NULL, NULL, |
| &new_changed_rev, |
| &new_changed_date, |
| &new_changed_author, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, |
| NULL, NULL, |
| wcroot, local_relpath, |
| scratch_pool, scratch_pool)); |
| } |
| } |
| |
| SVN_ERR(svn_wc__wq_build_file_commit(&work_item, |
| db, svn_dirent_join(wcroot->abspath, |
| local_relpath, |
| scratch_pool), |
| prop_mods, |
| scratch_pool, scratch_pool)); |
| } |
| |
| /* The new text base will be found in the pristine store by its checksum. */ |
| SVN_ERR(commit_node(wcroot, local_relpath, |
| new_revnum, new_changed_rev, |
| new_changed_date, new_changed_author, |
| checksum, |
| new_dav_cache, |
| !remove_changelist, |
| !remove_lock, |
| work_item, |
| scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /** Internal helper for svn_wc_process_committed_queue2(). |
| * Bump a commit item, collapsing local changes with the new repository |
| * information to a new BASE node. |
| * |
| * @a new_date is the (server-side) date of the new revision, or 0. |
| * |
| * @a rev_author is the (server-side) author of the new |
| * revision; it may be @c NULL. |
| * |
| * @a new_dav_cache is a hash of all the new dav properties for LOCAL_RELPATH. |
| * |
| * If @a remove_lock is set, release any user locks on @a |
| * local_abspath; otherwise keep them during processing. |
| * |
| * If @a remove_changelist is set, clear any changeset assignments |
| * from @a local_abspath; otherwise, keep such assignments. |
| * |
| * If @a new_sha1_checksum is non-NULL, use it to identify the node's pristine |
| * text. |
| * |
| * Set TOP_OF_RECURSE to TRUE to show that this the top of a possibly |
| * recursive commit operation. (Part of the legacy recurse handling) |
| */ |
| static svn_error_t * |
| process_committed_internal(svn_wc__db_t *db, |
| svn_wc__db_wcroot_t *wcroot, |
| const char *local_relpath, |
| svn_boolean_t recurse, |
| svn_boolean_t top_of_recurse, |
| svn_revnum_t new_revnum, |
| apr_time_t new_date, |
| const char *rev_author, |
| apr_hash_t *new_dav_cache, |
| svn_boolean_t remove_lock, |
| svn_boolean_t remove_changelist, |
| const svn_checksum_t *new_sha1_checksum, |
| apr_hash_t *items_by_relpath, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_status_t status; |
| svn_node_kind_t kind; |
| const svn_checksum_t *old_checksum; |
| svn_boolean_t prop_mods; |
| |
| SVN_ERR(svn_wc__db_read_info_internal(&status, &kind, NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, &old_checksum, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, &prop_mods, NULL, NULL, NULL, |
| wcroot, local_relpath, |
| scratch_pool, scratch_pool)); |
| |
| /* NOTE: be wary of making crazy semantic changes in this function, since |
| svn_wc_process_committed4() calls this. */ |
| |
| SVN_ERR(process_committed_leaf(db, wcroot, local_relpath, !top_of_recurse, |
| status, kind, prop_mods, old_checksum, |
| new_revnum, new_date, rev_author, |
| new_dav_cache, |
| remove_lock, remove_changelist, |
| new_sha1_checksum, |
| scratch_pool)); |
| |
| /* Only check for recursion on nodes that have children */ |
| if (kind != svn_node_dir |
| || status == svn_wc__db_status_not_present |
| || status == svn_wc__db_status_excluded |
| || status == svn_wc__db_status_server_excluded |
| /* Node deleted -> then no longer a directory */ |
| || status == svn_wc__db_status_deleted) |
| { |
| return SVN_NO_ERROR; |
| } |
| |
| if (recurse) |
| { |
| const apr_array_header_t *children; |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| int i; |
| |
| /* Read PATH's entries; this is the absolute path. */ |
| SVN_ERR(gather_children(&children, wcroot, local_relpath, |
| STMT_SELECT_NODE_CHILDREN, -1, |
| scratch_pool, iterpool)); |
| |
| /* Recursively loop over all children. */ |
| for (i = 0; i < children->nelts; i++) |
| { |
| const char *name = APR_ARRAY_IDX(children, i, const char *); |
| const char *this_relpath; |
| const commit_queue_item_t *cqi; |
| |
| svn_pool_clear(iterpool); |
| |
| this_relpath = svn_dirent_join(local_relpath, name, iterpool); |
| |
| new_sha1_checksum = NULL; |
| cqi = svn_hash_gets(items_by_relpath, this_relpath); |
| |
| if (cqi != NULL) |
| new_sha1_checksum = cqi->new_sha1_checksum; |
| |
| /* Recurse. Pass NULL for NEW_DAV_CACHE, because the |
| ones present in the current call are only applicable to |
| this one committed item. */ |
| SVN_ERR(process_committed_internal( |
| db, wcroot, this_relpath, |
| TRUE /* recurse */, |
| FALSE /* top_of_recurse */, |
| new_revnum, new_date, |
| rev_author, |
| NULL /* new_dav_cache */, |
| FALSE /* remove_lock */, |
| remove_changelist, |
| new_sha1_checksum, |
| items_by_relpath, |
| iterpool)); |
| } |
| |
| svn_pool_destroy(iterpool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Return TRUE if any item of QUEUE is a parent of ITEM and will be |
| processed recursively, return FALSE otherwise. |
| |
| The algorithmic complexity of this search implementation is O(queue |
| length), but it's quite quick. |
| */ |
| static svn_boolean_t |
| have_recursive_parent(const apr_array_header_t *all_items, |
| const commit_queue_item_t *item, |
| apr_pool_t *scratch_pool) |
| { |
| const char *local_relpath = item->local_relpath; |
| int i; |
| |
| for (i = 0; i < all_items->nelts; i++) |
| { |
| const commit_queue_item_t *qi |
| = APR_ARRAY_IDX(all_items, i, const commit_queue_item_t *); |
| |
| if (qi == item) |
| continue; |
| |
| if (qi->recurse && svn_relpath_skip_ancestor(qi->local_relpath, |
| local_relpath)) |
| { |
| return TRUE; |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| /* Compare function for svn_sort__array */ |
| static int |
| compare_queue_items(const void *v1, |
| const void *v2) |
| { |
| const commit_queue_item_t *cqi1 |
| = *(const commit_queue_item_t **)v1; |
| const commit_queue_item_t *cqi2 |
| = *(const commit_queue_item_t **)v2; |
| |
| return svn_path_compare_paths(cqi1->local_relpath, cqi2->local_relpath); |
| } |
| |
| /* Internal, locked version of svn_wc__db_process_commit_queue */ |
| static svn_error_t * |
| db_process_commit_queue(svn_wc__db_t *db, |
| svn_wc__db_commit_queue_t *queue, |
| svn_revnum_t new_revnum, |
| apr_time_t new_date, |
| const char *new_author, |
| apr_pool_t *scratch_pool) |
| { |
| apr_hash_t *items_by_relpath = NULL; |
| int j; |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| |
| svn_sort__array(queue->items, compare_queue_items); |
| |
| if (queue->have_recurse) |
| { |
| items_by_relpath = apr_hash_make(scratch_pool); |
| |
| for (j = 0; j < queue->items->nelts; j++) |
| { |
| commit_queue_item_t *cqi |
| = APR_ARRAY_IDX(queue->items, j, commit_queue_item_t *); |
| |
| svn_hash_sets(items_by_relpath, cqi->local_relpath, cqi); |
| } |
| } |
| |
| for (j = 0; j < queue->items->nelts; j++) |
| { |
| commit_queue_item_t *cqi |
| = APR_ARRAY_IDX(queue->items, j, commit_queue_item_t *); |
| |
| svn_pool_clear(iterpool); |
| |
| /* Skip this item if it is a child of a recursive item, because it has |
| been (or will be) accounted for when that recursive item was (or |
| will be) processed. */ |
| if (queue->have_recurse && have_recursive_parent(queue->items, cqi, |
| iterpool)) |
| continue; |
| |
| if (!cqi->committed) |
| { |
| if (cqi->remove_lock) |
| { |
| svn_skel_t *work_item; |
| |
| SVN_ERR(svn_wc__wq_build_sync_file_flags( |
| &work_item, |
| db, |
| svn_dirent_join( |
| queue->wcroot->abspath, |
| cqi->local_relpath, |
| iterpool), |
| iterpool, iterpool)); |
| |
| lock_remove_txn(queue->wcroot, cqi->local_relpath, work_item, |
| iterpool); |
| } |
| if (cqi->remove_changelist) |
| SVN_ERR(svn_wc__db_op_set_changelist(db, |
| svn_dirent_join( |
| queue->wcroot->abspath, |
| cqi->local_relpath, |
| iterpool), |
| NULL, NULL, |
| svn_depth_empty, |
| NULL, NULL, /* notify */ |
| NULL, NULL, /* cancel */ |
| iterpool)); |
| } |
| else |
| { |
| SVN_ERR(process_committed_internal( |
| db, queue->wcroot, cqi->local_relpath, |
| cqi->recurse, |
| TRUE /* top_of_recurse */, |
| new_revnum, new_date, new_author, |
| cqi->new_dav_cache, |
| cqi->remove_lock, |
| cqi->remove_changelist, |
| cqi->new_sha1_checksum, |
| items_by_relpath, |
| iterpool)); |
| } |
| } |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc__db_process_commit_queue(svn_wc__db_t *db, |
| svn_wc__db_commit_queue_t *queue, |
| svn_revnum_t new_revnum, |
| apr_time_t new_date, |
| const char *new_author, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_WC__DB_WITH_TXN(db_process_commit_queue(db, queue, |
| new_revnum, new_date, |
| new_author, scratch_pool), |
| queue->wcroot); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc__find_repos_node_in_wc(apr_array_header_t **local_abspath_list, |
| svn_wc__db_t *db, |
| const char *wri_abspath, |
| const char *repos_relpath, |
| svn_revnum_t rev, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_wcroot_t *wcroot; |
| const char *wri_relpath; |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); |
| |
| SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &wri_relpath, db, |
| wri_abspath, scratch_pool, |
| scratch_pool)); |
| VERIFY_USABLE_WCROOT(wcroot); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, |
| STMT_FIND_REPOS_PATH_IN_WC)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isr", wcroot->wc_id, repos_relpath, rev)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| *local_abspath_list = apr_array_make(result_pool, have_row ? 1 : 0, |
| sizeof(const char*)); |
| while (have_row) |
| { |
| const char *local_relpath; |
| const char *local_abspath; |
| |
| local_relpath = svn_sqlite__column_text(stmt, 0, NULL); |
| local_abspath = svn_dirent_join(wcroot->abspath, local_relpath, |
| result_pool); |
| APR_ARRAY_PUSH(*local_abspath_list, const char *) = local_abspath; |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| } |
| |
| return svn_error_trace(svn_sqlite__reset(stmt)); |
| } |
| |