| /* |
| * entries.c : manipulating the administrative `entries' file. |
| * |
| * ==================================================================== |
| * Licensed to the Subversion Corporation (SVN Corp.) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The SVN Corp. licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| #include <string.h> |
| #include <assert.h> |
| |
| #include <apr_strings.h> |
| |
| #include "svn_error.h" |
| #include "svn_types.h" |
| #include "svn_time.h" |
| #include "svn_pools.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_path.h" |
| #include "svn_ctype.h" |
| #include "svn_string.h" |
| |
| #include "wc.h" |
| #include "adm_files.h" |
| #include "adm_ops.h" |
| #include "entries.h" |
| #include "lock.h" |
| #include "tree_conflicts.h" |
| #include "wc_db.h" |
| #include "wc-queries.h" /* for STMT_* */ |
| |
| #include "svn_private_config.h" |
| #include "private/svn_wc_private.h" |
| #include "private/svn_sqlite.h" |
| #include "private/svn_skel.h" |
| |
| #define MAYBE_ALLOC(x,p) ((x) ? (x) : apr_pcalloc((p), sizeof(*(x)))) |
| |
| |
| /* Temporary structures which mirror the tables in wc-metadata.sql. |
| For detailed descriptions of each field, see that file. */ |
| typedef struct { |
| apr_int64_t wc_id; |
| const char *local_relpath; |
| apr_int64_t repos_id; |
| const char *repos_relpath; |
| const char *parent_relpath; |
| svn_wc__db_status_t presence; |
| svn_revnum_t revision; |
| svn_node_kind_t kind; /* ### should switch to svn_wc__db_kind_t */ |
| svn_checksum_t *checksum; |
| svn_filesize_t translated_size; |
| svn_revnum_t changed_rev; |
| apr_time_t changed_date; |
| const char *changed_author; |
| svn_depth_t depth; |
| apr_time_t last_mod_time; |
| apr_hash_t *properties; |
| } db_base_node_t; |
| |
| typedef struct { |
| apr_int64_t wc_id; |
| const char *local_relpath; |
| const char *parent_relpath; |
| svn_wc__db_status_t presence; |
| svn_node_kind_t kind; /* ### should switch to svn_wc__db_kind_t */ |
| apr_int64_t copyfrom_repos_id; |
| const char *copyfrom_repos_path; |
| svn_revnum_t copyfrom_revnum; |
| svn_boolean_t moved_here; |
| const char *moved_to; |
| svn_checksum_t *checksum; |
| svn_filesize_t translated_size; |
| svn_revnum_t changed_rev; |
| apr_time_t changed_date; |
| const char *changed_author; |
| svn_depth_t depth; |
| apr_time_t last_mod_time; |
| apr_hash_t *properties; |
| svn_boolean_t keep_local; |
| } db_working_node_t; |
| |
| typedef struct { |
| apr_int64_t wc_id; |
| const char *local_relpath; |
| const char *parent_relpath; |
| apr_hash_t *properties; |
| const char *conflict_old; |
| const char *conflict_new; |
| const char *conflict_working; |
| const char *prop_reject; |
| const char *changelist; |
| /* ### enum for text_mod */ |
| const char *tree_conflict_data; |
| } db_actual_node_t; |
| |
| |
| |
| /*** reading and writing the entries file ***/ |
| |
| |
| static svn_wc_entry_t * |
| alloc_entry(apr_pool_t *pool) |
| { |
| svn_wc_entry_t *entry = apr_pcalloc(pool, sizeof(*entry)); |
| entry->revision = SVN_INVALID_REVNUM; |
| entry->copyfrom_rev = SVN_INVALID_REVNUM; |
| entry->cmt_rev = SVN_INVALID_REVNUM; |
| entry->kind = svn_node_none; |
| entry->working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN; |
| entry->depth = svn_depth_infinity; |
| entry->file_external_path = NULL; |
| entry->file_external_peg_rev.kind = svn_opt_revision_unspecified; |
| entry->file_external_rev.kind = svn_opt_revision_unspecified; |
| return entry; |
| } |
| |
| |
| /* Is the entry in a 'hidden' state in the sense of the 'show_hidden' |
| * switches on svn_wc_entries_read(), svn_wc_walk_entries*(), etc.? */ |
| svn_error_t * |
| svn_wc__entry_is_hidden(svn_boolean_t *hidden, const svn_wc_entry_t *entry) |
| { |
| /* Note: the condition below may allow certain combinations that the |
| rest of the system will never reach (eg. absent/add). |
| |
| In English, the condition is: "the entry is not present, and I haven't |
| scheduled something over the top of it." */ |
| *hidden = ((entry->deleted |
| || entry->absent |
| || entry->depth == svn_depth_exclude) |
| && entry->schedule != svn_wc_schedule_add); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Use entry SRC to fill in blank portions of entry DST. SRC itself |
| may not have any blanks, of course. |
| Typically, SRC is a parent directory's own entry, and DST is some |
| child in that directory. */ |
| static void |
| take_from_entry(const svn_wc_entry_t *src, |
| svn_wc_entry_t *dst, |
| apr_pool_t *pool) |
| { |
| /* Inherits parent's revision if doesn't have a revision of one's |
| own, unless this is a subdirectory. */ |
| if ((dst->revision == SVN_INVALID_REVNUM) && (dst->kind != svn_node_dir)) |
| dst->revision = src->revision; |
| |
| /* Inherits parent's url if doesn't have a url of one's own. */ |
| if (! dst->url) |
| dst->url = svn_path_url_add_component2(src->url, dst->name, pool); |
| |
| if (! dst->repos) |
| dst->repos = src->repos; |
| |
| if ((! dst->uuid) |
| && (! ((dst->schedule == svn_wc_schedule_add) |
| || (dst->schedule == svn_wc_schedule_replace)))) |
| { |
| dst->uuid = src->uuid; |
| } |
| } |
| |
| static svn_error_t * |
| fetch_wc_id(apr_int64_t *wc_id, svn_sqlite__db_t *sdb) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_WCROOT_NULL)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| if (!have_row) |
| return svn_error_create(SVN_ERR_WC_DB_ERROR, NULL, _("No WC table entry")); |
| *wc_id = svn_sqlite__column_int(stmt, 0); |
| return svn_error_return(svn_sqlite__reset(stmt)); |
| } |
| |
| |
| static svn_error_t * |
| determine_keep_local(svn_boolean_t *keep_local, |
| svn_sqlite__db_t *sdb, |
| apr_int64_t wc_id, |
| const char *local_relpath) |
| { |
| svn_sqlite__stmt_t *stmt; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_KEEP_LOCAL_FLAG)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); |
| SVN_ERR(svn_sqlite__step_row(stmt)); |
| |
| *keep_local = svn_sqlite__column_boolean(stmt, 0); |
| |
| return svn_error_return(svn_sqlite__reset(stmt)); |
| } |
| |
| |
| /* Hit the database to check the file external information for the given |
| entry. The entry will be modified in place. */ |
| static svn_error_t * |
| check_file_external(svn_wc_entry_t *entry, |
| svn_sqlite__db_t *sdb, |
| apr_pool_t *result_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, |
| STMT_SELECT_FILE_EXTERNAL)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", |
| (apr_uint64_t)1 /* wc_id */, |
| entry->name)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| if (!svn_sqlite__column_is_null(stmt, 0)) |
| { |
| SVN_ERR(svn_wc__unserialize_file_external( |
| &entry->file_external_path, |
| &entry->file_external_peg_rev, |
| &entry->file_external_rev, |
| svn_sqlite__column_text(stmt, 0, NULL), |
| result_pool)); |
| |
| } |
| |
| return svn_error_return(svn_sqlite__reset(stmt)); |
| } |
| |
| |
| /* Fill in the following fields of ENTRY: |
| |
| REVISION |
| REPOS |
| UUID |
| CMT_REV |
| CMT_DATE |
| CMT_AUTHOR |
| TEXT_TIME |
| DEPTH |
| WORKING_SIZE |
| COPIED |
| |
| Return: KIND, REPOS_RELPATH, CHECKSUM |
| */ |
| static svn_error_t * |
| get_base_info_for_deleted(svn_wc_entry_t *entry, |
| svn_wc__db_kind_t *kind, |
| const char **repos_relpath, |
| const svn_checksum_t **checksum, |
| svn_wc__db_t *db, |
| const char *entry_abspath, |
| const svn_wc_entry_t *parent_entry, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_error_t *err; |
| |
| /* Get the information from the underlying BASE node. */ |
| err = svn_wc__db_base_get_info(NULL, kind, |
| &entry->revision, |
| NULL, NULL, NULL, |
| &entry->cmt_rev, |
| &entry->cmt_date, |
| &entry->cmt_author, |
| &entry->text_time, |
| &entry->depth, |
| checksum, |
| &entry->working_size, |
| NULL, |
| NULL, |
| db, |
| entry_abspath, |
| result_pool, |
| scratch_pool); |
| if (err) |
| { |
| const char *work_del_abspath; |
| const char *parent_repos_relpath; |
| const char *parent_abspath; |
| |
| if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) |
| return svn_error_return(err); |
| |
| /* No base node? This is a deleted child of a copy/move-here, |
| so we need to scan up the WORKING tree to find the root of |
| the deletion. Then examine its parent to discover its |
| future location in the repository. */ |
| svn_error_clear(err); |
| |
| SVN_ERR(svn_wc__db_scan_deletion(NULL, |
| NULL, |
| NULL, |
| &work_del_abspath, |
| db, entry_abspath, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR_ASSERT(work_del_abspath != NULL); |
| parent_abspath = svn_dirent_dirname(work_del_abspath, scratch_pool); |
| |
| SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, |
| &parent_repos_relpath, |
| &entry->repos, |
| &entry->uuid, |
| NULL, NULL, NULL, NULL, |
| db, parent_abspath, |
| result_pool, scratch_pool)); |
| |
| /* Now glue it all together */ |
| *repos_relpath = svn_relpath_join( |
| parent_repos_relpath, |
| svn_dirent_is_child(parent_abspath, |
| entry_abspath, |
| NULL), |
| /* ### should be result_pool? */ |
| scratch_pool); |
| } |
| else |
| { |
| SVN_ERR(svn_wc__db_scan_base_repos(repos_relpath, |
| &entry->repos, |
| &entry->uuid, |
| db, |
| entry_abspath, |
| result_pool, |
| scratch_pool)); |
| } |
| |
| /* Do some extra work for the child nodes. */ |
| if (parent_entry != NULL) |
| { |
| /* For child nodes without a revision, pick up the parent's |
| revision. */ |
| if (!SVN_IS_VALID_REVNUM(entry->revision)) |
| entry->revision = parent_entry->revision; |
| } |
| |
| /* For deleted nodes, our COPIED flag has a rather complex meaning. |
| |
| In general, COPIED means "an operation on an ancestor took care |
| of me." This typically refers to a copy of an ancestor (and |
| this node just came along for the ride). However, in certain |
| situations the COPIED flag is set for deleted nodes. |
| |
| First off, COPIED will *never* be set for nodes/subtrees that |
| are simply deleted. The deleted node/subtree *must* be under |
| an ancestor that has been copied. Plain additions do not count; |
| only copies (add-with-history). |
| |
| The basic algorithm to determine whether we live within a |
| copied subtree is as follows: |
| |
| 1) find the root of the deletion operation that affected us |
| (we may be that root, or an ancestor was deleted and took |
| us with it) |
| |
| 2) look at the root's *parent* and determine whether that was |
| a copy or a simple add. |
| |
| It would appear that we would be done at this point. Once we |
| determine that the parent was copied, then we could just set |
| the COPIED flag. |
| |
| Not so fast. Back to the general concept of "an ancestor |
| operation took care of me." Further consider two possibilities: |
| |
| 1) this node is scheduled for deletion from the copied subtree, |
| so at commit time, we copy then delete |
| |
| 2) this node is scheduled for deletion because a subtree was |
| deleted and then a copied subtree was added (causing a |
| replacement). at commit time, we delete a subtree, and then |
| copy a subtree. we do not need to specifically touch this |
| node -- all operations occur on ancestors. |
| |
| Given the "ancestor operation" concept, then in case (1) we |
| must *clear* the COPIED flag since we'll have more work to do. |
| In case (2), we *set* the COPIED flag to indicate that no |
| real work is going to happen on this node. |
| |
| Great fun. And just maybe the code reading the entries has no |
| bugs in interpreting that gobbledygook... but that *is* the |
| expectation of the code. Sigh. |
| |
| We can get a little bit of shortcut here if THIS_DIR is |
| also schduled for deletion. |
| */ |
| if (parent_entry != NULL |
| && parent_entry->schedule == svn_wc_schedule_delete) |
| { |
| /* ### not entirely sure that we can rely on the parent. for |
| ### example, what if we are a deletion of a BASE node, but |
| ### the parent is a deletion of a copied subtree? sigh. */ |
| |
| /* Child nodes simply inherit the parent's COPIED flag. */ |
| entry->copied = parent_entry->copied; |
| } |
| else |
| { |
| svn_boolean_t base_replaced; |
| const char *work_del_abspath; |
| |
| /* Find out details of our deletion. */ |
| SVN_ERR(svn_wc__db_scan_deletion(NULL, |
| &base_replaced, |
| NULL, |
| &work_del_abspath, |
| db, entry_abspath, |
| scratch_pool, scratch_pool)); |
| |
| /* If there is no deletion in the WORKING tree, then the |
| node is a child of a simple explicit deletion of the |
| BASE tree. It certainly isn't copied. If we *do* find |
| a deletion in the WORKING tree, then we need to discover |
| information about the parent. */ |
| if (work_del_abspath != NULL) |
| { |
| const char *parent_abspath; |
| svn_wc__db_status_t parent_status; |
| |
| /* We know the parent is in the WORKING tree (the topmost |
| node in a WORKING subtree cannot be deleted since you |
| would simply remove the whole subtree in that case). */ |
| parent_abspath = svn_dirent_dirname(work_del_abspath, |
| scratch_pool); |
| SVN_ERR(svn_wc__db_scan_addition(&parent_status, |
| NULL, |
| NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, |
| db, |
| parent_abspath, |
| scratch_pool, scratch_pool)); |
| if (parent_status == svn_wc__db_status_copied |
| || parent_status == svn_wc__db_status_moved_here) |
| { |
| /* The parent is copied/moved here, so WORK_DEL_ABSPATH |
| is the root of a deleted subtree. Our COPIED status |
| is now dependent upon whether the copied root is |
| replacing a BASE tree or not. |
| |
| But: if we are schedule-delete as a result of being |
| a copied DELETED node, then *always* mark COPIED. |
| Normal copies have cmt_* data; copied DELETED nodes |
| are missing this info. |
| |
| Note: MOVED_HERE is a concept foreign to this old |
| interface, but it is best represented as if a copy |
| had occurred, so we'll model it that way to old |
| clients. */ |
| if (SVN_IS_VALID_REVNUM(entry->cmt_rev)) |
| { |
| /* The scan_deletion call will tell us if there |
| was an explicit move-away of an ancestor (which |
| also means a replacement has occurred since |
| there is a WORKING tree that isn't simply |
| BASE deletions). The call will also tell us if |
| there was an implicit deletion caused by a |
| replacement. All stored in BASE_REPLACED. */ |
| entry->copied = base_replaced; |
| } |
| else |
| { |
| entry->copied = TRUE; |
| } |
| } |
| else |
| { |
| SVN_ERR_ASSERT(parent_status == svn_wc__db_status_added); |
| |
| /* Whoops. WORK_DEL_ABSPATH is scheduled for deletion, |
| yet the parent is scheduled for a plain addition. |
| This can occur when a subtree is deleted, and then |
| nodes are added *later*. Since the parent is a simple |
| add, then nothing has been copied. Nothing more to do. |
| |
| Note: if a subtree is added, *then* deletions are |
| made, the nodes should simply be removed from |
| version control. */ |
| } |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Read entries for PATH/LOCAL_ABSPATH from DB (wc-1 or wc-ng). The entries |
| will be allocated in RESULT_POOL, with temporary allocations in |
| SCRATCH_POOL. The entries are returned in RESULT_ENTRIES. */ |
| static svn_error_t * |
| read_entries_new(apr_hash_t **result_entries, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__db_t *sdb; |
| apr_hash_t *entries; |
| const apr_array_header_t *children; |
| apr_pool_t *handle_pool = svn_pool_create(scratch_pool); |
| apr_pool_t *iterpool = svn_pool_create(handle_pool); |
| int i; |
| const svn_wc_entry_t *parent_entry = NULL; |
| apr_uint64_t wc_id = 1; /* ### hacky. should remove. */ |
| |
| entries = apr_hash_make(result_pool); |
| |
| /* ### need database to determine: incomplete, keep_local, ACTUAL info. */ |
| SVN_ERR(svn_wc__db_temp_get_sdb(&sdb, db, local_abspath, FALSE, |
| handle_pool, iterpool)); |
| |
| SVN_ERR(svn_wc__db_read_children(&children, db, |
| local_abspath, |
| result_pool, iterpool)); |
| |
| /* HACK: Push the directory at the end of a constant array */ |
| APR_ARRAY_PUSH((apr_array_header_t *)children, const char *) = ""; |
| |
| /* Note that this loop order causes "this dir" to be processed first. |
| This is necessary because we may need to access the directory's |
| entry during processing of "added" nodes. */ |
| for (i = children->nelts; i--; ) |
| { |
| svn_wc__db_kind_t kind; |
| svn_wc__db_status_t status; |
| svn_wc__db_lock_t *lock; |
| const char *repos_relpath; |
| const svn_checksum_t *checksum; |
| svn_filesize_t translated_size; |
| svn_wc_entry_t *entry = alloc_entry(result_pool); |
| const char *entry_abspath; |
| const char *original_repos_relpath; |
| const char *original_root_url; |
| svn_boolean_t conflicted; |
| svn_boolean_t base_shadowed; |
| |
| svn_pool_clear(iterpool); |
| |
| entry->name = APR_ARRAY_IDX(children, i, const char *); |
| |
| /* If we're on THIS_DIR, then set up PARENT_ENTRY for later use. */ |
| if (*entry->name == '\0') |
| parent_entry = entry; |
| |
| entry_abspath = svn_dirent_join(local_abspath, entry->name, iterpool); |
| |
| SVN_ERR(svn_wc__db_read_info( |
| &status, |
| &kind, |
| &entry->revision, |
| &repos_relpath, |
| &entry->repos, |
| &entry->uuid, |
| &entry->cmt_rev, |
| &entry->cmt_date, |
| &entry->cmt_author, |
| &entry->text_time, |
| &entry->depth, |
| &checksum, |
| &translated_size, |
| NULL, |
| &entry->changelist, |
| &original_repos_relpath, |
| &original_root_url, |
| NULL, |
| &entry->copyfrom_rev, |
| NULL, |
| NULL, |
| &base_shadowed, |
| &conflicted, |
| &lock, |
| db, |
| entry_abspath, |
| result_pool, |
| iterpool)); |
| |
| if (strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) == 0) |
| { |
| /* get the tree conflict data. */ |
| apr_hash_t *tree_conflicts = NULL; |
| const apr_array_header_t *conflict_victims; |
| int k; |
| |
| SVN_ERR(svn_wc__db_read_conflict_victims(&conflict_victims, db, |
| local_abspath, iterpool, |
| iterpool)); |
| |
| for (k = 0; k < conflict_victims->nelts; k++) |
| { |
| int j; |
| const apr_array_header_t *child_conflicts; |
| const char *child_name; |
| const char *child_abspath; |
| |
| child_name = APR_ARRAY_IDX(conflict_victims, k, const char *); |
| child_abspath = svn_dirent_join(local_abspath, child_name, |
| iterpool); |
| |
| SVN_ERR(svn_wc__db_read_conflicts(&child_conflicts, |
| db, child_abspath, iterpool, |
| iterpool)); |
| |
| for (j = 0; j < child_conflicts->nelts; j++) |
| { |
| const svn_wc_conflict_description2_t *conflict = |
| APR_ARRAY_IDX(child_conflicts, j, |
| svn_wc_conflict_description2_t *); |
| |
| if (conflict->kind == svn_wc_conflict_kind_tree) |
| { |
| if (!tree_conflicts) |
| tree_conflicts = apr_hash_make(iterpool); |
| apr_hash_set(tree_conflicts, child_name, |
| APR_HASH_KEY_STRING, conflict); |
| } |
| } |
| } |
| |
| if (tree_conflicts) |
| { |
| SVN_ERR(svn_wc__write_tree_conflicts(&entry->tree_conflict_data, |
| tree_conflicts, |
| result_pool)); |
| } |
| } |
| |
| if (status == svn_wc__db_status_normal |
| || status == svn_wc__db_status_incomplete) |
| { |
| svn_boolean_t have_row = FALSE; |
| |
| /* Ugh. During a checkout, it is possible that we are constructing |
| a subdirectory "over" a not-present directory. The read_info() |
| will return information out of the wc.db in the subdir. We |
| need to detect this situation and create a DELETED entry |
| instead. */ |
| if (kind == svn_wc__db_kind_dir) |
| { |
| svn_sqlite__stmt_t *stmt; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, |
| STMT_SELECT_NOT_PRESENT)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, entry->name)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| } |
| |
| if (have_row) |
| { |
| /* Just like a normal "not-present" node: schedule=normal |
| and DELETED. */ |
| entry->schedule = svn_wc_schedule_normal; |
| entry->deleted = TRUE; |
| } |
| else |
| { |
| /* Plain old BASE node. */ |
| entry->schedule = svn_wc_schedule_normal; |
| |
| /* Grab inherited repository information, if necessary. */ |
| if (repos_relpath == NULL) |
| { |
| SVN_ERR(svn_wc__db_scan_base_repos(&repos_relpath, |
| &entry->repos, |
| &entry->uuid, |
| db, |
| entry_abspath, |
| result_pool, |
| iterpool)); |
| } |
| |
| entry->incomplete = (status == svn_wc__db_status_incomplete); |
| } |
| } |
| else if (status == svn_wc__db_status_deleted |
| || status == svn_wc__db_status_obstructed_delete) |
| { |
| /* ### we don't have to worry about moves, so this is a delete. */ |
| entry->schedule = svn_wc_schedule_delete; |
| |
| /* ### keep_local ... ugh. hacky. */ |
| /* We only read keep_local in the directory itself, because we |
| can't rely on the actual record being available in the parent |
| stub when the directory is recorded as deleted in the directory |
| itself. (This last value is the status that brought us in this |
| if block). |
| |
| This is safe because we will only write this flag in the |
| directory itself (see mark_deleted() in adm_ops.c), and also |
| because we will never use keep_local in the final version of |
| WC-NG. With a central db and central pristine store we can |
| remove working copy directories directly. So any left over |
| directories after the delete operation are always kept locally. |
| */ |
| if (*entry->name == '\0') |
| SVN_ERR(determine_keep_local(&entry->keep_local, sdb, |
| wc_id, entry->name)); |
| } |
| else if (status == svn_wc__db_status_added |
| || status == svn_wc__db_status_obstructed_add) |
| { |
| svn_wc__db_status_t work_status; |
| svn_revnum_t original_revision; |
| |
| /* For child nodes, pick up the parent's revision. */ |
| if (*entry->name != '\0') |
| { |
| assert(parent_entry != NULL); |
| assert(entry->revision == SVN_INVALID_REVNUM); |
| |
| entry->revision = parent_entry->revision; |
| } |
| |
| if (base_shadowed) |
| { |
| svn_wc__db_status_t base_status; |
| |
| /* ENTRY->REVISION is overloaded. When a node is schedule-add |
| or -replace, then REVISION refers to the BASE node's revision |
| that is being overwritten. We need to fetch it now. */ |
| SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, |
| &entry->revision, |
| NULL, NULL, NULL, |
| NULL, NULL, NULL, |
| NULL, NULL, NULL, |
| NULL, NULL, NULL, |
| db, entry_abspath, |
| iterpool, iterpool)); |
| |
| if (base_status == svn_wc__db_status_not_present) |
| { |
| /* ### the underlying node is DELETED in this revision. */ |
| entry->deleted = TRUE; |
| |
| /* This is an add since there isn't a node to replace. */ |
| entry->schedule = svn_wc_schedule_add; |
| } |
| else |
| entry->schedule = svn_wc_schedule_replace; |
| } |
| else |
| { |
| /* If we are reading child directories, then we need to |
| correctly populate the DELETED flag. WC_DB normally |
| wants to provide all of a directory's metadata from |
| its own area. But this information is stored only in |
| the parent directory, so we need to call a custom API |
| to fetch this value. |
| |
| ### we should start generating BASE_NODE rows for THIS_DIR |
| ### in the subdir. future step because it is harder. */ |
| if (kind == svn_wc__db_kind_dir && *entry->name != '\0') |
| { |
| SVN_ERR(svn_wc__db_temp_is_dir_deleted(&entry->deleted, |
| &entry->revision, |
| db, entry_abspath, |
| iterpool)); |
| } |
| if (entry->deleted) |
| { |
| /* There was a DELETED marker in the parent, meaning |
| that we truly are shadowing a base node. It isn't |
| called a 'replace' though (the BASE is pretending |
| not to exist). */ |
| entry->schedule = svn_wc_schedule_add; |
| } |
| else |
| { |
| /* There was NOT a 'not-present' BASE_NODE in the parent |
| directory. And there is no BASE_NODE in this directory. |
| Therefore, we are looking at some kind of add/copy |
| rather than a replace. */ |
| |
| /* ### if this looks like a plain old add, then rev=0. */ |
| if (!SVN_IS_VALID_REVNUM(entry->copyfrom_rev) |
| && !SVN_IS_VALID_REVNUM(entry->cmt_rev)) |
| entry->revision = 0; |
| |
| if (status == svn_wc__db_status_obstructed_add) |
| entry->revision = SVN_INVALID_REVNUM; |
| |
| /* ### when we're reading a directory that is not present, |
| ### then it must be "normal" rather than "add". */ |
| if (*entry->name == '\0' |
| && status == svn_wc__db_status_obstructed_add) |
| entry->schedule = svn_wc_schedule_normal; |
| else |
| entry->schedule = svn_wc_schedule_add; |
| } |
| } |
| |
| SVN_ERR(svn_wc__db_scan_addition(&work_status, |
| NULL, |
| &repos_relpath, |
| &entry->repos, |
| &entry->uuid, |
| NULL, NULL, NULL, &original_revision, |
| db, |
| entry_abspath, |
| result_pool, iterpool)); |
| |
| if (!SVN_IS_VALID_REVNUM(entry->cmt_rev) |
| && original_repos_relpath == NULL) |
| { |
| /* There is NOT a last-changed revision (last-changed date and |
| author may be unknown, but we can always check the rev). |
| The absence of a revision implies this node was added WITHOUT |
| any history. Avoid the COPIED checks in the else block. */ |
| /* ### scan_addition may need to be updated to avoid returning |
| ### status_copied in this case. */ |
| } |
| else if (work_status == svn_wc__db_status_copied) |
| { |
| entry->copied = TRUE; |
| |
| /* If this is a child of a copied subtree, then it should be |
| schedule_normal. */ |
| if (original_repos_relpath == NULL) |
| { |
| /* ### what if there is a BASE node under there? */ |
| entry->schedule = svn_wc_schedule_normal; |
| } |
| |
| /* Copied nodes need to mirror their copyfrom_rev, if they |
| don't have a revision of their own already. */ |
| if (!SVN_IS_VALID_REVNUM(entry->revision)) |
| entry->revision = original_revision; |
| } |
| |
| /* Does this node have copyfrom_* information? */ |
| if (original_repos_relpath != NULL) |
| { |
| const char *parent_abspath; |
| svn_boolean_t set_copyfrom = TRUE; |
| svn_error_t *err; |
| const char *op_root_abspath; |
| const char *parent_repos_relpath; |
| const char *parent_root_url; |
| |
| SVN_ERR_ASSERT(work_status == svn_wc__db_status_copied); |
| |
| /* When we insert entries into the database, we will construct |
| additional copyfrom records for mixed-revision copies. The |
| old entries would simply record the different revision in |
| the entry->revision field. That is not available within |
| wc-ng, so additional copies are made (see the logic inside |
| write_entry()). However, when reading these back *out* of |
| the database, the additional copies look like new "Added" |
| nodes rather than a simple mixed-rev working copy. |
| |
| That would be a behavior change if we did not compensate. |
| If there is copyfrom information for this node, then the |
| code below looks at the parent to detect if it *also* has |
| copyfrom information, and if the copyfrom_url would align |
| properly. If it *does*, then we omit storing copyfrom_url |
| and copyfrom_rev (ie. inherit the copyfrom info like a |
| normal child), and update entry->revision with the |
| copyfrom_rev in order to (re)create the mixed-rev copied |
| subtree that was originally presented for storage. */ |
| |
| /* Get the copyfrom information from our parent. |
| |
| Note that the parent could be added/copied/moved-here. There |
| is no way for it to be deleted/moved-away and have *this* |
| node appear as copied. */ |
| parent_abspath = svn_dirent_dirname(entry_abspath, iterpool); |
| err = svn_wc__db_scan_addition(NULL, |
| &op_root_abspath, |
| NULL, NULL, NULL, |
| &parent_repos_relpath, |
| &parent_root_url, |
| NULL, NULL, |
| db, |
| parent_abspath, |
| iterpool, iterpool); |
| if (err) |
| { |
| if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) |
| return svn_error_return(err); |
| svn_error_clear(err); |
| } |
| else if (parent_root_url != NULL |
| && strcmp(original_root_url, parent_root_url) == 0) |
| { |
| const char *relpath_to_entry = svn_dirent_is_child( |
| op_root_abspath, entry_abspath, NULL); |
| const char *entry_repos_relpath = svn_relpath_join( |
| parent_repos_relpath, relpath_to_entry, iterpool); |
| |
| /* The copyfrom repos roots matched. |
| |
| Now we look to see if the copyfrom path of the parent |
| would align with our own path. If so, then it means |
| this copyfrom was spontaneously created and inserted |
| for mixed-rev purposes and can be eliminated without |
| changing the semantics of a mixed-rev copied subtree. |
| |
| See notes/api-errata/wc003.txt for some additional |
| detail, and potential issues. */ |
| if (strcmp(entry_repos_relpath, original_repos_relpath) == 0) |
| { |
| /* Don't set the copyfrom_url and clear out the |
| copyfrom_rev. Thus, this node becomes a child |
| of a copied subtree (rather than its own root). */ |
| set_copyfrom = FALSE; |
| entry->copyfrom_rev = SVN_INVALID_REVNUM; |
| |
| /* Children in a copied subtree are schedule normal |
| since we don't plan to actually *do* anything with |
| them. Their operation is implied by ancestors. */ |
| entry->schedule = svn_wc_schedule_normal; |
| |
| /* And *finally* we turn this entry into the mixed |
| revision node that it was intended to be. This |
| node's revision is taken from the copyfrom record |
| that we spontaneously constructed. */ |
| entry->revision = original_revision; |
| } |
| } |
| |
| if (set_copyfrom) |
| entry->copyfrom_url = |
| svn_path_url_add_component2(original_root_url, |
| original_repos_relpath, |
| result_pool); |
| } |
| } |
| else if (status == svn_wc__db_status_not_present) |
| { |
| /* ### buh. 'deleted' nodes are actually supposed to be |
| ### schedule "normal" since we aren't going to actually *do* |
| ### anything to this node at commit time. */ |
| entry->schedule = svn_wc_schedule_normal; |
| entry->deleted = TRUE; |
| } |
| else if (status == svn_wc__db_status_obstructed) |
| { |
| /* ### set some values that should (hopefully) let this directory |
| ### be usable. */ |
| entry->revision = SVN_INVALID_REVNUM; |
| } |
| else if (status == svn_wc__db_status_absent) |
| { |
| entry->absent = TRUE; |
| } |
| else if (status == svn_wc__db_status_excluded) |
| { |
| entry->schedule = svn_wc_schedule_normal; |
| entry->depth = svn_depth_exclude; |
| } |
| else |
| { |
| /* ### We aren't using this status. Yet. */ |
| SVN_ERR_ASSERT(status == svn_wc__db_status_excluded); |
| continue; |
| } |
| |
| /* ### higher levels want repos information about deleted nodes, even |
| ### tho they are not "part of" a repository any more. */ |
| if (entry->schedule == svn_wc_schedule_delete) |
| { |
| SVN_ERR(get_base_info_for_deleted(entry, |
| &kind, |
| &repos_relpath, |
| &checksum, |
| db, entry_abspath, |
| parent_entry, |
| result_pool, iterpool)); |
| } |
| |
| /* ### default to the infinite depth if we don't know it. */ |
| if (entry->depth == svn_depth_unknown) |
| entry->depth = svn_depth_infinity; |
| |
| if (kind == svn_wc__db_kind_dir) |
| entry->kind = svn_node_dir; |
| else if (kind == svn_wc__db_kind_file) |
| entry->kind = svn_node_file; |
| else if (kind == svn_wc__db_kind_symlink) |
| entry->kind = svn_node_file; /* ### no symlink kind */ |
| else |
| entry->kind = svn_node_unknown; |
| |
| SVN_ERR_ASSERT(repos_relpath != NULL |
| || entry->schedule == svn_wc_schedule_delete |
| || status == svn_wc__db_status_obstructed |
| || status == svn_wc__db_status_obstructed_delete |
| ); |
| if (repos_relpath) |
| entry->url = svn_path_url_add_component2(entry->repos, |
| repos_relpath, |
| result_pool); |
| |
| if (checksum) |
| entry->checksum = svn_checksum_to_cstring(checksum, result_pool); |
| |
| if (conflicted) |
| { |
| const apr_array_header_t *conflicts; |
| int j; |
| SVN_ERR(svn_wc__db_read_conflicts(&conflicts, db, entry_abspath, |
| iterpool, iterpool)); |
| |
| for (j = 0; j < conflicts->nelts; j++) |
| { |
| const svn_wc_conflict_description2_t *cd; |
| cd = APR_ARRAY_IDX(conflicts, j, |
| const svn_wc_conflict_description2_t *); |
| |
| switch (cd->kind) |
| { |
| case svn_wc_conflict_kind_text: |
| entry->conflict_old = apr_pstrdup(result_pool, |
| cd->base_file); |
| entry->conflict_new = apr_pstrdup(result_pool, |
| cd->their_file); |
| entry->conflict_wrk = apr_pstrdup(result_pool, |
| cd->my_file); |
| break; |
| case svn_wc_conflict_kind_property: |
| entry->prejfile = apr_pstrdup(result_pool, |
| cd->their_file); |
| break; |
| case svn_wc_conflict_kind_tree: |
| break; |
| } |
| } |
| } |
| |
| if (lock) |
| { |
| entry->lock_token = lock->token; |
| entry->lock_owner = lock->owner; |
| entry->lock_comment = lock->comment; |
| entry->lock_creation_date = lock->date; |
| } |
| |
| /* Let's check for a file external. |
| ### right now this is ugly, since we have no good way querying |
| ### for a file external OR retrieving properties. ugh. */ |
| if (entry->kind == svn_node_file) |
| SVN_ERR(check_file_external(entry, sdb, result_pool)); |
| |
| entry->working_size = translated_size; |
| |
| apr_hash_set(entries, entry->name, APR_HASH_KEY_STRING, entry); |
| } |
| |
| svn_pool_destroy(handle_pool); |
| |
| *result_entries = entries; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| read_entries(apr_hash_t **entries, |
| svn_wc__db_t *db, |
| const char *wcroot_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| int wc_format; |
| |
| SVN_ERR(svn_wc__db_temp_get_format(&wc_format, db, wcroot_abspath, |
| scratch_pool)); |
| |
| if (wc_format < SVN_WC__WC_NG_VERSION) |
| return svn_error_return(svn_wc__read_entries_old(entries, |
| wcroot_abspath, |
| result_pool, |
| scratch_pool)); |
| |
| return svn_error_return(read_entries_new(entries, db, wcroot_abspath, |
| result_pool, scratch_pool)); |
| } |
| |
| |
| /* For a given LOCAL_ABSPATH, using DB, return the directory in which the |
| entry information is located, and the entry name to access that entry. |
| |
| KIND and NEED_PARENT_STUB are as in svn_wc__get_entry(). |
| |
| Return the results in RESULT_POOL and use SCRATCH_POOL for temporary |
| allocations. */ |
| static svn_error_t * |
| get_entry_access_info(const char **adm_abspath, |
| const char **entry_name, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_node_kind_t kind, |
| svn_boolean_t need_parent_stub, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc_adm_access_t *adm_access; |
| svn_boolean_t read_from_subdir = FALSE; |
| |
| /* Can't ask for the parent stub if the node is a file. */ |
| SVN_ERR_ASSERT(!need_parent_stub || kind != svn_node_file); |
| |
| /* If the caller didn't know the node kind, then stat the path. Maybe |
| it is really there, and we can speed up the steps below. */ |
| if (kind == svn_node_unknown) |
| { |
| svn_node_kind_t on_disk; |
| |
| /* Do we already have an access baton for LOCAL_ABSPATH? */ |
| adm_access = svn_wc__adm_retrieve_internal2(db, local_abspath, |
| scratch_pool); |
| if (adm_access) |
| { |
| /* Sweet. The node is a directory. */ |
| on_disk = svn_node_dir; |
| } |
| else |
| { |
| svn_boolean_t special; |
| |
| /* What's on disk? */ |
| SVN_ERR(svn_io_check_special_path(local_abspath, &on_disk, &special, |
| scratch_pool)); |
| } |
| |
| if (on_disk != svn_node_dir) |
| { |
| /* If this is *anything* besides a directory (FILE, NONE, or |
| UNKNOWN), then we cannot treat it as a versioned directory |
| containing entries to read. Leave READ_FROM_SUBDIR as FALSE, |
| so that the parent will be examined. |
| |
| For NONE and UNKNOWN, it may be that metadata exists for the |
| node, even though on-disk is unhelpful. |
| |
| If NEED_PARENT_STUB is TRUE, and the entry is not a DIRECTORY, |
| then we'll error. |
| |
| If NEED_PARENT_STUB if FALSE, and we successfully read a stub, |
| then this on-disk node is obstructing the read. */ |
| } |
| else |
| { |
| /* We found a directory for this UNKNOWN node. Determine whether |
| we need to read inside it. */ |
| read_from_subdir = !need_parent_stub; |
| } |
| } |
| else if (kind == svn_node_dir && !need_parent_stub) |
| { |
| read_from_subdir = TRUE; |
| } |
| |
| if (read_from_subdir) |
| { |
| /* KIND must be a DIR or UNKNOWN (and we found a subdir). We want |
| the "real" data, so treat LOCAL_ABSPATH as a versioned directory. */ |
| *adm_abspath = apr_pstrdup(result_pool, local_abspath); |
| *entry_name = ""; |
| } |
| else |
| { |
| /* FILE node needs to read the parent directory. Or a DIR node |
| needs to read from the parent to get at the stub entry. Or this |
| is an UNKNOWN node, and we need to examine the parent. */ |
| svn_dirent_split(local_abspath, adm_abspath, entry_name, result_pool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__get_entry(const svn_wc_entry_t **entry, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_boolean_t allow_unversioned, |
| svn_node_kind_t kind, |
| svn_boolean_t need_parent_stub, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *dir_abspath; |
| const char *entry_name; |
| svn_wc_adm_access_t *adm_access; |
| apr_hash_t *entries; |
| apr_pool_t *source_pool; |
| |
| /* Can't ask for the parent stub if the node is a file. */ |
| SVN_ERR_ASSERT(!need_parent_stub || kind != svn_node_file); |
| |
| SVN_ERR(get_entry_access_info(&dir_abspath, &entry_name, db, local_abspath, |
| kind, need_parent_stub, scratch_pool, |
| scratch_pool)); |
| |
| /* Is there an existing access baton for this path? */ |
| adm_access = svn_wc__adm_retrieve_internal2(db, dir_abspath, scratch_pool); |
| if (adm_access == NULL) |
| { |
| svn_error_t *err; |
| |
| /* No access baton. Just read the entries into the scratch pool. |
| No place to cache them, so they won't stick around. |
| |
| NOTE: if KIND is UNKNOWN and we decided to examine the *parent* |
| directory, then it is possible we moved out of the working copy. |
| If the on-disk node is a DIR, and we asked for a stub, then we |
| obviously can't provide that (parent has no info). If the on-disk |
| node is a FILE/NONE/UNKNOWN, then it is obstructing the real |
| LOCAL_ABSPATH (or it was never a versioned item). In all these |
| cases, the read_entries() will (properly) throw an error. |
| |
| NOTE: if KIND is a DIR and we asked for the real data, but it is |
| obstructed on-disk by some other node kind (NONE, FILE, UNKNOWN), |
| then this will throw an error. */ |
| |
| err = read_entries(&entries, db, dir_abspath, |
| scratch_pool, scratch_pool); |
| if (err) |
| { |
| if (err->apr_err != SVN_ERR_WC_MISSING || kind != svn_node_unknown |
| || *entry_name != '\0') |
| return svn_error_return(err); |
| svn_error_clear(err); |
| |
| /* The caller didn't know the node type, we saw a directory there, |
| we attempted to read that directory, and then wc_db reports |
| that it is NOT a working copy directory. It is possible that |
| one of two things has happened: |
| |
| 1) a directory is obstructing a file in the parent |
| 2) the (versioned) directory's contents have been removed |
| |
| Let's assume situation (1); if that is true, then we can just |
| return the newly-found data. |
| |
| If we assumed (2), then a valid result still won't help us |
| since the caller asked for the actual contents, not the stub. |
| However, if we assume (1) and get back a stub, then we have |
| verified a missing, versioned directory, and can return an |
| error describing that. */ |
| err = svn_wc__get_entry(entry, db, local_abspath, allow_unversioned, |
| svn_node_file, FALSE, |
| result_pool, scratch_pool); |
| if (err == SVN_NO_ERROR) |
| return SVN_NO_ERROR; |
| if (err->apr_err != SVN_ERR_NODE_UNEXPECTED_KIND) |
| return svn_error_return(err); |
| svn_error_clear(err); |
| |
| /* We asked for a FILE, but the node found is a DIR. Thus, we |
| are looking at a stub. Originally, we tried to read into the |
| subdir because NEED_PARENT_STUB is FALSE. The stub we just |
| read is not going to work for the caller, so inform them of |
| the missing subdirectory. */ |
| SVN_ERR_ASSERT(*entry != NULL && (*entry)->kind == svn_node_dir); |
| return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, |
| _("Admin area of '%s' is missing"), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| } |
| |
| /* The entries are allocated in scratch_pool right now. */ |
| source_pool = scratch_pool; |
| } |
| else |
| { |
| /* We have a place to cache the results. */ |
| |
| /* The entries are currently, or will be, allocated in the access |
| baton's pool. */ |
| source_pool = svn_wc_adm_access_pool(adm_access); |
| |
| entries = svn_wc__adm_access_entries(adm_access); |
| if (entries == NULL) |
| { |
| /* See note above about reading the entries for an UNKNOWN. */ |
| SVN_ERR(read_entries(&entries, db, dir_abspath, |
| source_pool, scratch_pool)); |
| svn_wc__adm_access_set_entries(adm_access, entries); |
| } |
| } |
| |
| *entry = apr_hash_get(entries, entry_name, APR_HASH_KEY_STRING); |
| if (*entry == NULL) |
| { |
| if (allow_unversioned) |
| return SVN_NO_ERROR; |
| return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, |
| _("'%s' is not under version control"), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| } |
| |
| /* Give the caller a valid entry. */ |
| if (result_pool != source_pool) |
| *entry = svn_wc_entry_dup(*entry, result_pool); |
| |
| /* The caller had the wrong information. */ |
| if ((kind == svn_node_file && (*entry)->kind != svn_node_file) |
| || (kind == svn_node_dir && (*entry)->kind != svn_node_dir)) |
| return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, |
| _("'%s' is not of the right kind"), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| |
| if (kind == svn_node_unknown) |
| { |
| /* They wanted a (directory) stub, but this isn't a directory. */ |
| if (need_parent_stub && (*entry)->kind != svn_node_dir) |
| return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, |
| _("'%s' is not of the right kind"), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| |
| /* The actual (directory) information was wanted, but we got a stub. */ |
| if (!need_parent_stub |
| && (*entry)->kind == svn_node_dir |
| && *(*entry)->name != '\0') |
| return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, |
| _("'%s' is not of the right kind"), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__get_entry_versioned(const svn_wc_entry_t **entry, |
| svn_wc_context_t *wc_ctx, |
| const char *local_abspath, |
| svn_node_kind_t kind, |
| svn_boolean_t show_hidden, |
| svn_boolean_t need_parent_stub, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_error_t *err; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| /* We call this with allow_unversioned=TRUE, since the error returned is |
| different than our callers currently expect. We catch the NULL entry |
| below and return the correct error. */ |
| err = svn_wc__get_entry(entry, wc_ctx->db, local_abspath, TRUE, kind, |
| need_parent_stub, result_pool, scratch_pool); |
| if (err && (err->apr_err == SVN_ERR_WC_MISSING |
| || err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND |
| || err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND)) |
| { |
| svn_error_clear(err); |
| *entry = NULL; |
| } |
| else if (err) |
| return svn_error_return(err); |
| |
| |
| if (*entry && !show_hidden) |
| { |
| svn_boolean_t hidden; |
| |
| SVN_ERR(svn_wc__entry_is_hidden(&hidden, *entry)); |
| if (hidden) |
| *entry = NULL; |
| } |
| |
| if (! *entry) |
| return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, |
| _("'%s' is not under version control"), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__maybe_get_entry(const svn_wc_entry_t **entry, |
| svn_wc_context_t *wc_ctx, |
| const char *local_abspath, |
| svn_node_kind_t kind, |
| svn_boolean_t show_hidden, |
| svn_boolean_t need_parent_stub, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_error_t *err; |
| |
| err = svn_wc__get_entry_versioned(entry, wc_ctx, local_abspath, |
| kind, show_hidden, need_parent_stub, |
| result_pool, scratch_pool); |
| |
| if (err && err->apr_err == SVN_ERR_ENTRY_NOT_FOUND) |
| { |
| svn_error_clear(err); |
| *entry = NULL; |
| } |
| else if (err) |
| return svn_error_return(err); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__node_is_deleted(svn_boolean_t *deleted, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *scratch_pool) |
| { |
| const svn_wc_entry_t *entry; |
| svn_error_t *err; |
| |
| /* ### rewrite this in terms of wc_db. */ |
| |
| err = svn_wc__get_entry(&entry, db, local_abspath, FALSE, |
| svn_node_unknown, TRUE, scratch_pool, scratch_pool); |
| if (err) |
| { |
| if (err->apr_err != SVN_ERR_NODE_UNEXPECTED_KIND) |
| return svn_error_return(err); |
| |
| /* We asked for the parent stub, but got a file. No big deal. We have |
| what we wanted for a file. */ |
| svn_error_clear(err); |
| } |
| |
| *deleted = entry->deleted; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* TODO ### Rewrite doc string to mention ENTRIES_ALL; not ADM_ACCESS. |
| |
| Prune the deleted entries from the cached entries in ADM_ACCESS, and |
| return that collection in *ENTRIES_PRUNED. SCRATCH_POOL is used for local, |
| short term, memory allocation, RESULT_POOL for permanent stuff. */ |
| static svn_error_t * |
| prune_deleted(apr_hash_t **entries_pruned, |
| apr_hash_t *entries_all, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| apr_hash_index_t *hi; |
| |
| if (!entries_all) |
| { |
| *entries_pruned = NULL; |
| return SVN_NO_ERROR; |
| } |
| |
| /* I think it will be common for there to be no deleted entries, so |
| it is worth checking for that case as we can optimise it. */ |
| for (hi = apr_hash_first(scratch_pool, entries_all); |
| hi; |
| hi = apr_hash_next(hi)) |
| { |
| svn_boolean_t hidden; |
| |
| SVN_ERR(svn_wc__entry_is_hidden(&hidden, |
| svn_apr_hash_index_val(hi))); |
| if (hidden) |
| break; |
| } |
| |
| if (! hi) |
| { |
| /* There are no deleted entries, so we can use the full hash */ |
| *entries_pruned = entries_all; |
| return SVN_NO_ERROR; |
| } |
| |
| /* Construct pruned hash without deleted entries */ |
| *entries_pruned = apr_hash_make(result_pool); |
| for (hi = apr_hash_first(scratch_pool, entries_all); |
| hi; |
| hi = apr_hash_next(hi)) |
| { |
| const void *key = svn_apr_hash_index_key(hi); |
| const svn_wc_entry_t *entry = svn_apr_hash_index_val(hi); |
| svn_boolean_t hidden; |
| |
| SVN_ERR(svn_wc__entry_is_hidden(&hidden, entry)); |
| if (!hidden) |
| apr_hash_set(*entries_pruned, key, APR_HASH_KEY_STRING, entry); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc_entries_read(apr_hash_t **entries, |
| svn_wc_adm_access_t *adm_access, |
| svn_boolean_t show_hidden, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *new_entries; |
| |
| new_entries = svn_wc__adm_access_entries(adm_access); |
| if (! new_entries) |
| { |
| svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); |
| const char *local_abspath = svn_wc__adm_access_abspath(adm_access); |
| apr_pool_t *result_pool = svn_wc_adm_access_pool(adm_access); |
| |
| SVN_ERR(read_entries(&new_entries, db, local_abspath, |
| result_pool, pool)); |
| svn_wc__adm_access_set_entries(adm_access, new_entries); |
| } |
| |
| if (show_hidden) |
| *entries = new_entries; |
| else |
| SVN_ERR(prune_deleted(entries, new_entries, |
| svn_wc_adm_access_pool(adm_access), |
| pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__set_depth(svn_wc__db_t *db, |
| const char *local_dir_abspath, |
| svn_depth_t depth, |
| apr_pool_t *scratch_pool) |
| { |
| const char *parent_abspath; |
| const char *base_name; |
| svn_wc_adm_access_t *adm_access; |
| svn_wc_entry_t *entry; |
| |
| SVN_ERR_ASSERT(depth >= svn_depth_empty && depth <= svn_depth_infinity); |
| |
| svn_dirent_split(local_dir_abspath, &parent_abspath, &base_name, |
| scratch_pool); |
| |
| /* Update the entry cache */ |
| adm_access = svn_wc__adm_retrieve_internal2(db, parent_abspath, |
| scratch_pool); |
| |
| /* Update parent? */ |
| if (adm_access != NULL) |
| { |
| apr_hash_t *entries = svn_wc__adm_access_entries(adm_access); |
| |
| entry = (entries != NULL) |
| ? apr_hash_get(entries, base_name, APR_HASH_KEY_STRING) |
| : NULL; |
| |
| if (entry != NULL) |
| { |
| entry->depth = (depth == svn_depth_exclude) ? svn_depth_exclude |
| : svn_depth_infinity; |
| } |
| } |
| |
| /* ### setting depth exclude on a wcroot breaks svn_wc_crop() */ |
| if (depth != svn_depth_exclude) |
| { |
| /* We aren't excluded, so fetch the entries for the directory, and write |
| our depth there. */ |
| adm_access = svn_wc__adm_retrieve_internal2(db, local_dir_abspath, |
| scratch_pool); |
| if (adm_access != NULL) |
| { |
| apr_hash_t *entries = svn_wc__adm_access_entries(adm_access); |
| |
| entry = (entries != NULL) |
| ? apr_hash_get(entries, "", APR_HASH_KEY_STRING) |
| : NULL; |
| |
| if (entry != NULL) |
| entry->depth = depth; |
| } |
| } |
| |
| return svn_error_return(svn_wc__db_temp_op_set_dir_depth(db, |
| local_dir_abspath, |
| depth, |
| FALSE, |
| scratch_pool)); |
| } |
| |
| static svn_error_t * |
| insert_base_node(svn_sqlite__db_t *sdb, |
| const db_base_node_t *base_node, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, |
| STMT_INSERT_BASE_NODE_FOR_ENTRY)); |
| |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 1, base_node->wc_id)); |
| SVN_ERR(svn_sqlite__bind_text(stmt, 2, base_node->local_relpath)); |
| |
| if (base_node->repos_id) |
| { |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 3, base_node->repos_id)); |
| SVN_ERR(svn_sqlite__bind_text(stmt, 4, base_node->repos_relpath)); |
| } |
| |
| if (base_node->parent_relpath) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 5, base_node->parent_relpath)); |
| |
| if (base_node->presence == svn_wc__db_status_not_present) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 6, "not-present")); |
| else if (base_node->presence == svn_wc__db_status_normal) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 6, "normal")); |
| else if (base_node->presence == svn_wc__db_status_absent) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 6, "absent")); |
| else if (base_node->presence == svn_wc__db_status_incomplete) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 6, "incomplete")); |
| else if (base_node->presence == svn_wc__db_status_excluded) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 6, "excluded")); |
| |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 7, base_node->revision)); |
| |
| /* ### in per-subdir operation, if we're about to write a directory and |
| ### it is *not* "this dir", then we're writing a row in the parent |
| ### directory about the child. note that in the kind. */ |
| /* ### kind might be "symlink" or "unknown" */ |
| if (base_node->kind == svn_node_dir && *base_node->local_relpath != '\0') |
| SVN_ERR(svn_sqlite__bind_text(stmt, 8, "subdir")); |
| else |
| SVN_ERR(svn_sqlite__bind_text(stmt, 8, |
| svn_node_kind_to_word(base_node->kind))); |
| |
| if (base_node->checksum) |
| SVN_ERR(svn_sqlite__bind_checksum(stmt, 9, base_node->checksum, |
| scratch_pool)); |
| |
| if (base_node->translated_size != SVN_INVALID_FILESIZE) |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 10, base_node->translated_size)); |
| |
| /* ### strictly speaking, changed_rev should be valid for present nodes. */ |
| if (SVN_IS_VALID_REVNUM(base_node->changed_rev)) |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 11, base_node->changed_rev)); |
| if (base_node->changed_date) |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 12, base_node->changed_date)); |
| if (base_node->changed_author) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 13, base_node->changed_author)); |
| |
| SVN_ERR(svn_sqlite__bind_text(stmt, 14, svn_depth_to_word(base_node->depth))); |
| |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 15, base_node->last_mod_time)); |
| |
| if (base_node->properties) |
| SVN_ERR(svn_sqlite__bind_properties(stmt, 16, base_node->properties, |
| scratch_pool)); |
| |
| /* Execute and reset the insert clause. */ |
| return svn_error_return(svn_sqlite__insert(NULL, stmt)); |
| } |
| |
| static svn_error_t * |
| insert_working_node(svn_sqlite__db_t *sdb, |
| const db_working_node_t *working_node, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_WORKING_NODE)); |
| |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 1, working_node->wc_id)); |
| SVN_ERR(svn_sqlite__bind_text(stmt, 2, working_node->local_relpath)); |
| SVN_ERR(svn_sqlite__bind_text(stmt, 3, working_node->parent_relpath)); |
| |
| /* ### need rest of values */ |
| if (working_node->presence == svn_wc__db_status_normal) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 4, "normal")); |
| else if (working_node->presence == svn_wc__db_status_not_present) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 4, "not-present")); |
| else if (working_node->presence == svn_wc__db_status_base_deleted) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 4, "base-deleted")); |
| else if (working_node->presence == svn_wc__db_status_incomplete) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 4, "incomplete")); |
| else if (working_node->presence == svn_wc__db_status_excluded) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 4, "excluded")); |
| |
| /* ### in per-subdir operation, if we're about to write a directory and |
| ### it is *not* "this dir", then we're writing a row in the parent |
| ### directory about the child. note that in the kind. */ |
| if (working_node->kind == svn_node_dir |
| && *working_node->local_relpath != '\0') |
| SVN_ERR(svn_sqlite__bind_text(stmt, 5, "subdir")); |
| else |
| SVN_ERR(svn_sqlite__bind_text(stmt, 5, |
| svn_node_kind_to_word(working_node->kind))); |
| |
| if (working_node->copyfrom_repos_path) |
| { |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 6, |
| working_node->copyfrom_repos_id)); |
| SVN_ERR(svn_sqlite__bind_text(stmt, 7, |
| working_node->copyfrom_repos_path)); |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 8, working_node->copyfrom_revnum)); |
| } |
| |
| if (working_node->moved_here) |
| SVN_ERR(svn_sqlite__bind_int(stmt, 9, working_node->moved_here)); |
| |
| if (working_node->moved_to) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 10, working_node->moved_to)); |
| |
| if (working_node->checksum) |
| SVN_ERR(svn_sqlite__bind_checksum(stmt, 11, working_node->checksum, |
| scratch_pool)); |
| |
| if (working_node->translated_size != SVN_INVALID_FILESIZE) |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 12, working_node->translated_size)); |
| |
| if (SVN_IS_VALID_REVNUM(working_node->changed_rev)) |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 13, working_node->changed_rev)); |
| if (working_node->changed_date) |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 14, working_node->changed_date)); |
| if (working_node->changed_author) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 15, working_node->changed_author)); |
| |
| SVN_ERR(svn_sqlite__bind_text(stmt, 16, |
| svn_depth_to_word(working_node->depth))); |
| |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 17, working_node->last_mod_time)); |
| |
| if (working_node->properties) |
| SVN_ERR(svn_sqlite__bind_properties(stmt, 18, working_node->properties, |
| scratch_pool)); |
| |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 19, working_node->keep_local)); |
| |
| /* Execute and reset the insert clause. */ |
| return svn_error_return(svn_sqlite__insert(NULL, stmt)); |
| } |
| |
| static svn_error_t * |
| insert_actual_node(svn_sqlite__db_t *sdb, |
| const db_actual_node_t *actual_node, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_ACTUAL_NODE)); |
| |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 1, actual_node->wc_id)); |
| SVN_ERR(svn_sqlite__bind_text(stmt, 2, actual_node->local_relpath)); |
| SVN_ERR(svn_sqlite__bind_text(stmt, 3, actual_node->parent_relpath)); |
| |
| if (actual_node->properties) |
| SVN_ERR(svn_sqlite__bind_properties(stmt, 4, actual_node->properties, |
| scratch_pool)); |
| |
| if (actual_node->conflict_old) |
| { |
| SVN_ERR(svn_sqlite__bind_text(stmt, 5, actual_node->conflict_old)); |
| SVN_ERR(svn_sqlite__bind_text(stmt, 6, actual_node->conflict_new)); |
| SVN_ERR(svn_sqlite__bind_text(stmt, 7, actual_node->conflict_working)); |
| } |
| |
| if (actual_node->prop_reject) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 8, actual_node->prop_reject)); |
| |
| if (actual_node->changelist) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 9, actual_node->changelist)); |
| |
| /* ### column 10 is text_mod */ |
| |
| if (actual_node->tree_conflict_data) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 11, actual_node->tree_conflict_data)); |
| |
| /* Execute and reset the insert clause. */ |
| return svn_error_return(svn_sqlite__insert(NULL, stmt)); |
| } |
| |
| |
| /* Write the information for ENTRY to WC_DB. The WC_ID, REPOS_ID and |
| REPOS_ROOT will all be used for writing ENTRY. |
| ### transitioning from straight sql to using the wc_db APIs. For the |
| ### time being, we'll need both parameters. */ |
| static svn_error_t * |
| write_entry(svn_wc__db_t *db, |
| svn_sqlite__db_t *sdb, |
| apr_int64_t wc_id, |
| apr_int64_t repos_id, |
| const char *repos_root, |
| const svn_wc_entry_t *entry, |
| const char *local_relpath, |
| const char *entry_abspath, |
| const svn_wc_entry_t *this_dir, |
| svn_boolean_t always_create_actual, |
| svn_boolean_t create_locks, |
| apr_pool_t *scratch_pool) |
| { |
| db_base_node_t *base_node = NULL; |
| db_working_node_t *working_node = NULL; |
| db_actual_node_t *actual_node = NULL; |
| const char *parent_relpath; |
| |
| if (*local_relpath == '\0') |
| parent_relpath = NULL; |
| else |
| parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool); |
| |
| switch (entry->schedule) |
| { |
| case svn_wc_schedule_normal: |
| if (entry->copied) |
| working_node = MAYBE_ALLOC(working_node, scratch_pool); |
| else |
| base_node = MAYBE_ALLOC(base_node, scratch_pool); |
| break; |
| |
| case svn_wc_schedule_add: |
| working_node = MAYBE_ALLOC(working_node, scratch_pool); |
| break; |
| |
| case svn_wc_schedule_delete: |
| working_node = MAYBE_ALLOC(working_node, scratch_pool); |
| /* If the entry is part of a REPLACED (not COPIED) subtree, |
| then it needs a BASE node. */ |
| if (! (entry->copied |
| || (this_dir->copied |
| && this_dir->schedule == svn_wc_schedule_add))) |
| base_node = MAYBE_ALLOC(base_node, scratch_pool); |
| break; |
| |
| case svn_wc_schedule_replace: |
| working_node = MAYBE_ALLOC(working_node, scratch_pool); |
| base_node = MAYBE_ALLOC(base_node, scratch_pool); |
| break; |
| } |
| |
| /* Something deleted in this revision means there should always be a |
| BASE node to indicate the not-present node. */ |
| if (entry->deleted) |
| { |
| base_node = MAYBE_ALLOC(base_node, scratch_pool); |
| } |
| |
| if (entry->copied) |
| { |
| /* Make sure we get a WORKING_NODE inserted. The copyfrom information |
| will occur here or on a parent, as appropriate. */ |
| working_node = MAYBE_ALLOC(working_node, scratch_pool); |
| |
| if (entry->copyfrom_url) |
| { |
| const char *relative_url; |
| |
| working_node->copyfrom_repos_id = repos_id; |
| relative_url = svn_uri_is_child(repos_root, entry->copyfrom_url, |
| NULL); |
| if (relative_url == NULL) |
| working_node->copyfrom_repos_path = ""; |
| else |
| { |
| /* copyfrom_repos_path is NOT a URI. decode into repos path. */ |
| working_node->copyfrom_repos_path = |
| svn_path_uri_decode(relative_url, scratch_pool); |
| } |
| working_node->copyfrom_revnum = entry->copyfrom_rev; |
| } |
| else |
| { |
| const char *parent_abspath = svn_dirent_dirname(entry_abspath, |
| scratch_pool); |
| const char *op_root_abspath; |
| const char *original_repos_relpath; |
| svn_revnum_t original_revision; |
| svn_error_t *err; |
| |
| /* The parent will *always* have info in the WORKING tree, since |
| we've been designated as COPIED but do not have our own |
| COPYFROM information. Therefore, our parent or a more distant |
| ancestor has that information. Grab the data. */ |
| err = svn_wc__db_scan_addition( |
| NULL, |
| &op_root_abspath, |
| NULL, NULL, NULL, |
| &original_repos_relpath, NULL, NULL, &original_revision, |
| db, |
| parent_abspath, |
| scratch_pool, scratch_pool); |
| |
| /* We could be reading the entries while in a transitional state |
| during an add/copy operation. The scan_addition *does* throw |
| errors sometimes. So clear anything that may come out of it, |
| and perform the copyfrom construction only when it looks like |
| we have a good/real set of return values. */ |
| svn_error_clear(err); |
| |
| /* We may have been copied from a mixed-rev working copy. We need |
| to simulate additional copies around revision changes. The old |
| code could separately store the revision, but NG needs to create |
| copies at each change. */ |
| if (err == NULL |
| && op_root_abspath != NULL |
| && original_repos_relpath != NULL |
| && SVN_IS_VALID_REVNUM(original_revision) |
| /* above is valid result testing. below is the key test. */ |
| && original_revision != entry->revision) |
| { |
| const char *relpath_to_entry = svn_dirent_is_child( |
| op_root_abspath, entry_abspath, NULL); |
| const char *new_copyfrom_relpath = svn_relpath_join( |
| original_repos_relpath, relpath_to_entry, scratch_pool); |
| |
| working_node->copyfrom_repos_id = repos_id; |
| working_node->copyfrom_repos_path = new_copyfrom_relpath; |
| working_node->copyfrom_revnum = entry->revision; |
| } |
| } |
| } |
| |
| if (entry->keep_local) |
| { |
| SVN_ERR_ASSERT(working_node != NULL); |
| SVN_ERR_ASSERT(entry->schedule == svn_wc_schedule_delete); |
| working_node->keep_local = TRUE; |
| } |
| |
| if (entry->absent) |
| { |
| SVN_ERR_ASSERT(working_node == NULL); |
| SVN_ERR_ASSERT(base_node != NULL); |
| base_node->presence = svn_wc__db_status_absent; |
| } |
| |
| if (entry->conflict_old) |
| { |
| actual_node = MAYBE_ALLOC(actual_node, scratch_pool); |
| actual_node->conflict_old = entry->conflict_old; |
| actual_node->conflict_new = entry->conflict_new; |
| actual_node->conflict_working = entry->conflict_wrk; |
| } |
| |
| if (entry->prejfile) |
| { |
| actual_node = MAYBE_ALLOC(actual_node, scratch_pool); |
| actual_node->prop_reject = entry->prejfile; |
| } |
| |
| if (entry->changelist) |
| { |
| actual_node = MAYBE_ALLOC(actual_node, scratch_pool); |
| actual_node->changelist = entry->changelist; |
| } |
| |
| /* ### set the text_mod value? */ |
| |
| if (entry->tree_conflict_data) |
| { |
| actual_node = MAYBE_ALLOC(actual_node, scratch_pool); |
| actual_node->tree_conflict_data = entry->tree_conflict_data; |
| } |
| |
| if (entry->file_external_path != NULL) |
| { |
| base_node = MAYBE_ALLOC(base_node, scratch_pool); |
| } |
| |
| /* Insert the base node. */ |
| if (base_node) |
| { |
| base_node->wc_id = wc_id; |
| base_node->local_relpath = local_relpath; |
| base_node->parent_relpath = parent_relpath; |
| base_node->revision = entry->revision; |
| base_node->last_mod_time = entry->text_time; |
| base_node->translated_size = entry->working_size; |
| |
| if (entry->depth != svn_depth_exclude) |
| base_node->depth = entry->depth; |
| else |
| { |
| base_node->presence = svn_wc__db_status_excluded; |
| base_node->depth = svn_depth_infinity; |
| } |
| |
| if (entry->deleted) |
| { |
| SVN_ERR_ASSERT(!entry->incomplete); |
| |
| base_node->presence = svn_wc__db_status_not_present; |
| /* ### should be svn_node_unknown, but let's store what we have. */ |
| base_node->kind = entry->kind; |
| } |
| else |
| { |
| base_node->kind = entry->kind; |
| |
| if (entry->incomplete) |
| { |
| /* ### nobody should have set the presence. */ |
| SVN_ERR_ASSERT(base_node->presence == svn_wc__db_status_normal); |
| base_node->presence = svn_wc__db_status_incomplete; |
| } |
| } |
| |
| if (entry->kind == svn_node_dir) |
| base_node->checksum = NULL; |
| else |
| SVN_ERR(svn_checksum_parse_hex(&base_node->checksum, svn_checksum_md5, |
| entry->checksum, scratch_pool)); |
| |
| if (repos_root) |
| { |
| base_node->repos_id = repos_id; |
| |
| /* repos_relpath is NOT a URI. decode as appropriate. */ |
| if (entry->url != NULL) |
| { |
| const char *relative_url = svn_uri_is_child(repos_root, |
| entry->url, |
| scratch_pool); |
| |
| if (relative_url == NULL) |
| base_node->repos_relpath = ""; |
| else |
| base_node->repos_relpath = svn_path_uri_decode(relative_url, |
| scratch_pool); |
| } |
| else |
| { |
| const char *base_path = svn_uri_is_child(repos_root, |
| this_dir->url, |
| scratch_pool); |
| if (base_path == NULL) |
| base_node->repos_relpath = entry->name; |
| else |
| base_node->repos_relpath = |
| svn_dirent_join(svn_path_uri_decode(base_path, scratch_pool), |
| entry->name, |
| scratch_pool); |
| } |
| } |
| |
| /* TODO: These values should always be present, if they are missing |
| during an upgrade, set a flag, and then ask the user to talk to the |
| server. |
| |
| Note: cmt_rev is the distinguishing value. The others may be 0 or |
| NULL if the corresponding revprop has been deleted. */ |
| base_node->changed_rev = entry->cmt_rev; |
| base_node->changed_date = entry->cmt_date; |
| base_node->changed_author = entry->cmt_author; |
| |
| SVN_ERR(insert_base_node(sdb, base_node, scratch_pool)); |
| |
| /* We have to insert the lock after the base node, because the node |
| must exist to lookup various bits of repos related information for |
| the abs path. */ |
| if (entry->lock_token && create_locks) |
| { |
| svn_wc__db_lock_t lock; |
| |
| lock.token = entry->lock_token; |
| lock.owner = entry->lock_owner; |
| lock.comment = entry->lock_comment; |
| lock.date = entry->lock_creation_date; |
| |
| SVN_ERR(svn_wc__db_lock_add(db, entry_abspath, &lock, scratch_pool)); |
| } |
| |
| /* Now, update the file external information. |
| ### This is a hack! */ |
| if (entry->file_external_path) |
| { |
| svn_sqlite__stmt_t *stmt; |
| const char *str; |
| |
| SVN_ERR(svn_wc__serialize_file_external(&str, |
| entry->file_external_path, |
| &entry->file_external_peg_rev, |
| &entry->file_external_rev, |
| scratch_pool)); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, |
| STMT_UPDATE_FILE_EXTERNAL)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "iss", |
| (apr_uint64_t)1 /* wc_id */, |
| entry->name, |
| str)); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| } |
| } |
| |
| /* Insert the working node. */ |
| if (working_node) |
| { |
| working_node->wc_id = wc_id; |
| working_node->local_relpath = local_relpath; |
| working_node->parent_relpath = parent_relpath; |
| working_node->changed_rev = SVN_INVALID_REVNUM; |
| working_node->last_mod_time = entry->text_time; |
| working_node->translated_size = entry->working_size; |
| |
| if (entry->depth != svn_depth_exclude) |
| working_node->depth = entry->depth; |
| else |
| { |
| working_node->presence = svn_wc__db_status_excluded; |
| working_node->depth = svn_depth_infinity; |
| } |
| |
| if (entry->kind == svn_node_dir) |
| working_node->checksum = NULL; |
| else |
| SVN_ERR(svn_checksum_parse_hex(&working_node->checksum, |
| svn_checksum_md5, |
| entry->checksum, scratch_pool)); |
| |
| if (entry->schedule == svn_wc_schedule_delete) |
| { |
| if (entry->incomplete) |
| { |
| /* A transition from a schedule-delete state to incomplete |
| is most likely caused by svn_wc_remove_from_revision_control. |
| By setting this node's presence to 'incomplete', we will |
| lose the scheduling information, but this directory is |
| being deleted (by the logs) ... we won't need the state. */ |
| working_node->presence = svn_wc__db_status_incomplete; |
| } |
| else |
| { |
| /* If the entry is part of a COPIED (not REPLACED) subtree, |
| then the deletion is referring to the WORKING node, not |
| the BASE node. */ |
| if (entry->copied |
| || (this_dir->copied |
| && this_dir->schedule == svn_wc_schedule_add)) |
| working_node->presence = svn_wc__db_status_not_present; |
| else |
| working_node->presence = svn_wc__db_status_base_deleted; |
| } |
| |
| /* ### should be svn_node_unknown, but let's store what we have. */ |
| working_node->kind = entry->kind; |
| } |
| else |
| { |
| /* presence == normal */ |
| working_node->kind = entry->kind; |
| |
| if (entry->incomplete) |
| { |
| /* We shouldn't be overwriting another status. */ |
| SVN_ERR_ASSERT(working_node->presence |
| == svn_wc__db_status_normal); |
| working_node->presence = svn_wc__db_status_incomplete; |
| } |
| } |
| |
| /* These should generally be unset for added and deleted files, |
| and contain whatever information we have for copied files. Let's |
| just store whatever we have. |
| |
| Note: cmt_rev is the distinguishing value. The others may be 0 or |
| NULL if the corresponding revprop has been deleted. */ |
| working_node->changed_rev = entry->cmt_rev; |
| working_node->changed_date = entry->cmt_date; |
| working_node->changed_author = entry->cmt_author; |
| |
| SVN_ERR(insert_working_node(sdb, working_node, scratch_pool)); |
| } |
| |
| /* Insert the actual node. */ |
| if (actual_node || always_create_actual) |
| { |
| actual_node = MAYBE_ALLOC(actual_node, scratch_pool); |
| |
| actual_node->wc_id = wc_id; |
| actual_node->local_relpath = local_relpath; |
| actual_node->parent_relpath = parent_relpath; |
| |
| SVN_ERR(insert_actual_node(sdb, actual_node, scratch_pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| struct entries_write_baton |
| { |
| svn_wc__db_t *db; |
| apr_int64_t repos_id; |
| apr_int64_t wc_id; |
| const char *local_abspath; |
| apr_hash_t *entries; |
| }; |
| |
| /* Writes entries inside a sqlite transaction |
| Implements svn_sqlite__transaction_callback_t. */ |
| static svn_error_t * |
| entries_write_new_cb(void *baton, |
| svn_sqlite__db_t *sdb, |
| apr_pool_t *scratch_pool) |
| { |
| struct entries_write_baton *ewb = baton; |
| svn_wc__db_t *db = ewb->db; |
| const char *local_abspath = ewb->local_abspath; |
| const svn_wc_entry_t *this_dir; |
| apr_hash_index_t *hi; |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| const char *repos_root; |
| |
| /* Get a copy of the "this dir" entry for comparison purposes. */ |
| this_dir = apr_hash_get(ewb->entries, SVN_WC_ENTRY_THIS_DIR, |
| APR_HASH_KEY_STRING); |
| |
| /* If there is no "this dir" entry, something is wrong. */ |
| if (! this_dir) |
| return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, |
| _("No default entry in directory '%s'"), |
| svn_dirent_local_style(local_abspath, |
| iterpool)); |
| repos_root = this_dir->repos; |
| |
| /* Write out "this dir" */ |
| SVN_ERR(write_entry(db, sdb, ewb->wc_id, ewb->repos_id, repos_root, |
| this_dir, SVN_WC_ENTRY_THIS_DIR, local_abspath, |
| this_dir, FALSE, FALSE, iterpool)); |
| |
| for (hi = apr_hash_first(scratch_pool, ewb->entries); hi; |
| hi = apr_hash_next(hi)) |
| { |
| const char *name = svn_apr_hash_index_key(hi); |
| const svn_wc_entry_t *this_entry = svn_apr_hash_index_val(hi); |
| const char *child_abspath; |
| |
| svn_pool_clear(iterpool); |
| |
| /* Don't rewrite the "this dir" entry! */ |
| if (strcmp(name, SVN_WC_ENTRY_THIS_DIR) == 0) |
| continue; |
| |
| /* Write the entry. Pass TRUE for create locks, because we still |
| use this function for upgrading old working copies. */ |
| child_abspath = svn_dirent_join(local_abspath, name, iterpool); |
| SVN_ERR(write_entry(db, sdb, ewb->wc_id, ewb->repos_id, repos_root, |
| this_entry, name, child_abspath, this_dir, |
| FALSE, TRUE, |
| iterpool)); |
| } |
| |
| svn_pool_destroy(iterpool); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__write_upgraded_entries(svn_wc__db_t *db, |
| svn_sqlite__db_t *sdb, |
| apr_int64_t repos_id, |
| apr_int64_t wc_id, |
| const char *local_abspath, |
| apr_hash_t *entries, |
| apr_pool_t *scratch_pool) |
| { |
| struct entries_write_baton ewb; |
| |
| ewb.db = db; |
| ewb.repos_id = repos_id; |
| ewb.wc_id = wc_id; |
| ewb.local_abspath = local_abspath; |
| ewb.entries = entries; |
| |
| /* Run this operation in a transaction to speed up SQLite. |
| See http://www.sqlite.org/faq.html#q19 for more details */ |
| return svn_error_return( |
| svn_sqlite__with_transaction(sdb, entries_write_new_cb, &ewb, |
| scratch_pool)); |
| } |
| |
| struct write_one_entry_baton |
| { |
| svn_wc__db_t *db; |
| const char *local_abspath; |
| const svn_wc_entry_t *this_dir; |
| const svn_wc_entry_t *this_entry; |
| }; |
| |
| /* Rewrites a single entry inside a sqlite transaction |
| Implements svn_sqlite__transaction_callback_t. */ |
| static svn_error_t * |
| write_one_entry_cb(void *baton, |
| svn_sqlite__db_t *sdb, |
| apr_pool_t *scratch_pool) |
| { |
| struct write_one_entry_baton *woeb = baton; |
| svn_wc__db_t *db = woeb->db; |
| const char *local_abspath = woeb->local_abspath; |
| const svn_wc_entry_t *this_dir = woeb->this_dir; |
| const svn_wc_entry_t *this_entry = woeb->this_entry; |
| const char *this_abspath = svn_dirent_join(local_abspath, this_entry->name, |
| scratch_pool); |
| const void *base_props = NULL; |
| const void *working_props = NULL; |
| const void *actual_props = NULL; |
| apr_size_t base_prop_len; |
| apr_size_t working_prop_len; |
| apr_size_t actual_prop_len; |
| apr_hash_t *dav_cache; |
| const svn_checksum_t *base_checksum; |
| svn_sqlite__stmt_t *stmt; |
| const char *repos_root; |
| apr_int64_t repos_id; |
| apr_int64_t wc_id; |
| svn_error_t *err; |
| svn_boolean_t got_row; |
| |
| SVN_ERR_ASSERT(this_dir && this_entry); |
| |
| /* Get the repos ID. */ |
| if (this_dir->uuid != NULL) |
| { |
| /* ### does this need to be done on a per-entry basis instead of |
| ### the per-directory way we do it now? me thinks yes... |
| ### |
| ### when do we harvest repository entries which no longer have |
| ### any members? */ |
| SVN_ERR(svn_wc__db_repos_ensure(&repos_id, db, local_abspath, |
| this_dir->repos, this_dir->uuid, |
| scratch_pool)); |
| repos_root = this_dir->repos; |
| } |
| else |
| { |
| repos_id = 0; |
| repos_root = NULL; |
| } |
| |
| SVN_ERR(fetch_wc_id(&wc_id, sdb)); |
| |
| /* Before we nuke all the nodes, we need to get a few values */ |
| |
| /* The dav cache is not in STMT_SELECT_BASE_NODE */ |
| err = svn_wc__db_base_get_dav_cache(&dav_cache, db, this_abspath, |
| scratch_pool, scratch_pool); |
| if (err) |
| { |
| if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) |
| return svn_error_return(err); |
| svn_error_clear(err); /* No BASE record */ |
| dav_cache = NULL; |
| } |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_BASE_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, this_entry->name)); |
| SVN_ERR(svn_sqlite__step(&got_row, stmt)); |
| if (got_row) |
| { |
| base_props = svn_sqlite__column_blob(stmt, 13, &base_prop_len, |
| scratch_pool); |
| |
| err = svn_sqlite__column_checksum(&base_checksum, stmt, 5, scratch_pool); |
| SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); |
| } |
| else |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_WORKING_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, this_entry->name)); |
| SVN_ERR(svn_sqlite__step(&got_row, stmt)); |
| if (got_row) |
| { |
| /* No need to store the working checksum, that is stored in the entry */ |
| working_props = svn_sqlite__column_blob(stmt, 15, &working_prop_len, |
| scratch_pool); |
| } |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_ACTUAL_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, this_entry->name)); |
| SVN_ERR(svn_sqlite__step(&got_row, stmt)); |
| if (got_row) |
| { |
| actual_props = svn_sqlite__column_blob(stmt, 6, &actual_prop_len, |
| scratch_pool); |
| } |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| /* Remove the WORKING, BASE and ACTUAL nodes for this entry */ |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_DELETE_WORKING_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, this_entry->name)); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_DELETE_BASE_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, this_entry->name)); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_DELETE_ACTUAL_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, this_entry->name)); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| |
| SVN_ERR(write_entry(db, sdb, wc_id, repos_id, repos_root, this_entry, |
| this_entry->name, this_abspath, this_dir, |
| actual_props != NULL, FALSE, scratch_pool)); |
| |
| if (dav_cache) |
| SVN_ERR(svn_wc__db_base_set_dav_cache(db, this_abspath, dav_cache, |
| scratch_pool)); |
| |
| if (base_props) |
| { |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_UPDATE_BASE_PROPS)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isb", wc_id, this_entry->name, |
| base_props, base_prop_len)); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| } |
| |
| if (working_props) |
| { |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, |
| STMT_UPDATE_WORKING_PROPS)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isb", wc_id, this_entry->name, |
| working_props, working_prop_len)); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| } |
| |
| if (actual_props) |
| { |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, |
| STMT_UPDATE_ACTUAL_PROPS)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "isb", wc_id, this_entry->name, |
| actual_props, actual_prop_len)); |
| SVN_ERR(svn_sqlite__step_done(stmt)); |
| } |
| |
| /* TODO: Update base checksum if needed */ |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| write_one_entry(svn_wc__db_t *db, |
| const char *local_abspath, |
| const svn_wc_entry_t *this_dir, |
| const svn_wc_entry_t *this_entry, |
| apr_pool_t *scratch_pool) |
| { |
| struct write_one_entry_baton woeb; |
| svn_sqlite__db_t *sdb; |
| |
| /* ### need the SDB so we can jam rows directly into it. */ |
| SVN_ERR(svn_wc__db_temp_get_sdb(&sdb, db, local_abspath, FALSE, |
| scratch_pool, scratch_pool)); |
| woeb.db = db; |
| woeb.local_abspath = local_abspath; |
| woeb.this_dir = this_dir; |
| woeb.this_entry = this_entry; |
| |
| /* Run this operation in a transaction to speed up SQLite. |
| See http://www.sqlite.org/faq.html#q19 for more details */ |
| return svn_error_return( |
| svn_sqlite__with_transaction(sdb, write_one_entry_cb, &woeb, |
| scratch_pool)); |
| } |
| |
| |
| |
| /* Update an entry NAME in ENTRIES, according to the combination of |
| entry data found in ENTRY and masked by MODIFY_FLAGS. If the entry |
| already exists, the requested changes will be folded (merged) into |
| the entry's existing state. If the entry doesn't exist, the entry |
| will be created with exactly those properties described by the set |
| of changes. Also cleanups meaningless fields combinations. |
| |
| The SVN_WC__ENTRY_MODIFY_FORCE flag is ignored. |
| |
| POOL may be used to allocate memory referenced by ENTRIES. |
| */ |
| static svn_error_t * |
| fold_entry(apr_hash_t *entries, |
| const char *name, |
| apr_uint64_t modify_flags, |
| const svn_wc_entry_t *entry, |
| apr_pool_t *pool) |
| { |
| svn_wc_entry_t *cur_entry |
| = apr_hash_get(entries, name, APR_HASH_KEY_STRING); |
| |
| SVN_ERR_ASSERT(name != NULL); |
| |
| if (! cur_entry) |
| cur_entry = alloc_entry(pool); |
| |
| /* Name (just a safeguard here, really) */ |
| if (! cur_entry->name) |
| cur_entry->name = apr_pstrdup(pool, name); |
| |
| /* Revision */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_REVISION) |
| cur_entry->revision = entry->revision; |
| |
| /* Ancestral URL in repository */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_URL) |
| cur_entry->url = entry->url ? apr_pstrdup(pool, entry->url) : NULL; |
| |
| /* Kind */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_KIND) |
| cur_entry->kind = entry->kind; |
| |
| /* Schedule */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE) |
| cur_entry->schedule = entry->schedule; |
| |
| /* Checksum */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_CHECKSUM) |
| cur_entry->checksum = entry->checksum |
| ? apr_pstrdup(pool, entry->checksum) |
| : NULL; |
| |
| /* Copy-related stuff */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_COPIED) |
| cur_entry->copied = entry->copied; |
| |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_COPYFROM_URL) |
| cur_entry->copyfrom_url = entry->copyfrom_url |
| ? apr_pstrdup(pool, entry->copyfrom_url) |
| : NULL; |
| |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_COPYFROM_REV) |
| cur_entry->copyfrom_rev = entry->copyfrom_rev; |
| |
| /* Deleted state */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_DELETED) |
| cur_entry->deleted = entry->deleted; |
| |
| /* Absent state */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_ABSENT) |
| cur_entry->absent = entry->absent; |
| |
| /* Incomplete state */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_INCOMPLETE) |
| cur_entry->incomplete = entry->incomplete; |
| |
| /* Text/prop modification times */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_TEXT_TIME) |
| cur_entry->text_time = entry->text_time; |
| |
| /* Conflict stuff */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_CONFLICT_OLD) |
| cur_entry->conflict_old = entry->conflict_old |
| ? apr_pstrdup(pool, entry->conflict_old) |
| : NULL; |
| |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_CONFLICT_NEW) |
| cur_entry->conflict_new = entry->conflict_new |
| ? apr_pstrdup(pool, entry->conflict_new) |
| : NULL; |
| |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_CONFLICT_WRK) |
| cur_entry->conflict_wrk = entry->conflict_wrk |
| ? apr_pstrdup(pool, entry->conflict_wrk) |
| : NULL; |
| |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_PREJFILE) |
| cur_entry->prejfile = entry->prejfile |
| ? apr_pstrdup(pool, entry->prejfile) |
| : NULL; |
| |
| /* Last-commit stuff */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_CMT_REV) |
| cur_entry->cmt_rev = entry->cmt_rev; |
| |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_CMT_DATE) |
| cur_entry->cmt_date = entry->cmt_date; |
| |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_CMT_AUTHOR) |
| cur_entry->cmt_author = entry->cmt_author |
| ? apr_pstrdup(pool, entry->cmt_author) |
| : NULL; |
| |
| /* LOCK flags are no longer passed to entry_modify(). */ |
| |
| /* changelist is no longer modified with this function. */ |
| |
| /* has-props, prop-mods, cachable-props, and present-props are deprecated, |
| so we do not copy them. */ |
| |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_KEEP_LOCAL) |
| cur_entry->keep_local = entry->keep_local; |
| |
| /* Note that we don't bother to fold entry->depth, because it is |
| only meaningful on the this-dir entry anyway. */ |
| |
| /* tree_conflict_data is never modified via entry_t. */ |
| |
| /* Absorb defaults from the parent dir, if any, unless this is a |
| subdir entry. */ |
| if (cur_entry->kind != svn_node_dir) |
| { |
| const svn_wc_entry_t *default_entry |
| = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING); |
| if (default_entry) |
| take_from_entry(default_entry, cur_entry, pool); |
| } |
| |
| /* Cleanup meaningless fields */ |
| |
| /* ### svn_wc_schedule_delete is the minimal value. We need it because it's |
| impossible to NULLify copyfrom_url with log-instructions. |
| |
| Note that I tried to find the smallest collection not to clear these |
| fields for, but this condition still fails the test suite: |
| |
| !(entry->schedule == svn_wc_schedule_add |
| || entry->schedule == svn_wc_schedule_replace |
| || (entry->schedule == svn_wc_schedule_normal && entry->copied))) |
| |
| */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE |
| && entry->schedule == svn_wc_schedule_delete) |
| { |
| cur_entry->copied = FALSE; |
| cur_entry->copyfrom_rev = SVN_INVALID_REVNUM; |
| cur_entry->copyfrom_url = NULL; |
| } |
| |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_WORKING_SIZE) |
| cur_entry->working_size = entry->working_size; |
| |
| /* keep_local makes sense only when we are going to delete directory. */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE |
| && entry->schedule != svn_wc_schedule_delete) |
| { |
| cur_entry->keep_local = FALSE; |
| } |
| |
| /* File externals. */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_FILE_EXTERNAL) |
| { |
| cur_entry->file_external_path = (entry->file_external_path |
| ? apr_pstrdup(pool, |
| entry->file_external_path) |
| : NULL); |
| cur_entry->file_external_peg_rev = entry->file_external_peg_rev; |
| cur_entry->file_external_rev = entry->file_external_rev; |
| } |
| |
| /* Make sure the entry exists in the entries hash. Possibly it |
| already did, in which case this could have been skipped, but what |
| the heck. */ |
| apr_hash_set(entries, cur_entry->name, APR_HASH_KEY_STRING, cur_entry); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__entry_remove(svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc_adm_access_t *adm_access; |
| const char *name; |
| const char *parent_dir; |
| |
| /* First: Update the entry cache */ |
| svn_dirent_split(local_abspath, &parent_dir, &name, scratch_pool); |
| adm_access = svn_wc__adm_retrieve_internal2(db, parent_dir, scratch_pool); |
| |
| if (adm_access != NULL) |
| { |
| apr_hash_t *entries = svn_wc__adm_access_entries(adm_access); |
| |
| if (entries != NULL) |
| apr_hash_set(entries, name, APR_HASH_KEY_STRING, NULL); |
| } |
| |
| /* And then remove it from the database */ |
| return svn_error_return(svn_wc__db_temp_op_remove_entry(db, local_abspath, |
| FALSE, |
| scratch_pool)); |
| } |
| |
| |
| /* Our general purpose intelligence module for handling a scheduling change |
| to a single entry. |
| |
| Given an ENTRY with name NAME, examine the caller's requested scheduling |
| change and the current state of the entry and its directory entry |
| THIS_DIR_ENTRY, which can be equal to ENTRY. |
| |
| Determine the final schedule for the entry based on NEW_SCHEDULE and the |
| entries. |
| |
| The output can be: |
| * *SKIP_SCHEDULE_CHANGE set to true, when no schedule change is necessary. |
| * *DELETE_ENTRY true, when the entry should just be removed. |
| * Or a schedule change. |
| |
| In all these cases *RESULT_SCHEDULE contains the new schedule value. |
| |
| SCRATCH_POOL can be used for local allocations. |
| */ |
| static svn_error_t * |
| fold_scheduling(svn_boolean_t *skip_schedule_change, |
| svn_boolean_t *delete_entry, |
| svn_wc_schedule_t *result_schedule, |
| const svn_wc_entry_t *this_dir_entry, |
| const svn_wc_entry_t *entry, |
| svn_wc_schedule_t new_schedule, |
| const char *name, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(this_dir_entry); |
| |
| *skip_schedule_change = FALSE; |
| *delete_entry = FALSE; |
| *result_schedule = new_schedule; |
| |
| /* The only operation valid on an item not already in revision |
| control is addition. */ |
| if (! entry) |
| { |
| if (new_schedule == svn_wc_schedule_add) |
| return SVN_NO_ERROR; |
| else |
| return |
| svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL, |
| _("'%s' is not under version control"), |
| name); |
| } |
| |
| /* At this point, we know the following things: |
| |
| 1. There is already an entry for this item in the entries file |
| whose existence is either _normal or _added (or about to |
| become such), which for our purposes mean the same thing. |
| |
| 2. We have been asked to merge in a state change, not to |
| explicitly set the state. */ |
| |
| /* Here are some cases that are parent-directory sensitive. |
| Basically, we make sure that we are not allowing versioned |
| resources to just sorta dangle below directories marked for |
| deletion. */ |
| if ((entry != this_dir_entry) |
| && (this_dir_entry->schedule == svn_wc_schedule_delete)) |
| { |
| if (new_schedule == svn_wc_schedule_add) |
| return |
| svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL, |
| _("Can't add '%s' to deleted directory; " |
| "try undeleting its parent directory first"), |
| name); |
| if (new_schedule == svn_wc_schedule_replace) |
| return |
| svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL, |
| _("Can't replace '%s' in deleted directory; " |
| "try undeleting its parent directory first"), |
| name); |
| } |
| |
| if (entry->absent && (new_schedule == svn_wc_schedule_add)) |
| { |
| return svn_error_createf |
| (SVN_ERR_WC_SCHEDULE_CONFLICT, NULL, |
| _("'%s' is marked as absent, so it cannot be scheduled for addition"), |
| name); |
| } |
| |
| switch (entry->schedule) |
| { |
| case svn_wc_schedule_normal: |
| switch (new_schedule) |
| { |
| case svn_wc_schedule_normal: |
| /* Normal is a trivial no-op case. Reset the |
| schedule modification bit and move along. */ |
| *skip_schedule_change = TRUE; |
| return SVN_NO_ERROR; |
| |
| case svn_wc_schedule_delete: |
| case svn_wc_schedule_replace: |
| /* These are all good. */ |
| return SVN_NO_ERROR; |
| |
| case svn_wc_schedule_add: |
| /* You can't add something that's already been added to |
| revision control... unless it's got a 'deleted' state */ |
| if (! entry->deleted) |
| return |
| svn_error_createf |
| (SVN_ERR_WC_SCHEDULE_CONFLICT, NULL, |
| _("Entry '%s' is already under version control"), name); |
| } |
| break; |
| |
| case svn_wc_schedule_add: |
| switch (new_schedule) |
| { |
| case svn_wc_schedule_normal: |
| case svn_wc_schedule_add: |
| case svn_wc_schedule_replace: |
| /* These are all no-op cases. Normal is obvious, as is add. |
| ### The 'add' case is not obvious: above, we throw an error if |
| ### already versioned, so why not here too? |
| Replace on an entry marked for addition breaks down to |
| (add + (delete + add)), which resolves to just (add), and |
| since this entry is already marked with (add), this too |
| is a no-op. */ |
| *skip_schedule_change = TRUE; |
| return SVN_NO_ERROR; |
| |
| case svn_wc_schedule_delete: |
| /* Not-yet-versioned item being deleted. If the original |
| entry was not marked as "deleted", then remove the entry. |
| Else, return the entry to a 'normal' state, preserving |
| ### What does it mean for an entry be schedule-add and |
| ### deleted at once, and why change schedule to normal? |
| the "deleted" flag. Check that we are not trying to |
| remove the SVN_WC_ENTRY_THIS_DIR entry as that would |
| leave the entries file in an invalid state. */ |
| SVN_ERR_ASSERT(entry != this_dir_entry); |
| if (! entry->deleted) |
| *delete_entry = TRUE; |
| else |
| *result_schedule = svn_wc_schedule_normal; |
| return SVN_NO_ERROR; |
| } |
| break; |
| |
| case svn_wc_schedule_delete: |
| switch (new_schedule) |
| { |
| case svn_wc_schedule_normal: |
| /* Reverting a delete results in normal */ |
| return SVN_NO_ERROR; |
| |
| case svn_wc_schedule_delete: |
| /* These are no-op cases. */ |
| *skip_schedule_change = TRUE; |
| return SVN_NO_ERROR; |
| |
| |
| case svn_wc_schedule_add: |
| /* Re-adding an entry marked for deletion? This is really a |
| replace operation. */ |
| *result_schedule = svn_wc_schedule_replace; |
| return SVN_NO_ERROR; |
| |
| |
| case svn_wc_schedule_replace: |
| /* Replacing an item marked for deletion breaks down to |
| (delete + (delete + add)), which might deserve a warning, |
| but whatever. */ |
| return SVN_NO_ERROR; |
| |
| } |
| break; |
| |
| case svn_wc_schedule_replace: |
| switch (new_schedule) |
| { |
| case svn_wc_schedule_normal: |
| /* Reverting replacements results normal. */ |
| return SVN_NO_ERROR; |
| |
| case svn_wc_schedule_add: |
| /* Adding a to-be-replaced entry breaks down to ((delete + |
| add) + add) which might deserve a warning, but we'll just |
| no-op it. */ |
| case svn_wc_schedule_replace: |
| /* Replacing a to-be-replaced entry breaks down to ((delete |
| + add) + (delete + add)), which is insane! Make up your |
| friggin' mind, dude! :-) Well, we'll no-op this one, |
| too. */ |
| *skip_schedule_change = TRUE; |
| return SVN_NO_ERROR; |
| |
| |
| case svn_wc_schedule_delete: |
| /* Deleting a to-be-replaced entry breaks down to ((delete + |
| add) + delete) which resolves to a flat deletion. */ |
| *result_schedule = svn_wc_schedule_delete; |
| return SVN_NO_ERROR; |
| |
| } |
| break; |
| |
| default: |
| return |
| svn_error_createf |
| (SVN_ERR_WC_SCHEDULE_CONFLICT, NULL, |
| _("Entry '%s' has illegal schedule"), name); |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| svn_error_t * |
| svn_wc__entry_modify2(svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_node_kind_t kind, |
| svn_boolean_t parent_stub, |
| svn_wc_entry_t *entry, |
| apr_uint64_t modify_flags, |
| apr_pool_t *scratch_pool) |
| { |
| apr_pool_t *subpool = svn_pool_create(scratch_pool); |
| svn_error_t *err; |
| apr_hash_t *entries; |
| svn_wc_adm_access_t *adm_access; |
| const char *adm_abspath; |
| const char *name; |
| |
| SVN_ERR_ASSERT(entry); |
| |
| SVN_ERR(get_entry_access_info(&adm_abspath, &name, db, local_abspath, |
| kind, parent_stub, subpool, subpool)); |
| |
| /* Load ADM_ABSPATH's whole entries file: |
| Is there an existing access baton for this path? */ |
| adm_access = svn_wc__adm_retrieve_internal2(db, adm_abspath, subpool); |
| if (adm_access == NULL) |
| { |
| /* ### should we have some kind of write check here? */ |
| |
| /* Don't bother caching entries; we've got no place to store 'em. */ |
| SVN_ERR(read_entries(&entries, db, adm_abspath, subpool, subpool)); |
| } |
| else |
| { |
| /* Are we allowed to write to this admin area? */ |
| SVN_ERR(svn_wc__write_check(db, svn_wc__adm_access_abspath(adm_access), |
| subpool)); |
| |
| SVN_ERR(svn_wc_entries_read(&entries, adm_access, TRUE, subpool)); |
| } |
| |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE) |
| { |
| /* We may just want to force the scheduling change in. Otherwise, |
| call our special function to fold the change in. */ |
| if (!(modify_flags & SVN_WC__ENTRY_MODIFY_FORCE)) |
| { |
| svn_boolean_t skip_schedule_change; |
| svn_boolean_t delete_entry; |
| |
| /* If scheduling changes were made, we have a special routine to |
| manage those modifications. */ |
| SVN_ERR(fold_scheduling(&skip_schedule_change, |
| &delete_entry, |
| &entry->schedule, |
| apr_hash_get(entries, "", |
| APR_HASH_KEY_STRING), |
| apr_hash_get(entries, name, |
| APR_HASH_KEY_STRING), |
| entry->schedule, |
| name, subpool)); |
| |
| /* Check if the scheduling folding resulted in removing this entry */ |
| if (delete_entry) |
| { |
| SVN_ERR(svn_wc__entry_remove(db, local_abspath, subpool)); |
| svn_pool_destroy(subpool); |
| return SVN_NO_ERROR; |
| } |
| |
| if (skip_schedule_change) |
| modify_flags &= ~SVN_WC__ENTRY_MODIFY_SCHEDULE; |
| } |
| } |
| |
| /* Fold in the changes, and write them out. */ |
| SVN_ERR(fold_entry(entries, name, modify_flags, entry, |
| adm_access |
| ? svn_wc_adm_access_pool(adm_access) |
| : subpool)); |
| |
| err = write_one_entry(db, adm_abspath, |
| apr_hash_get(entries, "", APR_HASH_KEY_STRING), |
| apr_hash_get(entries, name, APR_HASH_KEY_STRING), |
| subpool); |
| |
| svn_pool_destroy(subpool); /* Close wc.db handles */ |
| |
| SVN_ERR(err); |
| |
| if (adm_access) |
| { |
| /* ### is this needed? didn't we already pull the hash from here? */ |
| svn_wc__adm_access_set_entries(adm_access, entries); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_wc_entry_t * |
| svn_wc_entry_dup(const svn_wc_entry_t *entry, apr_pool_t *pool) |
| { |
| svn_wc_entry_t *dupentry = apr_palloc(pool, sizeof(*dupentry)); |
| |
| /* Perform a trivial copy ... */ |
| *dupentry = *entry; |
| |
| /* ...and then re-copy stuff that needs to be duped into our pool. */ |
| if (entry->name) |
| dupentry->name = apr_pstrdup(pool, entry->name); |
| if (entry->url) |
| dupentry->url = apr_pstrdup(pool, entry->url); |
| if (entry->repos) |
| dupentry->repos = apr_pstrdup(pool, entry->repos); |
| if (entry->uuid) |
| dupentry->uuid = apr_pstrdup(pool, entry->uuid); |
| if (entry->copyfrom_url) |
| dupentry->copyfrom_url = apr_pstrdup(pool, entry->copyfrom_url); |
| if (entry->conflict_old) |
| dupentry->conflict_old = apr_pstrdup(pool, entry->conflict_old); |
| if (entry->conflict_new) |
| dupentry->conflict_new = apr_pstrdup(pool, entry->conflict_new); |
| if (entry->conflict_wrk) |
| dupentry->conflict_wrk = apr_pstrdup(pool, entry->conflict_wrk); |
| if (entry->prejfile) |
| dupentry->prejfile = apr_pstrdup(pool, entry->prejfile); |
| if (entry->checksum) |
| dupentry->checksum = apr_pstrdup(pool, entry->checksum); |
| if (entry->cmt_author) |
| dupentry->cmt_author = apr_pstrdup(pool, entry->cmt_author); |
| if (entry->lock_token) |
| dupentry->lock_token = apr_pstrdup(pool, entry->lock_token); |
| if (entry->lock_owner) |
| dupentry->lock_owner = apr_pstrdup(pool, entry->lock_owner); |
| if (entry->lock_comment) |
| dupentry->lock_comment = apr_pstrdup(pool, entry->lock_comment); |
| if (entry->changelist) |
| dupentry->changelist = apr_pstrdup(pool, entry->changelist); |
| |
| /* NOTE: we do not dup cachable_props or present_props since they |
| are deprecated. Use "" to indicate "nothing cachable or cached". */ |
| dupentry->cachable_props = ""; |
| dupentry->present_props = ""; |
| |
| if (entry->tree_conflict_data) |
| dupentry->tree_conflict_data = apr_pstrdup(pool, |
| entry->tree_conflict_data); |
| if (entry->file_external_path) |
| dupentry->file_external_path = apr_pstrdup(pool, |
| entry->file_external_path); |
| return dupentry; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__tweak_entry(svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_node_kind_t kind, |
| svn_boolean_t parent_stub, |
| const char *new_url, |
| svn_revnum_t new_rev, |
| svn_boolean_t allow_removal, |
| apr_pool_t *scratch_pool) |
| { |
| const svn_wc_entry_t *entry; |
| svn_wc_entry_t tmp_entry; |
| apr_uint64_t modify_flags = 0; |
| |
| SVN_ERR(svn_wc__get_entry(&entry, db, local_abspath, FALSE, kind, |
| parent_stub, scratch_pool, scratch_pool)); |
| |
| if (new_url != NULL |
| && (! entry->url || strcmp(new_url, entry->url))) |
| { |
| modify_flags |= SVN_WC__ENTRY_MODIFY_URL; |
| tmp_entry.url = new_url; |
| } |
| |
| if ((SVN_IS_VALID_REVNUM(new_rev)) |
| && (entry->schedule != svn_wc_schedule_add) |
| && (entry->schedule != svn_wc_schedule_replace) |
| && (entry->copied != TRUE) |
| && (entry->revision != new_rev)) |
| { |
| modify_flags |= SVN_WC__ENTRY_MODIFY_REVISION; |
| tmp_entry.revision = new_rev; |
| } |
| |
| /* As long as this function is only called as a helper to |
| svn_wc__do_update_cleanup, then it's okay to remove any entry |
| under certain circumstances: |
| |
| If the entry is still marked 'deleted', then the server did not |
| re-add it. So it's really gone in this revision, thus we remove |
| the entry. |
| |
| If the entry is still marked 'absent' and yet is not the same |
| revision as new_rev, then the server did not re-add it, nor |
| re-absent it, so we can remove the entry. |
| |
| ### This function cannot always determine whether removal is |
| ### appropriate, hence the ALLOW_REMOVAL flag. It's all a bit of a |
| ### mess. */ |
| if (allow_removal |
| && (entry->deleted || (entry->absent && entry->revision != new_rev))) |
| { |
| SVN_ERR(svn_wc__entry_remove(db, local_abspath, scratch_pool)); |
| } |
| else if (modify_flags) |
| { |
| SVN_ERR(svn_wc__entry_modify2(db, local_abspath, entry->kind, parent_stub, |
| &tmp_entry, modify_flags, scratch_pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /*** Generic Entry Walker */ |
| |
| /* A recursive entry-walker, helper for svn_wc_walk_entries3(). |
| * |
| * For this directory (DIRPATH, ADM_ACCESS), call the "found_entry" callback |
| * in WALK_CALLBACKS, passing WALK_BATON to it. Then, for each versioned |
| * entry in this directory, call the "found entry" callback and then recurse |
| * (if it is a directory and if DEPTH allows). |
| * |
| * If SHOW_HIDDEN is true, include entries that are in a 'deleted' or |
| * 'absent' state (and not scheduled for re-addition), else skip them. |
| * |
| * Call CANCEL_FUNC with CANCEL_BATON to allow cancellation. |
| */ |
| static svn_error_t * |
| walker_helper(const char *dirpath, |
| svn_wc_adm_access_t *adm_access, |
| const svn_wc_entry_callbacks2_t *walk_callbacks, |
| void *walk_baton, |
| svn_depth_t depth, |
| svn_boolean_t show_hidden, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *pool) |
| { |
| apr_pool_t *subpool = svn_pool_create(pool); |
| apr_hash_t *entries; |
| apr_hash_index_t *hi; |
| svn_wc_entry_t *dot_entry; |
| svn_error_t *err; |
| svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); |
| |
| err = svn_wc_entries_read(&entries, adm_access, show_hidden, pool); |
| |
| if (err) |
| SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool)); |
| |
| /* As promised, always return the '.' entry first. */ |
| dot_entry = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, |
| APR_HASH_KEY_STRING); |
| if (! dot_entry) |
| return walk_callbacks->handle_error |
| (dirpath, svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, |
| _("Directory '%s' has no THIS_DIR entry"), |
| svn_dirent_local_style(dirpath, pool)), |
| walk_baton, pool); |
| |
| /* Call the "found entry" callback for this directory as a "this dir" |
| * entry. Note that if this directory has been reached by recursion, this |
| * is the second visit as it will already have been visited once as a |
| * child entry of its parent. */ |
| |
| err = walk_callbacks->found_entry(dirpath, dot_entry, walk_baton, subpool); |
| |
| |
| if(err) |
| SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool)); |
| |
| if (depth == svn_depth_empty) |
| return SVN_NO_ERROR; |
| |
| /* Loop over each of the other entries. */ |
| for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) |
| { |
| const char *name = svn_apr_hash_index_key(hi); |
| const svn_wc_entry_t *current_entry = svn_apr_hash_index_val(hi); |
| const char *entrypath; |
| const char *entry_abspath; |
| svn_boolean_t hidden; |
| |
| svn_pool_clear(subpool); |
| |
| /* See if someone wants to cancel this operation. */ |
| if (cancel_func) |
| SVN_ERR(cancel_func(cancel_baton)); |
| |
| /* Skip the "this dir" entry. */ |
| if (strcmp(current_entry->name, SVN_WC_ENTRY_THIS_DIR) == 0) |
| continue; |
| |
| entrypath = svn_dirent_join(dirpath, name, subpool); |
| SVN_ERR(svn_wc__entry_is_hidden(&hidden, current_entry)); |
| SVN_ERR(svn_dirent_get_absolute(&entry_abspath, entrypath, subpool)); |
| |
| /* Call the "found entry" callback for this entry. (For a directory, |
| * this is the first visit: as a child.) */ |
| if (current_entry->kind == svn_node_file |
| || depth >= svn_depth_immediates) |
| { |
| err = walk_callbacks->found_entry(entrypath, current_entry, |
| walk_baton, subpool); |
| |
| if (err) |
| SVN_ERR(walk_callbacks->handle_error(entrypath, err, |
| walk_baton, pool)); |
| } |
| |
| /* Recurse into this entry if appropriate. */ |
| if (current_entry->kind == svn_node_dir |
| && !hidden |
| && depth >= svn_depth_immediates) |
| { |
| svn_wc_adm_access_t *entry_access; |
| svn_depth_t depth_below_here = depth; |
| |
| if (depth == svn_depth_immediates) |
| depth_below_here = svn_depth_empty; |
| |
| entry_access = svn_wc__adm_retrieve_internal2(db, entry_abspath, |
| subpool); |
| |
| if (entry_access) |
| SVN_ERR(walker_helper(entrypath, entry_access, |
| walk_callbacks, walk_baton, |
| depth_below_here, show_hidden, |
| cancel_func, cancel_baton, |
| subpool)); |
| } |
| } |
| |
| svn_pool_destroy(subpool); |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc__walker_default_error_handler(const char *path, |
| svn_error_t *err, |
| void *walk_baton, |
| apr_pool_t *pool) |
| { |
| /* Note: don't trace this. We don't want to insert a false "stack frame" |
| onto an error generated elsewhere. */ |
| return svn_error_return(err); |
| } |
| |
| |
| /* The public API. */ |
| svn_error_t * |
| svn_wc_walk_entries3(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| const svn_wc_entry_callbacks2_t *walk_callbacks, |
| void *walk_baton, |
| svn_depth_t walk_depth, |
| svn_boolean_t show_hidden, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *pool) |
| { |
| const char *local_abspath; |
| svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); |
| svn_error_t *err; |
| svn_wc__db_kind_t kind; |
| svn_depth_t depth; |
| |
| SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); |
| err = svn_wc__db_read_info(NULL, &kind, NULL, |
| NULL, NULL, NULL, |
| NULL, NULL, NULL, |
| NULL, &depth, |
| NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, |
| db, local_abspath, |
| pool, pool); |
| if (err) |
| { |
| if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) |
| return svn_error_return(err); |
| /* Remap into SVN_ERR_UNVERSIONED_RESOURCE. */ |
| svn_error_clear(err); |
| return walk_callbacks->handle_error( |
| path, svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL, |
| _("'%s' is not under version control"), |
| svn_dirent_local_style(local_abspath, pool)), |
| walk_baton, pool); |
| } |
| |
| if (kind == svn_wc__db_kind_file || depth == svn_depth_exclude) |
| { |
| const svn_wc_entry_t *entry; |
| |
| /* ### we should stop passing out entry structures. |
| ### |
| ### we should not call handle_error for an error the *callback* |
| ### gave us. let it deal with the problem before returning. */ |
| |
| if (!show_hidden) |
| { |
| svn_boolean_t hidden; |
| SVN_ERR(svn_wc__db_node_hidden(&hidden, db, local_abspath, pool)); |
| |
| if (hidden) |
| { |
| /* The fool asked to walk a "hidden" node. Report the node as |
| unversioned. |
| |
| ### this is incorrect behavior. see depth_test 36. the walk |
| ### API will be revamped to avoid entry structures. we should |
| ### be able to solve the problem with the new API. (since we |
| ### shouldn't return a hidden entry here) */ |
| return walk_callbacks->handle_error( |
| path, svn_error_createf( |
| SVN_ERR_UNVERSIONED_RESOURCE, NULL, |
| _("'%s' is not under version control"), |
| svn_dirent_local_style(local_abspath, pool)), |
| walk_baton, pool); |
| } |
| } |
| |
| SVN_ERR(svn_wc__get_entry(&entry, db, local_abspath, FALSE, |
| svn_node_file, FALSE, pool, pool)); |
| |
| err = walk_callbacks->found_entry(path, entry, walk_baton, pool); |
| if (err) |
| return walk_callbacks->handle_error(path, err, walk_baton, pool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| if (kind == svn_wc__db_kind_dir) |
| return walker_helper(path, adm_access, walk_callbacks, walk_baton, |
| walk_depth, show_hidden, cancel_func, cancel_baton, |
| pool); |
| |
| return walk_callbacks->handle_error( |
| path, svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL, |
| _("'%s' has an unrecognized node kind"), |
| svn_dirent_local_style(local_abspath, pool)), |
| walk_baton, pool); |
| } |
| |
| svn_error_t * |
| svn_wc_mark_missing_deleted(const char *path, |
| svn_wc_adm_access_t *parent, |
| apr_pool_t *pool) |
| { |
| svn_node_kind_t pkind; |
| const char *local_abspath; |
| svn_wc__db_t *db = svn_wc__adm_get_db(parent); |
| |
| SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); |
| |
| SVN_ERR(svn_io_check_path(path, &pkind, pool)); |
| |
| if (pkind == svn_node_none) |
| { |
| svn_wc_entry_t tmp_entry; |
| |
| tmp_entry.deleted = TRUE; |
| tmp_entry.schedule = svn_wc_schedule_normal; |
| |
| return svn_error_return( |
| svn_wc__entry_modify2(db, local_abspath, svn_node_unknown, FALSE, |
| &tmp_entry, |
| (SVN_WC__ENTRY_MODIFY_DELETED |
| | SVN_WC__ENTRY_MODIFY_SCHEDULE |
| | SVN_WC__ENTRY_MODIFY_FORCE), |
| pool)); |
| } |
| |
| return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL, |
| _("Unexpectedly found '%s': " |
| "path is marked 'missing'"), |
| svn_dirent_local_style(path, pool)); |
| } |