diff options
Diffstat (limited to 'subversion/libsvn_wc')
58 files changed, 79290 insertions, 0 deletions
diff --git a/subversion/libsvn_wc/README b/subversion/libsvn_wc/README new file mode 100644 index 000000000000..b5fc5293cd96 --- /dev/null +++ b/subversion/libsvn_wc/README @@ -0,0 +1,195 @@ + Oh Most High and Fragrant Emacs, please be in -*- text -*- mode! + +############################################################################## +### The vast majority of this file is completely out-of-date as a result ### +### of the ongoing work known as WC-NG. Please consult that documentation ### +### for a more relevant and complete reference. ### +### (See the files in notes/wc-ng ) ### +############################################################################## + + +This is the library described in the section "The working copy +management library" of svn-design.texi. It performs local operations +in the working copy, tweaking administrative files and versioned data. +It does not communicate directly with a repository; instead, other +libraries that do talk to the repository call into this library to +make queries and changes in the working copy. + +Note: This document attempts to describe (insofar as development is still +a moving target) the current working copy layout. For historic layouts, +consulting the versioned history of this file (yay version control!) + + +The Problem We're Solving +------------------------- + +The working copy is arranged as a directory tree, which, at checkout, +mirrors a tree rooted at some node in the repository. Over time, the +working copy accumulates uncommitted changes, some of which may affect +its tree layout. By commit time, the working copy's layout could be +arbitrarily different from the repository tree on which it was based. + +Furthermore, updates/commits do not always involve the entire tree, so +it is possible for the working copy to go a very long time without +being a perfect mirror of some tree in the repository. + + +One Way We're Not Solving It +---------------------------- + +Updates and commits are about merging two trees that share a common +ancestor, but have diverged since that ancestor. In real life, one of +the trees comes from the working copy, the other from the repository. +But when thinking about how to merge two such trees, we can ignore the +question of which is the working copy and which is the repository, +because the principles involved are symmetrical. + +Why do we say symmetrical? + +It's tempting to think of a change as being either "from" the working +copy or "in" the repository. But the true source of a change is some +committer -- each change represents some developer's intention toward +a file or a tree, and a conflict is what happens when two intentions +are incompatible (or their compatibility cannot be automatically +determined). + +It doesn't matter in what order the intentions were discovered -- +which has already made it into the repository versus which exists only +in someone's working copy. Incompatibility is incompatibility, +independent of timing. + +In fact, a working copy can be viewed as a "branch" off the +repository, and the changes committed in the repository *since* then +represent another, divergent branch. Thus, every update or commit is +a general branch-merge problem: + + - An update is an attempt to merge the repository's branch into the + working copy's branch, and the attempt may fail wholly or + partially depending on the number of conflicts. + + - A commit is an attempt to merge the working copy's branch into + the repository. The exact same algorithm is used as with + updates, the only difference being that a commit must succeed + completely or not at all. That last condition is merely a + usability decision: the repository tree is shared by many + people, so folding both sides of a conflict into it to aid + resolution would actually make it less usable, not more. On the + other hand, representing both sides of a conflict in a working + copy is often helpful to the person who owns that copy. + +So below we consider the general problem of how to merge two trees +that have a common ancestor. The concrete tree layout discussed will +be that of the working copy, because this library needs to know +exactly how to massage a working copy from one state to another. + + +Structure of the Working Copy +----------------------------- + +Working copy meta-information is stored in a single .svn/ subdirectory, in +the root of a given working copy. For the purposes of storage, directories +pull in through the use of svn:externals are considered separate working +copies. + + .svn/wc.db /* SQLite database containing node metadata. */ + pristine/ /* Sharded directory containing base files. */ + tmp/ /* Local tmp area. */ + +`wc.db': + A self-contained SQLite database containing all the metadata Subversion + needs to track for this working copy. The schema is described by + libsvn_wc/wc-metadata.sql. + +`pristine': + Each file in the working copy has a corresponding unmodified version in + the .svn/pristine subdirectory. This files are stored by the SHA-1 + hash of their contents, sharded into 256 subdirectories based upon the + first two characters of the hex expansion of the hash. In this way, + multiple identical files can share the same pristine representation. + + Pristines are used for sending diffs back to the server, etc. + + +How the client applies an update delta +-------------------------------------- + +Updating is more than just bringing changes down from the repository; +it's also folding those changes into the working copy. Getting the +right changes is the easy part -- folding them in is hard. + +Before we examine how Subversion handles this, let's look at what CVS +does: + + 1. Unmodified portions of the working copy are simply brought + up-to-date. The server sends a forward diff, the client applies + it. + + 2. Locally modified portions are "merged", where possible. That + is, the changes from the repository are incorporated into the + local changes in an intelligent way (if the diff application + succeeds, then no conflict, else go to 3...) + + 3. Where merging is not possible, a conflict is flagged, and *both* + sides of the conflict are folded into the local file in such a + way that it's easy for the developer to figure out what + happened. (And the old locally-modified file is saved under a + temp name, just in case.) + +It would be nice for Subversion to do things this way too; +unfortunately, that's not possible in every case. + +CVS has a wonderfully simplifying limitation: it doesn't version +directories, so never has tree-structure conflicts. Given that only +textual conflicts are possible, there is usually a natural way to +express both sides of a conflict -- just include the opposing texts +inside the file, delimited with conflict markers. (Or for binary +files, make both revisions available under temporary names.) + +While Subversion can behave the same way for textual conflicts, the +situation is more complex for trees. There is sometimes no way for a +working copy to reflect both sides of a tree conflict without being +more confusing than helpful. How does one put "conflict markers" into +a directory, especially when what was a directory might now be a file, +or vice-versa? + +Therefore, while Subversion does everything it can to fold conflicts +intelligently (doing at least as well as CVS does), in extreme cases +it is acceptable for the Subversion client to punt, saying in effect +"Your working copy is too out of whack; please move it aside, check +out a fresh one, redo your changes in the fresh copy, and commit from +that." (This response may also apply to subtrees of the working copy, +of course). + +Usually it offers more detail than that, too. In addition to the +overall out-of-whackness message, it can say "Directory foo was +renamed to bar, conflicting with your new file bar; file blah was +deleted, conflicting with your local change to file blah, ..." and so +on. The important thing is that these are informational only -- they +tell the user what's wrong, but they don't try to fix it +automatically. + +All this is purely a matter of *client-side* intelligence. Nothing in +the repository logic or protocol affects the client's ability to fold +conflicts. So as we get smarter, and/or as there is demand for more +informative conflicting updates, the client's behavior can improve and +punting can become a rare event. We should start out with a _simple_ +conflict-folding algorithm initially, though. + + +Text and Property Components +---------------------------- + +A Subversion working copy keeps track of *two* forks per file, much +like the way MacOS files have "data" forks and "resource" forks. Each +file under revision control has its "text" and "properties" tracked +with different timestamps and different conflict (reject) files. In +this vein, each file's status-line has two columns which describe the +file's state. + +Examples: + + -- glub.c --> glub.c is completely up-to-date. + U- foo.c --> foo.c's textual component was updated. + -M bar.c --> bar.c's properties have been locally modified + UC baz.c --> baz.c has had both components patched, but a + local property change is creating a conflict. diff --git a/subversion/libsvn_wc/adm_crawler.c b/subversion/libsvn_wc/adm_crawler.c new file mode 100644 index 000000000000..e5935a274eab --- /dev/null +++ b/subversion/libsvn_wc/adm_crawler.c @@ -0,0 +1,1239 @@ +/* + * adm_crawler.c: report local WC mods to an Editor. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + +#include <string.h> + +#include <apr_pools.h> +#include <apr_file_io.h> +#include <apr_hash.h> + +#include "svn_hash.h" +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_wc.h" +#include "svn_io.h" +#include "svn_delta.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" + +#include "private/svn_wc_private.h" + +#include "wc.h" +#include "adm_files.h" +#include "translate.h" +#include "workqueue.h" +#include "conflicts.h" + +#include "svn_private_config.h" + + +/* Helper for report_revisions_and_depths(). + + Perform an atomic restoration of the file LOCAL_ABSPATH; that is, copy + the file's text-base to the administrative tmp area, and then move + that file to LOCAL_ABSPATH with possible translations/expansions. If + USE_COMMIT_TIMES is set, then set working file's timestamp to + last-commit-time. Either way, set entry-timestamp to match that of + the working file when all is finished. + + If MARK_RESOLVED_TEXT_CONFLICT is TRUE, mark as resolved any existing + text conflict on LOCAL_ABSPATH. + + Not that a valid access baton with a write lock to the directory of + LOCAL_ABSPATH must be available in DB.*/ +static svn_error_t * +restore_file(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t use_commit_times, + svn_boolean_t mark_resolved_text_conflict, + apr_pool_t *scratch_pool) +{ + svn_skel_t *work_item; + + SVN_ERR(svn_wc__wq_build_file_install(&work_item, + db, local_abspath, + NULL /* source_abspath */, + use_commit_times, + TRUE /* record_fileinfo */, + scratch_pool, scratch_pool)); + /* ### we need an existing path for wq_add. not entirely WRI_ABSPATH yet */ + SVN_ERR(svn_wc__db_wq_add(db, + svn_dirent_dirname(local_abspath, scratch_pool), + work_item, scratch_pool)); + + /* Run the work item immediately. */ + SVN_ERR(svn_wc__wq_run(db, local_abspath, + NULL, NULL, /* ### nice to have cancel_func/baton */ + scratch_pool)); + + /* Remove any text conflict */ + if (mark_resolved_text_conflict) + SVN_ERR(svn_wc__mark_resolved_text_conflict(db, local_abspath, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_restore(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t use_commit_times, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_node_kind_t disk_kind; + const svn_checksum_t *checksum; + + SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); + + if (disk_kind != svn_node_none) + return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL, + _("The existing node '%s' can not be restored."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &checksum, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + if (status != svn_wc__db_status_normal + && !((status == svn_wc__db_status_added + || status == svn_wc__db_status_incomplete) + && (kind == svn_node_dir + || (kind == svn_node_file && checksum != NULL) + /* || (kind == svn_node_symlink && target)*/))) + { + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("The node '%s' can not be restored."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + if (kind == svn_node_file || kind == svn_node_symlink) + SVN_ERR(restore_file(wc_ctx->db, local_abspath, use_commit_times, + FALSE /*mark_resolved_text_conflict*/, + scratch_pool)); + else + SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Try to restore LOCAL_ABSPATH of node type KIND and if successfull, + notify that the node is restored. Use DB for accessing the working copy. + If USE_COMMIT_TIMES is set, then set working file's timestamp to + last-commit-time. + + This function does all temporary allocations in SCRATCH_POOL + */ +static svn_error_t * +restore_node(svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + svn_boolean_t use_commit_times, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + if (kind == svn_node_file || kind == svn_node_symlink) + { + /* Recreate file from text-base; mark any text conflict as resolved */ + SVN_ERR(restore_file(db, local_abspath, use_commit_times, + TRUE /*mark_resolved_text_conflict*/, + scratch_pool)); + } + else if (kind == svn_node_dir) + { + /* Recreating a directory is just a mkdir */ + SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool)); + } + + /* ... report the restoration to the caller. */ + if (notify_func != NULL) + { + svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, + svn_wc_notify_restore, + scratch_pool); + notify->kind = svn_node_file; + (*notify_func)(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; +} + +/* The recursive crawler that describes a mixed-revision working + copy to an RA layer. Used to initiate updates. + + This is a depth-first recursive walk of the children of DIR_ABSPATH + (not including DIR_ABSPATH itself) using DB. Look at each node and + check if its revision is different than DIR_REV. If so, report this + fact to REPORTER. If a node has a different URL than expected, or + a different depth than its parent, report that to REPORTER. + + Report DIR_ABSPATH to the reporter as REPORT_RELPATH. + + Alternatively, if REPORT_EVERYTHING is set, then report all + children unconditionally. + + DEPTH is actually the *requested* depth for the update-like + operation for which we are reporting working copy state. However, + certain requested depths affect the depth of the report crawl. For + example, if the requested depth is svn_depth_empty, there's no + point descending into subdirs, no matter what their depths. So: + + If DEPTH is svn_depth_empty, don't report any files and don't + descend into any subdirs. If svn_depth_files, report files but + still don't descend into subdirs. If svn_depth_immediates, report + files, and report subdirs themselves but not their entries. If + svn_depth_infinity or svn_depth_unknown, report everything all the + way down. (That last sentence might sound counterintuitive, but + since you can't go deeper than the local ambient depth anyway, + requesting svn_depth_infinity really means "as deep as the various + parts of this working copy go". Of course, the information that + comes back from the server will be different for svn_depth_unknown + than for svn_depth_infinity.) + + DIR_REPOS_RELPATH, DIR_REPOS_ROOT and DIR_DEPTH are the repository + relative path, the repository root and depth stored on the directory, + passed here to avoid another database query. + + DEPTH_COMPATIBILITY_TRICK means the same thing here as it does + in svn_wc_crawl_revisions5(). + + If RESTORE_FILES is set, then unexpectedly missing working files + will be restored from text-base and NOTIFY_FUNC/NOTIFY_BATON + will be called to report the restoration. USE_COMMIT_TIMES is + passed to restore_file() helper. */ +static svn_error_t * +report_revisions_and_depths(svn_wc__db_t *db, + const char *dir_abspath, + const char *report_relpath, + svn_revnum_t dir_rev, + const char *dir_repos_relpath, + const char *dir_repos_root, + svn_depth_t dir_depth, + const svn_ra_reporter3_t *reporter, + void *report_baton, + svn_boolean_t restore_files, + svn_depth_t depth, + svn_boolean_t honor_depth_exclude, + svn_boolean_t depth_compatibility_trick, + svn_boolean_t report_everything, + svn_boolean_t use_commit_times, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + apr_hash_t *base_children; + apr_hash_t *dirents; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_index_t *hi; + svn_error_t *err; + + + /* Get both the SVN Entries and the actual on-disk entries. Also + notice that we're picking up hidden entries too (read_children never + hides children). */ + SVN_ERR(svn_wc__db_base_get_children_info(&base_children, db, dir_abspath, + scratch_pool, iterpool)); + + if (restore_files) + { + err = svn_io_get_dirents3(&dirents, dir_abspath, TRUE, + scratch_pool, scratch_pool); + + if (err && (APR_STATUS_IS_ENOENT(err->apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) + { + svn_error_clear(err); + /* There is no directory, and if we could create the directory + we would have already created it when walking the parent + directory */ + restore_files = FALSE; + dirents = NULL; + } + else + SVN_ERR(err); + } + else + dirents = NULL; + + /*** Do the real reporting and recursing. ***/ + + /* Looping over current directory's BASE children: */ + for (hi = apr_hash_first(scratch_pool, base_children); + hi != NULL; + hi = apr_hash_next(hi)) + { + const char *child = svn__apr_hash_index_key(hi); + const char *this_report_relpath; + const char *this_abspath; + svn_boolean_t this_switched = FALSE; + struct svn_wc__db_base_info_t *ths = svn__apr_hash_index_val(hi); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + /* Clear the iteration subpool here because the loop has a bunch + of 'continue' jump statements. */ + svn_pool_clear(iterpool); + + /* Compute the paths and URLs we need. */ + this_report_relpath = svn_relpath_join(report_relpath, child, iterpool); + this_abspath = svn_dirent_join(dir_abspath, child, iterpool); + + /*** File Externals **/ + if (ths->update_root) + { + /* File externals are ... special. We ignore them. */; + continue; + } + + /* First check for exclusion */ + if (ths->status == svn_wc__db_status_excluded) + { + if (honor_depth_exclude) + { + /* Report the excluded path, no matter whether report_everything + flag is set. Because the report_everything flag indicates + that the server will treat the wc as empty and thus push + full content of the files/subdirs. But we want to prevent the + server from pushing the full content of this_path at us. */ + + /* The server does not support link_path report on excluded + path. We explicitly prohibit this situation in + svn_wc_crop_tree(). */ + SVN_ERR(reporter->set_path(report_baton, + this_report_relpath, + dir_rev, + svn_depth_exclude, + FALSE, + NULL, + iterpool)); + } + else + { + /* We want to pull in the excluded target. So, report it as + deleted, and server will respond properly. */ + if (! report_everything) + SVN_ERR(reporter->delete_path(report_baton, + this_report_relpath, iterpool)); + } + continue; + } + + /*** The Big Tests: ***/ + if (ths->status == svn_wc__db_status_server_excluded + || ths->status == svn_wc__db_status_not_present) + { + /* If the entry is 'absent' or 'not-present', make sure the server + knows it's gone... + ...unless we're reporting everything, in which case we're + going to report it missing later anyway. + + This instructs the server to send it back to us, if it is + now available (an addition after a not-present state), or if + it is now authorized (change in authz for the absent item). */ + if (! report_everything) + SVN_ERR(reporter->delete_path(report_baton, this_report_relpath, + iterpool)); + continue; + } + + /* Is the entry NOT on the disk? We may be able to restore it. */ + if (restore_files + && svn_hash_gets(dirents, child) == NULL) + { + svn_wc__db_status_t wrk_status; + svn_node_kind_t wrk_kind; + const svn_checksum_t *checksum; + + SVN_ERR(svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + &checksum, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + db, this_abspath, iterpool, iterpool)); + + if ((wrk_status == svn_wc__db_status_normal + || wrk_status == svn_wc__db_status_added + || wrk_status == svn_wc__db_status_incomplete) + && (wrk_kind == svn_node_dir || checksum)) + { + svn_node_kind_t dirent_kind; + + /* It is possible on a case insensitive system that the + entry is not really missing, but just cased incorrectly. + In this case we can't overwrite it with the pristine + version */ + SVN_ERR(svn_io_check_path(this_abspath, &dirent_kind, iterpool)); + + if (dirent_kind == svn_node_none) + { + SVN_ERR(restore_node(db, this_abspath, wrk_kind, + use_commit_times, notify_func, + notify_baton, iterpool)); + } + } + } + + /* And finally prepare for reporting */ + if (!ths->repos_relpath) + { + ths->repos_relpath = svn_relpath_join(dir_repos_relpath, child, + iterpool); + } + else + { + const char *childname + = svn_relpath_skip_ancestor(dir_repos_relpath, ths->repos_relpath); + + if (childname == NULL || strcmp(childname, child) != 0) + { + this_switched = TRUE; + } + } + + /* Tweak THIS_DEPTH to a useful value. */ + if (ths->depth == svn_depth_unknown) + ths->depth = svn_depth_infinity; + + /*** Files ***/ + if (ths->kind == svn_node_file + || ths->kind == svn_node_symlink) + { + if (report_everything) + { + /* Report the file unconditionally, one way or another. */ + if (this_switched) + SVN_ERR(reporter->link_path(report_baton, + this_report_relpath, + svn_path_url_add_component2( + dir_repos_root, + ths->repos_relpath, iterpool), + ths->revnum, + ths->depth, + FALSE, + ths->lock ? ths->lock->token : NULL, + iterpool)); + else + SVN_ERR(reporter->set_path(report_baton, + this_report_relpath, + ths->revnum, + ths->depth, + FALSE, + ths->lock ? ths->lock->token : NULL, + iterpool)); + } + + /* Possibly report a disjoint URL ... */ + else if (this_switched) + SVN_ERR(reporter->link_path(report_baton, + this_report_relpath, + svn_path_url_add_component2( + dir_repos_root, + ths->repos_relpath, iterpool), + ths->revnum, + ths->depth, + FALSE, + ths->lock ? ths->lock->token : NULL, + iterpool)); + /* ... or perhaps just a differing revision or lock token, + or the mere presence of the file in a depth-empty dir. */ + else if (ths->revnum != dir_rev + || ths->lock + || dir_depth == svn_depth_empty) + SVN_ERR(reporter->set_path(report_baton, + this_report_relpath, + ths->revnum, + ths->depth, + FALSE, + ths->lock ? ths->lock->token : NULL, + iterpool)); + } /* end file case */ + + /*** Directories (in recursive mode) ***/ + else if (ths->kind == svn_node_dir + && (depth > svn_depth_files + || depth == svn_depth_unknown)) + { + svn_boolean_t is_incomplete; + svn_boolean_t start_empty; + svn_depth_t report_depth = ths->depth; + + is_incomplete = (ths->status == svn_wc__db_status_incomplete); + start_empty = is_incomplete; + + if (!SVN_DEPTH_IS_RECURSIVE(depth)) + report_depth = svn_depth_empty; + + /* When a <= 1.6 working copy is upgraded without some of its + subdirectories we miss some information in the database. If we + report the revision as -1, the update editor will receive an + add_directory() while it still knows the directory. + + This would raise strange tree conflicts and probably assertions + as it would a BASE vs BASE conflict */ + if (is_incomplete && !SVN_IS_VALID_REVNUM(ths->revnum)) + ths->revnum = dir_rev; + + if (depth_compatibility_trick + && ths->depth <= svn_depth_files + && depth > ths->depth) + { + start_empty = TRUE; + } + + if (report_everything) + { + /* Report the dir unconditionally, one way or another... */ + if (this_switched) + SVN_ERR(reporter->link_path(report_baton, + this_report_relpath, + svn_path_url_add_component2( + dir_repos_root, + ths->repos_relpath, iterpool), + ths->revnum, + report_depth, + start_empty, + ths->lock ? ths->lock->token + : NULL, + iterpool)); + else + SVN_ERR(reporter->set_path(report_baton, + this_report_relpath, + ths->revnum, + report_depth, + start_empty, + ths->lock ? ths->lock->token : NULL, + iterpool)); + } + else if (this_switched) + { + /* ...or possibly report a disjoint URL ... */ + SVN_ERR(reporter->link_path(report_baton, + this_report_relpath, + svn_path_url_add_component2( + dir_repos_root, + ths->repos_relpath, iterpool), + ths->revnum, + report_depth, + start_empty, + ths->lock ? ths->lock->token : NULL, + iterpool)); + } + else if (ths->revnum != dir_rev + || ths->lock + || is_incomplete + || dir_depth == svn_depth_empty + || dir_depth == svn_depth_files + || (dir_depth == svn_depth_immediates + && ths->depth != svn_depth_empty) + || (ths->depth < svn_depth_infinity + && SVN_DEPTH_IS_RECURSIVE(depth))) + { + /* ... or perhaps just a differing revision, lock token, + incomplete subdir, the mere presence of the directory + in a depth-empty or depth-files dir, or if the parent + dir is at depth-immediates but the child is not at + depth-empty. Also describe shallow subdirs if we are + trying to set depth to infinity. */ + SVN_ERR(reporter->set_path(report_baton, + this_report_relpath, + ths->revnum, + report_depth, + start_empty, + ths->lock ? ths->lock->token : NULL, + iterpool)); + } + + /* Finally, recurse if necessary and appropriate. */ + if (SVN_DEPTH_IS_RECURSIVE(depth)) + { + const char *repos_relpath = ths->repos_relpath; + + if (repos_relpath == NULL) + { + repos_relpath = svn_relpath_join(dir_repos_relpath, child, + iterpool); + } + + SVN_ERR(report_revisions_and_depths(db, + this_abspath, + this_report_relpath, + ths->revnum, + repos_relpath, + dir_repos_root, + ths->depth, + reporter, report_baton, + restore_files, depth, + honor_depth_exclude, + depth_compatibility_trick, + start_empty, + use_commit_times, + cancel_func, cancel_baton, + notify_func, notify_baton, + iterpool)); + } + } /* end directory case */ + } /* end main entries loop */ + + /* We're done examining this dir's entries, so free everything. */ + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/*------------------------------------------------------------------*/ +/*** Public Interfaces ***/ + + +svn_error_t * +svn_wc_crawl_revisions5(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const svn_ra_reporter3_t *reporter, + void *report_baton, + svn_boolean_t restore_files, + svn_depth_t depth, + svn_boolean_t honor_depth_exclude, + svn_boolean_t depth_compatibility_trick, + svn_boolean_t use_commit_times, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + svn_error_t *fserr, *err; + svn_revnum_t target_rev = SVN_INVALID_REVNUM; + svn_boolean_t start_empty; + svn_wc__db_status_t status; + svn_node_kind_t target_kind; + const char *repos_relpath, *repos_root_url; + svn_depth_t target_depth; + svn_wc__db_lock_t *target_lock; + svn_node_kind_t disk_kind; + svn_depth_t report_depth; + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* Get the base rev, which is the first revnum that entries will be + compared to, and some other WC info about the target. */ + err = svn_wc__db_base_get_info(&status, &target_kind, &target_rev, + &repos_relpath, &repos_root_url, + NULL, NULL, NULL, NULL, &target_depth, + NULL, NULL, &target_lock, + NULL, NULL, NULL, + db, local_abspath, scratch_pool, + scratch_pool); + + if (err + || (status != svn_wc__db_status_normal + && status != svn_wc__db_status_incomplete)) + { + if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + + /* We don't know about this node, so all we have to do is tell + the reporter that we don't know this node. + + But first we have to start the report by sending some basic + information for the root. */ + + if (depth == svn_depth_unknown) + depth = svn_depth_infinity; + + SVN_ERR(reporter->set_path(report_baton, "", 0, depth, FALSE, + NULL, scratch_pool)); + SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool)); + + /* Finish the report, which causes the update editor to be + driven. */ + SVN_ERR(reporter->finish_report(report_baton, scratch_pool)); + + return SVN_NO_ERROR; + } + + if (target_depth == svn_depth_unknown) + target_depth = svn_depth_infinity; + + start_empty = (status == svn_wc__db_status_incomplete); + if (depth_compatibility_trick + && target_depth <= svn_depth_immediates + && depth > target_depth) + { + start_empty = TRUE; + } + + if (restore_files) + SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); + else + disk_kind = svn_node_unknown; + + /* Determine if there is a missing node that should be restored */ + if (restore_files + && disk_kind == svn_node_none) + { + svn_wc__db_status_t wrk_status; + svn_node_kind_t wrk_kind; + const svn_checksum_t *checksum; + + err = svn_wc__db_read_info(&wrk_status, &wrk_kind, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, &checksum, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, + db, local_abspath, + scratch_pool, scratch_pool); + + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + wrk_status = svn_wc__db_status_not_present; + wrk_kind = svn_node_file; + } + else + SVN_ERR(err); + + if ((wrk_status == svn_wc__db_status_normal + || wrk_status == svn_wc__db_status_added + || wrk_status == svn_wc__db_status_incomplete) + && (wrk_kind == svn_node_dir || checksum)) + { + SVN_ERR(restore_node(wc_ctx->db, local_abspath, + wrk_kind, use_commit_times, + notify_func, notify_baton, + scratch_pool)); + } + } + + { + report_depth = target_depth; + + if (honor_depth_exclude + && depth != svn_depth_unknown + && depth < target_depth) + report_depth = depth; + + /* The first call to the reporter merely informs it that the + top-level directory being updated is at BASE_REV. Its PATH + argument is ignored. */ + SVN_ERR(reporter->set_path(report_baton, "", target_rev, report_depth, + start_empty, NULL, scratch_pool)); + } + if (target_kind == svn_node_dir) + { + if (depth != svn_depth_empty) + { + /* Recursively crawl ROOT_DIRECTORY and report differing + revisions. */ + err = report_revisions_and_depths(wc_ctx->db, + local_abspath, + "", + target_rev, + repos_relpath, + repos_root_url, + report_depth, + reporter, report_baton, + restore_files, depth, + honor_depth_exclude, + depth_compatibility_trick, + start_empty, + use_commit_times, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool); + if (err) + goto abort_report; + } + } + + else if (target_kind == svn_node_file || target_kind == svn_node_symlink) + { + const char *parent_abspath, *base; + svn_wc__db_status_t parent_status; + const char *parent_repos_relpath; + + svn_dirent_split(&parent_abspath, &base, local_abspath, + scratch_pool); + + /* We can assume a file is in the same repository as its parent + directory, so we only look at the relpath. */ + err = svn_wc__db_base_get_info(&parent_status, NULL, NULL, + &parent_repos_relpath, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, parent_abspath, + scratch_pool, scratch_pool); + + if (err) + goto abort_report; + + if (strcmp(repos_relpath, + svn_relpath_join(parent_repos_relpath, base, + scratch_pool)) != 0) + { + /* This file is disjoint with respect to its parent + directory. Since we are looking at the actual target of + the report (not some file in a subdirectory of a target + directory), and that target is a file, we need to pass an + empty string to link_path. */ + err = reporter->link_path(report_baton, + "", + svn_path_url_add_component2( + repos_root_url, + repos_relpath, + scratch_pool), + target_rev, + svn_depth_infinity, + FALSE, + target_lock ? target_lock->token : NULL, + scratch_pool); + if (err) + goto abort_report; + } + else if (target_lock) + { + /* If this entry is a file node, we just want to report that + node's revision. Since we are looking at the actual target + of the report (not some file in a subdirectory of a target + directory), and that target is a file, we need to pass an + empty string to set_path. */ + err = reporter->set_path(report_baton, "", target_rev, + svn_depth_infinity, + FALSE, + target_lock ? target_lock->token : NULL, + scratch_pool); + if (err) + goto abort_report; + } + } + + /* Finish the report, which causes the update editor to be driven. */ + return svn_error_trace(reporter->finish_report(report_baton, scratch_pool)); + + abort_report: + /* Clean up the fs transaction. */ + if ((fserr = reporter->abort_report(report_baton, scratch_pool))) + { + fserr = svn_error_quick_wrap(fserr, _("Error aborting report")); + svn_error_compose(err, fserr); + } + return svn_error_trace(err); +} + +/*** Copying stream ***/ + +/* A copying stream is a bit like the unix tee utility: + * + * It reads the SOURCE when asked for data and while returning it, + * also writes the same data to TARGET. + */ +struct copying_stream_baton +{ + /* Stream to read input from. */ + svn_stream_t *source; + + /* Stream to write all data read to. */ + svn_stream_t *target; +}; + + +/* */ +static svn_error_t * +read_handler_copy(void *baton, char *buffer, apr_size_t *len) +{ + struct copying_stream_baton *btn = baton; + + SVN_ERR(svn_stream_read(btn->source, buffer, len)); + + return svn_stream_write(btn->target, buffer, len); +} + +/* */ +static svn_error_t * +close_handler_copy(void *baton) +{ + struct copying_stream_baton *btn = baton; + + SVN_ERR(svn_stream_close(btn->target)); + return svn_stream_close(btn->source); +} + + +/* Return a stream - allocated in POOL - which reads its input + * from SOURCE and, while returning that to the caller, at the + * same time writes that to TARGET. + */ +static svn_stream_t * +copying_stream(svn_stream_t *source, + svn_stream_t *target, + apr_pool_t *pool) +{ + struct copying_stream_baton *baton; + svn_stream_t *stream; + + baton = apr_palloc(pool, sizeof (*baton)); + baton->source = source; + baton->target = target; + + stream = svn_stream_create(baton, pool); + svn_stream_set_read(stream, read_handler_copy); + svn_stream_set_close(stream, close_handler_copy); + + return stream; +} + + +/* Set *STREAM to a stream from which the caller can read the pristine text + * of the working version of the file at LOCAL_ABSPATH. If the working + * version of LOCAL_ABSPATH has no pristine text because it is locally + * added, set *STREAM to an empty stream. If the working version of + * LOCAL_ABSPATH is not a file, return an error. + * + * Set *EXPECTED_MD5_CHECKSUM to the recorded MD5 checksum. + * + * Arrange for the actual checksum of the text to be calculated and written + * into *ACTUAL_MD5_CHECKSUM when the stream is read. + */ +static svn_error_t * +read_and_checksum_pristine_text(svn_stream_t **stream, + const svn_checksum_t **expected_md5_checksum, + svn_checksum_t **actual_md5_checksum, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_stream_t *base_stream; + + SVN_ERR(svn_wc__get_pristine_contents(&base_stream, NULL, db, local_abspath, + result_pool, scratch_pool)); + if (base_stream == NULL) + { + base_stream = svn_stream_empty(result_pool); + *expected_md5_checksum = NULL; + *actual_md5_checksum = NULL; + } + else + { + const svn_checksum_t *expected_md5; + + SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &expected_md5, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool)); + if (expected_md5 == NULL) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Pristine checksum for file '%s' is missing"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + if (expected_md5->kind != svn_checksum_md5) + SVN_ERR(svn_wc__db_pristine_get_md5(&expected_md5, db, local_abspath, + expected_md5, + result_pool, scratch_pool)); + *expected_md5_checksum = expected_md5; + + /* Arrange to set ACTUAL_MD5_CHECKSUM to the MD5 of what is *actually* + found when the base stream is read. */ + base_stream = svn_stream_checksummed2(base_stream, actual_md5_checksum, + NULL, svn_checksum_md5, TRUE, + result_pool); + } + + *stream = base_stream; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__internal_transmit_text_deltas(const char **tempfile, + const svn_checksum_t **new_text_base_md5_checksum, + const svn_checksum_t **new_text_base_sha1_checksum, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t fulltext, + const svn_delta_editor_t *editor, + void *file_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_txdelta_window_handler_t handler; + void *wh_baton; + const svn_checksum_t *expected_md5_checksum; /* recorded MD5 of BASE_S. */ + svn_checksum_t *verify_checksum; /* calc'd MD5 of BASE_STREAM */ + svn_checksum_t *local_md5_checksum; /* calc'd MD5 of LOCAL_STREAM */ + svn_checksum_t *local_sha1_checksum; /* calc'd SHA1 of LOCAL_STREAM */ + const char *new_pristine_tmp_abspath; + svn_error_t *err; + svn_stream_t *base_stream; /* delta source */ + svn_stream_t *local_stream; /* delta target: LOCAL_ABSPATH transl. to NF */ + + /* Translated input */ + SVN_ERR(svn_wc__internal_translated_stream(&local_stream, db, + local_abspath, local_abspath, + SVN_WC_TRANSLATE_TO_NF, + scratch_pool, scratch_pool)); + + /* If the caller wants a copy of the working file translated to + * repository-normal form, make the copy by tee-ing the stream and set + * *TEMPFILE to the path to it. This is only needed for the 1.6 API, + * 1.7 doesn't set TEMPFILE. Even when using the 1.6 API this file + * is not used by the functions that would have used it when using + * the 1.6 code. It's possible that 3rd party users (if there are any) + * might expect this file to be a text-base. */ + if (tempfile) + { + svn_stream_t *tempstream; + + /* It can't be the same location as in 1.6 because the admin directory + no longer exists. */ + SVN_ERR(svn_stream_open_unique(&tempstream, tempfile, + NULL, svn_io_file_del_none, + result_pool, scratch_pool)); + + /* Wrap the translated stream with a new stream that writes the + translated contents into the new text base file as we read from it. + Note that the new text base file will be closed when the new stream + is closed. */ + local_stream = copying_stream(local_stream, tempstream, scratch_pool); + } + if (new_text_base_sha1_checksum) + { + svn_stream_t *new_pristine_stream; + + SVN_ERR(svn_wc__open_writable_base(&new_pristine_stream, + &new_pristine_tmp_abspath, + NULL, &local_sha1_checksum, + db, local_abspath, + scratch_pool, scratch_pool)); + local_stream = copying_stream(local_stream, new_pristine_stream, + scratch_pool); + } + + /* If sending a full text is requested, or if there is no pristine text + * (e.g. the node is locally added), then set BASE_STREAM to an empty + * stream and leave EXPECTED_MD5_CHECKSUM and VERIFY_CHECKSUM as NULL. + * + * Otherwise, set BASE_STREAM to a stream providing the base (source) text + * for the delta, set EXPECTED_MD5_CHECKSUM to its stored MD5 checksum, + * and arrange for its VERIFY_CHECKSUM to be calculated later. */ + if (! fulltext) + { + /* We will be computing a delta against the pristine contents */ + /* We need the expected checksum to be an MD-5 checksum rather than a + * SHA-1 because we want to pass it to apply_textdelta(). */ + SVN_ERR(read_and_checksum_pristine_text(&base_stream, + &expected_md5_checksum, + &verify_checksum, + db, local_abspath, + scratch_pool, scratch_pool)); + } + else + { + /* Send a fulltext. */ + base_stream = svn_stream_empty(scratch_pool); + expected_md5_checksum = NULL; + verify_checksum = NULL; + } + + /* Tell the editor that we're about to apply a textdelta to the + file baton; the editor returns to us a window consumer and baton. */ + { + /* apply_textdelta() is working against a base with this checksum */ + const char *base_digest_hex = NULL; + + if (expected_md5_checksum) + /* ### Why '..._display()'? expected_md5_checksum should never be all- + * zero, but if it is, we would want to pass NULL not an all-zero + * digest to apply_textdelta(), wouldn't we? */ + base_digest_hex = svn_checksum_to_cstring_display(expected_md5_checksum, + scratch_pool); + + SVN_ERR(editor->apply_textdelta(file_baton, base_digest_hex, scratch_pool, + &handler, &wh_baton)); + } + + /* Run diff processing, throwing windows at the handler. */ + err = svn_txdelta_run(base_stream, local_stream, + handler, wh_baton, + svn_checksum_md5, &local_md5_checksum, + NULL, NULL, + scratch_pool, scratch_pool); + + /* Close the two streams to force writing the digest */ + err = svn_error_compose_create(err, svn_stream_close(base_stream)); + err = svn_error_compose_create(err, svn_stream_close(local_stream)); + + /* If we have an error, it may be caused by a corrupt text base, + so check the checksum. */ + if (expected_md5_checksum && verify_checksum + && !svn_checksum_match(expected_md5_checksum, verify_checksum)) + { + /* The entry checksum does not match the actual text + base checksum. Extreme badness. Of course, + theoretically we could just switch to + fulltext transmission here, and everything would + work fine; after all, we're going to replace the + text base with a new one in a moment anyway, and + we'd fix the checksum then. But it's better to + error out. People should know that their text + bases are getting corrupted, so they can + investigate. Other commands could be affected, + too, such as `svn diff'. */ + + if (tempfile) + err = svn_error_compose_create( + err, + svn_io_remove_file2(*tempfile, TRUE, scratch_pool)); + + err = svn_error_compose_create( + svn_checksum_mismatch_err(expected_md5_checksum, verify_checksum, + scratch_pool, + _("Checksum mismatch for text base of '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool)), + err); + + return svn_error_create(SVN_ERR_WC_CORRUPT_TEXT_BASE, err, NULL); + } + + /* Now, handle that delta transmission error if any, so we can stop + thinking about it after this point. */ + SVN_ERR_W(err, apr_psprintf(scratch_pool, + _("While preparing '%s' for commit"), + svn_dirent_local_style(local_abspath, + scratch_pool))); + + if (new_text_base_md5_checksum) + *new_text_base_md5_checksum = svn_checksum_dup(local_md5_checksum, + result_pool); + if (new_text_base_sha1_checksum) + { + SVN_ERR(svn_wc__db_pristine_install(db, new_pristine_tmp_abspath, + local_sha1_checksum, + local_md5_checksum, + scratch_pool)); + *new_text_base_sha1_checksum = svn_checksum_dup(local_sha1_checksum, + result_pool); + } + + /* Close the file baton, and get outta here. */ + return svn_error_trace( + editor->close_file(file_baton, + svn_checksum_to_cstring(local_md5_checksum, + scratch_pool), + scratch_pool)); +} + +svn_error_t * +svn_wc_transmit_text_deltas3(const svn_checksum_t **new_text_base_md5_checksum, + const svn_checksum_t **new_text_base_sha1_checksum, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t fulltext, + const svn_delta_editor_t *editor, + void *file_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_wc__internal_transmit_text_deltas(NULL, + new_text_base_md5_checksum, + new_text_base_sha1_checksum, + wc_ctx->db, local_abspath, + fulltext, editor, + file_baton, result_pool, + scratch_pool); +} + +svn_error_t * +svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db, + const char *local_abspath, + const svn_delta_editor_t *editor, + void *baton, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + apr_array_header_t *propmods; + svn_node_kind_t kind; + + SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath, + FALSE /* allow_missing */, + FALSE /* show_deleted */, + FALSE /* show_hidden */, + iterpool)); + + if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, iterpool)); + + /* Get an array of local changes by comparing the hashes. */ + SVN_ERR(svn_wc__internal_propdiff(&propmods, NULL, db, local_abspath, + scratch_pool, iterpool)); + + /* Apply each local change to the baton */ + for (i = 0; i < propmods->nelts; i++) + { + const svn_prop_t *p = &APR_ARRAY_IDX(propmods, i, svn_prop_t); + + svn_pool_clear(iterpool); + + if (kind == svn_node_file) + SVN_ERR(editor->change_file_prop(baton, p->name, p->value, + iterpool)); + else + SVN_ERR(editor->change_dir_prop(baton, p->name, p->value, + iterpool)); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_transmit_prop_deltas2(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const svn_delta_editor_t *editor, + void *baton, + apr_pool_t *scratch_pool) +{ + return svn_wc__internal_transmit_prop_deltas(wc_ctx->db, local_abspath, + editor, baton, scratch_pool); +} diff --git a/subversion/libsvn_wc/adm_files.c b/subversion/libsvn_wc/adm_files.c new file mode 100644 index 000000000000..11ad277d92d3 --- /dev/null +++ b/subversion/libsvn_wc/adm_files.c @@ -0,0 +1,584 @@ +/* + * adm_files.c: helper routines for handling files & dirs in the + * working copy administrative area (creating, + * deleting, opening, and closing). This is the only + * code that actually knows where administrative + * information is kept. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <stdarg.h> +#include <apr_pools.h> +#include <apr_file_io.h> +#include <apr_strings.h> + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_io.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" + +#include "wc.h" +#include "adm_files.h" +#include "entries.h" +#include "lock.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" + + +/*** File names in the adm area. ***/ + +/* The default name of the WC admin directory. This name is always + checked by svn_wc_is_adm_dir. */ +static const char default_adm_dir_name[] = ".svn"; + +/* The name that is actually used for the WC admin directory. The + commonest case where this won't be the default is in Windows + ASP.NET development environments, which used to choke on ".svn". */ +static const char *adm_dir_name = default_adm_dir_name; + + +svn_boolean_t +svn_wc_is_adm_dir(const char *name, apr_pool_t *pool) +{ + return (0 == strcmp(name, adm_dir_name) + || 0 == strcmp(name, default_adm_dir_name)); +} + + +const char * +svn_wc_get_adm_dir(apr_pool_t *pool) +{ + return adm_dir_name; +} + + +svn_error_t * +svn_wc_set_adm_dir(const char *name, apr_pool_t *pool) +{ + /* This is the canonical list of administrative directory names. + + FIXME: + An identical list is used in + libsvn_subr/opt.c:svn_opt__args_to_target_array(), + but that function can't use this list, because that use would + create a circular dependency between libsvn_wc and libsvn_subr. + Make sure changes to the lists are always synchronized! */ + static const char *valid_dir_names[] = { + default_adm_dir_name, + "_svn", + NULL + }; + + const char **dir_name; + for (dir_name = valid_dir_names; *dir_name; ++dir_name) + if (0 == strcmp(name, *dir_name)) + { + /* Use the pointer to the statically allocated string + constant, to avoid potential pool lifetime issues. */ + adm_dir_name = *dir_name; + return SVN_NO_ERROR; + } + return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, + _("'%s' is not a valid administrative " + "directory name"), + svn_dirent_local_style(name, pool)); +} + + +const char * +svn_wc__adm_child(const char *path, + const char *child, + apr_pool_t *result_pool) +{ + return svn_dirent_join_many(result_pool, + path, + adm_dir_name, + child, + NULL); +} + + +svn_boolean_t +svn_wc__adm_area_exists(const char *adm_abspath, + apr_pool_t *pool) +{ + const char *path = svn_wc__adm_child(adm_abspath, NULL, pool); + svn_node_kind_t kind; + svn_error_t *err; + + err = svn_io_check_path(path, &kind, pool); + if (err) + { + svn_error_clear(err); + /* Return early, since kind is undefined in this case. */ + return FALSE; + } + + return kind != svn_node_none; +} + + + +/*** Making and using files in the adm area. ***/ + + +/* */ +static svn_error_t * +make_adm_subdir(const char *path, + const char *subdir, + apr_pool_t *pool) +{ + const char *fullpath; + + fullpath = svn_wc__adm_child(path, subdir, pool); + + return svn_io_dir_make(fullpath, APR_OS_DEFAULT, pool); +} + + + +/*** Syncing files in the adm area. ***/ + + +svn_error_t * +svn_wc__text_base_path_to_read(const char **result_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + const svn_checksum_t *checksum; + + SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL, NULL, + &checksum, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + /* Sanity */ + if (kind != svn_node_file) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Can only get the pristine contents of files; " + "'%s' is not a file"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + if (status == svn_wc__db_status_not_present) + /* We know that the delete of this node has been committed. + This should be the same as if called on an unknown path. */ + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("Cannot get the pristine contents of '%s' " + "because its delete is already committed"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + else if (status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_incomplete) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot get the pristine contents of '%s' " + "because it has an unexpected status"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + if (checksum == NULL) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Node '%s' has no pristine text"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + SVN_ERR(svn_wc__db_pristine_get_path(result_abspath, db, local_abspath, + checksum, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__get_pristine_contents(svn_stream_t **contents, + svn_filesize_t *size, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + const svn_checksum_t *sha1_checksum; + + if (size) + *size = SVN_INVALID_FILESIZE; + + SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL, NULL, + &sha1_checksum, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + /* Sanity */ + if (kind != svn_node_file) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Can only get the pristine contents of files; " + "'%s' is not a file"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + if (status == svn_wc__db_status_added && !sha1_checksum) + { + /* Simply added. The pristine base does not exist. */ + *contents = NULL; + return SVN_NO_ERROR; + } + else if (status == svn_wc__db_status_not_present) + /* We know that the delete of this node has been committed. + This should be the same as if called on an unknown path. */ + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("Cannot get the pristine contents of '%s' " + "because its delete is already committed"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + else if (status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_incomplete) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot get the pristine contents of '%s' " + "because it has an unexpected status"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + if (sha1_checksum) + SVN_ERR(svn_wc__db_pristine_read(contents, size, db, local_abspath, + sha1_checksum, + result_pool, scratch_pool)); + else + *contents = NULL; + + return SVN_NO_ERROR; +} + + +/*** Opening and closing files in the adm area. ***/ + +svn_error_t * +svn_wc__open_adm_stream(svn_stream_t **stream, + const char *dir_abspath, + const char *fname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *local_abspath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath)); + + local_abspath = svn_wc__adm_child(dir_abspath, fname, scratch_pool); + return svn_error_trace(svn_stream_open_readonly(stream, local_abspath, + result_pool, scratch_pool)); +} + + +svn_error_t * +svn_wc__open_writable_base(svn_stream_t **stream, + const char **temp_base_abspath, + svn_checksum_t **md5_checksum, + svn_checksum_t **sha1_checksum, + svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *temp_dir_abspath; + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + + SVN_ERR(svn_wc__db_pristine_get_tempdir(&temp_dir_abspath, db, wri_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_unique(stream, + temp_base_abspath, + temp_dir_abspath, + svn_io_file_del_none, + result_pool, scratch_pool)); + if (md5_checksum) + *stream = svn_stream_checksummed2(*stream, NULL, md5_checksum, + svn_checksum_md5, FALSE, result_pool); + if (sha1_checksum) + *stream = svn_stream_checksummed2(*stream, NULL, sha1_checksum, + svn_checksum_sha1, FALSE, result_pool); + + return SVN_NO_ERROR; +} + + + +/*** Checking for and creating administrative subdirs. ***/ + + +/* */ +static svn_error_t * +init_adm_tmp_area(const char *path, apr_pool_t *pool) +{ + /* SVN_WC__ADM_TMP */ + SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_TMP, pool)); + + return SVN_NO_ERROR; +} + + +/* Set up a new adm area for PATH, with REPOS_* as the repos info, and + INITIAL_REV as the starting revision. The entries file starts out + marked as 'incomplete. The adm area starts out locked; remember to + unlock it when done. */ +static svn_error_t * +init_adm(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t initial_rev, + svn_depth_t depth, + apr_pool_t *pool) +{ + /* First, make an empty administrative area. */ + SVN_ERR(svn_io_dir_make_hidden(svn_wc__adm_child(local_abspath, NULL, pool), + APR_OS_DEFAULT, pool)); + + /** Make subdirectories. ***/ + + /* SVN_WC__ADM_PRISTINE */ + SVN_ERR(make_adm_subdir(local_abspath, SVN_WC__ADM_PRISTINE, pool)); + + /* ### want to add another directory? do a format bump to ensure that + ### all existing working copies get the new directories. or maybe + ### create-on-demand (more expensive) */ + + /** Init the tmp area. ***/ + SVN_ERR(init_adm_tmp_area(local_abspath, pool)); + + /* Create the SDB. */ + SVN_ERR(svn_wc__db_init(db, local_abspath, + repos_relpath, repos_root_url, repos_uuid, + initial_rev, depth, + pool)); + + /* Stamp ENTRIES and FORMAT files for old clients. */ + SVN_ERR(svn_io_file_create(svn_wc__adm_child(local_abspath, + SVN_WC__ADM_ENTRIES, + pool), + SVN_WC__NON_ENTRIES_STRING, + pool)); + SVN_ERR(svn_io_file_create(svn_wc__adm_child(local_abspath, + SVN_WC__ADM_FORMAT, + pool), + SVN_WC__NON_ENTRIES_STRING, + pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__internal_ensure_adm(svn_wc__db_t *db, + const char *local_abspath, + const char *url, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + int format; + const char *original_repos_relpath; + const char *original_root_url; + svn_boolean_t is_op_root; + const char *repos_relpath = svn_uri_skip_ancestor(repos_root_url, url, + scratch_pool); + svn_wc__db_status_t status; + const char *db_repos_relpath, *db_repos_root_url, *db_repos_uuid; + svn_revnum_t db_revision; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(url != NULL); + SVN_ERR_ASSERT(repos_root_url != NULL); + SVN_ERR_ASSERT(repos_uuid != NULL); + SVN_ERR_ASSERT(repos_relpath != NULL); + + SVN_ERR(svn_wc__internal_check_wc(&format, db, local_abspath, TRUE, + scratch_pool)); + + /* Early out: we know we're not dealing with an existing wc, so + just create one. */ + if (format == 0) + return svn_error_trace(init_adm(db, local_abspath, + repos_relpath, repos_root_url, repos_uuid, + revision, depth, scratch_pool)); + + SVN_ERR(svn_wc__db_read_info(&status, NULL, + &db_revision, &db_repos_relpath, + &db_repos_root_url, &db_repos_uuid, + NULL, NULL, NULL, NULL, NULL, NULL, + &original_repos_relpath, &original_root_url, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &is_op_root, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool)); + + /* When the directory exists and is scheduled for deletion or is not-present + * do not check the revision or the URL. The revision can be any + * arbitrary revision and the URL may differ if the add is + * being driven from a merge which will have a different URL. */ + if (status != svn_wc__db_status_deleted + && status != svn_wc__db_status_not_present) + { + /* ### Should we match copyfrom_revision? */ + if (db_revision != revision) + return + svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Revision %ld doesn't match existing " + "revision %ld in '%s'"), + revision, db_revision, local_abspath); + + if (!db_repos_root_url) + { + if (status == svn_wc__db_status_added) + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, + &db_repos_relpath, + &db_repos_root_url, + &db_repos_uuid, + NULL, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(svn_wc__db_scan_base_repos(&db_repos_relpath, + &db_repos_root_url, + &db_repos_uuid, + db, local_abspath, + scratch_pool, scratch_pool)); + } + + /* The caller gives us a URL which should match the entry. However, + some callers compensate for an old problem in entry->url and pass + the copyfrom_url instead. See ^/notes/api-errata/1.7/wc002.txt. As + a result, we allow the passed URL to match copyfrom_url if it + does not match the entry's primary URL. */ + if (strcmp(db_repos_uuid, repos_uuid) + || strcmp(db_repos_root_url, repos_root_url) + || !svn_relpath_skip_ancestor(db_repos_relpath, repos_relpath)) + { + if (!is_op_root /* copy_from was set on op-roots only */ + || original_root_url == NULL + || strcmp(original_root_url, repos_root_url) + || strcmp(original_repos_relpath, repos_relpath)) + return + svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("URL '%s' (uuid: '%s') doesn't match existing " + "URL '%s' (uuid: '%s') in '%s'"), + url, + db_repos_uuid, + svn_path_url_add_component2(db_repos_root_url, + db_repos_relpath, + scratch_pool), + repos_uuid, + local_abspath); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_ensure_adm4(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *url, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__internal_ensure_adm(wc_ctx->db, local_abspath, url, repos_root_url, + repos_uuid, revision, depth, scratch_pool)); +} + +svn_error_t * +svn_wc__adm_destroy(svn_wc__db_t *db, + const char *dir_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_boolean_t is_wcroot; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath)); + + SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool)); + + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, dir_abspath, scratch_pool)); + + /* Well, the coast is clear for blowing away the administrative + directory, which also removes remaining locks */ + + /* Now close the DB, and we can delete the working copy */ + if (is_wcroot) + { + SVN_ERR(svn_wc__db_drop_root(db, dir_abspath, scratch_pool)); + SVN_ERR(svn_io_remove_dir2(svn_wc__adm_child(dir_abspath, NULL, + scratch_pool), + FALSE, + cancel_func, cancel_baton, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__adm_cleanup_tmp_area(svn_wc__db_t *db, + const char *adm_abspath, + apr_pool_t *scratch_pool) +{ + const char *tmp_path; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(adm_abspath)); + + SVN_ERR(svn_wc__write_check(db, adm_abspath, scratch_pool)); + + /* Get the path to the tmp area, and blow it away. */ + tmp_path = svn_wc__adm_child(adm_abspath, SVN_WC__ADM_TMP, scratch_pool); + + SVN_ERR(svn_io_remove_dir2(tmp_path, TRUE, NULL, NULL, scratch_pool)); + + /* Now, rebuild the tmp area. */ + return svn_error_trace(init_adm_tmp_area(adm_abspath, scratch_pool)); +} + + +svn_error_t * +svn_wc__get_tmpdir(const char **tmpdir_abspath, + svn_wc_context_t *wc_ctx, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(tmpdir_abspath, + wc_ctx->db, wri_abspath, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/adm_files.h b/subversion/libsvn_wc/adm_files.h new file mode 100644 index 000000000000..37121499b2a1 --- /dev/null +++ b/subversion/libsvn_wc/adm_files.h @@ -0,0 +1,161 @@ +/* + * adm_files.h : handles locations inside the wc adm area + * (This should be the only code that actually knows + * *where* things are in .svn/. If you can't get to + * something via these interfaces, something's wrong.) + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#ifndef SVN_LIBSVN_WC_ADM_FILES_H +#define SVN_LIBSVN_WC_ADM_FILES_H + +#include <apr_pools.h> +#include "svn_types.h" + +#include "props.h" +#include "wc_db.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +/* Return a path to CHILD in the administrative area of PATH. If CHILD is + NULL, then the path to the admin area is returned. The result is + allocated in RESULT_POOL. */ +const char *svn_wc__adm_child(const char *path, + const char *child, + apr_pool_t *result_pool); + +/* Return TRUE if the administrative area exists for this directory. */ +svn_boolean_t svn_wc__adm_area_exists(const char *adm_abspath, + apr_pool_t *pool); + + +/* Set *CONTENTS to a readonly stream on the pristine text of the working + * version of the file LOCAL_ABSPATH in DB. If the file is locally copied + * or moved to this path, this means the pristine text of the copy source, + * even if the file replaces a previously existing base node at this path. + * + * Set *CONTENTS to NULL if there is no pristine text because the file is + * locally added (even if it replaces an existing base node). Return an + * error if there is no pristine text for any other reason. + * + * If SIZE is not NULL, set *SIZE to the length of the pristine stream in + * BYTES or to SVN_INVALID_FILESIZE if no pristine is available for this + * file. + * + * For more detail, see the description of svn_wc_get_pristine_contents2(). + */ +svn_error_t * +svn_wc__get_pristine_contents(svn_stream_t **contents, + svn_filesize_t *size, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Set *RESULT_ABSPATH to the absolute path to a readable file containing + the WC-1 "normal text-base" of LOCAL_ABSPATH in DB. + + "Normal text-base" means the same as in svn_wc__text_base_path(). + ### May want to check the callers' exact requirements and replace this + definition with something easier to comprehend. + + What the callers want: + A path to a file that will remain available and unchanged as long as + the caller wants it - such as for the lifetime of RESULT_POOL. + + What the current implementation provides: + A path to the file in the pristine store. This file will be removed or + replaced the next time this or another Subversion client updates the WC. + + If the node LOCAL_ABSPATH has no such pristine text, return an error of + type SVN_ERR_WC_PATH_UNEXPECTED_STATUS. + + Allocate *RESULT_PATH in RESULT_POOL. */ +svn_error_t * +svn_wc__text_base_path_to_read(const char **result_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/*** Opening all kinds of adm files ***/ + +/* Open `PATH/<adminstrative_subdir>/FNAME'. */ +svn_error_t *svn_wc__open_adm_stream(svn_stream_t **stream, + const char *dir_abspath, + const char *fname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Open a writable stream to a temporary (normal or revert) text base, + associated with the versioned file LOCAL_ABSPATH in DB. Set *STREAM to + the opened stream and *TEMP_BASE_ABSPATH to the path to the temporary + file. The temporary file will have an arbitrary unique name, in contrast + to the deterministic name that svn_wc__text_base_deterministic_tmp_path() + returns. + + Arrange that, on stream closure, *MD5_CHECKSUM and *SHA1_CHECKSUM will be + set to the MD-5 and SHA-1 checksums respectively of that file. + MD5_CHECKSUM and/or SHA1_CHECKSUM may be NULL if not wanted. + + Allocate the new stream, path and checksums in RESULT_POOL. + */ +svn_error_t * +svn_wc__open_writable_base(svn_stream_t **stream, + const char **temp_base_abspath, + svn_checksum_t **md5_checksum, + svn_checksum_t **sha1_checksum, + svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Blow away the admistrative directory associated with DIR_ABSPATH. + For single-db this doesn't perform actual work unless the wcroot is passed. + */ +svn_error_t *svn_wc__adm_destroy(svn_wc__db_t *db, + const char *dir_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + + +/* Cleanup the temporary storage area of the administrative + directory (assuming temp and admin areas exist). */ +svn_error_t * +svn_wc__adm_cleanup_tmp_area(svn_wc__db_t *db, + const char *adm_abspath, + apr_pool_t *scratch_pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_WC_ADM_FILES_H */ diff --git a/subversion/libsvn_wc/adm_ops.c b/subversion/libsvn_wc/adm_ops.c new file mode 100644 index 000000000000..1f391fcc372f --- /dev/null +++ b/subversion/libsvn_wc/adm_ops.c @@ -0,0 +1,1400 @@ +/* + * adm_ops.c: routines for affecting working copy administrative + * information. NOTE: this code doesn't know where the adm + * info is actually stored. Instead, generic handles to + * adm data are requested via a reference to some PATH + * (PATH being a regular, non-administrative directory or + * file in the working copy). + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <string.h> +#include <stdlib.h> + +#include <apr_pools.h> +#include <apr_hash.h> +#include <apr_time.h> +#include <apr_errno.h> + +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" +#include "svn_wc.h" +#include "svn_io.h" +#include "svn_time.h" +#include "svn_sorts.h" + +#include "wc.h" +#include "adm_files.h" +#include "conflicts.h" +#include "workqueue.h" + +#include "svn_private_config.h" +#include "private/svn_subr_private.h" +#include "private/svn_dep_compat.h" + + +struct svn_wc_committed_queue_t +{ + /* The pool in which ->queue is allocated. */ + apr_pool_t *pool; + /* Mapping (const char *) local_abspath to (committed_queue_item_t *). */ + apr_hash_t *queue; + /* Is any item in the queue marked as 'recursive'? */ + svn_boolean_t have_recursive; +}; + +typedef struct committed_queue_item_t +{ + const char *local_abspath; + svn_boolean_t recurse; + svn_boolean_t no_unlock; + svn_boolean_t keep_changelist; + + /* The pristine text checksum. */ + const svn_checksum_t *sha1_checksum; + + apr_hash_t *new_dav_cache; +} committed_queue_item_t; + + +apr_pool_t * +svn_wc__get_committed_queue_pool(const struct svn_wc_committed_queue_t *queue) +{ + return queue->pool; +} + + + +/*** Finishing updates and commits. ***/ + +/* Queue work items that will finish a commit of the file or directory + * LOCAL_ABSPATH in DB: + * - queue the removal of any "revert-base" props and text files; + * - queue an update of the DB entry for this node + * + * ### The Pristine Store equivalent should be: + * - remember the old BASE_NODE and WORKING_NODE pristine text c'sums; + * - queue an update of the DB entry for this node (incl. updating the + * BASE_NODE c'sum and setting the WORKING_NODE c'sum to NULL); + * - queue deletion of the old pristine texts by the remembered checksums. + * + * CHECKSUM is the checksum of the new text base for LOCAL_ABSPATH, and must + * be provided if there is one, else NULL. + * + * STATUS, KIND, PROP_MODS and OLD_CHECKSUM are the current in-db values of + * the node LOCAL_ABSPATH. + */ +static svn_error_t * +process_committed_leaf(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t via_recurse, + svn_wc__db_status_t status, + svn_node_kind_t kind, + svn_boolean_t prop_mods, + const svn_checksum_t *old_checksum, + svn_revnum_t new_revnum, + apr_time_t new_changed_date, + const char *new_changed_author, + apr_hash_t *new_dav_cache, + svn_boolean_t no_unlock, + svn_boolean_t keep_changelist, + const svn_checksum_t *checksum, + apr_pool_t *scratch_pool) +{ + svn_revnum_t new_changed_rev = new_revnum; + svn_skel_t *work_item = NULL; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + { + const char *adm_abspath; + + if (kind == svn_node_dir) + adm_abspath = local_abspath; + else + adm_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + SVN_ERR(svn_wc__write_check(db, adm_abspath, scratch_pool)); + } + + if (status == svn_wc__db_status_deleted) + { + return svn_error_trace( + svn_wc__db_base_remove( + db, local_abspath, + FALSE /* keep_as_working */, + FALSE /* queue_deletes */, + (! via_recurse) + ? new_revnum : SVN_INVALID_REVNUM, + NULL, NULL, + scratch_pool)); + } + else if (status == svn_wc__db_status_not_present) + { + /* We are committing the leaf of a copy operation. + We leave the not-present marker to allow pulling in excluded + children of a copy. + + The next update will remove the not-present marker. */ + + return SVN_NO_ERROR; + } + + SVN_ERR_ASSERT(status == svn_wc__db_status_normal + || status == svn_wc__db_status_incomplete + || status == svn_wc__db_status_added); + + if (kind != svn_node_dir) + { + /* If we sent a delta (meaning: post-copy modification), + then this file will appear in the queue and so we should have + its checksum already. */ + if (checksum == NULL) + { + /* It was copied and not modified. We must have a text + base for it. And the node should have a checksum. */ + SVN_ERR_ASSERT(old_checksum != NULL); + + checksum = old_checksum; + + /* Is the node completely unmodified and are we recursing? */ + if (via_recurse && !prop_mods) + { + /* If a copied node itself is not modified, but the op_root of + the copy is committed we have to make sure that changed_rev, + changed_date and changed_author don't change or the working + copy used for committing will show different last modified + information then a clean checkout of exactly the same + revisions. (Issue #3676) */ + + SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, + NULL, &new_changed_rev, + &new_changed_date, + &new_changed_author, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + } + } + + SVN_ERR(svn_wc__wq_build_file_commit(&work_item, + db, local_abspath, + prop_mods, + scratch_pool, scratch_pool)); + } + + /* The new text base will be found in the pristine store by its checksum. */ + SVN_ERR(svn_wc__db_global_commit(db, local_abspath, + new_revnum, new_changed_rev, + new_changed_date, new_changed_author, + checksum, + NULL /* new_children */, + new_dav_cache, + keep_changelist, + no_unlock, + work_item, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__process_committed_internal(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t recurse, + svn_boolean_t top_of_recurse, + svn_revnum_t new_revnum, + apr_time_t new_date, + const char *rev_author, + apr_hash_t *new_dav_cache, + svn_boolean_t no_unlock, + svn_boolean_t keep_changelist, + const svn_checksum_t *sha1_checksum, + const svn_wc_committed_queue_t *queue, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + const svn_checksum_t *old_checksum; + svn_boolean_t prop_mods; + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &old_checksum, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, &prop_mods, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + /* NOTE: be wary of making crazy semantic changes in this function, since + svn_wc_process_committed4() calls this. */ + + SVN_ERR(process_committed_leaf(db, local_abspath, !top_of_recurse, + status, kind, prop_mods, old_checksum, + new_revnum, new_date, rev_author, + new_dav_cache, + no_unlock, keep_changelist, + sha1_checksum, + scratch_pool)); + + /* Only check for recursion on nodes that have children */ + if (kind != svn_node_file + || status == svn_wc__db_status_not_present + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_server_excluded + /* Node deleted -> then no longer a directory */ + || status == svn_wc__db_status_deleted) + { + return SVN_NO_ERROR; + } + + if (recurse) + { + const apr_array_header_t *children; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + + /* Read PATH's entries; this is the absolute path. */ + SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath, + scratch_pool, iterpool)); + + /* Recursively loop over all children. */ + for (i = 0; i < children->nelts; i++) + { + const char *name = APR_ARRAY_IDX(children, i, const char *); + const char *this_abspath; + const committed_queue_item_t *cqi; + + svn_pool_clear(iterpool); + + this_abspath = svn_dirent_join(local_abspath, name, iterpool); + + sha1_checksum = NULL; + cqi = svn_hash_gets(queue->queue, this_abspath); + + if (cqi != NULL) + sha1_checksum = cqi->sha1_checksum; + + /* Recurse. Pass NULL for NEW_DAV_CACHE, because the + ones present in the current call are only applicable to + this one committed item. */ + SVN_ERR(svn_wc__process_committed_internal( + db, this_abspath, + TRUE /* recurse */, + FALSE /* top_of_recurse */, + new_revnum, new_date, + rev_author, + NULL /* new_dav_cache */, + TRUE /* no_unlock */, + keep_changelist, + sha1_checksum, + queue, + iterpool)); + } + + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + + +apr_hash_t * +svn_wc__prop_array_to_hash(const apr_array_header_t *props, + apr_pool_t *result_pool) +{ + int i; + apr_hash_t *prophash; + + if (props == NULL || props->nelts == 0) + return NULL; + + prophash = apr_hash_make(result_pool); + + for (i = 0; i < props->nelts; i++) + { + const svn_prop_t *prop = APR_ARRAY_IDX(props, i, const svn_prop_t *); + if (prop->value != NULL) + svn_hash_sets(prophash, prop->name, prop->value); + } + + return prophash; +} + + +svn_wc_committed_queue_t * +svn_wc_committed_queue_create(apr_pool_t *pool) +{ + svn_wc_committed_queue_t *q; + + q = apr_palloc(pool, sizeof(*q)); + q->pool = pool; + q->queue = apr_hash_make(pool); + q->have_recursive = FALSE; + + return q; +} + + +svn_error_t * +svn_wc_queue_committed3(svn_wc_committed_queue_t *queue, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t recurse, + const apr_array_header_t *wcprop_changes, + svn_boolean_t remove_lock, + svn_boolean_t remove_changelist, + const svn_checksum_t *sha1_checksum, + apr_pool_t *scratch_pool) +{ + committed_queue_item_t *cqi; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + queue->have_recursive |= recurse; + + /* Use the same pool as the one QUEUE was allocated in, + to prevent lifetime issues. Intermediate operations + should use SCRATCH_POOL. */ + + /* Add to the array with paths and options */ + cqi = apr_palloc(queue->pool, sizeof(*cqi)); + cqi->local_abspath = local_abspath; + cqi->recurse = recurse; + cqi->no_unlock = !remove_lock; + cqi->keep_changelist = !remove_changelist; + cqi->sha1_checksum = sha1_checksum; + cqi->new_dav_cache = svn_wc__prop_array_to_hash(wcprop_changes, queue->pool); + + svn_hash_sets(queue->queue, local_abspath, cqi); + + return SVN_NO_ERROR; +} + + +/* Return TRUE if any item of QUEUE is a parent of ITEM and will be + processed recursively, return FALSE otherwise. + + The algorithmic complexity of this search implementation is O(queue + length), but it's quite quick. +*/ +static svn_boolean_t +have_recursive_parent(apr_hash_t *queue, + const committed_queue_item_t *item, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + const char *local_abspath = item->local_abspath; + + for (hi = apr_hash_first(scratch_pool, queue); hi; hi = apr_hash_next(hi)) + { + const committed_queue_item_t *qi = svn__apr_hash_index_val(hi); + + if (qi == item) + continue; + + if (qi->recurse && svn_dirent_is_child(qi->local_abspath, local_abspath, + NULL)) + return TRUE; + } + + return FALSE; +} + + +svn_error_t * +svn_wc_process_committed_queue2(svn_wc_committed_queue_t *queue, + svn_wc_context_t *wc_ctx, + svn_revnum_t new_revnum, + const char *rev_date, + const char *rev_author, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *sorted_queue; + int i; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_time_t new_date; + apr_hash_t *run_wqs = apr_hash_make(scratch_pool); + apr_hash_index_t *hi; + + if (rev_date) + SVN_ERR(svn_time_from_cstring(&new_date, rev_date, iterpool)); + else + new_date = 0; + + /* Process the queued items in order of their paths. (The requirement is + * probably just that a directory must be processed before its children.) */ + sorted_queue = svn_sort__hash(queue->queue, svn_sort_compare_items_as_paths, + scratch_pool); + for (i = 0; i < sorted_queue->nelts; i++) + { + const svn_sort__item_t *sort_item + = &APR_ARRAY_IDX(sorted_queue, i, svn_sort__item_t); + const committed_queue_item_t *cqi = sort_item->value; + const char *wcroot_abspath; + + svn_pool_clear(iterpool); + + /* Skip this item if it is a child of a recursive item, because it has + been (or will be) accounted for when that recursive item was (or + will be) processed. */ + if (queue->have_recursive && have_recursive_parent(queue->queue, cqi, + iterpool)) + continue; + + SVN_ERR(svn_wc__process_committed_internal( + wc_ctx->db, cqi->local_abspath, + cqi->recurse, + TRUE /* top_of_recurse */, + new_revnum, new_date, rev_author, + cqi->new_dav_cache, + cqi->no_unlock, + cqi->keep_changelist, + cqi->sha1_checksum, queue, + iterpool)); + + /* Don't run the wq now, but remember that we must call it for this + working copy */ + SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, + wc_ctx->db, cqi->local_abspath, + iterpool, iterpool)); + + if (! svn_hash_gets(run_wqs, wcroot_abspath)) + { + wcroot_abspath = apr_pstrdup(scratch_pool, wcroot_abspath); + svn_hash_sets(run_wqs, wcroot_abspath, wcroot_abspath); + } + } + + /* Make sure nothing happens if this function is called again. */ + apr_hash_clear(queue->queue); + + /* Ok; everything is committed now. Now we can start calling callbacks */ + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + for (hi = apr_hash_first(scratch_pool, run_wqs); + hi; + hi = apr_hash_next(hi)) + { + const char *wcroot_abspath = svn__apr_hash_index_key(hi); + + svn_pool_clear(iterpool); + + SVN_ERR(svn_wc__wq_run(wc_ctx->db, wcroot_abspath, + cancel_func, cancel_baton, + iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Schedule the single node at LOCAL_ABSPATH, of kind KIND, for addition in + * its parent directory in the WC. It will have the regular properties + * provided in PROPS, or none if that is NULL. + * + * If the node is a file, set its on-disk executable and read-only bits to + * match its properties and lock state, + * ### only if it has an svn:executable or svn:needs-lock property. + * ### This is to match the previous behaviour of setting its props + * afterwards by calling svn_wc_prop_set4(), but is not very clean. + * + * Sync the on-disk executable and read-only bits accordingly. + */ +static svn_error_t * +add_from_disk(svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + const apr_hash_t *props, + apr_pool_t *scratch_pool) +{ + if (kind == svn_node_file) + { + svn_skel_t *work_item = NULL; + + if (props && (svn_prop_get_value(props, SVN_PROP_EXECUTABLE) + || svn_prop_get_value(props, SVN_PROP_NEEDS_LOCK))) + SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_op_add_file(db, local_abspath, props, work_item, + scratch_pool)); + if (work_item) + SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool)); + } + else + { + SVN_ERR(svn_wc__db_op_add_directory(db, local_abspath, props, NULL, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +/* Set *REPOS_ROOT_URL and *REPOS_UUID to the repository of the parent of + LOCAL_ABSPATH. REPOS_ROOT_URL and/or REPOS_UUID may be NULL if not + wanted. Check that the parent of LOCAL_ABSPATH is a versioned directory + in a state in which a new child node can be scheduled for addition; + return an error if not. */ +static svn_error_t * +check_can_add_to_parent(const char **repos_root_url, + const char **repos_uuid, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + svn_wc__db_status_t parent_status; + svn_node_kind_t parent_kind; + svn_error_t *err; + + SVN_ERR(svn_wc__write_check(db, parent_abspath, scratch_pool)); + + err = svn_wc__db_read_info(&parent_status, &parent_kind, NULL, + NULL, repos_root_url, repos_uuid, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, parent_abspath, result_pool, scratch_pool); + + if (err + || parent_status == svn_wc__db_status_not_present + || parent_status == svn_wc__db_status_excluded + || parent_status == svn_wc__db_status_server_excluded) + { + return + svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, err, + _("Can't find parent directory's node while" + " trying to add '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + else if (parent_status == svn_wc__db_status_deleted) + { + return + svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL, + _("Can't add '%s' to a parent directory" + " scheduled for deletion"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + else if (parent_kind != svn_node_dir) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Can't schedule an addition of '%s'" + " below a not-directory node"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + /* If we haven't found the repository info yet, find it now. */ + if ((repos_root_url && ! *repos_root_url) + || (repos_uuid && ! *repos_uuid)) + { + if (parent_status == svn_wc__db_status_added) + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, + repos_root_url, repos_uuid, NULL, + NULL, NULL, NULL, + db, parent_abspath, + result_pool, scratch_pool)); + else + SVN_ERR(svn_wc__db_scan_base_repos(NULL, + repos_root_url, repos_uuid, + db, parent_abspath, + result_pool, scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +/* Check that the on-disk item at LOCAL_ABSPATH can be scheduled for + * addition to its WC parent directory. + * + * Set *KIND_P to the kind of node to be added, *DB_ROW_EXISTS_P to whether + * it is already a versioned path, and if so, *IS_WC_ROOT_P to whether it's + * a WC root. + * + * ### The checks here, and the outputs, are geared towards svn_wc_add4(). + */ +static svn_error_t * +check_can_add_node(svn_node_kind_t *kind_p, + svn_boolean_t *db_row_exists_p, + svn_boolean_t *is_wc_root_p, + svn_wc__db_t *db, + const char *local_abspath, + const char *copyfrom_url, + svn_revnum_t copyfrom_rev, + apr_pool_t *scratch_pool) +{ + const char *base_name = svn_dirent_basename(local_abspath, scratch_pool); + svn_boolean_t is_wc_root; + svn_node_kind_t kind; + svn_boolean_t is_special; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(!copyfrom_url || (svn_uri_is_canonical(copyfrom_url, + scratch_pool) + && SVN_IS_VALID_REVNUM(copyfrom_rev))); + + /* Check that the proposed node has an acceptable name. */ + if (svn_wc_is_adm_dir(base_name, scratch_pool)) + return svn_error_createf + (SVN_ERR_ENTRY_FORBIDDEN, NULL, + _("Can't create an entry with a reserved name while trying to add '%s'"), + svn_dirent_local_style(local_abspath, scratch_pool)); + + SVN_ERR(svn_path_check_valid(local_abspath, scratch_pool)); + + /* Make sure something's there; set KIND and *KIND_P. */ + SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special, + scratch_pool)); + if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("'%s' not found"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + if (kind == svn_node_unknown) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Unsupported node kind for path '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + if (kind_p) + *kind_p = kind; + + /* Determine whether a DB row for this node EXISTS, and whether it + IS_WC_ROOT. If it exists, check that it is in an acceptable state for + adding the new node; if not, return an error. */ + { + svn_wc__db_status_t status; + svn_boolean_t conflicted; + svn_boolean_t exists; + svn_error_t *err + = svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + &conflicted, + NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + exists = FALSE; + is_wc_root = FALSE; + } + else + { + is_wc_root = FALSE; + exists = TRUE; + + /* Note that the node may be in conflict even if it does not + * exist on disk (certain tree conflict scenarios). */ + if (conflicted) + return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL, + _("'%s' is an existing item in conflict; " + "please mark the conflict as resolved " + "before adding a new item here"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + switch (status) + { + case svn_wc__db_status_not_present: + break; + case svn_wc__db_status_deleted: + /* A working copy root should never have a WORKING_NODE */ + SVN_ERR_ASSERT(!is_wc_root); + break; + case svn_wc__db_status_normal: + SVN_ERR(svn_wc__db_is_wcroot(&is_wc_root, db, local_abspath, + scratch_pool)); + + if (is_wc_root && copyfrom_url) + { + /* Integrate a sub working copy in a parent working copy + (legacy behavior) */ + break; + } + else if (is_wc_root && is_special) + { + /* Adding a symlink to a working copy root. + (special_tests.py 23: externals as symlink targets) */ + break; + } + /* else: Fall through in default error */ + + default: + return svn_error_createf( + SVN_ERR_ENTRY_EXISTS, NULL, + _("'%s' is already under version control"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + } /* err */ + + if (db_row_exists_p) + *db_row_exists_p = exists; + if (is_wc_root_p) + *is_wc_root_p = is_wc_root; + } + + return SVN_NO_ERROR; +} + + +/* Convert the nested pristine working copy rooted at LOCAL_ABSPATH into + * a copied subtree in the outer working copy. + * + * LOCAL_ABSPATH must be the root of a nested working copy that has no + * local modifications. The parent directory of LOCAL_ABSPATH must be a + * versioned directory in the outer WC, and must belong to the same + * repository as the nested WC. The nested WC will be integrated into the + * parent's WC, and will no longer be a separate WC. */ +static svn_error_t * +integrate_nested_wc_as_copy(svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + const char *moved_abspath; + + /* Drop any references to the wc that is to be rewritten */ + SVN_ERR(svn_wc__db_drop_root(db, local_abspath, scratch_pool)); + + /* Move the admin dir from the wc to a temporary location: MOVED_ABSPATH */ + { + const char *tmpdir_abspath; + const char *moved_adm_abspath; + const char *adm_abspath; + + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db, + svn_dirent_dirname(local_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_open_unique_file3(NULL, &moved_abspath, tmpdir_abspath, + svn_io_file_del_on_close, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_dir_make(moved_abspath, APR_OS_DEFAULT, scratch_pool)); + + adm_abspath = svn_wc__adm_child(local_abspath, "", scratch_pool); + moved_adm_abspath = svn_wc__adm_child(moved_abspath, "", scratch_pool); + SVN_ERR(svn_io_file_move(adm_abspath, moved_adm_abspath, scratch_pool)); + } + + /* Copy entries from temporary location into the main db */ + SVN_ERR(svn_wc_copy3(wc_ctx, moved_abspath, local_abspath, + TRUE /* metadata_only */, + NULL, NULL, NULL, NULL, scratch_pool)); + + /* Cleanup the temporary admin dir */ + SVN_ERR(svn_wc__db_drop_root(db, moved_abspath, scratch_pool)); + SVN_ERR(svn_io_remove_dir2(moved_abspath, FALSE, NULL, NULL, + scratch_pool)); + + /* The subdir is now part of our parent working copy. Our caller assumes + that we return the new node locked, so obtain a lock if we didn't + receive the lock via our depth infinity lock */ + { + svn_boolean_t owns_lock; + + SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, local_abspath, + FALSE, scratch_pool)); + if (!owns_lock) + SVN_ERR(svn_wc__db_wclock_obtain(db, local_abspath, 0, FALSE, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_add4(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + const char *copyfrom_url, + svn_revnum_t copyfrom_rev, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + svn_node_kind_t kind; + svn_boolean_t db_row_exists; + svn_boolean_t is_wc_root; + const char *repos_root_url; + const char *repos_uuid; + + SVN_ERR(check_can_add_node(&kind, &db_row_exists, &is_wc_root, + db, local_abspath, copyfrom_url, copyfrom_rev, + scratch_pool)); + + /* Get REPOS_ROOT_URL and REPOS_UUID. Check that the + parent is a versioned directory in an acceptable state. */ + SVN_ERR(check_can_add_to_parent(&repos_root_url, &repos_uuid, + db, local_abspath, scratch_pool, + scratch_pool)); + + /* If we're performing a repos-to-WC copy, check that the copyfrom + repository is the same as the parent dir's repository. */ + if (copyfrom_url && !svn_uri__is_ancestor(repos_root_url, copyfrom_url)) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("The URL '%s' has a different repository " + "root than its parent"), copyfrom_url); + + /* Verify that we can actually integrate the inner working copy */ + if (is_wc_root) + { + const char *repos_relpath, *inner_repos_root_url, *inner_repos_uuid; + const char *inner_url; + + SVN_ERR(svn_wc__db_scan_base_repos(&repos_relpath, + &inner_repos_root_url, + &inner_repos_uuid, + db, local_abspath, + scratch_pool, scratch_pool)); + + if (strcmp(inner_repos_uuid, repos_uuid) + || strcmp(repos_root_url, inner_repos_root_url)) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Can't schedule the working copy at '%s' " + "from repository '%s' with uuid '%s' " + "for addition under a working copy from " + "repository '%s' with uuid '%s'."), + svn_dirent_local_style(local_abspath, + scratch_pool), + inner_repos_root_url, inner_repos_uuid, + repos_root_url, repos_uuid); + + inner_url = svn_path_url_add_component2(repos_root_url, repos_relpath, + scratch_pool); + + if (strcmp(copyfrom_url, inner_url)) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Can't add '%s' with URL '%s', but with " + "the data from '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool), + copyfrom_url, inner_url); + } + + if (!copyfrom_url) /* Case 2a: It's a simple add */ + { + SVN_ERR(add_from_disk(db, local_abspath, kind, NULL, + scratch_pool)); + if (kind == svn_node_dir && !db_row_exists) + { + /* If using the legacy 1.6 interface the parent lock may not + be recursive and add is expected to lock the new dir. + + ### Perhaps the lock should be created in the same + transaction that adds the node? */ + svn_boolean_t owns_lock; + + SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, local_abspath, + FALSE, scratch_pool)); + if (!owns_lock) + SVN_ERR(svn_wc__db_wclock_obtain(db, local_abspath, 0, FALSE, + scratch_pool)); + } + } + else if (!is_wc_root) /* Case 2b: It's a copy from the repository */ + { + if (kind == svn_node_file) + { + /* This code should never be used, as it doesn't install proper + pristine and/or properties. But it was not an error in the old + version of this function. + + ===> Use svn_wc_add_repos_file4() directly! */ + svn_stream_t *content = svn_stream_empty(scratch_pool); + + SVN_ERR(svn_wc_add_repos_file4(wc_ctx, local_abspath, + content, NULL, NULL, NULL, + copyfrom_url, copyfrom_rev, + cancel_func, cancel_baton, + scratch_pool)); + } + else + { + const char *repos_relpath = + svn_uri_skip_ancestor(repos_root_url, copyfrom_url, scratch_pool); + + SVN_ERR(svn_wc__db_op_copy_dir(db, local_abspath, + apr_hash_make(scratch_pool), + copyfrom_rev, 0, NULL, + repos_relpath, + repos_root_url, repos_uuid, + copyfrom_rev, + NULL /* children */, FALSE, depth, + NULL /* conflicts */, + NULL /* work items */, + scratch_pool)); + } + } + else /* Case 1: Integrating a separate WC into this one, in place */ + { + SVN_ERR(integrate_nested_wc_as_copy(wc_ctx, local_abspath, + scratch_pool)); + } + + /* Report the addition to the caller. */ + if (notify_func != NULL) + { + svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, + svn_wc_notify_add, + scratch_pool); + notify->kind = kind; + (*notify_func)(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_add_from_disk2(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const apr_hash_t *props, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t kind; + + SVN_ERR(check_can_add_node(&kind, NULL, NULL, wc_ctx->db, local_abspath, + NULL, SVN_INVALID_REVNUM, scratch_pool)); + SVN_ERR(check_can_add_to_parent(NULL, NULL, wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + /* Canonicalize and check the props */ + if (props) + { + apr_hash_t *new_props; + + SVN_ERR(svn_wc__canonicalize_props( + &new_props, + local_abspath, kind, props, FALSE /* skip_some_checks */, + scratch_pool, scratch_pool)); + props = new_props; + } + + /* Add to the DB and maybe update on-disk executable read-only bits */ + SVN_ERR(add_from_disk(wc_ctx->db, local_abspath, kind, props, + scratch_pool)); + + /* Report the addition to the caller. */ + if (notify_func != NULL) + { + svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, + svn_wc_notify_add, + scratch_pool); + notify->kind = kind; + notify->mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); + (*notify_func)(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; +} + +/* Return a path where nothing exists on disk, within the admin directory + belonging to the WCROOT_ABSPATH directory. */ +static const char * +nonexistent_path(const char *wcroot_abspath, apr_pool_t *scratch_pool) +{ + return svn_wc__adm_child(wcroot_abspath, SVN_WC__ADM_NONEXISTENT_PATH, + scratch_pool); +} + + +svn_error_t * +svn_wc_get_pristine_copy_path(const char *path, + const char **pristine_path, + apr_pool_t *pool) +{ + svn_wc__db_t *db; + const char *local_abspath; + svn_error_t *err; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + + SVN_ERR(svn_wc__db_open(&db, NULL, FALSE, TRUE, pool, pool)); + /* DB is now open. This is seemingly a "light" function that a caller + may use repeatedly despite error return values. The rest of this + function should aggressively close DB, even in the error case. */ + + err = svn_wc__text_base_path_to_read(pristine_path, db, local_abspath, + pool, pool); + if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + { + /* The node doesn't exist, so return a non-existent path located + in WCROOT/.svn/ */ + const char *wcroot_abspath; + + svn_error_clear(err); + + err = svn_wc__db_get_wcroot(&wcroot_abspath, db, local_abspath, + pool, pool); + if (err == NULL) + *pristine_path = nonexistent_path(wcroot_abspath, pool); + } + + return svn_error_compose_create(err, svn_wc__db_close(db)); +} + + +svn_error_t * +svn_wc_get_pristine_contents2(svn_stream_t **contents, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__get_pristine_contents(contents, NULL, + wc_ctx->db, + local_abspath, + result_pool, + scratch_pool)); +} + + +typedef struct get_pristine_lazyopen_baton_t +{ + svn_wc_context_t *wc_ctx; + const char *wri_abspath; + const svn_checksum_t *checksum; + +} get_pristine_lazyopen_baton_t; + + +/* Implements svn_stream_lazyopen_func_t */ +static svn_error_t * +get_pristine_lazyopen_func(svn_stream_t **stream, + void *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + get_pristine_lazyopen_baton_t *b = baton; + const svn_checksum_t *sha1_checksum; + + /* svn_wc__db_pristine_read() wants a SHA1, so if we have an MD5, + we'll use it to lookup the SHA1. */ + if (b->checksum->kind == svn_checksum_sha1) + sha1_checksum = b->checksum; + else + SVN_ERR(svn_wc__db_pristine_get_sha1(&sha1_checksum, b->wc_ctx->db, + b->wri_abspath, b->checksum, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_pristine_read(stream, NULL, b->wc_ctx->db, + b->wri_abspath, sha1_checksum, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__get_pristine_contents_by_checksum(svn_stream_t **contents, + svn_wc_context_t *wc_ctx, + const char *wri_abspath, + const svn_checksum_t *checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t present; + + *contents = NULL; + + SVN_ERR(svn_wc__db_pristine_check(&present, wc_ctx->db, wri_abspath, + checksum, scratch_pool)); + + if (present) + { + get_pristine_lazyopen_baton_t *gpl_baton; + + gpl_baton = apr_pcalloc(result_pool, sizeof(*gpl_baton)); + gpl_baton->wc_ctx = wc_ctx; + gpl_baton->wri_abspath = wri_abspath; + gpl_baton->checksum = checksum; + + *contents = svn_stream_lazyopen_create(get_pristine_lazyopen_func, + gpl_baton, FALSE, result_pool); + } + + return SVN_NO_ERROR; +} + + + +svn_error_t * +svn_wc_add_lock2(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const svn_lock_t *lock, + apr_pool_t *scratch_pool) +{ + svn_wc__db_lock_t db_lock; + svn_error_t *err; + const svn_string_t *needs_lock; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* ### Enable after fixing callers */ + /*SVN_ERR(svn_wc__write_check(wc_ctx->db, + svn_dirent_dirname(local_abspath, scratch_pool), + scratch_pool));*/ + + db_lock.token = lock->token; + db_lock.owner = lock->owner; + db_lock.comment = lock->comment; + db_lock.date = lock->creation_date; + err = svn_wc__db_lock_add(wc_ctx->db, local_abspath, &db_lock, scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + /* Remap the error. */ + svn_error_clear(err); + return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, + _("'%s' is not under version control"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + /* if svn:needs-lock is present, then make the file read-write. */ + err = svn_wc__internal_propget(&needs_lock, wc_ctx->db, local_abspath, + SVN_PROP_NEEDS_LOCK, scratch_pool, + scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + { + /* The node has non wc representation (e.g. deleted), so + we don't want to touch the in-wc file */ + svn_error_clear(err); + return SVN_NO_ERROR; + } + SVN_ERR(err); + + if (needs_lock) + SVN_ERR(svn_io_set_file_read_write(local_abspath, FALSE, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_remove_lock2(svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + const svn_string_t *needs_lock; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* ### Enable after fixing callers */ + /*SVN_ERR(svn_wc__write_check(wc_ctx->db, + svn_dirent_dirname(local_abspath, scratch_pool), + scratch_pool));*/ + + err = svn_wc__db_lock_remove(wc_ctx->db, local_abspath, scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + /* Remap the error. */ + svn_error_clear(err); + return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, + _("'%s' is not under version control"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + /* if svn:needs-lock is present, then make the file read-only. */ + err = svn_wc__internal_propget(&needs_lock, wc_ctx->db, local_abspath, + SVN_PROP_NEEDS_LOCK, scratch_pool, + scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + + svn_error_clear(err); + return SVN_NO_ERROR; /* Node is shadowed and/or deleted, + so we shouldn't apply its lock */ + } + + if (needs_lock) + SVN_ERR(svn_io_set_file_read_only(local_abspath, FALSE, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_set_changelist2(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *new_changelist, + svn_depth_t depth, + const apr_array_header_t *changelist_filter, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + /* Assert that we aren't being asked to set an empty changelist. */ + SVN_ERR_ASSERT(! (new_changelist && new_changelist[0] == '\0')); + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_op_set_changelist(wc_ctx->db, local_abspath, + new_changelist, changelist_filter, + depth, notify_func, notify_baton, + cancel_func, cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +struct get_cl_fn_baton +{ + svn_wc__db_t *db; + apr_hash_t *clhash; + svn_changelist_receiver_t callback_func; + void *callback_baton; +}; + + +static svn_error_t * +get_node_changelist(const char *local_abspath, + svn_node_kind_t kind, + void *baton, + apr_pool_t *scratch_pool) +{ + struct get_cl_fn_baton *b = baton; + const char *changelist; + + SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, &changelist, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + b->db, local_abspath, + scratch_pool, scratch_pool)); + + if (svn_wc__internal_changelist_match(b->db, local_abspath, b->clhash, + scratch_pool)) + SVN_ERR(b->callback_func(b->callback_baton, local_abspath, + changelist, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_get_changelists(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + const apr_array_header_t *changelist_filter, + svn_changelist_receiver_t callback_func, + void *callback_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + struct get_cl_fn_baton gnb; + + gnb.db = wc_ctx->db; + gnb.clhash = NULL; + gnb.callback_func = callback_func; + gnb.callback_baton = callback_baton; + + if (changelist_filter) + SVN_ERR(svn_hash_from_cstring_keys(&gnb.clhash, changelist_filter, + scratch_pool)); + + return svn_error_trace( + svn_wc__internal_walk_children(wc_ctx->db, local_abspath, FALSE, + changelist_filter, get_node_changelist, + &gnb, depth, + cancel_func, cancel_baton, + scratch_pool)); + +} + + +svn_boolean_t +svn_wc__internal_changelist_match(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *clhash, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + const char *changelist; + + if (clhash == NULL) + return TRUE; + + err = svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, &changelist, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool); + if (err) + { + svn_error_clear(err); + return FALSE; + } + + return (changelist + && svn_hash_gets((apr_hash_t *)clhash, changelist) != NULL); +} + + +svn_boolean_t +svn_wc__changelist_match(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const apr_hash_t *clhash, + apr_pool_t *scratch_pool) +{ + return svn_wc__internal_changelist_match(wc_ctx->db, local_abspath, clhash, + scratch_pool); +} diff --git a/subversion/libsvn_wc/ambient_depth_filter_editor.c b/subversion/libsvn_wc/ambient_depth_filter_editor.c new file mode 100644 index 000000000000..ff9a5c397a7e --- /dev/null +++ b/subversion/libsvn_wc/ambient_depth_filter_editor.c @@ -0,0 +1,715 @@ +/* + * ambient_depth_filter_editor.c -- provide a svn_delta_editor_t which wraps + * another editor and provides + * *ambient* depth-based filtering + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "svn_delta.h" +#include "svn_wc.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" + +#include "wc.h" + +/* + Notes on the general depth-filtering strategy. + ============================================== + + When a depth-aware (>= 1.5) client pulls an update from a + non-depth-aware server, the server may send back too much data, + because it doesn't hear what the client tells it about the + "requested depth" of the update (the "foo" in "--depth=foo"), nor + about the "ambient depth" of each working copy directory. + + For example, suppose a 1.5 client does this against a 1.4 server: + + $ svn co --depth=empty -rSOME_OLD_REV http://url/repos/blah/ wc + $ cd wc + $ svn up + + In the initial checkout, the requested depth is 'empty', so the + depth-filtering editor (see libsvn_delta/depth_filter_editor.c) + that wraps the main update editor transparently filters out all + the unwanted calls. + + In the 'svn up', the requested depth is unspecified, meaning that + the ambient depth(s) of the working copy should be preserved. + Since there's only one directory, and its depth is 'empty', + clearly we should filter out or render no-ops all editor calls + after open_root(), except maybe for change_dir_prop() on the + top-level directory. (Note that the server will have stuff to + send down, because we checked out at an old revision in the first + place, to set up this scenario.) + + The depth-filtering editor won't help us here. It only filters + based on the requested depth, it never looks in the working copy + to get ambient depths. So the update editor itself will have to + filter out the unwanted calls -- or better yet, it will have to + be wrapped in a filtering editor that does the job. + + This is that filtering editor. + + Most of the work is done at the moment of baton construction. + When a file or dir is opened, we create its baton with the + appropriate ambient depth, either taking the depth directly from + the corresponding working copy object (if available), or from its + parent baton. In the latter case, we don't just copy the parent + baton's depth, but rather use it to choose the correct depth for + this child. The usual depth demotion rules apply, with the + additional stipulation that as soon as we find a subtree is not + present at all, due to being omitted for depth reasons, we set the + ambiently_excluded flag in its baton, which signals that + all descendant batons should be ignored. + (In fact, we may just re-use the parent baton, since none of the + other fields will be used anyway.) + + See issues #2842 and #2897 for more. +*/ + + +/*** Batons, and the Toys That Create Them ***/ + +struct edit_baton +{ + const svn_delta_editor_t *wrapped_editor; + void *wrapped_edit_baton; + svn_wc__db_t *db; + const char *anchor_abspath; + const char *target; +}; + +struct file_baton +{ + svn_boolean_t ambiently_excluded; + struct edit_baton *edit_baton; + void *wrapped_baton; +}; + +struct dir_baton +{ + svn_boolean_t ambiently_excluded; + svn_depth_t ambient_depth; + struct edit_baton *edit_baton; + const char *abspath; + void *wrapped_baton; +}; + +/* Fetch the STATUS, KIND and DEPTH of the base node at LOCAL_ABSPATH. + * If there is no such base node, report 'normal', 'unknown' and 'unknown' + * respectively. + * + * STATUS and/or DEPTH may be NULL if not wanted; KIND must not be NULL. + */ +static svn_error_t * +ambient_read_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_depth_t *depth, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + SVN_ERR_ASSERT(kind != NULL); + + err = svn_wc__db_base_get_info(status, kind, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, depth, NULL, NULL, + NULL, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + + *kind = svn_node_unknown; + if (status) + *status = svn_wc__db_status_normal; + if (depth) + *depth = svn_depth_unknown; + + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +make_dir_baton(struct dir_baton **d_p, + const char *path, + struct edit_baton *eb, + struct dir_baton *pb, + svn_boolean_t added, + apr_pool_t *pool) +{ + struct dir_baton *d; + + SVN_ERR_ASSERT(path || (! pb)); + + if (pb && pb->ambiently_excluded) + { + /* Just re-use the parent baton, since the only field that + matters is ambiently_excluded. */ + *d_p = pb; + return SVN_NO_ERROR; + } + + /* Okay, no easy out, so allocate and initialize a dir baton. */ + d = apr_pcalloc(pool, sizeof(*d)); + + if (path) + d->abspath = svn_dirent_join(eb->anchor_abspath, path, pool); + else + d->abspath = apr_pstrdup(pool, eb->anchor_abspath); + + /* The svn_depth_unknown means that: 1) pb is the anchor; 2) there + is an non-null target, for which we are preparing the baton. + This enables explicitly pull in the target. */ + if (pb && pb->ambient_depth != svn_depth_unknown) + { + svn_boolean_t exclude; + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_boolean_t exists = TRUE; + + if (!added) + { + SVN_ERR(ambient_read_info(&status, &kind, NULL, + eb->db, d->abspath, pool)); + } + else + { + status = svn_wc__db_status_not_present; + kind = svn_node_unknown; + } + + exists = (kind != svn_node_unknown); + + if (pb->ambient_depth == svn_depth_empty + || pb->ambient_depth == svn_depth_files) + { + /* This is not a depth upgrade, and the parent directory is + depth==empty or depth==files. So if the parent doesn't + already have an entry for the new dir, then the parent + doesn't want the new dir at all, thus we should initialize + it with ambiently_excluded=TRUE. */ + exclude = !exists; + } + else + { + /* If the parent expect all children by default, only exclude + it whenever it is explicitly marked as exclude. */ + exclude = exists && (status == svn_wc__db_status_excluded); + } + if (exclude) + { + d->ambiently_excluded = TRUE; + *d_p = d; + return SVN_NO_ERROR; + } + } + + d->edit_baton = eb; + /* We'll initialize this differently in add_directory and + open_directory. */ + d->ambient_depth = svn_depth_unknown; + + *d_p = d; + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +make_file_baton(struct file_baton **f_p, + struct dir_baton *pb, + const char *path, + svn_boolean_t added, + apr_pool_t *pool) +{ + struct file_baton *f = apr_pcalloc(pool, sizeof(*f)); + struct edit_baton *eb = pb->edit_baton; + svn_wc__db_status_t status; + svn_node_kind_t kind; + const char *abspath; + + SVN_ERR_ASSERT(path); + + if (pb->ambiently_excluded) + { + f->ambiently_excluded = TRUE; + *f_p = f; + return SVN_NO_ERROR; + } + + abspath = svn_dirent_join(eb->anchor_abspath, path, pool); + + if (!added) + { + SVN_ERR(ambient_read_info(&status, &kind, NULL, + eb->db, abspath, pool)); + } + else + { + status = svn_wc__db_status_not_present; + kind = svn_node_unknown; + } + + if (pb->ambient_depth == svn_depth_empty) + { + /* This is not a depth upgrade, and the parent directory is + depth==empty. So if the parent doesn't + already have an entry for the file, then the parent + doesn't want to hear about the file at all. */ + + if (status == svn_wc__db_status_not_present + || status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded + || kind == svn_node_unknown) + { + f->ambiently_excluded = TRUE; + *f_p = f; + return SVN_NO_ERROR; + } + } + + /* If pb->ambient_depth == svn_depth_unknown we are pulling + in new nodes */ + if (pb->ambient_depth != svn_depth_unknown + && status == svn_wc__db_status_excluded) + { + f->ambiently_excluded = TRUE; + *f_p = f; + return SVN_NO_ERROR; + } + + f->edit_baton = pb->edit_baton; + + *f_p = f; + return SVN_NO_ERROR; +} + + +/*** Editor Functions ***/ + +/* An svn_delta_editor_t function. */ +static svn_error_t * +set_target_revision(void *edit_baton, + svn_revnum_t target_revision, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + + /* Nothing depth-y to filter here. */ + return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton, + target_revision, pool); +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +open_root(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **root_baton) +{ + struct edit_baton *eb = edit_baton; + struct dir_baton *b; + + SVN_ERR(make_dir_baton(&b, NULL, eb, NULL, FALSE, pool)); + *root_baton = b; + + if (b->ambiently_excluded) + return SVN_NO_ERROR; + + if (! *eb->target) + { + /* For an update with a NULL target, this is equivalent to open_dir(): */ + svn_node_kind_t kind; + svn_wc__db_status_t status; + svn_depth_t depth; + + /* Read the depth from the entry. */ + SVN_ERR(ambient_read_info(&status, &kind, &depth, + eb->db, eb->anchor_abspath, + pool)); + + if (kind != svn_node_unknown + && status != svn_wc__db_status_not_present + && status != svn_wc__db_status_excluded + && status != svn_wc__db_status_server_excluded) + { + b->ambient_depth = depth; + } + } + + return eb->wrapped_editor->open_root(eb->wrapped_edit_baton, base_revision, + pool, &b->wrapped_baton); +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t base_revision, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + + if (pb->ambiently_excluded) + return SVN_NO_ERROR; + + if (pb->ambient_depth < svn_depth_immediates) + { + /* If the entry we want to delete doesn't exist, that's OK. + It's probably an old server that doesn't understand + depths. */ + svn_node_kind_t kind; + svn_wc__db_status_t status; + const char *abspath; + + abspath = svn_dirent_join(eb->anchor_abspath, path, pool); + + SVN_ERR(ambient_read_info(&status, &kind, NULL, + eb->db, abspath, pool)); + + if (kind == svn_node_unknown + || status == svn_wc__db_status_not_present + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_server_excluded) + return SVN_NO_ERROR; + } + + return eb->wrapped_editor->delete_entry(path, base_revision, + pb->wrapped_baton, pool); +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +add_directory(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct dir_baton *b = NULL; + + SVN_ERR(make_dir_baton(&b, path, eb, pb, TRUE, pool)); + *child_baton = b; + + if (b->ambiently_excluded) + return SVN_NO_ERROR; + + /* It's not excluded, so what should we treat the ambient depth as + being? */ + if (strcmp(eb->target, path) == 0) + { + /* The target of the edit is being added, so make it + infinity. */ + b->ambient_depth = svn_depth_infinity; + } + else if (pb->ambient_depth == svn_depth_immediates) + { + b->ambient_depth = svn_depth_empty; + } + else + { + /* There may be a requested depth < svn_depth_infinity, but + that's okay, libsvn_delta/depth_filter_editor.c will filter + further calls out for us anyway, and the update_editor will + do the right thing when it creates the directory. */ + b->ambient_depth = svn_depth_infinity; + } + + return eb->wrapped_editor->add_directory(path, pb->wrapped_baton, + copyfrom_path, + copyfrom_revision, + pool, &b->wrapped_baton); +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +open_directory(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct dir_baton *b; + const char *local_abspath; + svn_node_kind_t kind; + svn_wc__db_status_t status; + svn_depth_t depth; + + SVN_ERR(make_dir_baton(&b, path, eb, pb, FALSE, pool)); + *child_baton = b; + + if (b->ambiently_excluded) + return SVN_NO_ERROR; + + SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_baton, + base_revision, pool, + &b->wrapped_baton)); + /* Note that for the update editor, the open_directory above will + flush the logs of pb's directory, which might be important for + this svn_wc_entry call. */ + + local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool); + + SVN_ERR(ambient_read_info(&status, &kind, &depth, + eb->db, local_abspath, pool)); + + if (kind != svn_node_unknown + && status != svn_wc__db_status_not_present + && status != svn_wc__db_status_excluded + && status != svn_wc__db_status_server_excluded) + { + b->ambient_depth = depth; + } + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +add_file(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct file_baton *b = NULL; + + SVN_ERR(make_file_baton(&b, pb, path, TRUE, pool)); + *child_baton = b; + + if (b->ambiently_excluded) + return SVN_NO_ERROR; + + return eb->wrapped_editor->add_file(path, pb->wrapped_baton, + copyfrom_path, copyfrom_revision, + pool, &b->wrapped_baton); +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +open_file(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct file_baton *b; + + SVN_ERR(make_file_baton(&b, pb, path, FALSE, pool)); + *child_baton = b; + if (b->ambiently_excluded) + return SVN_NO_ERROR; + + return eb->wrapped_editor->open_file(path, pb->wrapped_baton, + base_revision, pool, + &b->wrapped_baton); +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +apply_textdelta(void *file_baton, + const char *base_checksum, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct file_baton *fb = file_baton; + struct edit_baton *eb = fb->edit_baton; + + /* For filtered files, we just consume the textdelta. */ + if (fb->ambiently_excluded) + { + *handler = svn_delta_noop_window_handler; + *handler_baton = NULL; + return SVN_NO_ERROR; + } + + return eb->wrapped_editor->apply_textdelta(fb->wrapped_baton, + base_checksum, pool, + handler, handler_baton); +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +close_file(void *file_baton, + const char *text_checksum, + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + struct edit_baton *eb = fb->edit_baton; + + if (fb->ambiently_excluded) + return SVN_NO_ERROR; + + return eb->wrapped_editor->close_file(fb->wrapped_baton, + text_checksum, pool); +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +absent_file(const char *path, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + + if (pb->ambiently_excluded) + return SVN_NO_ERROR; + + return eb->wrapped_editor->absent_file(path, pb->wrapped_baton, pool); +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +close_directory(void *dir_baton, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + struct edit_baton *eb = db->edit_baton; + + if (db->ambiently_excluded) + return SVN_NO_ERROR; + + return eb->wrapped_editor->close_directory(db->wrapped_baton, pool); +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +absent_directory(const char *path, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + + /* Don't report absent items in filtered directories. */ + if (pb->ambiently_excluded) + return SVN_NO_ERROR; + + return eb->wrapped_editor->absent_directory(path, pb->wrapped_baton, pool); +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +change_file_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + struct edit_baton *eb = fb->edit_baton; + + if (fb->ambiently_excluded) + return SVN_NO_ERROR; + + return eb->wrapped_editor->change_file_prop(fb->wrapped_baton, + name, value, pool); +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +change_dir_prop(void *dir_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + struct edit_baton *eb = db->edit_baton; + + if (db->ambiently_excluded) + return SVN_NO_ERROR; + + return eb->wrapped_editor->change_dir_prop(db->wrapped_baton, + name, value, pool); +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +close_edit(void *edit_baton, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool); +} + +svn_error_t * +svn_wc__ambient_depth_filter_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_wc__db_t *db, + const char *anchor_abspath, + const char *target, + const svn_delta_editor_t *wrapped_editor, + void *wrapped_edit_baton, + apr_pool_t *result_pool) +{ + svn_delta_editor_t *depth_filter_editor; + struct edit_baton *eb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath)); + + depth_filter_editor = svn_delta_default_editor(result_pool); + depth_filter_editor->set_target_revision = set_target_revision; + depth_filter_editor->open_root = open_root; + depth_filter_editor->delete_entry = delete_entry; + depth_filter_editor->add_directory = add_directory; + depth_filter_editor->open_directory = open_directory; + depth_filter_editor->change_dir_prop = change_dir_prop; + depth_filter_editor->close_directory = close_directory; + depth_filter_editor->absent_directory = absent_directory; + depth_filter_editor->add_file = add_file; + depth_filter_editor->open_file = open_file; + depth_filter_editor->apply_textdelta = apply_textdelta; + depth_filter_editor->change_file_prop = change_file_prop; + depth_filter_editor->close_file = close_file; + depth_filter_editor->absent_file = absent_file; + depth_filter_editor->close_edit = close_edit; + + eb = apr_pcalloc(result_pool, sizeof(*eb)); + eb->wrapped_editor = wrapped_editor; + eb->wrapped_edit_baton = wrapped_edit_baton; + eb->db = db; + eb->anchor_abspath = anchor_abspath; + eb->target = target; + + *editor = depth_filter_editor; + *edit_baton = eb; + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/cleanup.c b/subversion/libsvn_wc/cleanup.c new file mode 100644 index 000000000000..8ffb87e0fe1c --- /dev/null +++ b/subversion/libsvn_wc/cleanup.c @@ -0,0 +1,231 @@ +/* + * cleanup.c: handle cleaning up workqueue items + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <string.h> + +#include "svn_wc.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_io.h" +#include "svn_dirent_uri.h" + +#include "wc.h" +#include "adm_files.h" +#include "lock.h" +#include "workqueue.h" + +#include "private/svn_wc_private.h" +#include "svn_private_config.h" + + +/*** Recursively do log things. ***/ + +/* */ +static svn_error_t * +can_be_cleaned(int *wc_format, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_wc__internal_check_wc(wc_format, db, + local_abspath, FALSE, scratch_pool)); + + /* a "version" of 0 means a non-wc directory */ + if (*wc_format == 0) + return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, + _("'%s' is not a working copy directory"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + if (*wc_format < SVN_WC__WC_NG_VERSION) + return svn_error_create(SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL, + _("Log format too old, please use " + "Subversion 1.6 or earlier")); + + return SVN_NO_ERROR; +} + +/* Do a modifed check for LOCAL_ABSPATH, and all working children, to force + timestamp repair. */ +static svn_error_t * +repair_timestamps(svn_wc__db_t *db, + const char *local_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t kind; + svn_wc__db_status_t status; + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + SVN_ERR(svn_wc__db_read_info(&status, &kind, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool)); + + if (status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_deleted + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_not_present) + return SVN_NO_ERROR; + + if (kind == svn_node_file + || kind == svn_node_symlink) + { + svn_boolean_t modified; + SVN_ERR(svn_wc__internal_file_modified_p(&modified, + db, local_abspath, FALSE, + scratch_pool)); + } + else if (kind == svn_node_dir) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + const apr_array_header_t *children; + int i; + + SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, + local_abspath, + scratch_pool, + iterpool)); + for (i = 0; i < children->nelts; ++i) + { + const char *child_abspath; + + svn_pool_clear(iterpool); + + child_abspath = svn_dirent_join(local_abspath, + APR_ARRAY_IDX(children, i, + const char *), + iterpool); + + SVN_ERR(repair_timestamps(db, child_abspath, + cancel_func, cancel_baton, iterpool)); + } + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +cleanup_internal(svn_wc__db_t *db, + const char *dir_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + int wc_format; + svn_boolean_t is_wcroot; + const char *lock_abspath; + + /* Can we even work with this directory? */ + SVN_ERR(can_be_cleaned(&wc_format, db, dir_abspath, scratch_pool)); + + /* We cannot obtain a lock on a directory that's within a locked + subtree, so always run cleanup from the lock owner. */ + SVN_ERR(svn_wc__db_wclock_find_root(&lock_abspath, db, dir_abspath, + scratch_pool, scratch_pool)); + if (lock_abspath) + dir_abspath = lock_abspath; + SVN_ERR(svn_wc__db_wclock_obtain(db, dir_abspath, -1, TRUE, scratch_pool)); + + /* Run our changes before the subdirectories. We may not have to recurse + if we blow away a subdir. */ + if (wc_format >= SVN_WC__HAS_WORK_QUEUE) + SVN_ERR(svn_wc__wq_run(db, dir_abspath, cancel_func, cancel_baton, + scratch_pool)); + + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, dir_abspath, scratch_pool)); + +#ifdef SVN_DEBUG + SVN_ERR(svn_wc__db_verify(db, dir_abspath, scratch_pool)); +#endif + + /* Perform these operations if we lock the entire working copy. + Note that we really need to check a wcroot value and not + svn_wc__check_wcroot() as that function, will just return true + once we start sharing databases with externals. + */ + if (is_wcroot) + { + /* Cleanup the tmp area of the admin subdir, if running the log has not + removed it! The logs have been run, so anything left here has no hope + of being useful. */ + SVN_ERR(svn_wc__adm_cleanup_tmp_area(db, dir_abspath, scratch_pool)); + + /* Remove unreferenced pristine texts */ + SVN_ERR(svn_wc__db_pristine_cleanup(db, dir_abspath, scratch_pool)); + } + + SVN_ERR(repair_timestamps(db, dir_abspath, cancel_func, cancel_baton, + scratch_pool)); + + /* All done, toss the lock */ + SVN_ERR(svn_wc__db_wclock_release(db, dir_abspath, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* ### possibly eliminate the WC_CTX parameter? callers really shouldn't + ### be doing anything *but* running a cleanup, and we need a special + ### DB anyway. ... *shrug* ... consider later. */ +svn_error_t * +svn_wc_cleanup3(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* We need a DB that allows a non-empty work queue (though it *will* + auto-upgrade). We'll handle everything manually. */ + SVN_ERR(svn_wc__db_open(&db, + NULL /* ### config */, FALSE, FALSE, + scratch_pool, scratch_pool)); + + SVN_ERR(cleanup_internal(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + + /* The DAV cache suffers from flakiness from time to time, and the + pre-1.7 prescribed workarounds aren't as user-friendly in WC-NG. */ + SVN_ERR(svn_wc__db_base_clear_dav_cache_recursive(db, local_abspath, + scratch_pool)); + + SVN_ERR(svn_wc__db_vacuum(db, local_abspath, scratch_pool)); + + /* We're done with this DB, so proactively close it. */ + SVN_ERR(svn_wc__db_close(db)); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/conflicts.c b/subversion/libsvn_wc/conflicts.c new file mode 100644 index 000000000000..7a491883b6cc --- /dev/null +++ b/subversion/libsvn_wc/conflicts.c @@ -0,0 +1,3141 @@ +/* + * conflicts.c: routines for managing conflict data. + * NOTE: this code doesn't know where the conflict is + * actually stored. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <string.h> + +#include <apr_pools.h> +#include <apr_tables.h> +#include <apr_hash.h> +#include <apr_errno.h> + +#include "svn_hash.h" +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_wc.h" +#include "svn_io.h" +#include "svn_diff.h" + +#include "wc.h" +#include "wc_db.h" +#include "conflicts.h" +#include "workqueue.h" +#include "props.h" + +#include "private/svn_wc_private.h" +#include "private/svn_skel.h" +#include "private/svn_string_private.h" + +#include "svn_private_config.h" + +/* -------------------------------------------------------------------- + * Conflict skel management + */ + +svn_skel_t * +svn_wc__conflict_skel_create(apr_pool_t *result_pool) +{ + svn_skel_t *conflict_skel = svn_skel__make_empty_list(result_pool); + + /* Add empty CONFLICTS list */ + svn_skel__prepend(svn_skel__make_empty_list(result_pool), conflict_skel); + + /* Add empty WHY list */ + svn_skel__prepend(svn_skel__make_empty_list(result_pool), conflict_skel); + + return conflict_skel; +} + +svn_error_t * +svn_wc__conflict_skel_is_complete(svn_boolean_t *complete, + const svn_skel_t *conflict_skel) +{ + *complete = FALSE; + + if (svn_skel__list_length(conflict_skel) < 2) + return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL, + _("Not a conflict skel")); + + if (svn_skel__list_length(conflict_skel->children) < 2) + return SVN_NO_ERROR; /* WHY is not set */ + + if (svn_skel__list_length(conflict_skel->children->next) == 0) + return SVN_NO_ERROR; /* No conflict set */ + + *complete = TRUE; + return SVN_NO_ERROR; +} + +/* Serialize a svn_wc_conflict_version_t before the existing data in skel */ +static svn_error_t * +conflict__prepend_location(svn_skel_t *skel, + const svn_wc_conflict_version_t *location, + svn_boolean_t allow_NULL, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *loc; + SVN_ERR_ASSERT(location || allow_NULL); + + if (!location) + { + svn_skel__prepend(svn_skel__make_empty_list(result_pool), skel); + return SVN_NO_ERROR; + } + + /* ("subversion" repos_root_url repos_uuid repos_relpath rev kind) */ + loc = svn_skel__make_empty_list(result_pool); + + svn_skel__prepend_str(svn_node_kind_to_word(location->node_kind), + loc, result_pool); + + svn_skel__prepend_int(location->peg_rev, loc, result_pool); + + svn_skel__prepend_str(apr_pstrdup(result_pool, location->path_in_repos), loc, + result_pool); + + if (!location->repos_uuid) /* Can theoretically be NULL */ + svn_skel__prepend(svn_skel__make_empty_list(result_pool), loc); + else + svn_skel__prepend_str(location->repos_uuid, loc, result_pool); + + svn_skel__prepend_str(apr_pstrdup(result_pool, location->repos_url), loc, + result_pool); + + svn_skel__prepend_str(SVN_WC__CONFLICT_SRC_SUBVERSION, loc, result_pool); + + svn_skel__prepend(loc, skel); + return SVN_NO_ERROR; +} + +/* Deserialize a svn_wc_conflict_version_t from the skel. + Set *LOCATION to NULL when the data is not a svn_wc_conflict_version_t. */ +static svn_error_t * +conflict__read_location(svn_wc_conflict_version_t **location, + const svn_skel_t *skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *repos_root_url; + const char *repos_uuid; + const char *repos_relpath; + svn_revnum_t revision; + apr_int64_t v; + svn_node_kind_t node_kind; /* note that 'none' is a legitimate value */ + const char *kind_str; + + const svn_skel_t *c = skel->children; + + if (!svn_skel__matches_atom(c, SVN_WC__CONFLICT_SRC_SUBVERSION)) + { + *location = NULL; + return SVN_NO_ERROR; + } + c = c->next; + + repos_root_url = apr_pstrmemdup(result_pool, c->data, c->len); + c = c->next; + + if (c->is_atom) + repos_uuid = apr_pstrmemdup(result_pool, c->data, c->len); + else + repos_uuid = NULL; + c = c->next; + + repos_relpath = apr_pstrmemdup(result_pool, c->data, c->len); + c = c->next; + + SVN_ERR(svn_skel__parse_int(&v, c, scratch_pool)); + revision = (svn_revnum_t)v; + c = c->next; + + kind_str = apr_pstrmemdup(scratch_pool, c->data, c->len); + node_kind = svn_node_kind_from_word(kind_str); + + *location = svn_wc_conflict_version_create2(repos_root_url, + repos_uuid, + repos_relpath, + revision, + node_kind, + result_pool); + return SVN_NO_ERROR; +} + +/* Get the operation part of CONFLICT_SKELL or NULL if no operation is set + at this time */ +static svn_error_t * +conflict__get_operation(svn_skel_t **why, + const svn_skel_t *conflict_skel) +{ + SVN_ERR_ASSERT(conflict_skel + && conflict_skel->children + && conflict_skel->children->next + && !conflict_skel->children->next->is_atom); + + *why = conflict_skel->children; + + if (!(*why)->children) + *why = NULL; /* Operation is not set yet */ + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__conflict_skel_set_op_update(svn_skel_t *conflict_skel, + const svn_wc_conflict_version_t *original, + const svn_wc_conflict_version_t *target, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *why; + svn_skel_t *origins; + + SVN_ERR_ASSERT(conflict_skel + && conflict_skel->children + && conflict_skel->children->next + && !conflict_skel->children->next->is_atom); + + SVN_ERR(conflict__get_operation(&why, conflict_skel)); + + SVN_ERR_ASSERT(why == NULL); /* No operation set */ + + why = conflict_skel->children; + + origins = svn_skel__make_empty_list(result_pool); + + SVN_ERR(conflict__prepend_location(origins, target, TRUE, + result_pool, scratch_pool)); + SVN_ERR(conflict__prepend_location(origins, original, TRUE, + result_pool, scratch_pool)); + + svn_skel__prepend(origins, why); + svn_skel__prepend_str(SVN_WC__CONFLICT_OP_UPDATE, why, result_pool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_skel_set_op_switch(svn_skel_t *conflict_skel, + const svn_wc_conflict_version_t *original, + const svn_wc_conflict_version_t *target, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *why; + svn_skel_t *origins; + + SVN_ERR_ASSERT(conflict_skel + && conflict_skel->children + && conflict_skel->children->next + && !conflict_skel->children->next->is_atom); + + SVN_ERR(conflict__get_operation(&why, conflict_skel)); + + SVN_ERR_ASSERT(why == NULL); /* No operation set */ + + why = conflict_skel->children; + + origins = svn_skel__make_empty_list(result_pool); + + SVN_ERR(conflict__prepend_location(origins, target, TRUE, + result_pool, scratch_pool)); + SVN_ERR(conflict__prepend_location(origins, original, TRUE, + result_pool, scratch_pool)); + + svn_skel__prepend(origins, why); + svn_skel__prepend_str(SVN_WC__CONFLICT_OP_SWITCH, why, result_pool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_skel_set_op_merge(svn_skel_t *conflict_skel, + const svn_wc_conflict_version_t *left, + const svn_wc_conflict_version_t *right, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *why; + svn_skel_t *origins; + + SVN_ERR_ASSERT(conflict_skel + && conflict_skel->children + && conflict_skel->children->next + && !conflict_skel->children->next->is_atom); + + SVN_ERR(conflict__get_operation(&why, conflict_skel)); + + SVN_ERR_ASSERT(why == NULL); /* No operation set */ + + why = conflict_skel->children; + + origins = svn_skel__make_empty_list(result_pool); + + SVN_ERR(conflict__prepend_location(origins, right, TRUE, + result_pool, scratch_pool)); + + SVN_ERR(conflict__prepend_location(origins, left, TRUE, + result_pool, scratch_pool)); + + svn_skel__prepend(origins, why); + svn_skel__prepend_str(SVN_WC__CONFLICT_OP_MERGE, why, result_pool); + + return SVN_NO_ERROR; +} + +/* Gets the conflict data of the specified type CONFLICT_TYPE from + CONFLICT_SKEL, or NULL if no such conflict is recorded */ +static svn_error_t * +conflict__get_conflict(svn_skel_t **conflict, + const svn_skel_t *conflict_skel, + const char *conflict_type) +{ + svn_skel_t *c; + + SVN_ERR_ASSERT(conflict_skel + && conflict_skel->children + && conflict_skel->children->next + && !conflict_skel->children->next->is_atom); + + for(c = conflict_skel->children->next->children; + c; + c = c->next) + { + if (svn_skel__matches_atom(c->children, conflict_type)) + { + *conflict = c; + return SVN_NO_ERROR; + } + } + + *conflict = NULL; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_skel_add_text_conflict(svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + const char *mine_abspath, + const char *their_old_abspath, + const char *their_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *text_conflict; + svn_skel_t *markers; + + SVN_ERR(conflict__get_conflict(&text_conflict, conflict_skel, + SVN_WC__CONFLICT_KIND_TEXT)); + + SVN_ERR_ASSERT(!text_conflict); /* ### Use proper error? */ + + /* Current skel format + ("text" + (OLD MINE OLD-THEIRS THEIRS)) */ + + text_conflict = svn_skel__make_empty_list(result_pool); + markers = svn_skel__make_empty_list(result_pool); + +if (their_abspath) + { + const char *their_relpath; + + SVN_ERR(svn_wc__db_to_relpath(&their_relpath, + db, wri_abspath, their_abspath, + result_pool, scratch_pool)); + svn_skel__prepend_str(their_relpath, markers, result_pool); + } + else + svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers); + + if (mine_abspath) + { + const char *mine_relpath; + + SVN_ERR(svn_wc__db_to_relpath(&mine_relpath, + db, wri_abspath, mine_abspath, + result_pool, scratch_pool)); + svn_skel__prepend_str(mine_relpath, markers, result_pool); + } + else + svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers); + + if (their_old_abspath) + { + const char *original_relpath; + + SVN_ERR(svn_wc__db_to_relpath(&original_relpath, + db, wri_abspath, their_old_abspath, + result_pool, scratch_pool)); + svn_skel__prepend_str(original_relpath, markers, result_pool); + } + else + svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers); + + svn_skel__prepend(markers, text_conflict); + svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_TEXT, text_conflict, + result_pool); + + /* And add it to the conflict skel */ + svn_skel__prepend(text_conflict, conflict_skel->children->next); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_skel_add_prop_conflict(svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + const char *marker_abspath, + const apr_hash_t *mine_props, + const apr_hash_t *their_old_props, + const apr_hash_t *their_props, + const apr_hash_t *conflicted_prop_names, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *prop_conflict; + svn_skel_t *props; + svn_skel_t *conflict_names; + svn_skel_t *markers; + apr_hash_index_t *hi; + + SVN_ERR(conflict__get_conflict(&prop_conflict, conflict_skel, + SVN_WC__CONFLICT_KIND_PROP)); + + SVN_ERR_ASSERT(!prop_conflict); /* ### Use proper error? */ + + /* This function currently implements: + ("prop" + ("marker_relpath") + prop-conflicted_prop_names + old-props + mine-props + their-props) + NULL lists are recorded as "" */ + /* ### Seems that this may not match what we read out. Read-out of + * 'theirs-old' comes as NULL. */ + + prop_conflict = svn_skel__make_empty_list(result_pool); + + if (their_props) + { + SVN_ERR(svn_skel__unparse_proplist(&props, their_props, result_pool)); + svn_skel__prepend(props, prop_conflict); + } + else + svn_skel__prepend_str("", prop_conflict, result_pool); /* No their_props */ + + if (mine_props) + { + SVN_ERR(svn_skel__unparse_proplist(&props, mine_props, result_pool)); + svn_skel__prepend(props, prop_conflict); + } + else + svn_skel__prepend_str("", prop_conflict, result_pool); /* No mine_props */ + + if (their_old_props) + { + SVN_ERR(svn_skel__unparse_proplist(&props, their_old_props, + result_pool)); + svn_skel__prepend(props, prop_conflict); + } + else + svn_skel__prepend_str("", prop_conflict, result_pool); /* No old_props */ + + conflict_names = svn_skel__make_empty_list(result_pool); + for (hi = apr_hash_first(scratch_pool, (apr_hash_t *)conflicted_prop_names); + hi; + hi = apr_hash_next(hi)) + { + svn_skel__prepend_str(apr_pstrdup(result_pool, + svn__apr_hash_index_key(hi)), + conflict_names, + result_pool); + } + svn_skel__prepend(conflict_names, prop_conflict); + + markers = svn_skel__make_empty_list(result_pool); + + if (marker_abspath) + { + const char *marker_relpath; + SVN_ERR(svn_wc__db_to_relpath(&marker_relpath, db, wri_abspath, + marker_abspath, + result_pool, scratch_pool)); + + svn_skel__prepend_str(marker_relpath, markers, result_pool); + } +/*else // ### set via svn_wc__conflict_create_markers + svn_skel__prepend(svn_skel__make_empty_list(result_pool), markers);*/ + + svn_skel__prepend(markers, prop_conflict); + + svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_PROP, prop_conflict, result_pool); + + /* And add it to the conflict skel */ + svn_skel__prepend(prop_conflict, conflict_skel->children->next); + + return SVN_NO_ERROR; +} + +/* A map for svn_wc_conflict_reason_t values. */ +static const svn_token_map_t local_change_map[] = +{ + { "edited", svn_wc_conflict_reason_edited }, + { "obstructed", svn_wc_conflict_reason_obstructed }, + { "deleted", svn_wc_conflict_reason_deleted }, + { "missing", svn_wc_conflict_reason_missing }, + { "unversioned", svn_wc_conflict_reason_unversioned }, + { "added", svn_wc_conflict_reason_added }, + { "replaced", svn_wc_conflict_reason_replaced }, + { "moved-away", svn_wc_conflict_reason_moved_away }, + { "moved-here", svn_wc_conflict_reason_moved_here }, + { NULL } +}; + +static const svn_token_map_t incoming_change_map[] = +{ + { "edited", svn_wc_conflict_action_edit }, + { "added", svn_wc_conflict_action_add }, + { "deleted", svn_wc_conflict_action_delete }, + { "replaced", svn_wc_conflict_action_replace }, + { NULL } +}; + +svn_error_t * +svn_wc__conflict_skel_add_tree_conflict(svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + svn_wc_conflict_reason_t local_change, + svn_wc_conflict_action_t incoming_change, + const char *move_src_op_root_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *tree_conflict; + svn_skel_t *markers; + + SVN_ERR(conflict__get_conflict(&tree_conflict, conflict_skel, + SVN_WC__CONFLICT_KIND_TREE)); + + SVN_ERR_ASSERT(!tree_conflict); /* ### Use proper error? */ + + SVN_ERR_ASSERT(local_change == svn_wc_conflict_reason_moved_away + || !move_src_op_root_abspath); /* ### Use proper error? */ + + tree_conflict = svn_skel__make_empty_list(result_pool); + + if (local_change == svn_wc_conflict_reason_moved_away + && move_src_op_root_abspath) + { + const char *move_src_op_root_relpath; + + SVN_ERR(svn_wc__db_to_relpath(&move_src_op_root_relpath, + db, wri_abspath, + move_src_op_root_abspath, + result_pool, scratch_pool)); + + svn_skel__prepend_str(move_src_op_root_relpath, tree_conflict, + result_pool); + } + + svn_skel__prepend_str( + svn_token__to_word(incoming_change_map, incoming_change), + tree_conflict, result_pool); + + svn_skel__prepend_str( + svn_token__to_word(local_change_map, local_change), + tree_conflict, result_pool); + + /* Tree conflicts have no marker files */ + markers = svn_skel__make_empty_list(result_pool); + svn_skel__prepend(markers, tree_conflict); + + svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_TREE, tree_conflict, + result_pool); + + /* And add it to the conflict skel */ + svn_skel__prepend(tree_conflict, conflict_skel->children->next); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_skel_resolve(svn_boolean_t *completely_resolved, + svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + svn_boolean_t resolve_text, + const char *resolve_prop, + svn_boolean_t resolve_tree, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *op; + svn_skel_t **pconflict; + SVN_ERR(conflict__get_operation(&op, conflict_skel)); + + if (!op) + return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL, + _("Not a completed conflict skel")); + + /* We are going to drop items from a linked list. Instead of keeping + a pointer to the item we want to drop we store a pointer to the + pointer of what we may drop, to allow setting it to the next item. */ + + pconflict = &(conflict_skel->children->next->children); + while (*pconflict) + { + svn_skel_t *c = (*pconflict)->children; + + if (resolve_text + && svn_skel__matches_atom(c, SVN_WC__CONFLICT_KIND_TEXT)) + { + /* Remove the text conflict from the linked list */ + *pconflict = (*pconflict)->next; + continue; + } + else if (resolve_prop + && svn_skel__matches_atom(c, SVN_WC__CONFLICT_KIND_PROP)) + { + svn_skel_t **ppropnames = &(c->next->next->children); + + if (resolve_prop[0] == '\0') + *ppropnames = NULL; /* remove all conflicted property names */ + else + while (*ppropnames) + { + if (svn_skel__matches_atom(*ppropnames, resolve_prop)) + { + *ppropnames = (*ppropnames)->next; + break; + } + ppropnames = &((*ppropnames)->next); + } + + /* If no conflicted property names left */ + if (!c->next->next->children) + { + /* Remove the propery conflict skel from the linked list */ + *pconflict = (*pconflict)->next; + continue; + } + } + else if (resolve_tree + && svn_skel__matches_atom(c, SVN_WC__CONFLICT_KIND_TREE)) + { + /* Remove the tree conflict from the linked list */ + *pconflict = (*pconflict)->next; + continue; + } + + pconflict = &((*pconflict)->next); + } + + if (completely_resolved) + { + /* Nice, we can just call the complete function */ + svn_boolean_t complete_conflict; + SVN_ERR(svn_wc__conflict_skel_is_complete(&complete_conflict, + conflict_skel)); + + *completely_resolved = !complete_conflict; + } + return SVN_NO_ERROR; +} + + +/* A map for svn_wc_operation_t values. */ +static const svn_token_map_t operation_map[] = +{ + { "", svn_wc_operation_none }, + { SVN_WC__CONFLICT_OP_UPDATE, svn_wc_operation_update }, + { SVN_WC__CONFLICT_OP_SWITCH, svn_wc_operation_switch }, + { SVN_WC__CONFLICT_OP_MERGE, svn_wc_operation_merge }, + { NULL } +}; + +svn_error_t * +svn_wc__conflict_read_info(svn_wc_operation_t *operation, + const apr_array_header_t **locations, + svn_boolean_t *text_conflicted, + svn_boolean_t *prop_conflicted, + svn_boolean_t *tree_conflicted, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *op; + const svn_skel_t *c; + + SVN_ERR(conflict__get_operation(&op, conflict_skel)); + + if (!op) + return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL, + _("Not a completed conflict skel")); + + c = op->children; + if (operation) + { + int value = svn_token__from_mem(operation_map, c->data, c->len); + + if (value != SVN_TOKEN_UNKNOWN) + *operation = value; + else + *operation = svn_wc_operation_none; + } + c = c->next; + + if (locations && c->children) + { + const svn_skel_t *loc_skel; + svn_wc_conflict_version_t *loc; + apr_array_header_t *locs = apr_array_make(result_pool, 2, sizeof(loc)); + + for (loc_skel = c->children; loc_skel; loc_skel = loc_skel->next) + { + SVN_ERR(conflict__read_location(&loc, loc_skel, result_pool, + scratch_pool)); + + APR_ARRAY_PUSH(locs, svn_wc_conflict_version_t *) = loc; + } + + *locations = locs; + } + else if (locations) + *locations = NULL; + + if (text_conflicted) + { + svn_skel_t *c_skel; + SVN_ERR(conflict__get_conflict(&c_skel, conflict_skel, + SVN_WC__CONFLICT_KIND_TEXT)); + + *text_conflicted = (c_skel != NULL); + } + + if (prop_conflicted) + { + svn_skel_t *c_skel; + SVN_ERR(conflict__get_conflict(&c_skel, conflict_skel, + SVN_WC__CONFLICT_KIND_PROP)); + + *prop_conflicted = (c_skel != NULL); + } + + if (tree_conflicted) + { + svn_skel_t *c_skel; + SVN_ERR(conflict__get_conflict(&c_skel, conflict_skel, + SVN_WC__CONFLICT_KIND_TREE)); + + *tree_conflicted = (c_skel != NULL); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__conflict_read_text_conflict(const char **mine_abspath, + const char **their_old_abspath, + const char **their_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *text_conflict; + const svn_skel_t *m; + + SVN_ERR(conflict__get_conflict(&text_conflict, conflict_skel, + SVN_WC__CONFLICT_KIND_TEXT)); + + if (!text_conflict) + return svn_error_create(SVN_ERR_WC_MISSING, NULL, _("Conflict not set")); + + m = text_conflict->children->next->children; + + if (their_old_abspath) + { + if (m->is_atom) + { + const char *original_relpath; + + original_relpath = apr_pstrmemdup(scratch_pool, m->data, m->len); + SVN_ERR(svn_wc__db_from_relpath(their_old_abspath, + db, wri_abspath, original_relpath, + result_pool, scratch_pool)); + } + else + *their_old_abspath = NULL; + } + m = m->next; + + if (mine_abspath) + { + if (m->is_atom) + { + const char *mine_relpath; + + mine_relpath = apr_pstrmemdup(scratch_pool, m->data, m->len); + SVN_ERR(svn_wc__db_from_relpath(mine_abspath, + db, wri_abspath, mine_relpath, + result_pool, scratch_pool)); + } + else + *mine_abspath = NULL; + } + m = m->next; + + if (their_abspath) + { + if (m->is_atom) + { + const char *their_relpath; + + their_relpath = apr_pstrmemdup(scratch_pool, m->data, m->len); + SVN_ERR(svn_wc__db_from_relpath(their_abspath, + db, wri_abspath, their_relpath, + result_pool, scratch_pool)); + } + else + *their_abspath = NULL; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_read_prop_conflict(const char **marker_abspath, + apr_hash_t **mine_props, + apr_hash_t **their_old_props, + apr_hash_t **their_props, + apr_hash_t **conflicted_prop_names, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *prop_conflict; + const svn_skel_t *c; + + SVN_ERR(conflict__get_conflict(&prop_conflict, conflict_skel, + SVN_WC__CONFLICT_KIND_PROP)); + + if (!prop_conflict) + return svn_error_create(SVN_ERR_WC_MISSING, NULL, _("Conflict not set")); + + c = prop_conflict->children; + + c = c->next; /* Skip "prop" */ + + /* Get marker file */ + if (marker_abspath) + { + const char *marker_relpath; + + if (c->children && c->children->is_atom) + { + marker_relpath = apr_pstrmemdup(result_pool, c->children->data, + c->children->len); + + SVN_ERR(svn_wc__db_from_relpath(marker_abspath, db, wri_abspath, + marker_relpath, + result_pool, scratch_pool)); + } + else + *marker_abspath = NULL; + } + c = c->next; + + /* Get conflicted properties */ + if (conflicted_prop_names) + { + const svn_skel_t *name; + *conflicted_prop_names = apr_hash_make(result_pool); + + for (name = c->children; name; name = name->next) + { + svn_hash_sets(*conflicted_prop_names, + apr_pstrmemdup(result_pool, name->data, name->len), + ""); + } + } + c = c->next; + + /* Get original properties */ + if (their_old_props) + { + if (c->is_atom) + *their_old_props = apr_hash_make(result_pool); + else + SVN_ERR(svn_skel__parse_proplist(their_old_props, c, result_pool)); + } + c = c->next; + + /* Get mine properties */ + if (mine_props) + { + if (c->is_atom) + *mine_props = apr_hash_make(result_pool); + else + SVN_ERR(svn_skel__parse_proplist(mine_props, c, result_pool)); + } + c = c->next; + + /* Get their properties */ + if (their_props) + { + if (c->is_atom) + *their_props = apr_hash_make(result_pool); + else + SVN_ERR(svn_skel__parse_proplist(their_props, c, result_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_read_tree_conflict(svn_wc_conflict_reason_t *local_change, + svn_wc_conflict_action_t *incoming_change, + const char **move_src_op_root_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *tree_conflict; + const svn_skel_t *c; + svn_boolean_t is_moved_away = FALSE; + + SVN_ERR(conflict__get_conflict(&tree_conflict, conflict_skel, + SVN_WC__CONFLICT_KIND_TREE)); + + if (!tree_conflict) + return svn_error_create(SVN_ERR_WC_MISSING, NULL, _("Conflict not set")); + + c = tree_conflict->children; + + c = c->next; /* Skip "tree" */ + + c = c->next; /* Skip markers */ + + { + int value = svn_token__from_mem(local_change_map, c->data, c->len); + + if (local_change) + { + if (value != SVN_TOKEN_UNKNOWN) + *local_change = value; + else + *local_change = svn_wc_conflict_reason_edited; + } + + is_moved_away = (value == svn_wc_conflict_reason_moved_away); + } + c = c->next; + + if (incoming_change) + { + int value = svn_token__from_mem(incoming_change_map, c->data, c->len); + + if (value != SVN_TOKEN_UNKNOWN) + *incoming_change = value; + else + *incoming_change = svn_wc_conflict_action_edit; + } + + c = c->next; + + if (move_src_op_root_abspath) + { + /* Only set for update and switch tree conflicts */ + if (c && is_moved_away) + { + const char *move_src_op_root_relpath + = apr_pstrmemdup(scratch_pool, c->data, c->len); + + SVN_ERR(svn_wc__db_from_relpath(move_src_op_root_abspath, + db, wri_abspath, + move_src_op_root_relpath, + result_pool, scratch_pool)); + } + else + *move_src_op_root_abspath = NULL; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_read_markers(const apr_array_header_t **markers, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *conflict; + apr_array_header_t *list = NULL; + + SVN_ERR_ASSERT(conflict_skel != NULL); + + /* Walk the conflicts */ + for (conflict = conflict_skel->children->next->children; + conflict; + conflict = conflict->next) + { + const svn_skel_t *marker; + + /* Get the list of markers stored per conflict */ + for (marker = conflict->children->next->children; + marker; + marker = marker->next) + { + /* Skip placeholders */ + if (! marker->is_atom) + continue; + + if (! list) + list = apr_array_make(result_pool, 4, sizeof(const char *)); + + SVN_ERR(svn_wc__db_from_relpath( + &APR_ARRAY_PUSH(list, const char*), + db, wri_abspath, + apr_pstrmemdup(scratch_pool, marker->data, + marker->len), + result_pool, scratch_pool)); + } + } + *markers = list; + + return SVN_NO_ERROR; +} + +/* -------------------------------------------------------------------- + */ +/* Helper for svn_wc__conflict_create_markers */ +static svn_skel_t * +prop_conflict_skel_new(apr_pool_t *result_pool) +{ + svn_skel_t *operation = svn_skel__make_empty_list(result_pool); + svn_skel_t *result = svn_skel__make_empty_list(result_pool); + + svn_skel__prepend(operation, result); + return result; +} + + +/* Helper for prop_conflict_skel_add */ +static void +prepend_prop_value(const svn_string_t *value, + svn_skel_t *skel, + apr_pool_t *result_pool) +{ + svn_skel_t *value_skel = svn_skel__make_empty_list(result_pool); + + if (value != NULL) + { + const void *dup = apr_pmemdup(result_pool, value->data, value->len); + + svn_skel__prepend(svn_skel__mem_atom(dup, value->len, result_pool), + value_skel); + } + + svn_skel__prepend(value_skel, skel); +} + + +/* Helper for svn_wc__conflict_create_markers */ +static svn_error_t * +prop_conflict_skel_add( + svn_skel_t *skel, + const char *prop_name, + const svn_string_t *original_value, + const svn_string_t *mine_value, + const svn_string_t *incoming_value, + const svn_string_t *incoming_base_value, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *prop_skel = svn_skel__make_empty_list(result_pool); + + /* ### check that OPERATION has been filled in. */ + + /* See notes/wc-ng/conflict-storage */ + prepend_prop_value(incoming_base_value, prop_skel, result_pool); + prepend_prop_value(incoming_value, prop_skel, result_pool); + prepend_prop_value(mine_value, prop_skel, result_pool); + prepend_prop_value(original_value, prop_skel, result_pool); + svn_skel__prepend_str(apr_pstrdup(result_pool, prop_name), prop_skel, + result_pool); + svn_skel__prepend_str(SVN_WC__CONFLICT_KIND_PROP, prop_skel, result_pool); + + /* Now we append PROP_SKEL to the end of the provided conflict SKEL. */ + svn_skel__append(skel, prop_skel); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflict_create_markers(svn_skel_t **work_items, + svn_wc__db_t *db, + const char *local_abspath, + svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t prop_conflicted; + svn_wc_operation_t operation; + *work_items = NULL; + + SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, + NULL, &prop_conflicted, NULL, + db, local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + if (prop_conflicted) + { + const char *marker_abspath = NULL; + svn_node_kind_t kind; + const char *marker_dir; + const char *marker_name; + const char *marker_relpath; + + /* Ok, currently we have to do a few things for property conflicts: + - Create a marker file + - Create a WQ item that sets the marker name + - Create a WQ item that fills the marker with the expected data + + This can be simplified once we really store conflict_skel in wc.db */ + + SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool)); + + if (kind == svn_node_dir) + { + marker_dir = local_abspath; + marker_name = SVN_WC__THIS_DIR_PREJ; + } + else + svn_dirent_split(&marker_dir, &marker_name, local_abspath, + scratch_pool); + + SVN_ERR(svn_io_open_uniquely_named(NULL, &marker_abspath, + marker_dir, + marker_name, + SVN_WC__PROP_REJ_EXT, + svn_io_file_del_none, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_to_relpath(&marker_relpath, db, local_abspath, + marker_abspath, result_pool, result_pool)); + + /* And store the marker in the skel */ + { + svn_skel_t *prop_conflict; + SVN_ERR(conflict__get_conflict(&prop_conflict, conflict_skel, + SVN_WC__CONFLICT_KIND_PROP)); + + svn_skel__prepend_str(marker_relpath, prop_conflict->children->next, + result_pool); + } + + /* Store the data in the WQ item in the same format used as 1.7. + Once we store the data in DB it is easier to just read it back + from the workqueue */ + { + svn_skel_t *prop_data; + apr_hash_index_t *hi; + apr_hash_t *old_props; + apr_hash_t *mine_props; + apr_hash_t *their_original_props; + apr_hash_t *their_props; + apr_hash_t *conflicted_props; + + SVN_ERR(svn_wc__conflict_read_prop_conflict(NULL, + &mine_props, + &their_original_props, + &their_props, + &conflicted_props, + db, local_abspath, + conflict_skel, + scratch_pool, + scratch_pool)); + + if (operation == svn_wc_operation_merge) + SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + old_props = their_original_props; + + prop_data = prop_conflict_skel_new(result_pool); + + for (hi = apr_hash_first(scratch_pool, conflicted_props); + hi; + hi = apr_hash_next(hi)) + { + const char *propname = svn__apr_hash_index_key(hi); + + SVN_ERR(prop_conflict_skel_add( + prop_data, propname, + old_props + ? svn_hash_gets(old_props, propname) + : NULL, + mine_props + ? svn_hash_gets(mine_props, propname) + : NULL, + their_props + ? svn_hash_gets(their_props, propname) + : NULL, + their_original_props + ? svn_hash_gets(their_original_props, propname) + : NULL, + result_pool, scratch_pool)); + } + + SVN_ERR(svn_wc__wq_build_prej_install(work_items, + db, local_abspath, + prop_data, + scratch_pool, scratch_pool)); + } + } + + return SVN_NO_ERROR; +} + +/* Helper function for the three apply_* functions below, used when + * merging properties together. + * + * Given property PROPNAME on LOCAL_ABSPATH, and four possible property + * values, generate four tmpfiles and pass them to CONFLICT_FUNC callback. + * This gives the client an opportunity to interactively resolve the + * property conflict. + * + * BASE_VAL/WORKING_VAL represent the current state of the working + * copy, and INCOMING_OLD_VAL/INCOMING_NEW_VAL represents the incoming + * propchange. Any of these values might be NULL, indicating either + * non-existence or intent-to-delete. + * + * If the callback isn't available, or if it responds with + * 'choose_postpone', then set *CONFLICT_REMAINS to TRUE and return. + * + * If the callback responds with a choice of 'base', 'theirs', 'mine', + * or 'merged', then install the proper value into ACTUAL_PROPS and + * set *CONFLICT_REMAINS to FALSE. + */ +static svn_error_t * +generate_propconflict(svn_boolean_t *conflict_remains, + svn_wc__db_t *db, + const char *local_abspath, + svn_wc_operation_t operation, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + const char *propname, + const svn_string_t *base_val, + const svn_string_t *working_val, + const svn_string_t *incoming_old_val, + const svn_string_t *incoming_new_val, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + apr_pool_t *scratch_pool) +{ + svn_wc_conflict_result_t *result = NULL; + svn_wc_conflict_description2_t *cdesc; + const char *dirpath = svn_dirent_dirname(local_abspath, scratch_pool); + svn_node_kind_t kind; + const svn_string_t *new_value = NULL; + + SVN_ERR(svn_wc__db_read_kind(&kind, db, local_abspath, + FALSE /* allow_missing */, + FALSE /* show_deleted */, + FALSE /* show_hidden */, + scratch_pool)); + + if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + cdesc = svn_wc_conflict_description_create_prop2( + local_abspath, + (kind == svn_node_dir) ? svn_node_dir : svn_node_file, + propname, scratch_pool); + + cdesc->operation = operation; + cdesc->src_left_version = left_version; + cdesc->src_right_version = right_version; + + /* Create a tmpfile for each of the string_t's we've got. */ + if (working_val) + { + const char *file_name; + + SVN_ERR(svn_io_write_unique(&file_name, dirpath, working_val->data, + working_val->len, + svn_io_file_del_on_pool_cleanup, + scratch_pool)); + cdesc->my_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); + } + + if (incoming_new_val) + { + const char *file_name; + + SVN_ERR(svn_io_write_unique(&file_name, dirpath, incoming_new_val->data, + incoming_new_val->len, + svn_io_file_del_on_pool_cleanup, + scratch_pool)); + cdesc->their_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); + } + + if (!base_val && !incoming_old_val) + { + /* If base and old are both NULL, then that's fine, we just let + base_file stay NULL as-is. Both agents are attempting to add a + new property. */ + } + + else if ((base_val && !incoming_old_val) + || (!base_val && incoming_old_val)) + { + /* If only one of base and old are defined, then we've got a + situation where one agent is attempting to add the property + for the first time, and the other agent is changing a + property it thinks already exists. In this case, we return + whichever older-value happens to be defined, so that the + conflict-callback can still attempt a 3-way merge. */ + + const svn_string_t *conflict_base_val = base_val ? base_val + : incoming_old_val; + const char *file_name; + + SVN_ERR(svn_io_write_unique(&file_name, dirpath, + conflict_base_val->data, + conflict_base_val->len, + svn_io_file_del_on_pool_cleanup, + scratch_pool)); + cdesc->base_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); + } + + else /* base and old are both non-NULL */ + { + const svn_string_t *conflict_base_val; + const char *file_name; + + if (! svn_string_compare(base_val, incoming_old_val)) + { + /* What happens if 'base' and 'old' don't match up? In an + ideal situation, they would. But if they don't, this is + a classic example of a patch 'hunk' failing to apply due + to a lack of context. For example: imagine that the user + is busy changing the property from a value of "cat" to + "dog", but the incoming propchange wants to change the + same property value from "red" to "green". Total context + mismatch. + + HOWEVER: we can still pass one of the two base values as + 'base_file' to the callback anyway. It's still useful to + present the working and new values to the user to + compare. */ + + if (working_val && svn_string_compare(base_val, working_val)) + conflict_base_val = incoming_old_val; + else + conflict_base_val = base_val; + } + else + { + conflict_base_val = base_val; + } + + SVN_ERR(svn_io_write_unique(&file_name, dirpath, conflict_base_val->data, + conflict_base_val->len, + svn_io_file_del_on_pool_cleanup, scratch_pool)); + cdesc->base_abspath = svn_dirent_join(dirpath, file_name, scratch_pool); + + if (working_val && incoming_new_val) + { + svn_stream_t *mergestream; + svn_diff_t *diff; + svn_diff_file_options_t *options = + svn_diff_file_options_create(scratch_pool); + + SVN_ERR(svn_stream_open_unique(&mergestream, &cdesc->merged_file, + NULL, svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + SVN_ERR(svn_diff_mem_string_diff3(&diff, conflict_base_val, + working_val, + incoming_new_val, options, scratch_pool)); + SVN_ERR(svn_diff_mem_string_output_merge2 + (mergestream, diff, conflict_base_val, working_val, + incoming_new_val, NULL, NULL, NULL, NULL, + svn_diff_conflict_display_modified_latest, scratch_pool)); + SVN_ERR(svn_stream_close(mergestream)); + } + } + + if (!incoming_old_val && incoming_new_val) + cdesc->action = svn_wc_conflict_action_add; + else if (incoming_old_val && !incoming_new_val) + cdesc->action = svn_wc_conflict_action_delete; + else + cdesc->action = svn_wc_conflict_action_edit; + + if (base_val && !working_val) + cdesc->reason = svn_wc_conflict_reason_deleted; + else if (!base_val && working_val) + cdesc->reason = svn_wc_conflict_reason_obstructed; + else + cdesc->reason = svn_wc_conflict_reason_edited; + + /* Invoke the interactive conflict callback. */ + { + SVN_ERR(conflict_func(&result, cdesc, conflict_baton, scratch_pool, + scratch_pool)); + } + if (result == NULL) + { + *conflict_remains = TRUE; + return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + NULL, _("Conflict callback violated API:" + " returned no results")); + } + + + switch (result->choice) + { + default: + case svn_wc_conflict_choose_postpone: + { + *conflict_remains = TRUE; + break; + } + case svn_wc_conflict_choose_mine_full: + { + /* No need to change actual_props; it already contains working_val */ + *conflict_remains = FALSE; + new_value = working_val; + break; + } + /* I think _mine_full and _theirs_full are appropriate for prop + behavior as well as the text behavior. There should even be + analogous behaviors for _mine and _theirs when those are + ready, namely: fold in all non-conflicting prop changes, and + then choose _mine side or _theirs side for conflicting ones. */ + case svn_wc_conflict_choose_theirs_full: + { + *conflict_remains = FALSE; + new_value = incoming_new_val; + break; + } + case svn_wc_conflict_choose_base: + { + *conflict_remains = FALSE; + new_value = base_val; + break; + } + case svn_wc_conflict_choose_merged: + { + svn_stringbuf_t *merged_stringbuf; + + if (!cdesc->merged_file && !result->merged_file) + return svn_error_create + (SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + NULL, _("Conflict callback violated API:" + " returned no merged file")); + + SVN_ERR(svn_stringbuf_from_file2(&merged_stringbuf, + result->merged_file ? + result->merged_file : + cdesc->merged_file, + scratch_pool)); + new_value = svn_stringbuf__morph_into_string(merged_stringbuf); + *conflict_remains = FALSE; + break; + } + } + + if (!*conflict_remains) + { + apr_hash_t *props; + + /* For now, just set the property values. This should really do some of the + more advanced things from svn_wc_prop_set() */ + + SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, scratch_pool, + scratch_pool)); + + svn_hash_sets(props, propname, new_value); + + SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, props, + FALSE, NULL, NULL, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* Resolve the text conflict on DB/LOCAL_ABSPATH in the manner specified + * by CHOICE. + * + * Set *WORK_ITEMS to new work items that will make the on-disk changes + * needed to complete the resolution (but not to mark it as resolved). + * Set *IS_RESOLVED to true if the conflicts are resolved; otherwise + * (which is only if CHOICE is 'postpone') to false. + * + * LEFT_ABSPATH, RIGHT_ABSPATH, and DETRANSLATED_TARGET are the + * input files to the 3-way merge that will be performed if CHOICE is + * 'theirs-conflict' or 'mine-conflict'. LEFT_ABSPATH is also the file + * that will be used if CHOICE is 'base', and RIGHT_ABSPATH if CHOICE is + * 'theirs-full'. MERGED_ABSPATH will be used if CHOICE is 'merged'. + * + * DETRANSLATED_TARGET is the detranslated version of 'mine' (see + * detranslate_wc_file() above). MERGE_OPTIONS are passed to the + * diff3 implementation in case a 3-way merge has to be carried out. + */ +static svn_error_t * +eval_text_conflict_func_result(svn_skel_t **work_items, + svn_boolean_t *is_resolved, + svn_wc__db_t *db, + const char *local_abspath, + svn_wc_conflict_choice_t choice, + const apr_array_header_t *merge_options, + const char *left_abspath, + const char *right_abspath, + const char *merged_abspath, + const char *detranslated_target, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *install_from_abspath = NULL; + svn_boolean_t remove_source = FALSE; + + *work_items = NULL; + + switch (choice) + { + /* If the callback wants to use one of the fulltexts + to resolve the conflict, so be it.*/ + case svn_wc_conflict_choose_base: + { + install_from_abspath = left_abspath; + *is_resolved = TRUE; + break; + } + case svn_wc_conflict_choose_theirs_full: + { + install_from_abspath = right_abspath; + *is_resolved = TRUE; + break; + } + case svn_wc_conflict_choose_mine_full: + { + install_from_abspath = detranslated_target; + *is_resolved = TRUE; + break; + } + case svn_wc_conflict_choose_theirs_conflict: + case svn_wc_conflict_choose_mine_conflict: + { + const char *chosen_abspath; + const char *temp_dir; + svn_stream_t *chosen_stream; + svn_diff_t *diff; + svn_diff_conflict_display_style_t style; + svn_diff_file_options_t *diff3_options; + + diff3_options = svn_diff_file_options_create(scratch_pool); + + if (merge_options) + SVN_ERR(svn_diff_file_options_parse(diff3_options, + merge_options, + scratch_pool)); + + style = choice == svn_wc_conflict_choose_theirs_conflict + ? svn_diff_conflict_display_latest + : svn_diff_conflict_display_modified; + + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db, + local_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_unique(&chosen_stream, &chosen_abspath, + temp_dir, svn_io_file_del_none, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_diff_file_diff3_2(&diff, + left_abspath, + detranslated_target, right_abspath, + diff3_options, scratch_pool)); + SVN_ERR(svn_diff_file_output_merge2(chosen_stream, diff, + left_abspath, + detranslated_target, + right_abspath, + /* markers ignored */ + NULL, NULL, + NULL, NULL, + style, + scratch_pool)); + SVN_ERR(svn_stream_close(chosen_stream)); + + install_from_abspath = chosen_abspath; + remove_source = TRUE; + *is_resolved = TRUE; + break; + } + + /* For the case of 3-way file merging, we don't + really distinguish between these return values; + if the callback claims to have "generally + resolved" the situation, we still interpret + that as "OK, we'll assume the merged version is + good to use". */ + case svn_wc_conflict_choose_merged: + { + install_from_abspath = merged_abspath; + *is_resolved = TRUE; + break; + } + case svn_wc_conflict_choose_postpone: + default: + { + /* Assume conflict remains. */ + *is_resolved = FALSE; + return SVN_NO_ERROR; + } + } + + SVN_ERR_ASSERT(install_from_abspath != NULL); + + { + svn_skel_t *work_item; + + SVN_ERR(svn_wc__wq_build_file_install(&work_item, + db, local_abspath, + install_from_abspath, + FALSE /* use_commit_times */, + FALSE /* record_fileinfo */, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + if (remove_source) + { + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, + db, local_abspath, + install_from_abspath, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + } + } + + return SVN_NO_ERROR; +} + + +/* Create a new file in the same directory as LOCAL_ABSPATH, with the + same basename as LOCAL_ABSPATH, with a ".edited" extension, and set + *WORK_ITEM to a new work item that will copy and translate from the file + SOURCE_ABSPATH to that new file. It will be translated from repository- + normal form to working-copy form according to the versioned properties + of LOCAL_ABSPATH that are current when the work item is executed. + + DB should have a write lock for the directory containing SOURCE. + + Allocate *WORK_ITEM in RESULT_POOL. */ +static svn_error_t * +save_merge_result(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + const char *source_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *edited_copy_abspath; + const char *dir_abspath; + const char *filename; + + svn_dirent_split(&dir_abspath, &filename, local_abspath, scratch_pool); + + /* ### Should use preserved-conflict-file-exts. */ + /* Create the .edited file within this file's DIR_ABSPATH */ + SVN_ERR(svn_io_open_uniquely_named(NULL, + &edited_copy_abspath, + dir_abspath, + filename, + ".edited", + svn_io_file_del_none, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__wq_build_file_copy_translated(work_item, + db, local_abspath, + source_abspath, + edited_copy_abspath, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + + +/* Call the conflict resolver callback for a text conflict, and resolve + * the conflict if it tells us to do so. + * + * Assume that there is a text conflict on the path DB/LOCAL_ABSPATH. + * + * Call CONFLICT_FUNC with CONFLICT_BATON to find out whether and how + * it wants to resolve the conflict. Pass it a conflict description + * containing OPERATION, LEFT/RIGHT_ABSPATH, LEFT/RIGHT_VERSION, + * RESULT_TARGET and DETRANSLATED_TARGET. + * + * If the callback returns a resolution other than 'postpone', then + * perform that requested resolution and prepare to mark the conflict + * as resolved. + * + * Return *WORK_ITEMS that will do the on-disk work required to complete + * the resolution (but not to mark the conflict as resolved), and set + * *WAS_RESOLVED to true, if it was resolved. Set *WORK_ITEMS to NULL + * and *WAS_RESOLVED to FALSE otherwise. + * + * RESULT_TARGET is the path to the merged file produced by the internal + * or external 3-way merge, which may contain conflict markers, in + * repository normal form. DETRANSLATED_TARGET is the 'mine' version of + * the file, also in RNF. + */ +static svn_error_t * +resolve_text_conflict(svn_skel_t **work_items, + svn_boolean_t *was_resolved, + svn_wc__db_t *db, + const char *local_abspath, + const apr_array_header_t *merge_options, + svn_wc_operation_t operation, + const char *left_abspath, + const char *right_abspath, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + const char *result_target, + const char *detranslated_target, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_conflict_result_t *result; + svn_skel_t *work_item; + svn_wc_conflict_description2_t *cdesc; + apr_hash_t *props; + + *work_items = NULL; + *was_resolved = FALSE; + + /* Give the conflict resolution callback a chance to clean + up the conflicts before we mark the file 'conflicted' */ + + SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, + scratch_pool, scratch_pool)); + + cdesc = svn_wc_conflict_description_create_text2(local_abspath, + scratch_pool); + cdesc->is_binary = FALSE; + cdesc->mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); + cdesc->base_abspath = left_abspath; + cdesc->their_abspath = right_abspath; + cdesc->my_abspath = detranslated_target; + cdesc->merged_file = result_target; + cdesc->operation = operation; + cdesc->src_left_version = left_version; + cdesc->src_right_version = right_version; + + SVN_ERR(conflict_func(&result, cdesc, conflict_baton, scratch_pool, + scratch_pool)); + if (result == NULL) + return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Conflict callback violated API:" + " returned no results")); + + if (result->save_merged) + { + SVN_ERR(save_merge_result(work_items, + db, local_abspath, + /* Look for callback's own + merged-file first: */ + result->merged_file + ? result->merged_file + : result_target, + result_pool, scratch_pool)); + } + + if (result->choice != svn_wc_conflict_choose_postpone) + { + SVN_ERR(eval_text_conflict_func_result(&work_item, + was_resolved, + db, local_abspath, + result->choice, + merge_options, + left_abspath, + right_abspath, + /* ### Sure this is an abspath? */ + result->merged_file + ? result->merged_file + : result_target, + detranslated_target, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + } + else + *was_resolved = FALSE; + + return SVN_NO_ERROR; +} + + +static svn_error_t * +setup_tree_conflict_desc(svn_wc_conflict_description2_t **desc, + svn_wc__db_t *db, + const char *local_abspath, + svn_wc_operation_t operation, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + svn_wc_conflict_reason_t local_change, + svn_wc_conflict_action_t incoming_change, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t tc_kind; + + if (left_version) + tc_kind = left_version->node_kind; + else if (right_version) + tc_kind = right_version->node_kind; + else + tc_kind = svn_node_file; /* Avoid assertion */ + + *desc = svn_wc_conflict_description_create_tree2(local_abspath, tc_kind, + operation, + left_version, right_version, + result_pool); + (*desc)->reason = local_change; + (*desc)->action = incoming_change; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__conflict_invoke_resolver(svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflict_skel, + const apr_array_header_t *merge_options, + svn_wc_conflict_resolver_func2_t resolver_func, + void *resolver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_boolean_t text_conflicted; + svn_boolean_t prop_conflicted; + svn_boolean_t tree_conflicted; + svn_wc_operation_t operation; + const apr_array_header_t *locations; + const svn_wc_conflict_version_t *left_version = NULL; + const svn_wc_conflict_version_t *right_version = NULL; + + SVN_ERR(svn_wc__conflict_read_info(&operation, &locations, + &text_conflicted, &prop_conflicted, + &tree_conflicted, + db, local_abspath, conflict_skel, + scratch_pool, scratch_pool)); + + if (locations && locations->nelts > 0) + left_version = APR_ARRAY_IDX(locations, 0, const svn_wc_conflict_version_t *); + + if (locations && locations->nelts > 1) + right_version = APR_ARRAY_IDX(locations, 1, const svn_wc_conflict_version_t *); + + /* Quick and dirty compatibility wrapper. My guess would be that most resolvers + would want to look at all properties at the same time. + + ### svn currently only invokes this from the merge code to collect the list of + ### conflicted paths. Eventually this code will be the base for 'svn resolve' + ### and at that time the test coverage will improve + */ + if (prop_conflicted) + { + apr_hash_t *old_props; + apr_hash_t *mine_props; + apr_hash_t *their_props; + apr_hash_t *old_their_props; + apr_hash_t *conflicted; + apr_pool_t *iterpool; + apr_hash_index_t *hi; + svn_boolean_t mark_resolved = TRUE; + + SVN_ERR(svn_wc__conflict_read_prop_conflict(NULL, + &mine_props, + &old_their_props, + &their_props, + &conflicted, + db, local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + if (operation == svn_wc_operation_merge) + SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + old_props = old_their_props; + + iterpool = svn_pool_create(scratch_pool); + + for (hi = apr_hash_first(scratch_pool, conflicted); + hi; + hi = apr_hash_next(hi)) + { + const char *propname = svn__apr_hash_index_key(hi); + svn_boolean_t conflict_remains = TRUE; + + svn_pool_clear(iterpool); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + SVN_ERR(generate_propconflict(&conflict_remains, + db, local_abspath, + operation, + left_version, + right_version, + propname, + old_props + ? svn_hash_gets(old_props, propname) + : NULL, + mine_props + ? svn_hash_gets(mine_props, propname) + : NULL, + old_their_props + ? svn_hash_gets(old_their_props, propname) + : NULL, + their_props + ? svn_hash_gets(their_props, propname) + : NULL, + resolver_func, resolver_baton, + iterpool)); + + if (conflict_remains) + mark_resolved = FALSE; + } + + if (mark_resolved) + { + SVN_ERR(svn_wc__mark_resolved_prop_conflicts(db, local_abspath, + scratch_pool)); + } + } + + if (text_conflicted) + { + const char *mine_abspath; + const char *their_original_abspath; + const char *their_abspath; + svn_skel_t *work_items; + svn_boolean_t was_resolved; + + SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath, + &their_original_abspath, + &their_abspath, + db, local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + SVN_ERR(resolve_text_conflict(&work_items, &was_resolved, + db, local_abspath, + merge_options, + operation, + their_original_abspath, their_abspath, + left_version, right_version, + local_abspath, mine_abspath, + resolver_func, resolver_baton, + scratch_pool, scratch_pool)); + + if (was_resolved) + { + if (work_items) + { + SVN_ERR(svn_wc__db_wq_add(db, local_abspath, work_items, + scratch_pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, + cancel_func, cancel_baton, + scratch_pool)); + } + SVN_ERR(svn_wc__mark_resolved_text_conflict(db, local_abspath, + scratch_pool)); + } + } + + if (tree_conflicted) + { + svn_wc_conflict_reason_t local_change; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_result_t *result; + svn_wc_conflict_description2_t *desc; + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&local_change, + &incoming_change, + NULL, + db, local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + SVN_ERR(setup_tree_conflict_desc(&desc, + db, local_abspath, + operation, left_version, right_version, + local_change, incoming_change, + scratch_pool, scratch_pool)); + + /* Tell the resolver func about this conflict. */ + SVN_ERR(resolver_func(&result, desc, resolver_baton, scratch_pool, + scratch_pool)); + + /* Ignore the result. We cannot apply it here since this code runs + * during an update or merge operation. Tree conflicts are always + * postponed and resolved after the operation has completed. */ + } + + return SVN_NO_ERROR; +} + +/* Read all property conflicts contained in CONFLICT_SKEL into + * individual conflict descriptions, and append those descriptions + * to the CONFLICTS array. + * + * If NOT create_tempfiles, always create a legacy property conflict + * descriptor. + * + * Use NODE_KIND, OPERATION and shallow copies of LEFT_VERSION and + * RIGHT_VERSION, rather than reading them from CONFLICT_SKEL. + * + * Allocate results in RESULT_POOL. SCRATCH_POOL is used for temporary + * allocations. */ +static svn_error_t * +read_prop_conflicts(apr_array_header_t *conflicts, + svn_wc__db_t *db, + const char *local_abspath, + svn_skel_t *conflict_skel, + svn_boolean_t create_tempfiles, + svn_node_kind_t node_kind, + svn_wc_operation_t operation, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *prop_reject_file; + apr_hash_t *my_props; + apr_hash_t *their_old_props; + apr_hash_t *their_props; + apr_hash_t *conflicted_props; + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + SVN_ERR(svn_wc__conflict_read_prop_conflict(&prop_reject_file, + &my_props, + &their_old_props, + &their_props, + &conflicted_props, + db, local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + if ((! create_tempfiles) || apr_hash_count(conflicted_props) == 0) + { + /* Legacy prop conflict with only a .reject file. */ + svn_wc_conflict_description2_t *desc; + + desc = svn_wc_conflict_description_create_prop2(local_abspath, + node_kind, + "", result_pool); + + /* ### This should be changed. The prej file should be stored + * ### separately from the other files. We need to rev the + * ### conflict description struct for this. */ + desc->their_abspath = apr_pstrdup(result_pool, prop_reject_file); + + desc->operation = operation; + desc->src_left_version = left_version; + desc->src_right_version = right_version; + + APR_ARRAY_PUSH(conflicts, svn_wc_conflict_description2_t*) = desc; + + return SVN_NO_ERROR; + } + + iterpool = svn_pool_create(scratch_pool); + for (hi = apr_hash_first(scratch_pool, conflicted_props); + hi; + hi = apr_hash_next(hi)) + { + const char *propname = svn__apr_hash_index_key(hi); + svn_string_t *old_value; + svn_string_t *my_value; + svn_string_t *their_value; + svn_wc_conflict_description2_t *desc; + + svn_pool_clear(iterpool); + + desc = svn_wc_conflict_description_create_prop2(local_abspath, + node_kind, + propname, + result_pool); + + desc->operation = operation; + desc->src_left_version = left_version; + desc->src_right_version = right_version; + + desc->property_name = apr_pstrdup(result_pool, propname); + + my_value = svn_hash_gets(my_props, propname); + their_value = svn_hash_gets(their_props, propname); + old_value = svn_hash_gets(their_old_props, propname); + + /* Compute the incoming side of the conflict ('action'). */ + if (their_value == NULL) + desc->action = svn_wc_conflict_action_delete; + else if (old_value == NULL) + desc->action = svn_wc_conflict_action_add; + else + desc->action = svn_wc_conflict_action_edit; + + /* Compute the local side of the conflict ('reason'). */ + if (my_value == NULL) + desc->reason = svn_wc_conflict_reason_deleted; + else if (old_value == NULL) + desc->reason = svn_wc_conflict_reason_added; + else + desc->reason = svn_wc_conflict_reason_edited; + + /* ### This should be changed. The prej file should be stored + * ### separately from the other files. We need to rev the + * ### conflict description struct for this. */ + desc->their_abspath = apr_pstrdup(result_pool, prop_reject_file); + + /* ### This should be changed. The conflict description for + * ### props should contain these values as svn_string_t, + * ### rather than in temporary files. We need to rev the + * ### conflict description struct for this. */ + if (my_value) + { + svn_stream_t *s; + apr_size_t len; + + SVN_ERR(svn_stream_open_unique(&s, &desc->my_abspath, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, iterpool)); + len = my_value->len; + SVN_ERR(svn_stream_write(s, my_value->data, &len)); + SVN_ERR(svn_stream_close(s)); + } + + if (their_value) + { + svn_stream_t *s; + apr_size_t len; + + /* ### Currently, their_abspath is used for the prop reject file. + * ### Put their value into merged instead... + * ### We need to rev the conflict description struct to fix this. */ + SVN_ERR(svn_stream_open_unique(&s, &desc->merged_file, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, iterpool)); + len = their_value->len; + SVN_ERR(svn_stream_write(s, their_value->data, &len)); + SVN_ERR(svn_stream_close(s)); + } + + if (old_value) + { + svn_stream_t *s; + apr_size_t len; + + SVN_ERR(svn_stream_open_unique(&s, &desc->base_abspath, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, iterpool)); + len = old_value->len; + SVN_ERR(svn_stream_write(s, old_value->data, &len)); + SVN_ERR(svn_stream_close(s)); + } + + APR_ARRAY_PUSH(conflicts, svn_wc_conflict_description2_t*) = desc; + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__read_conflicts(const apr_array_header_t **conflicts, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t create_tempfiles, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *conflict_skel; + apr_array_header_t *cflcts; + svn_boolean_t prop_conflicted; + svn_boolean_t text_conflicted; + svn_boolean_t tree_conflicted; + svn_wc_operation_t operation; + const apr_array_header_t *locations; + const svn_wc_conflict_version_t *left_version = NULL; + const svn_wc_conflict_version_t *right_version = NULL; + + SVN_ERR(svn_wc__db_read_conflict(&conflict_skel, db, local_abspath, + scratch_pool, scratch_pool)); + + if (!conflict_skel) + { + /* Some callers expect not NULL */ + *conflicts = apr_array_make(result_pool, 0, + sizeof(svn_wc_conflict_description2_t*));; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_wc__conflict_read_info(&operation, &locations, &text_conflicted, + &prop_conflicted, &tree_conflicted, + db, local_abspath, conflict_skel, + result_pool, scratch_pool)); + + cflcts = apr_array_make(result_pool, 4, + sizeof(svn_wc_conflict_description2_t*)); + + if (locations && locations->nelts > 0) + left_version = APR_ARRAY_IDX(locations, 0, const svn_wc_conflict_version_t *); + if (locations && locations->nelts > 1) + right_version = APR_ARRAY_IDX(locations, 1, const svn_wc_conflict_version_t *); + + if (prop_conflicted) + { + svn_node_kind_t node_kind + = left_version ? left_version->node_kind : svn_node_unknown; + + SVN_ERR(read_prop_conflicts(cflcts, db, local_abspath, conflict_skel, + create_tempfiles, node_kind, + operation, left_version, right_version, + result_pool, scratch_pool)); + } + + if (text_conflicted) + { + svn_wc_conflict_description2_t *desc; + desc = svn_wc_conflict_description_create_text2(local_abspath, + result_pool); + + desc->operation = operation; + desc->src_left_version = left_version; + desc->src_right_version = right_version; + + SVN_ERR(svn_wc__conflict_read_text_conflict(&desc->my_abspath, + &desc->base_abspath, + &desc->their_abspath, + db, local_abspath, + conflict_skel, + result_pool, scratch_pool)); + + desc->merged_file = apr_pstrdup(result_pool, local_abspath); + + APR_ARRAY_PUSH(cflcts, svn_wc_conflict_description2_t*) = desc; + } + + if (tree_conflicted) + { + svn_wc_conflict_reason_t local_change; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_description2_t *desc; + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&local_change, + &incoming_change, + NULL, + db, local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + SVN_ERR(setup_tree_conflict_desc(&desc, + db, local_abspath, + operation, left_version, right_version, + local_change, incoming_change, + result_pool, scratch_pool)); + + APR_ARRAY_PUSH(cflcts, const svn_wc_conflict_description2_t *) = desc; + } + + *conflicts = cflcts; + return SVN_NO_ERROR; +} + + +/*** Resolving a conflict automatically ***/ + +/* Prepare to delete an artifact file at ARTIFACT_FILE_ABSPATH in the + * working copy at DB/WRI_ABSPATH. + * + * Set *WORK_ITEMS to a new work item that, when run, will delete the + * artifact file; or to NULL if there is no file to delete. + * + * Set *FILE_FOUND to TRUE if the artifact file is found on disk and its + * node kind is 'file'; otherwise do not change *FILE_FOUND. FILE_FOUND + * may be NULL if not required. + */ +static svn_error_t * +remove_artifact_file_if_exists(svn_skel_t **work_items, + svn_boolean_t *file_found, + svn_wc__db_t *db, + const char *wri_abspath, + const char *artifact_file_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + *work_items = NULL; + if (artifact_file_abspath) + { + svn_node_kind_t node_kind; + + SVN_ERR(svn_io_check_path(artifact_file_abspath, &node_kind, + scratch_pool)); + if (node_kind == svn_node_file) + { + SVN_ERR(svn_wc__wq_build_file_remove(work_items, + db, wri_abspath, + artifact_file_abspath, + result_pool, scratch_pool)); + if (file_found) + *file_found = TRUE; + } + } + + return SVN_NO_ERROR; +} + +/* + * Resolve the text conflict found in DB/LOCAL_ABSPATH according + * to CONFLICT_CHOICE. + * + * It is not an error if there is no text conflict. If a text conflict + * existed and was resolved, set *DID_RESOLVE to TRUE, else set it to FALSE. + * + * Note: When there are no conflict markers to remove there is no existing + * text conflict; just a database containing old information, which we should + * remove to avoid checking all the time. Resolving a text conflict by + * removing all the marker files is a fully supported scenario since + * Subversion 1.0. + */ +static svn_error_t * +resolve_text_conflict_on_node(svn_boolean_t *did_resolve, + svn_wc__db_t *db, + const char *local_abspath, + svn_wc_conflict_choice_t conflict_choice, + const char *merged_file, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const char *conflict_old = NULL; + const char *conflict_new = NULL; + const char *conflict_working = NULL; + const char *auto_resolve_src; + svn_skel_t *work_item; + svn_skel_t *work_items = NULL; + svn_skel_t *conflicts; + svn_wc_operation_t operation; + svn_boolean_t text_conflicted; + + *did_resolve = FALSE; + + SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + scratch_pool, scratch_pool)); + if (!conflicts) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, &text_conflicted, + NULL, NULL, db, local_abspath, conflicts, + scratch_pool, scratch_pool)); + if (!text_conflicted) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__conflict_read_text_conflict(&conflict_working, + &conflict_old, + &conflict_new, + db, local_abspath, conflicts, + scratch_pool, scratch_pool)); + + /* Handle automatic conflict resolution before the temporary files are + * deleted, if necessary. */ + switch (conflict_choice) + { + case svn_wc_conflict_choose_base: + auto_resolve_src = conflict_old; + break; + case svn_wc_conflict_choose_mine_full: + auto_resolve_src = conflict_working; + break; + case svn_wc_conflict_choose_theirs_full: + auto_resolve_src = conflict_new; + break; + case svn_wc_conflict_choose_merged: + auto_resolve_src = merged_file; + break; + case svn_wc_conflict_choose_theirs_conflict: + case svn_wc_conflict_choose_mine_conflict: + { + if (conflict_old && conflict_working && conflict_new) + { + const char *temp_dir; + svn_stream_t *tmp_stream = NULL; + svn_diff_t *diff; + svn_diff_conflict_display_style_t style = + conflict_choice == svn_wc_conflict_choose_theirs_conflict + ? svn_diff_conflict_display_latest + : svn_diff_conflict_display_modified; + + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, db, + local_abspath, + scratch_pool, + scratch_pool)); + SVN_ERR(svn_stream_open_unique(&tmp_stream, + &auto_resolve_src, + temp_dir, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_diff_file_diff3_2(&diff, + conflict_old, + conflict_working, + conflict_new, + svn_diff_file_options_create( + scratch_pool), + scratch_pool)); + SVN_ERR(svn_diff_file_output_merge2(tmp_stream, diff, + conflict_old, + conflict_working, + conflict_new, + /* markers ignored */ + NULL, NULL, NULL, NULL, + style, + scratch_pool)); + SVN_ERR(svn_stream_close(tmp_stream)); + } + else + auto_resolve_src = NULL; + break; + } + default: + return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Invalid 'conflict_result' argument")); + } + + if (auto_resolve_src) + { + SVN_ERR(svn_wc__wq_build_file_copy_translated( + &work_item, db, local_abspath, + auto_resolve_src, local_abspath, scratch_pool, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + + SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, + local_abspath, + scratch_pool, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + } + + /* Legacy behavior: Only report text conflicts as resolved when at least + one conflict marker file exists. + + If not the UI shows the conflict as already resolved + (and in this case we just remove the in-db conflict) */ + + SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve, + db, local_abspath, conflict_old, + scratch_pool, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + + SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve, + db, local_abspath, conflict_new, + scratch_pool, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + + SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve, + db, local_abspath, conflict_working, + scratch_pool, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + + SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, + TRUE, FALSE, FALSE, + work_items, scratch_pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* + * Resolve the property conflicts found in DB/LOCAL_ABSPATH according + * to CONFLICT_CHOICE. + * + * It is not an error if there is no prop conflict. If a prop conflict + * existed and was resolved, set *DID_RESOLVE to TRUE, else set it to FALSE. + * + * Note: When there are no conflict markers on-disk to remove there is + * no existing text conflict (unless we are still in the process of + * creating the text conflict and we didn't register a marker file yet). + * In this case the database contains old information, which we should + * remove to avoid checking the next time. Resolving a property conflict + * by just removing the marker file is a fully supported scenario since + * Subversion 1.0. + * + * ### TODO [JAF] The '*_full' and '*_conflict' choices should differ. + * In my opinion, 'mine_full'/'theirs_full' should select + * the entire set of properties from 'mine' or 'theirs' respectively, + * while 'mine_conflict'/'theirs_conflict' should select just the + * properties that are in conflict. Or, '_full' should select the + * entire property whereas '_conflict' should do a text merge within + * each property, selecting hunks. Or all three kinds of behaviour + * should be available (full set of props, full value of conflicting + * props, or conflicting text hunks). + * ### BH: If we make *_full select the full set of properties, we should + * check if we shouldn't make it also select the full text for files. + * + * ### TODO [JAF] All this complexity should not be down here in libsvn_wc + * but in a layer above. + * + * ### TODO [JAF] Options for 'base' should be like options for 'mine' and + * for 'theirs' -- choose full set of props, full value of conflicting + * props, or conflicting text hunks. + * + */ +static svn_error_t * +resolve_prop_conflict_on_node(svn_boolean_t *did_resolve, + svn_wc__db_t *db, + const char *local_abspath, + const char *conflicted_propname, + svn_wc_conflict_choice_t conflict_choice, + const char *merged_file, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const char *prop_reject_file; + apr_hash_t *mine_props; + apr_hash_t *their_old_props; + apr_hash_t *their_props; + apr_hash_t *conflicted_props; + apr_hash_t *old_props; + apr_hash_t *resolve_from = NULL; + svn_skel_t *work_items = NULL; + svn_skel_t *conflicts; + svn_wc_operation_t operation; + svn_boolean_t prop_conflicted; + + *did_resolve = FALSE; + + SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + scratch_pool, scratch_pool)); + + if (!conflicts) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, &prop_conflicted, + NULL, db, local_abspath, conflicts, + scratch_pool, scratch_pool)); + if (!prop_conflicted) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__conflict_read_prop_conflict(&prop_reject_file, + &mine_props, &their_old_props, + &their_props, &conflicted_props, + db, local_abspath, conflicts, + scratch_pool, scratch_pool)); + + if (operation == svn_wc_operation_merge) + SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + old_props = their_old_props; + + /* We currently handle *_conflict as *_full as this argument is currently + always applied for all conflicts on a node at the same time. Giving + an error would break some tests that assumed that this would just + resolve property conflicts to working. + + An alternative way to handle these conflicts would be to just copy all + property state from mine/theirs on the _full option instead of just the + conflicted properties. In some ways this feels like a sensible option as + that would take both properties and text from mine/theirs, but when not + both properties and text are conflicted we would fail in doing so. + */ + switch (conflict_choice) + { + case svn_wc_conflict_choose_base: + resolve_from = their_old_props ? their_old_props : old_props; + break; + case svn_wc_conflict_choose_mine_full: + case svn_wc_conflict_choose_mine_conflict: + resolve_from = mine_props; + break; + case svn_wc_conflict_choose_theirs_full: + case svn_wc_conflict_choose_theirs_conflict: + resolve_from = their_props; + break; + case svn_wc_conflict_choose_merged: + if (merged_file && conflicted_propname[0] != '\0') + { + apr_hash_t *actual_props; + svn_stream_t *stream; + svn_string_t *merged_propval; + + SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath, + scratch_pool, scratch_pool)); + resolve_from = actual_props; + + SVN_ERR(svn_stream_open_readonly(&stream, merged_file, + scratch_pool, scratch_pool)); + SVN_ERR(svn_string_from_stream(&merged_propval, stream, + scratch_pool, scratch_pool)); + svn_hash_sets(resolve_from, conflicted_propname, merged_propval); + } + else + resolve_from = NULL; + break; + default: + return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Invalid 'conflict_result' argument")); + } + + if (conflicted_props && apr_hash_count(conflicted_props) && resolve_from) + { + apr_hash_index_t *hi; + apr_hash_t *actual_props; + + SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath, + scratch_pool, scratch_pool)); + + for (hi = apr_hash_first(scratch_pool, conflicted_props); + hi; + hi = apr_hash_next(hi)) + { + const char *propname = svn__apr_hash_index_key(hi); + svn_string_t *new_value = NULL; + + new_value = svn_hash_gets(resolve_from, propname); + + svn_hash_sets(actual_props, propname, new_value); + } + SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, actual_props, + FALSE, NULL, NULL, + scratch_pool)); + } + + /* Legacy behavior: Only report property conflicts as resolved when the + property reject file exists + + If not the UI shows the conflict as already resolved + (and in this case we just remove the in-db conflict) */ + + { + svn_skel_t *work_item; + + SVN_ERR(remove_artifact_file_if_exists(&work_item, did_resolve, + db, local_abspath, prop_reject_file, + scratch_pool, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + } + + SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, FALSE, TRUE, FALSE, + work_items, scratch_pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* + * Resolve the tree conflict found in DB/LOCAL_ABSPATH according to + * CONFLICT_CHOICE. + * + * It is not an error if there is no tree conflict. If a tree conflict + * existed and was resolved, set *DID_RESOLVE to TRUE, else set it to FALSE. + * + * It is not an error if there is no tree conflict. + */ +static svn_error_t * +resolve_tree_conflict_on_node(svn_boolean_t *did_resolve, + svn_wc__db_t *db, + const char *local_abspath, + svn_wc_conflict_choice_t conflict_choice, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc_conflict_reason_t reason; + svn_wc_conflict_action_t action; + svn_skel_t *conflicts; + svn_wc_operation_t operation; + svn_boolean_t tree_conflicted; + + *did_resolve = FALSE; + + SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + scratch_pool, scratch_pool)); + if (!conflicts) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, NULL, + &tree_conflicted, db, local_abspath, + conflicts, scratch_pool, scratch_pool)); + if (!tree_conflicted) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, NULL, + db, local_abspath, + conflicts, + scratch_pool, scratch_pool)); + + if (operation == svn_wc_operation_update + || operation == svn_wc_operation_switch) + { + if (reason == svn_wc_conflict_reason_deleted || + reason == svn_wc_conflict_reason_replaced) + { + if (conflict_choice == svn_wc_conflict_choose_merged) + { + /* Break moves for any children moved out of this directory, + * and leave this directory deleted. */ + SVN_ERR(svn_wc__db_resolve_break_moved_away_children( + db, local_abspath, notify_func, notify_baton, + scratch_pool)); + *did_resolve = TRUE; + } + else if (conflict_choice == svn_wc_conflict_choose_mine_conflict) + { + /* Raised moved-away conflicts on any children moved out of + * this directory, and leave this directory deleted. + * The newly conflicted moved-away children will be updated + * if they are resolved with 'mine_conflict' as well. */ + SVN_ERR(svn_wc__db_resolve_delete_raise_moved_away( + db, local_abspath, notify_func, notify_baton, + scratch_pool)); + *did_resolve = TRUE; + } + else + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + NULL, + _("Tree conflict can only be resolved to " + "'working' or 'mine-conflict' state; " + "'%s' not resolved"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + else if (reason == svn_wc_conflict_reason_moved_away + && action == svn_wc_conflict_action_edit) + { + /* After updates, we can resolve local moved-away + * vs. any incoming change, either by updating the + * moved-away node (mine-conflict) or by breaking the + * move (theirs-conflict). */ + if (conflict_choice == svn_wc_conflict_choose_mine_conflict) + { + SVN_ERR(svn_wc__db_update_moved_away_conflict_victim( + db, local_abspath, + notify_func, notify_baton, + cancel_func, cancel_baton, + scratch_pool)); + *did_resolve = TRUE; + } + else if (conflict_choice == svn_wc_conflict_choose_merged) + { + /* We must break the move if the user accepts the current + * working copy state instead of updating the move. + * Else the move would be left in an invalid state. */ + + /* ### This breaks the move but leaves the conflict + ### involving the move until + ### svn_wc__db_op_mark_resolved. */ + SVN_ERR(svn_wc__db_resolve_break_moved_away(db, local_abspath, + notify_func, + notify_baton, + scratch_pool)); + *did_resolve = TRUE; + } + else + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + NULL, + _("Tree conflict can only be resolved to " + "'working' or 'mine-conflict' state; " + "'%s' not resolved"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + } + + if (! *did_resolve && conflict_choice != svn_wc_conflict_choose_merged) + { + /* For other tree conflicts, there is no way to pick + * theirs-full or mine-full, etc. Throw an error if the + * user expects us to be smarter than we really are. */ + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + NULL, + _("Tree conflict can only be " + "resolved to 'working' state; " + "'%s' not resolved"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, FALSE, FALSE, TRUE, + NULL, scratch_pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__mark_resolved_text_conflict(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_boolean_t ignored_result; + + return svn_error_trace(resolve_text_conflict_on_node( + &ignored_result, + db, local_abspath, + svn_wc_conflict_choose_merged, NULL, + NULL, NULL, + scratch_pool)); +} + +svn_error_t * +svn_wc__mark_resolved_prop_conflicts(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_boolean_t ignored_result; + + return svn_error_trace(resolve_prop_conflict_on_node( + &ignored_result, + db, local_abspath, "", + svn_wc_conflict_choose_merged, NULL, + NULL, NULL, + scratch_pool)); +} + + +/* Baton for conflict_status_walker */ +struct conflict_status_walker_baton +{ + svn_wc__db_t *db; + svn_boolean_t resolve_text; + const char *resolve_prop; + svn_boolean_t resolve_tree; + svn_wc_conflict_choice_t conflict_choice; + svn_wc_conflict_resolver_func2_t conflict_func; + void *conflict_baton; + svn_cancel_func_t cancel_func; + void *cancel_baton; + svn_wc_notify_func2_t notify_func; + void *notify_baton; +}; + +/* Implements svn_wc_status4_t to walk all conflicts to resolve. + */ +static svn_error_t * +conflict_status_walker(void *baton, + const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + struct conflict_status_walker_baton *cswb = baton; + svn_wc__db_t *db = cswb->db; + + const apr_array_header_t *conflicts; + apr_pool_t *iterpool; + int i; + svn_boolean_t resolved = FALSE; + + if (!status->conflicted) + return SVN_NO_ERROR; + + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_wc__read_conflicts(&conflicts, db, local_abspath, TRUE, + scratch_pool, iterpool)); + + for (i = 0; i < conflicts->nelts; i++) + { + const svn_wc_conflict_description2_t *cd; + svn_boolean_t did_resolve; + svn_wc_conflict_choice_t my_choice = cswb->conflict_choice; + const char *merged_file = NULL; + + cd = APR_ARRAY_IDX(conflicts, i, const svn_wc_conflict_description2_t *); + + svn_pool_clear(iterpool); + + if (my_choice == svn_wc_conflict_choose_unspecified) + { + svn_wc_conflict_result_t *result; + + if (!cswb->conflict_func) + return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("No conflict-callback and no " + "pre-defined conflict-choice provided")); + + SVN_ERR(cswb->conflict_func(&result, cd, cswb->conflict_baton, + iterpool, iterpool)); + + my_choice = result->choice; + merged_file = result->merged_file; + /* ### Bug: ignores result->save_merged */ + } + + + if (my_choice == svn_wc_conflict_choose_postpone) + continue; + + switch (cd->kind) + { + case svn_wc_conflict_kind_tree: + if (!cswb->resolve_tree) + break; + SVN_ERR(resolve_tree_conflict_on_node(&did_resolve, + db, + local_abspath, + my_choice, + cswb->notify_func, + cswb->notify_baton, + cswb->cancel_func, + cswb->cancel_baton, + iterpool)); + + resolved = TRUE; + break; + + case svn_wc_conflict_kind_text: + if (!cswb->resolve_text) + break; + + SVN_ERR(resolve_text_conflict_on_node(&did_resolve, + db, + local_abspath, + my_choice, + merged_file, + cswb->cancel_func, + cswb->cancel_baton, + iterpool)); + + if (did_resolve) + resolved = TRUE; + break; + + case svn_wc_conflict_kind_property: + if (!cswb->resolve_prop) + break; + + if (*cswb->resolve_prop != '\0' && + strcmp(cswb->resolve_prop, cd->property_name) != 0) + { + break; /* This is not the property we want to resolve. */ + } + + SVN_ERR(resolve_prop_conflict_on_node(&did_resolve, + db, + local_abspath, + cd->property_name, + my_choice, + merged_file, + cswb->cancel_func, + cswb->cancel_baton, + iterpool)); + + if (did_resolve) + resolved = TRUE; + break; + + default: + /* We can't resolve other conflict types */ + break; + } + } + + /* Notify */ + if (cswb->notify_func && resolved) + cswb->notify_func(cswb->notify_baton, + svn_wc_create_notify(local_abspath, + svn_wc_notify_resolved, + iterpool), + iterpool); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__resolve_conflicts(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t resolve_text, + const char *resolve_prop, + svn_boolean_t resolve_tree, + svn_wc_conflict_choice_t conflict_choice, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t kind; + svn_boolean_t conflicted; + struct conflict_status_walker_baton cswb; + + /* ### the underlying code does NOT support resolving individual + ### properties. bail out if the caller tries it. */ + if (resolve_prop != NULL && *resolve_prop != '\0') + return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, + U_("Resolving a single property is not (yet) " + "supported.")); + + /* ### Just a versioned check? */ + /* Conflicted is set to allow invoking on actual only nodes */ + SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, &conflicted, + NULL, NULL, NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + /* When the implementation still used the entry walker, depth + unknown was translated to infinity. */ + if (kind != svn_node_dir) + depth = svn_depth_empty; + else if (depth == svn_depth_unknown) + depth = svn_depth_infinity; + + cswb.db = wc_ctx->db; + cswb.resolve_text = resolve_text; + cswb.resolve_prop = resolve_prop; + cswb.resolve_tree = resolve_tree; + cswb.conflict_choice = conflict_choice; + + cswb.conflict_func = conflict_func; + cswb.conflict_baton = conflict_baton; + + cswb.cancel_func = cancel_func; + cswb.cancel_baton = cancel_baton; + + cswb.notify_func = notify_func; + cswb.notify_baton = notify_baton; + + if (notify_func) + notify_func(notify_baton, + svn_wc_create_notify(local_abspath, + svn_wc_notify_conflict_resolver_starting, + scratch_pool), + scratch_pool); + + SVN_ERR(svn_wc_walk_status(wc_ctx, + local_abspath, + depth, + FALSE /* get_all */, + FALSE /* no_ignore */, + TRUE /* ignore_text_mods */, + NULL /* ignore_patterns */, + conflict_status_walker, &cswb, + cancel_func, cancel_baton, + scratch_pool)); + + if (notify_func) + notify_func(notify_baton, + svn_wc_create_notify(local_abspath, + svn_wc_notify_conflict_resolver_done, + scratch_pool), + scratch_pool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_resolved_conflict5(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t resolve_text, + const char *resolve_prop, + svn_boolean_t resolve_tree, + svn_wc_conflict_choice_t conflict_choice, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__resolve_conflicts(wc_ctx, local_abspath, + depth, resolve_text, + resolve_prop, resolve_tree, + conflict_choice, + NULL, NULL, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); +} + +/* Constructor for the result-structure returned by conflict callbacks. */ +svn_wc_conflict_result_t * +svn_wc_create_conflict_result(svn_wc_conflict_choice_t choice, + const char *merged_file, + apr_pool_t *pool) +{ + svn_wc_conflict_result_t *result = apr_pcalloc(pool, sizeof(*result)); + result->choice = choice; + result->merged_file = merged_file; + result->save_merged = FALSE; + + /* If we add more fields to svn_wc_conflict_result_t, add them here. */ + + return result; +} diff --git a/subversion/libsvn_wc/conflicts.h b/subversion/libsvn_wc/conflicts.h new file mode 100644 index 000000000000..d4730653189b --- /dev/null +++ b/subversion/libsvn_wc/conflicts.h @@ -0,0 +1,443 @@ +/* + * conflicts.h: declarations related to conflicts + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_WC_CONFLICTS_H +#define SVN_WC_CONFLICTS_H + +#include <apr_pools.h> + +#include "svn_types.h" +#include "svn_wc.h" + +#include "wc_db.h" +#include "private/svn_skel.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +#define SVN_WC__CONFLICT_OP_UPDATE "update" +#define SVN_WC__CONFLICT_OP_SWITCH "switch" +#define SVN_WC__CONFLICT_OP_MERGE "merge" +#define SVN_WC__CONFLICT_OP_PATCH "patch" + +#define SVN_WC__CONFLICT_KIND_TEXT "text" +#define SVN_WC__CONFLICT_KIND_PROP "prop" +#define SVN_WC__CONFLICT_KIND_TREE "tree" +#define SVN_WC__CONFLICT_KIND_REJECT "reject" +#define SVN_WC__CONFLICT_KIND_OBSTRUCTED "obstructed" + +#define SVN_WC__CONFLICT_SRC_SUBVERSION "subversion" + +/* Return a new conflict skel, allocated in RESULT_POOL. + + Typically creating a conflict starts with calling this function and then + collecting details via one or more calls to svn_wc__conflict_skel_add_*(). + + The caller can then (when necessary) add operation details via + svn_wc__conflict_skel_set_op_*() and store the resulting conflict together + with the result of its operation in the working copy database. +*/ +svn_skel_t * +svn_wc__conflict_skel_create(apr_pool_t *result_pool); + +/* Return a boolean in *COMPLETE indicating whether CONFLICT_SKEL contains + everything needed for installing in the working copy database. + + This typically checks if CONFLICT_SKEL contains at least one conflict + and an operation. + */ +svn_error_t * +svn_wc__conflict_skel_is_complete(svn_boolean_t *complete, + const svn_skel_t *conflict_skel); + + +/* Set 'update' as the conflicting operation in CONFLICT_SKEL. + Allocate data stored in the skel in RESULT_POOL. + + ORIGINAL and TARGET specify the BASE node before and after updating. + + It is an error to set another operation to a conflict skel that + already has an operation. + + Do temporary allocations in SCRATCH_POOL. The new skel data is + completely stored in RESULT-POOL. */ +svn_error_t * +svn_wc__conflict_skel_set_op_update(svn_skel_t *conflict_skel, + const svn_wc_conflict_version_t *original, + const svn_wc_conflict_version_t *target, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Set 'switch' as the conflicting operation in CONFLICT_SKEL. + Allocate data stored in the skel in RESULT_POOL. + + ORIGINAL and TARGET specify the BASE node before and after switching. + + It is an error to set another operation to a conflict skel that + already has an operation. + + Do temporary allocations in SCRATCH_POOL. */ +svn_error_t * +svn_wc__conflict_skel_set_op_switch(svn_skel_t *conflict_skel, + const svn_wc_conflict_version_t *original, + const svn_wc_conflict_version_t *target, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Set 'merge' as conflicting operation in CONFLICT_SKEL. + Allocate data stored in the skel in RESULT_POOL. + + LEFT and RIGHT paths are the merge-left and merge-right merge + sources of the merge. + + It is an error to set another operation to a conflict skel that + already has an operation. + + Do temporary allocations in SCRATCH_POOL. */ +svn_error_t * +svn_wc__conflict_skel_set_op_merge(svn_skel_t *conflict_skel, + const svn_wc_conflict_version_t *left, + const svn_wc_conflict_version_t *right, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Add a text conflict to CONFLICT_SKEL. + Allocate data stored in the skel in RESULT_POOL. + + The DB, WRI_ABSPATH pair specifies in which working copy the conflict + will be recorded. (Needed for making the paths relative). + + MINE_ABSPATH, THEIR_OLD_ABSPATH and THEIR_ABSPATH specify the marker + files for this text conflict. Each of these values can be NULL to specify + that the node doesn't exist in this case. + + ### It is expected that in a future version we will also want to store + ### the sha1 checksum of these files to allow reinstalling the conflict + ### markers from the pristine store. + + It is an error to add another text conflict to a conflict skel that + already contains a text conflict. + + Do temporary allocations in SCRATCH_POOL. +*/ +svn_error_t * +svn_wc__conflict_skel_add_text_conflict(svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + const char *mine_abspath, + const char *their_old_abspath, + const char *their_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Add property conflict details to CONFLICT_SKEL. + Allocate data stored in the skel in RESULT_POOL. + + The DB, WRI_ABSPATH pair specifies in which working copy the conflict + will be recorded. (Needed for making the paths relative). + + The MARKER_ABSPATH is NULL when raising a conflict in v1.8+. See below. + + The MINE_PROPS, THEIR_OLD_PROPS and THEIR_PROPS are hashes mapping a + const char * property name to a const svn_string_t* value. + + The CONFLICTED_PROP_NAMES is a const char * property name value mapping + to "", recording which properties aren't resolved yet in the current + property values. + ### Needed for creating the marker file from this conflict data. + ### Would also allow per property marking as resolved. + ### Maybe useful for calling (legacy) conflict resolvers that expect one + ### property conflict per invocation. + + When raising a property conflict in the course of upgrading an old WC, + MARKER_ABSPATH is the path to the file containing a human-readable + description of the conflict, MINE_PROPS and THEIR_OLD_PROPS and + THEIR_PROPS are all NULL, and CONFLICTED_PROP_NAMES is an empty hash. + + It is an error to add another prop conflict to a conflict skel that + already contains a prop conflict. (A single call to this function can + record that multiple properties are in conflict.) + + Do temporary allocations in SCRATCH_POOL. +*/ +svn_error_t * +svn_wc__conflict_skel_add_prop_conflict(svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + const char *marker_abspath, + const apr_hash_t *mine_props, + const apr_hash_t *their_old_props, + const apr_hash_t *their_props, + const apr_hash_t *conflicted_prop_names, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Add a tree conflict to CONFLICT_SKEL. + Allocate data stored in the skel in RESULT_POOL. + + LOCAL_CHANGE is the local tree change made to the node. + INCOMING_CHANGE is the incoming change made to the node. + + MOVE_SRC_OP_ROOT_ABSPATH must be set when LOCAL_CHANGE is + svn_wc_conflict_reason_moved_away and NULL otherwise and the operation + is svn_wc_operation_update or svn_wc_operation_switch. It should be + set to the op-root of the move-away unless the move is inside a + delete in which case it should be set to the op-root of the delete + (the delete can be a replace). So given: + A/B/C moved away (1) + A deleted and replaced + A/B/C moved away (2) + A/B deleted + MOVE_SRC_OP_ROOT_ABSPATH should be A for a conflict associated + with (1), MOVE_SRC_OP_ROOT_ABSPATH should be A/B for a conflict + associated with (2). + + It is an error to add another tree conflict to a conflict skel that + already contains a tree conflict. (It is not an error, at this level, + to add a tree conflict to an existing text or property conflict skel.) + + Do temporary allocations in SCRATCH_POOL. +*/ +svn_error_t * +svn_wc__conflict_skel_add_tree_conflict(svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + svn_wc_conflict_reason_t local_change, + svn_wc_conflict_action_t incoming_change, + const char *move_src_op_root_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Allows resolving specific conflicts stored in CONFLICT_SKEL. + + When RESOLVE_TEXT is TRUE and CONFLICT_SKEL contains a text conflict, + resolve/remove the text conflict in CONFLICT_SKEL. + + When RESOLVE_PROP is "" and CONFLICT_SKEL contains a property conflict, + resolve/remove all property conflicts in CONFLICT_SKEL. + + When RESOLVE_PROP is not NULL and not "", remove the property conflict on + the property RESOLVE_PROP in CONFLICT_SKEL. When RESOLVE_PROP was the last + property in CONFLICT_SKEL remove the property conflict info from + CONFLICT_SKEL. + + When RESOLVE_TREE is TRUE and CONFLICT_SKEL contains a tree conflict, + resolve/remove the tree conflict in CONFLICT_SKEL. + + If COMPLETELY_RESOLVED is not NULL, then set *COMPLETELY_RESOLVED to TRUE, + when no conflict registration is left in CONFLICT_SKEL after editting, + otherwise to FALSE. + + Allocate data stored in the skel in RESULT_POOL. + + This functions edits CONFLICT_SKEL. New skels might be created in + RESULT_POOL. Temporary allocations will use SCRATCH_POOL. + */ +/* ### db, wri_abspath is currently unused. Remove? */ +svn_error_t * +svn_wc__conflict_skel_resolve(svn_boolean_t *completely_resolved, + svn_skel_t *conflict_skel, + svn_wc__db_t *db, + const char *wri_abspath, + svn_boolean_t resolve_text, + const char *resolve_prop, + svn_boolean_t resolve_tree, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* + * ----------------------------------------------------------- + * Reading conflict skels. Maybe this can be made private later + * ----------------------------------------------------------- + */ + +/* Read common information from CONFLICT_SKEL to determine the operation + * and merge origins. + * + * Output arguments can be NULL if the value is not necessary. + * + * Set *LOCATIONS to an array of (svn_wc_conflict_version_t *). For + * conflicts written by current code, there are 2 elements: index [0] is + * the 'old' or 'left' side and [1] is the 'new' or 'right' side. + * + * For conflicts written by 1.6 or 1.7 there are 2 locations for a tree + * conflict, but none for a text or property conflict. + * + * TEXT_, PROP_ and TREE_CONFLICTED (when not NULL) will be set to TRUE + * when the conflict contains the specified kind of conflict, otherwise + * to false. + * + * Allocate the result in RESULT_POOL. Perform temporary allocations in + * SCRATCH_POOL. + */ +svn_error_t * +svn_wc__conflict_read_info(svn_wc_operation_t *operation, + const apr_array_header_t **locations, + svn_boolean_t *text_conflicted, + svn_boolean_t *prop_conflicted, + svn_boolean_t *tree_conflicted, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Reads back the original data stored by svn_wc__conflict_skel_add_text_conflict() + * in CONFLICT_SKEL for a node in DB, WRI_ABSPATH. + * + * Values as documented for svn_wc__conflict_skel_add_text_conflict(). + * + * Output arguments can be NULL if the value is not necessary. + * + * Allocate the result in RESULT_POOL. Perform temporary allocations in + * SCRATCH_POOL. + */ +svn_error_t * +svn_wc__conflict_read_text_conflict(const char **mine_abspath, + const char **their_old_abspath, + const char **their_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Reads back the original data stored by svn_wc__conflict_skel_add_prop_conflict() + * in CONFLICT_SKEL for a node in DB, WRI_ABSPATH. + * + * Values as documented for svn_wc__conflict_skel_add_prop_conflict(). + * + * Output arguments can be NULL if the value is not necessary + * Allocate the result in RESULT_POOL. Perform temporary allocations in + * SCRATCH_POOL. + */ +svn_error_t * +svn_wc__conflict_read_prop_conflict(const char **marker_abspath, + apr_hash_t **mine_props, + apr_hash_t **their_old_props, + apr_hash_t **their_props, + apr_hash_t **conflicted_prop_names, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Reads back the original data stored by svn_wc__conflict_skel_add_tree_conflict() + * in CONFLICT_SKEL for a node in DB, WRI_ABSPATH. + * + * Values as documented for svn_wc__conflict_skel_add_tree_conflict(). + * + * Output arguments can be NULL if the value is not necessary + * Allocate the result in RESULT_POOL. Perform temporary allocations in + * SCRATCH_POOL. + */ +svn_error_t * +svn_wc__conflict_read_tree_conflict(svn_wc_conflict_reason_t *local_change, + svn_wc_conflict_action_t *incoming_change, + const char **move_src_op_root_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Reads in *MARKERS a list of const char * absolute paths of the marker files + referenced from CONFLICT_SKEL. + * Allocate the result in RESULT_POOL. Perform temporary allocations in + * SCRATCH_POOL. + */ +svn_error_t * +svn_wc__conflict_read_markers(const apr_array_header_t **markers, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Create the necessary marker files for the conflicts stored in + * CONFLICT_SKEL and return the work items to fill the markers from + * the work queue. + * + * Currently only used for property conflicts as text conflict markers + * are just in-wc files. + * + * Allocate the result in RESULT_POOL. Perform temporary allocations in + * SCRATCH_POOL. + */ +svn_error_t * +svn_wc__conflict_create_markers(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Call the interactive conflict resolver RESOLVER_FUNC with RESOLVER_BATON to + allow resolving the conflicts on LOCAL_ABSPATH. + + Call RESOLVER_FUNC once for each property conflict, and again for any + text conflict, and again for any tree conflict on the node. + + CONFLICT_SKEL contains the details of the conflicts on LOCAL_ABSPATH. + + Resolver actions are directly applied to the in-db state of LOCAL_ABSPATH, + so the conflict and the state in CONFLICT_SKEL must already be installed in + wc.db. */ +svn_error_t * +svn_wc__conflict_invoke_resolver(svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflict_skel, + const apr_array_header_t *merge_options, + svn_wc_conflict_resolver_func2_t resolver_func, + void *resolver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + + +/* Mark as resolved any text conflict on the node at DB/LOCAL_ABSPATH. */ +svn_error_t * +svn_wc__mark_resolved_text_conflict(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* Mark as resolved any prop conflicts on the node at DB/LOCAL_ABSPATH. */ +svn_error_t * +svn_wc__mark_resolved_prop_conflicts(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_WC_CONFLICTS_H */ diff --git a/subversion/libsvn_wc/context.c b/subversion/libsvn_wc/context.c new file mode 100644 index 000000000000..4bf236992c4f --- /dev/null +++ b/subversion/libsvn_wc/context.c @@ -0,0 +1,116 @@ +/* + * context.c: routines for managing a working copy context + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <apr_pools.h> + +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" + +#include "wc.h" +#include "wc_db.h" + +#include "svn_private_config.h" + + + +/* APR cleanup function used to explicitly close any of our dependent + data structures before we disappear ourselves. */ +static apr_status_t +close_ctx_apr(void *data) +{ + svn_wc_context_t *ctx = data; + + if (ctx->close_db_on_destroy) + { + svn_error_t *err = svn_wc__db_close(ctx->db); + if (err) + { + int result = err->apr_err; + svn_error_clear(err); + return result; + } + } + + return APR_SUCCESS; +} + + +svn_error_t * +svn_wc_context_create(svn_wc_context_t **wc_ctx, + const svn_config_t *config, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_context_t *ctx = apr_pcalloc(result_pool, sizeof(*ctx)); + + /* Create the state_pool, and open up a wc_db in it. + * Since config contains a private mutable member but C doesn't support + * we need to make it writable */ + ctx->state_pool = result_pool; + SVN_ERR(svn_wc__db_open(&ctx->db, (svn_config_t *)config, + FALSE, TRUE, ctx->state_pool, scratch_pool)); + ctx->close_db_on_destroy = TRUE; + + apr_pool_cleanup_register(result_pool, ctx, close_ctx_apr, + apr_pool_cleanup_null); + + *wc_ctx = ctx; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__context_create_with_db(svn_wc_context_t **wc_ctx, + svn_config_t *config, + svn_wc__db_t *db, + apr_pool_t *result_pool) +{ + svn_wc_context_t *ctx = apr_pcalloc(result_pool, sizeof(*ctx)); + + /* Create the state pool. We don't put the wc_db in it, because it's + already open in a separate pool somewhere. We also won't close the + wc_db when we destroy the context, since it's not ours to close. */ + ctx->state_pool = result_pool; + ctx->db = db; + ctx->close_db_on_destroy = FALSE; + + apr_pool_cleanup_register(result_pool, ctx, close_ctx_apr, + apr_pool_cleanup_null); + + *wc_ctx = ctx; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_context_destroy(svn_wc_context_t *wc_ctx) +{ + /* We added a cleanup when creating; just run it now to close the context. */ + apr_pool_cleanup_run(wc_ctx->state_pool, wc_ctx, close_ctx_apr); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/copy.c b/subversion/libsvn_wc/copy.c new file mode 100644 index 000000000000..1b82c2d8fdc9 --- /dev/null +++ b/subversion/libsvn_wc/copy.c @@ -0,0 +1,1048 @@ +/* + * copy.c: wc 'copy' functionality. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include <string.h> +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" + +#include "wc.h" +#include "workqueue.h" +#include "props.h" +#include "conflicts.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" + + +/*** Code. ***/ + +/* Make a copy of the filesystem node (or tree if RECURSIVE) at + SRC_ABSPATH under a temporary name in the directory + TMPDIR_ABSPATH and return the absolute path of the copy in + *DST_ABSPATH. Return the node kind of SRC_ABSPATH in *KIND. If + SRC_ABSPATH doesn't exist then set *DST_ABSPATH to NULL to indicate + that no copy was made. */ +static svn_error_t * +copy_to_tmpdir(svn_skel_t **work_item, + svn_node_kind_t *kind, + svn_wc__db_t *db, + const char *src_abspath, + const char *dst_abspath, + const char *tmpdir_abspath, + svn_boolean_t file_copy, + svn_boolean_t unversioned, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t is_special; + svn_io_file_del_t delete_when; + const char *dst_tmp_abspath; + svn_node_kind_t dsk_kind; + if (!kind) + kind = &dsk_kind; + + *work_item = NULL; + + SVN_ERR(svn_io_check_special_path(src_abspath, kind, &is_special, + scratch_pool)); + if (*kind == svn_node_none) + { + return SVN_NO_ERROR; + } + else if (*kind == svn_node_unknown) + { + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Source '%s' is unexpected kind"), + svn_dirent_local_style(src_abspath, + scratch_pool)); + } + else if (*kind == svn_node_dir || is_special) + delete_when = svn_io_file_del_on_close; + else /* the default case: (*kind == svn_node_file) */ + delete_when = svn_io_file_del_none; + + /* ### Do we need a pool cleanup to remove the copy? We can't use + ### svn_io_file_del_on_pool_cleanup above because a) it won't + ### handle the directory case and b) we need to be able to remove + ### the cleanup before queueing the move work item. */ + + if (file_copy && !unversioned) + { + svn_boolean_t modified; + /* It's faster to look for mods on the source now, as + the timestamp might match, than to examine the + destination later as the destination timestamp will + never match. */ + SVN_ERR(svn_wc__internal_file_modified_p(&modified, + db, src_abspath, + FALSE, scratch_pool)); + if (!modified) + { + /* Why create a temp copy if we can just reinstall from pristine? */ + SVN_ERR(svn_wc__wq_build_file_install(work_item, + db, dst_abspath, NULL, FALSE, + TRUE, + result_pool, scratch_pool)); + return SVN_NO_ERROR; + } + } + + /* Set DST_TMP_ABSPATH to a temporary unique path. If *KIND is file, leave + a file there and then overwrite it; otherwise leave no node on disk at + that path. In the latter case, something else might use that path + before we get around to using it a moment later, but never mind. */ + SVN_ERR(svn_io_open_unique_file3(NULL, &dst_tmp_abspath, tmpdir_abspath, + delete_when, scratch_pool, scratch_pool)); + + if (*kind == svn_node_dir) + { + if (file_copy) + SVN_ERR(svn_io_copy_dir_recursively( + src_abspath, + tmpdir_abspath, + svn_dirent_basename(dst_tmp_abspath, scratch_pool), + TRUE, /* copy_perms */ + cancel_func, cancel_baton, + scratch_pool)); + else + SVN_ERR(svn_io_dir_make(dst_tmp_abspath, APR_OS_DEFAULT, scratch_pool)); + } + else if (!is_special) + SVN_ERR(svn_io_copy_file(src_abspath, dst_tmp_abspath, + TRUE /* copy_perms */, + scratch_pool)); + else + SVN_ERR(svn_io_copy_link(src_abspath, dst_tmp_abspath, scratch_pool)); + + if (file_copy) + { + /* Remove 'read-only' from the destination file; it's a local add now. */ + SVN_ERR(svn_io_set_file_read_write(dst_tmp_abspath, + FALSE, scratch_pool)); + } + + SVN_ERR(svn_wc__wq_build_file_move(work_item, db, dst_abspath, + dst_tmp_abspath, dst_abspath, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Copy the versioned file SRC_ABSPATH in DB to the path DST_ABSPATH in DB. + If METADATA_ONLY is true, copy only the versioned metadata, + otherwise copy both the versioned metadata and the filesystem node (even + if it is the wrong kind, and recursively if it is a dir). + + If IS_MOVE is true, record move information in working copy meta + data in addition to copying the file. + + If the versioned file has a text conflict, and the .mine file exists in + the filesystem, copy the .mine file to DST_ABSPATH. Otherwise, copy the + versioned file itself. + + This also works for versioned symlinks that are stored in the db as + svn_node_file with svn:special set. */ +static svn_error_t * +copy_versioned_file(svn_wc__db_t *db, + const char *src_abspath, + const char *dst_abspath, + const char *dst_op_root_abspath, + const char *tmpdir_abspath, + svn_boolean_t metadata_only, + svn_boolean_t conflicted, + svn_boolean_t is_move, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_skel_t *work_items = NULL; + + /* In case we are copying from one WC to another (e.g. an external dir), + ensure the destination WC has a copy of the pristine text. */ + + /* Prepare a temp copy of the filesystem node. It is usually a file, but + copy recursively if it's a dir. */ + if (!metadata_only) + { + const char *my_src_abspath = NULL; + svn_boolean_t handle_as_unversioned = FALSE; + + /* By default, take the copy source as given. */ + my_src_abspath = src_abspath; + + if (conflicted) + { + svn_skel_t *conflict; + const char *conflict_working; + svn_error_t *err; + + /* Is there a text conflict at the source path? */ + SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath, + scratch_pool, scratch_pool)); + + err = svn_wc__conflict_read_text_conflict(&conflict_working, NULL, NULL, + db, src_abspath, conflict, + scratch_pool, + scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_MISSING) + { + /* not text conflicted */ + svn_error_clear(err); + conflict_working = NULL; + } + else + SVN_ERR(err); + + if (conflict_working) + { + svn_node_kind_t working_kind; + + /* Does the ".mine" file exist? */ + SVN_ERR(svn_io_check_path(conflict_working, &working_kind, + scratch_pool)); + + if (working_kind == svn_node_file) + { + /* Don't perform unmodified/pristine optimization */ + handle_as_unversioned = TRUE; + my_src_abspath = conflict_working; + } + } + } + + SVN_ERR(copy_to_tmpdir(&work_items, NULL, db, my_src_abspath, + dst_abspath, tmpdir_abspath, + TRUE /* file_copy */, + handle_as_unversioned /* unversioned */, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + } + + /* Copy the (single) node's metadata, and move the new filesystem node + into place. */ + SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath, + dst_op_root_abspath, is_move, work_items, + scratch_pool)); + + if (notify_func) + { + svn_wc_notify_t *notify + = svn_wc_create_notify(dst_abspath, svn_wc_notify_add, + scratch_pool); + notify->kind = svn_node_file; + + /* When we notify that we performed a copy, make sure we already did */ + if (work_items != NULL) + SVN_ERR(svn_wc__wq_run(db, dst_abspath, + cancel_func, cancel_baton, scratch_pool)); + (*notify_func)(notify_baton, notify, scratch_pool); + } + return SVN_NO_ERROR; +} + +/* Copy the versioned dir SRC_ABSPATH in DB to the path DST_ABSPATH in DB, + recursively. If METADATA_ONLY is true, copy only the versioned metadata, + otherwise copy both the versioned metadata and the filesystem nodes (even + if they are the wrong kind, and including unversioned children). + If IS_MOVE is true, record move information in working copy meta + data in addition to copying the directory. + + WITHIN_ONE_WC is TRUE if the copy/move is within a single working copy (root) + */ +static svn_error_t * +copy_versioned_dir(svn_wc__db_t *db, + const char *src_abspath, + const char *dst_abspath, + const char *dst_op_root_abspath, + const char *tmpdir_abspath, + svn_boolean_t metadata_only, + svn_boolean_t is_move, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_skel_t *work_items = NULL; + const char *dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool); + apr_hash_t *versioned_children; + apr_hash_t *conflicted_children; + apr_hash_t *disk_children; + apr_hash_index_t *hi; + svn_node_kind_t disk_kind; + apr_pool_t *iterpool; + + /* Prepare a temp copy of the single filesystem node (usually a dir). */ + if (!metadata_only) + { + SVN_ERR(copy_to_tmpdir(&work_items, &disk_kind, + db, src_abspath, dst_abspath, + tmpdir_abspath, + FALSE /* file_copy */, + FALSE /* unversioned */, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + } + + /* Copy the (single) node's metadata, and move the new filesystem node + into place. */ + SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath, + dst_op_root_abspath, is_move, work_items, + scratch_pool)); + + if (notify_func) + { + svn_wc_notify_t *notify + = svn_wc_create_notify(dst_abspath, svn_wc_notify_add, + scratch_pool); + notify->kind = svn_node_dir; + + /* When we notify that we performed a copy, make sure we already did */ + if (work_items != NULL) + SVN_ERR(svn_wc__wq_run(db, dir_abspath, + cancel_func, cancel_baton, scratch_pool)); + + (*notify_func)(notify_baton, notify, scratch_pool); + } + + if (!metadata_only && disk_kind == svn_node_dir) + /* All filesystem children, versioned and unversioned. We're only + interested in their names, so we can pass TRUE as the only_check_type + param. */ + SVN_ERR(svn_io_get_dirents3(&disk_children, src_abspath, TRUE, + scratch_pool, scratch_pool)); + else + disk_children = NULL; + + /* Copy all the versioned children */ + iterpool = svn_pool_create(scratch_pool); + SVN_ERR(svn_wc__db_read_children_info(&versioned_children, + &conflicted_children, + db, src_abspath, + scratch_pool, iterpool)); + for (hi = apr_hash_first(scratch_pool, versioned_children); + hi; + hi = apr_hash_next(hi)) + { + const char *child_name, *child_src_abspath, *child_dst_abspath; + struct svn_wc__db_info_t *info; + + svn_pool_clear(iterpool); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + child_name = svn__apr_hash_index_key(hi); + info = svn__apr_hash_index_val(hi); + child_src_abspath = svn_dirent_join(src_abspath, child_name, iterpool); + child_dst_abspath = svn_dirent_join(dst_abspath, child_name, iterpool); + + if (info->op_root) + SVN_ERR(svn_wc__db_op_copy_shadowed_layer(db, + child_src_abspath, + child_dst_abspath, + is_move, + scratch_pool)); + + if (info->status == svn_wc__db_status_normal + || info->status == svn_wc__db_status_added) + { + /* We have more work to do than just changing the DB */ + if (info->kind == svn_node_file) + { + /* We should skip this node if this child is a file external + (issues #3589, #4000) */ + if (!info->file_external) + SVN_ERR(copy_versioned_file(db, + child_src_abspath, + child_dst_abspath, + dst_op_root_abspath, + tmpdir_abspath, + metadata_only, info->conflicted, + is_move, + cancel_func, cancel_baton, + NULL, NULL, + iterpool)); + } + else if (info->kind == svn_node_dir) + SVN_ERR(copy_versioned_dir(db, + child_src_abspath, child_dst_abspath, + dst_op_root_abspath, tmpdir_abspath, + metadata_only, is_move, + cancel_func, cancel_baton, NULL, NULL, + iterpool)); + else + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("cannot handle node kind for '%s'"), + svn_dirent_local_style(child_src_abspath, + scratch_pool)); + } + else if (info->status == svn_wc__db_status_deleted + || info->status == svn_wc__db_status_not_present + || info->status == svn_wc__db_status_excluded) + { + /* This will be copied as some kind of deletion. Don't touch + any actual files */ + SVN_ERR(svn_wc__db_op_copy(db, child_src_abspath, + child_dst_abspath, dst_op_root_abspath, + is_move, NULL, iterpool)); + + /* Don't recurse on children while all we do is creating not-present + children */ + } + else if (info->status == svn_wc__db_status_incomplete) + { + /* Should go ahead and copy incomplete to incomplete? Try to + copy as much as possible, or give up early? */ + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot handle status of '%s'"), + svn_dirent_local_style(child_src_abspath, + iterpool)); + } + else + { + SVN_ERR_ASSERT(info->status == svn_wc__db_status_server_excluded); + + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot copy '%s' excluded by server"), + svn_dirent_local_style(child_src_abspath, + iterpool)); + } + + if (disk_children + && (info->status == svn_wc__db_status_normal + || info->status == svn_wc__db_status_added)) + { + /* Remove versioned child as it has been handled */ + svn_hash_sets(disk_children, child_name, NULL); + } + } + + /* Copy the remaining filesystem children, which are unversioned, skipping + any conflict-marker files. */ + if (disk_children && apr_hash_count(disk_children)) + { + apr_hash_t *marker_files; + + SVN_ERR(svn_wc__db_get_conflict_marker_files(&marker_files, db, + src_abspath, scratch_pool, + scratch_pool)); + + work_items = NULL; + + for (hi = apr_hash_first(scratch_pool, disk_children); hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + const char *unver_src_abspath, *unver_dst_abspath; + svn_skel_t *work_item; + + if (svn_wc_is_adm_dir(name, iterpool)) + continue; + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + svn_pool_clear(iterpool); + unver_src_abspath = svn_dirent_join(src_abspath, name, iterpool); + unver_dst_abspath = svn_dirent_join(dst_abspath, name, iterpool); + + if (marker_files && + svn_hash_gets(marker_files, unver_src_abspath)) + continue; + + SVN_ERR(copy_to_tmpdir(&work_item, NULL, db, unver_src_abspath, + unver_dst_abspath, tmpdir_abspath, + TRUE /* recursive */, TRUE /* unversioned */, + cancel_func, cancel_baton, + scratch_pool, iterpool)); + + if (work_item) + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + } + SVN_ERR(svn_wc__db_wq_add(db, dst_abspath, work_items, iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* The guts of svn_wc_copy3() and svn_wc_move(). + * The additional parameter IS_MOVE indicates whether this is a copy or + * a move operation. + * + * If MOVE_DEGRADED_TO_COPY is not NULL and a move had to be degraded + * to a copy, then set *MOVE_DEGRADED_TO_COPY. */ +static svn_error_t * +copy_or_move(svn_boolean_t *move_degraded_to_copy, + svn_wc_context_t *wc_ctx, + const char *src_abspath, + const char *dst_abspath, + svn_boolean_t metadata_only, + svn_boolean_t is_move, + svn_boolean_t allow_mixed_revisions, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + svn_node_kind_t src_db_kind; + const char *dstdir_abspath; + svn_boolean_t conflicted; + const char *tmpdir_abspath; + const char *src_wcroot_abspath; + const char *dst_wcroot_abspath; + svn_boolean_t within_one_wc; + svn_wc__db_status_t src_status; + svn_error_t *err; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); + + dstdir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool); + + /* Ensure DSTDIR_ABSPATH belongs to the same repository as SRC_ABSPATH; + throw an error if not. */ + { + svn_wc__db_status_t dstdir_status; + const char *src_repos_root_url, *dst_repos_root_url; + const char *src_repos_uuid, *dst_repos_uuid; + const char *src_repos_relpath; + + err = svn_wc__db_read_info(&src_status, &src_db_kind, NULL, + &src_repos_relpath, &src_repos_root_url, + &src_repos_uuid, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &conflicted, NULL, NULL, NULL, NULL, + NULL, NULL, + db, src_abspath, scratch_pool, scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + /* Replicate old error code and text */ + svn_error_clear(err); + return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, + _("'%s' is not under version control"), + svn_dirent_local_style(src_abspath, + scratch_pool)); + } + else + SVN_ERR(err); + + /* Do this now, as we know the right data is cached */ + SVN_ERR(svn_wc__db_get_wcroot(&src_wcroot_abspath, db, src_abspath, + scratch_pool, scratch_pool)); + + switch (src_status) + { + case svn_wc__db_status_deleted: + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Deleted node '%s' can't be copied."), + svn_dirent_local_style(src_abspath, + scratch_pool)); + + case svn_wc__db_status_excluded: + case svn_wc__db_status_server_excluded: + case svn_wc__db_status_not_present: + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(src_abspath, + scratch_pool)); + default: + break; + } + + if (is_move && ! strcmp(src_abspath, src_wcroot_abspath)) + { + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' is the root of a working copy and " + "cannot be moved"), + svn_dirent_local_style(src_abspath, + scratch_pool)); + } + if (is_move && src_repos_relpath && !src_repos_relpath[0]) + { + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' represents the repository root " + "and cannot be moved"), + svn_dirent_local_style(src_abspath, + scratch_pool)); + } + + err = svn_wc__db_read_info(&dstdir_status, NULL, NULL, NULL, + &dst_repos_root_url, &dst_repos_uuid, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, dstdir_abspath, + scratch_pool, scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + /* An unversioned destination directory exists on disk. */ + svn_error_clear(err); + return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, + _("'%s' is not under version control"), + svn_dirent_local_style(dstdir_abspath, + scratch_pool)); + } + else + SVN_ERR(err); + + /* Do this now, as we know the right data is cached */ + SVN_ERR(svn_wc__db_get_wcroot(&dst_wcroot_abspath, db, dstdir_abspath, + scratch_pool, scratch_pool)); + + if (!src_repos_root_url) + { + if (src_status == svn_wc__db_status_added) + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, + &src_repos_root_url, + &src_repos_uuid, NULL, NULL, NULL, + NULL, + db, src_abspath, + scratch_pool, scratch_pool)); + else + /* If not added, the node must have a base or we can't copy */ + SVN_ERR(svn_wc__db_scan_base_repos(NULL, &src_repos_root_url, + &src_repos_uuid, + db, src_abspath, + scratch_pool, scratch_pool)); + } + + if (!dst_repos_root_url) + { + if (dstdir_status == svn_wc__db_status_added) + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, + &dst_repos_root_url, + &dst_repos_uuid, NULL, NULL, NULL, + NULL, + db, dstdir_abspath, + scratch_pool, scratch_pool)); + else + /* If not added, the node must have a base or we can't copy */ + SVN_ERR(svn_wc__db_scan_base_repos(NULL, &dst_repos_root_url, + &dst_repos_uuid, + db, dstdir_abspath, + scratch_pool, scratch_pool)); + } + + if (strcmp(src_repos_root_url, dst_repos_root_url) != 0 + || strcmp(src_repos_uuid, dst_repos_uuid) != 0) + return svn_error_createf( + SVN_ERR_WC_INVALID_SCHEDULE, NULL, + _("Cannot copy to '%s', as it is not from repository '%s'; " + "it is from '%s'"), + svn_dirent_local_style(dst_abspath, scratch_pool), + src_repos_root_url, dst_repos_root_url); + + if (dstdir_status == svn_wc__db_status_deleted) + return svn_error_createf( + SVN_ERR_WC_INVALID_SCHEDULE, NULL, + _("Cannot copy to '%s' as it is scheduled for deletion"), + svn_dirent_local_style(dst_abspath, scratch_pool)); + /* ### should report dstdir_abspath instead of dst_abspath? */ + } + + /* TODO(#2843): Rework the error report. */ + /* Check if the copy target is missing or hidden and thus not exist on the + disk, before actually doing the file copy. */ + { + svn_wc__db_status_t dst_status; + + err = svn_wc__db_read_info(&dst_status, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + db, dst_abspath, scratch_pool, scratch_pool); + + if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + + if (!err) + switch (dst_status) + { + case svn_wc__db_status_excluded: + return svn_error_createf( + SVN_ERR_ENTRY_EXISTS, NULL, + _("'%s' is already under version control " + "but is excluded."), + svn_dirent_local_style(dst_abspath, scratch_pool)); + case svn_wc__db_status_server_excluded: + return svn_error_createf( + SVN_ERR_ENTRY_EXISTS, NULL, + _("'%s' is already under version control"), + svn_dirent_local_style(dst_abspath, scratch_pool)); + + case svn_wc__db_status_deleted: + case svn_wc__db_status_not_present: + break; /* OK to add */ + + default: + return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL, + _("There is already a versioned item '%s'"), + svn_dirent_local_style(dst_abspath, + scratch_pool)); + } + } + + /* Check that the target path is not obstructed, if required. */ + if (!metadata_only) + { + svn_node_kind_t dst_kind; + + /* (We need only to check the root of the copy, not every path inside + copy_versioned_file/_dir.) */ + SVN_ERR(svn_io_check_path(dst_abspath, &dst_kind, scratch_pool)); + if (dst_kind != svn_node_none) + return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL, + _("'%s' already exists and is in the way"), + svn_dirent_local_style(dst_abspath, + scratch_pool)); + } + + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir_abspath, db, + dstdir_abspath, + scratch_pool, scratch_pool)); + + within_one_wc = (strcmp(src_wcroot_abspath, dst_wcroot_abspath) == 0); + + if (is_move + && !within_one_wc) + { + if (move_degraded_to_copy) + *move_degraded_to_copy = TRUE; + + is_move = FALSE; + } + + if (!within_one_wc) + SVN_ERR(svn_wc__db_pristine_transfer(db, src_abspath, dst_wcroot_abspath, + cancel_func, cancel_baton, + scratch_pool)); + + if (src_db_kind == svn_node_file + || src_db_kind == svn_node_symlink) + { + err = copy_versioned_file(db, src_abspath, dst_abspath, dst_abspath, + tmpdir_abspath, + metadata_only, conflicted, is_move, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool); + } + else + { + if (is_move + && src_status == svn_wc__db_status_normal) + { + svn_revnum_t min_rev; + svn_revnum_t max_rev; + + /* Verify that the move source is a single-revision subtree. */ + SVN_ERR(svn_wc__db_min_max_revisions(&min_rev, &max_rev, db, + src_abspath, FALSE, scratch_pool)); + if (SVN_IS_VALID_REVNUM(min_rev) && SVN_IS_VALID_REVNUM(max_rev) && + min_rev != max_rev) + { + if (!allow_mixed_revisions) + return svn_error_createf(SVN_ERR_WC_MIXED_REVISIONS, NULL, + _("Cannot move mixed-revision " + "subtree '%s' [%ld:%ld]; " + "try updating it first"), + svn_dirent_local_style(src_abspath, + scratch_pool), + min_rev, max_rev); + + is_move = FALSE; + if (move_degraded_to_copy) + *move_degraded_to_copy = TRUE; + } + } + + err = copy_versioned_dir(db, src_abspath, dst_abspath, dst_abspath, + tmpdir_abspath, metadata_only, is_move, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool); + } + + if (err && svn_error_find_cause(err, SVN_ERR_CANCELLED)) + return svn_error_trace(err); + + if (is_move) + err = svn_error_compose_create(err, + svn_wc__db_op_handle_move_back(NULL, + db, dst_abspath, src_abspath, + NULL /* work_items */, + scratch_pool)); + + /* Run the work queue with the remaining work */ + SVN_ERR(svn_error_compose_create( + err, + svn_wc__wq_run(db, dst_abspath, + cancel_func, cancel_baton, + scratch_pool))); + + return SVN_NO_ERROR; +} + + +/* Public Interface */ + +svn_error_t * +svn_wc_copy3(svn_wc_context_t *wc_ctx, + const char *src_abspath, + const char *dst_abspath, + svn_boolean_t metadata_only, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + /* Verify that we have the required write lock. */ + SVN_ERR(svn_wc__write_check(wc_ctx->db, + svn_dirent_dirname(dst_abspath, scratch_pool), + scratch_pool)); + + return svn_error_trace(copy_or_move(NULL, wc_ctx, src_abspath, dst_abspath, + metadata_only, FALSE /* is_move */, + TRUE /* allow_mixed_revisions */, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); +} + + +/* Remove the conflict markers of NODE_ABSPATH, that were left over after + copying NODE_ABSPATH from SRC_ABSPATH. + + Only use this function when you know what you're doing. This function + explicitly ignores some case insensitivity issues! + + */ +static svn_error_t * +remove_node_conflict_markers(svn_wc__db_t *db, + const char *src_abspath, + const char *node_abspath, + apr_pool_t *scratch_pool) +{ + svn_skel_t *conflict; + + SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath, + scratch_pool, scratch_pool)); + + /* Do we have conflict markers that should be removed? */ + if (conflict != NULL) + { + const apr_array_header_t *markers; + int i; + const char *src_dir = svn_dirent_dirname(src_abspath, scratch_pool); + const char *dst_dir = svn_dirent_dirname(node_abspath, scratch_pool); + + SVN_ERR(svn_wc__conflict_read_markers(&markers, db, src_abspath, + conflict, + scratch_pool, scratch_pool)); + + /* No iterpool: Maximum number of possible conflict markers is 4 */ + for (i = 0; markers && (i < markers->nelts); i++) + { + const char *marker_abspath; + const char *child_relpath; + const char *child_abpath; + + marker_abspath = APR_ARRAY_IDX(markers, i, const char *); + + child_relpath = svn_dirent_is_child(src_dir, marker_abspath, NULL); + + if (child_relpath) + { + child_abpath = svn_dirent_join(dst_dir, child_relpath, + scratch_pool); + + SVN_ERR(svn_io_remove_file2(child_abpath, TRUE, scratch_pool)); + } + } + } + + return SVN_NO_ERROR; +} + +/* Remove all the conflict markers below SRC_DIR_ABSPATH, that were left over + after copying WC_DIR_ABSPATH from SRC_DIR_ABSPATH. + + This function doesn't remove the conflict markers on WC_DIR_ABSPATH + itself! + + Only use this function when you know what you're doing. This function + explicitly ignores some case insensitivity issues! + */ +static svn_error_t * +remove_all_conflict_markers(svn_wc__db_t *db, + const char *src_dir_abspath, + const char *wc_dir_abspath, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_t *nodes; + apr_hash_t *conflicts; /* Unused */ + apr_hash_index_t *hi; + + /* Reuse a status helper to obtain all subdirs and conflicts in a single + db transaction. */ + /* ### This uses a rifle to kill a fly. But at least it doesn't use heavy + artillery. */ + SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db, + src_dir_abspath, + scratch_pool, iterpool)); + + for (hi = apr_hash_first(scratch_pool, nodes); + hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + struct svn_wc__db_info_t *info = svn__apr_hash_index_val(hi); + + if (info->conflicted) + { + svn_pool_clear(iterpool); + SVN_ERR(remove_node_conflict_markers( + db, + svn_dirent_join(src_dir_abspath, name, iterpool), + svn_dirent_join(wc_dir_abspath, name, iterpool), + iterpool)); + } + if (info->kind == svn_node_dir) + { + svn_pool_clear(iterpool); + SVN_ERR(remove_all_conflict_markers( + db, + svn_dirent_join(src_dir_abspath, name, iterpool), + svn_dirent_join(wc_dir_abspath, name, iterpool), + iterpool)); + } + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__move2(svn_wc_context_t *wc_ctx, + const char *src_abspath, + const char *dst_abspath, + svn_boolean_t metadata_only, + svn_boolean_t allow_mixed_revisions, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + svn_boolean_t move_degraded_to_copy = FALSE; + svn_node_kind_t kind; + svn_boolean_t conflicted; + + /* Verify that we have the required write locks. */ + SVN_ERR(svn_wc__write_check(wc_ctx->db, + svn_dirent_dirname(src_abspath, scratch_pool), + scratch_pool)); + SVN_ERR(svn_wc__write_check(wc_ctx->db, + svn_dirent_dirname(dst_abspath, scratch_pool), + scratch_pool)); + + SVN_ERR(copy_or_move(&move_degraded_to_copy, + wc_ctx, src_abspath, dst_abspath, + TRUE /* metadata_only */, + TRUE /* is_move */, + allow_mixed_revisions, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); + + /* An interrupt at this point will leave the new copy marked as + moved-here but the source has not yet been deleted or marked as + moved-to. */ + + /* Should we be using a workqueue for this move? It's not clear. + What should happen if the copy above is interrupted? The user + may want to abort the move and a workqueue might interfere with + that. + + BH: On Windows it is not unlikely to encounter an access denied on + this line. Installing the move in the workqueue via the copy_or_move + might make it hard to recover from that situation, while the DB + is still in a valid state. So be careful when switching this over + to the workqueue. */ + if (!metadata_only) + SVN_ERR(svn_io_file_rename(src_abspath, dst_abspath, scratch_pool)); + + SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + &conflicted, NULL, NULL, NULL, + NULL, NULL, NULL, + db, src_abspath, + scratch_pool, scratch_pool)); + + if (kind == svn_node_dir) + SVN_ERR(remove_all_conflict_markers(db, src_abspath, dst_abspath, + scratch_pool)); + + if (conflicted) + SVN_ERR(remove_node_conflict_markers(db, src_abspath, dst_abspath, + scratch_pool)); + + SVN_ERR(svn_wc__db_op_delete(db, src_abspath, + move_degraded_to_copy ? NULL : dst_abspath, + TRUE /* delete_dir_externals */, + NULL /* conflict */, NULL /* work_items */, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/crop.c b/subversion/libsvn_wc/crop.c new file mode 100644 index 000000000000..2278476d2355 --- /dev/null +++ b/subversion/libsvn_wc/crop.c @@ -0,0 +1,361 @@ +/* + * crop.c: Cropping the WC + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + +#include "svn_wc.h" +#include "svn_pools.h" +#include "svn_error.h" +#include "svn_error_codes.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" + +#include "wc.h" +#include "workqueue.h" + +#include "svn_private_config.h" + +/* Helper function that crops the children of the LOCAL_ABSPATH, under the + * constraint of NEW_DEPTH. The DIR_PATH itself will never be cropped. The + * whole subtree should have been locked. + * + * DIR_DEPTH is the current depth of LOCAL_ABSPATH as stored in DB. + * + * If NOTIFY_FUNC is not null, each file and ROOT of subtree will be reported + * upon remove. + */ +static svn_error_t * +crop_children(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t dir_depth, + svn_depth_t new_depth, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + const apr_array_header_t *children; + apr_pool_t *iterpool; + int i; + + SVN_ERR_ASSERT(new_depth >= svn_depth_empty + && new_depth <= svn_depth_infinity); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + iterpool = svn_pool_create(pool); + + if (dir_depth == svn_depth_unknown) + dir_depth = svn_depth_infinity; + + /* Update the depth of target first, if needed. */ + if (dir_depth > new_depth) + SVN_ERR(svn_wc__db_op_set_base_depth(db, local_abspath, new_depth, + iterpool)); + + /* Looping over current directory's SVN entries: */ + SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath, pool, + iterpool)); + + for (i = 0; i < children->nelts; i++) + { + const char *child_name = APR_ARRAY_IDX(children, i, const char *); + const char *child_abspath; + svn_wc__db_status_t child_status; + svn_node_kind_t kind; + svn_depth_t child_depth; + + svn_pool_clear(iterpool); + + /* Get the next node */ + child_abspath = svn_dirent_join(local_abspath, child_name, iterpool); + + SVN_ERR(svn_wc__db_read_info(&child_status, &kind, NULL, NULL, NULL, + NULL,NULL, NULL, NULL, &child_depth, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + db, child_abspath, iterpool, iterpool)); + + if (child_status == svn_wc__db_status_server_excluded || + child_status == svn_wc__db_status_excluded || + child_status == svn_wc__db_status_not_present) + { + svn_depth_t remove_below = (kind == svn_node_dir) + ? svn_depth_immediates + : svn_depth_files; + if (new_depth < remove_below) + SVN_ERR(svn_wc__db_base_remove(db, child_abspath, + FALSE /* keep_as_working */, + FALSE /* queue_deletes */, + SVN_INVALID_REVNUM, + NULL, NULL, iterpool)); + + continue; + } + else if (kind == svn_node_file) + { + if (new_depth == svn_depth_empty) + SVN_ERR(svn_wc__db_op_remove_node(NULL, + db, child_abspath, + TRUE /* destroy */, + FALSE /* destroy_changes */, + SVN_INVALID_REVNUM, + svn_wc__db_status_not_present, + svn_node_none, + NULL, NULL, + cancel_func, cancel_baton, + iterpool)); + else + continue; + + } + else if (kind == svn_node_dir) + { + if (new_depth < svn_depth_immediates) + { + SVN_ERR(svn_wc__db_op_remove_node(NULL, + db, child_abspath, + TRUE /* destroy */, + FALSE /* destroy_changes */, + SVN_INVALID_REVNUM, + svn_wc__db_status_not_present, + svn_node_none, + NULL, NULL, + cancel_func, cancel_baton, + iterpool)); + } + else + { + SVN_ERR(crop_children(db, + child_abspath, + child_depth, + svn_depth_empty, + notify_func, + notify_baton, + cancel_func, + cancel_baton, + iterpool)); + continue; + } + } + else + { + return svn_error_createf + (SVN_ERR_NODE_UNKNOWN_KIND, NULL, _("Unknown node kind for '%s'"), + svn_dirent_local_style(child_abspath, iterpool)); + } + + if (notify_func) + { + svn_wc_notify_t *notify; + notify = svn_wc_create_notify(child_abspath, + svn_wc_notify_delete, + iterpool); + (*notify_func)(notify_baton, notify, iterpool); + } + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_exclude(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_boolean_t is_root, is_switched; + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_revnum_t revision; + const char *repos_relpath, *repos_root, *repos_uuid; + + SVN_ERR(svn_wc__db_is_switched(&is_root, &is_switched, NULL, + wc_ctx->db, local_abspath, scratch_pool)); + + if (is_root) + { + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Cannot exclude '%s': " + "it is a working copy root"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + if (is_switched) + { + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Cannot exclude '%s': " + "it is a switched path"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + SVN_ERR(svn_wc__db_read_info(&status, &kind, &revision, &repos_relpath, + &repos_root, &repos_uuid, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + switch (status) + { + case svn_wc__db_status_server_excluded: + case svn_wc__db_status_excluded: + case svn_wc__db_status_not_present: + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + case svn_wc__db_status_added: + /* Would have to check parents if we want to allow this */ + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Cannot exclude '%s': it is to be added " + "to the repository. Try commit instead"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + case svn_wc__db_status_deleted: + /* Would have to check parents if we want to allow this */ + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Cannot exclude '%s': it is to be deleted " + "from the repository. Try commit instead"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + case svn_wc__db_status_normal: + case svn_wc__db_status_incomplete: + default: + break; /* Ok to exclude */ + } + + /* Remove all working copy data below local_abspath */ + SVN_ERR(svn_wc__db_op_remove_node(NULL, + wc_ctx->db, local_abspath, + TRUE /* destroy */, + FALSE /* destroy_changes */, + revision, + svn_wc__db_status_excluded, + kind, + NULL, NULL, + cancel_func, cancel_baton, + scratch_pool)); + + SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath, + cancel_func, cancel_baton, + scratch_pool)); + + if (notify_func) + { + svn_wc_notify_t *notify; + notify = svn_wc_create_notify(local_abspath, + svn_wc_notify_exclude, + scratch_pool); + notify_func(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_crop_tree2(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_depth_t dir_depth; + + /* Only makes sense when the depth is restrictive. */ + if (depth == svn_depth_infinity) + return SVN_NO_ERROR; /* Nothing to crop */ + if (!(depth >= svn_depth_empty && depth < svn_depth_infinity)) + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Can only crop a working copy with a restrictive depth")); + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, &dir_depth, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + if (kind != svn_node_dir) + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Can only crop directories")); + + switch (status) + { + case svn_wc__db_status_not_present: + case svn_wc__db_status_server_excluded: + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + case svn_wc__db_status_deleted: + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Cannot crop '%s': it is going to be removed " + "from repository. Try commit instead"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + case svn_wc__db_status_added: + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Cannot crop '%s': it is to be added " + "to the repository. Try commit instead"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + case svn_wc__db_status_excluded: + return SVN_NO_ERROR; /* Nothing to do */ + + case svn_wc__db_status_normal: + case svn_wc__db_status_incomplete: + break; + + default: + SVN_ERR_MALFUNCTION(); + } + + SVN_ERR(crop_children(db, local_abspath, dir_depth, depth, + notify_func, notify_baton, + cancel_func, cancel_baton, scratch_pool)); + + return svn_error_trace(svn_wc__wq_run(db, local_abspath, + cancel_func, cancel_baton, + scratch_pool)); +} diff --git a/subversion/libsvn_wc/delete.c b/subversion/libsvn_wc/delete.c new file mode 100644 index 000000000000..37c8af08198c --- /dev/null +++ b/subversion/libsvn_wc/delete.c @@ -0,0 +1,508 @@ +/* + * delete.c: Handling of the in-wc side of the delete operation + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <string.h> +#include <stdlib.h> + +#include <apr_pools.h> + +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_wc.h" +#include "svn_io.h" + +#include "wc.h" +#include "adm_files.h" +#include "conflicts.h" +#include "workqueue.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" + + +/* Remove/erase PATH from the working copy. This involves deleting PATH + * from the physical filesystem. PATH is assumed to be an unversioned file + * or directory. + * + * If ignore_enoent is TRUE, ignore missing targets. + * + * If CANCEL_FUNC is non-null, invoke it with CANCEL_BATON at various + * points, return any error immediately. + */ +static svn_error_t * +erase_unversioned_from_wc(const char *path, + svn_boolean_t ignore_enoent, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + + /* Optimize the common case: try to delete the file */ + err = svn_io_remove_file2(path, ignore_enoent, scratch_pool); + if (err) + { + /* Then maybe it was a directory? */ + svn_error_clear(err); + + err = svn_io_remove_dir2(path, ignore_enoent, cancel_func, cancel_baton, + scratch_pool); + + if (err) + { + /* We're unlikely to end up here. But we need this fallback + to make sure we report the right error *and* try the + correct deletion at least once. */ + svn_node_kind_t kind; + + svn_error_clear(err); + SVN_ERR(svn_io_check_path(path, &kind, scratch_pool)); + if (kind == svn_node_file) + SVN_ERR(svn_io_remove_file2(path, ignore_enoent, scratch_pool)); + else if (kind == svn_node_dir) + SVN_ERR(svn_io_remove_dir2(path, ignore_enoent, + cancel_func, cancel_baton, + scratch_pool)); + else if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, + _("'%s' does not exist"), + svn_dirent_local_style(path, + scratch_pool)); + else + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Unsupported node kind for path '%s'"), + svn_dirent_local_style(path, + scratch_pool)); + + } + } + + return SVN_NO_ERROR; +} + +/* Helper for svn_wc__delete and svn_wc__delete_many */ +static svn_error_t * +create_delete_wq_items(svn_skel_t **work_items, + svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + svn_boolean_t conflicted, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + *work_items = NULL; + + /* Schedule the on-disk delete */ + if (kind == svn_node_dir) + SVN_ERR(svn_wc__wq_build_dir_remove(work_items, db, local_abspath, + local_abspath, + TRUE /* recursive */, + result_pool, scratch_pool)); + else + SVN_ERR(svn_wc__wq_build_file_remove(work_items, db, local_abspath, + local_abspath, + result_pool, scratch_pool)); + + /* Read conflicts, to allow deleting the markers after updating the DB */ + if (conflicted) + { + svn_skel_t *conflict; + const apr_array_header_t *markers; + int i; + + SVN_ERR(svn_wc__db_read_conflict(&conflict, db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__conflict_read_markers(&markers, db, local_abspath, + conflict, + scratch_pool, scratch_pool)); + + /* Maximum number of markers is 4, so no iterpool */ + for (i = 0; markers && i < markers->nelts; i++) + { + const char *marker_abspath; + svn_node_kind_t marker_kind; + + marker_abspath = APR_ARRAY_IDX(markers, i, const char *); + SVN_ERR(svn_io_check_path(marker_abspath, &marker_kind, + scratch_pool)); + + if (marker_kind == svn_node_file) + { + svn_skel_t *work_item; + + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db, + local_abspath, + marker_abspath, + result_pool, + scratch_pool)); + + *work_items = svn_wc__wq_merge(*work_items, work_item, + result_pool); + } + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__delete_many(svn_wc_context_t *wc_ctx, + const apr_array_header_t *targets, + svn_boolean_t keep_local, + svn_boolean_t delete_unversioned_target, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + svn_error_t *err; + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_skel_t *work_items = NULL; + apr_array_header_t *versioned_targets; + const char *local_abspath; + int i; + apr_pool_t *iterpool; + + iterpool = svn_pool_create(scratch_pool); + versioned_targets = apr_array_make(scratch_pool, targets->nelts, + sizeof(const char *)); + for (i = 0; i < targets->nelts; i++) + { + svn_boolean_t conflicted = FALSE; + const char *repos_relpath; + + svn_pool_clear(iterpool); + + local_abspath = APR_ARRAY_IDX(targets, i, const char *); + err = svn_wc__db_read_info(&status, &kind, NULL, &repos_relpath, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &conflicted, + NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, iterpool, iterpool); + + if (err) + { + if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + if (delete_unversioned_target && !keep_local) + SVN_ERR(erase_unversioned_from_wc(local_abspath, FALSE, + cancel_func, cancel_baton, + iterpool)); + continue; + } + else + return svn_error_trace(err); + } + + APR_ARRAY_PUSH(versioned_targets, const char *) = local_abspath; + + switch (status) + { + /* svn_wc__db_status_server_excluded handled by + * svn_wc__db_op_delete_many */ + case svn_wc__db_status_excluded: + case svn_wc__db_status_not_present: + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("'%s' cannot be deleted"), + svn_dirent_local_style(local_abspath, + iterpool)); + + /* Explicitly ignore other statii */ + default: + break; + } + + if (status == svn_wc__db_status_normal + && kind == svn_node_dir) + { + svn_boolean_t is_wcroot; + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, + iterpool)); + + if (is_wcroot) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' is the root of a working copy and " + "cannot be deleted"), + svn_dirent_local_style(local_abspath, + iterpool)); + } + if (repos_relpath && !repos_relpath[0]) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' represents the repository root " + "and cannot be deleted"), + svn_dirent_local_style(local_abspath, + iterpool)); + + /* Verify if we have a write lock on the parent of this node as we might + be changing the childlist of that directory. */ + SVN_ERR(svn_wc__write_check(db, svn_dirent_dirname(local_abspath, + iterpool), + iterpool)); + + /* Prepare the on-disk delete */ + if (!keep_local) + { + svn_skel_t *work_item; + + SVN_ERR(create_delete_wq_items(&work_item, db, local_abspath, kind, + conflicted, + scratch_pool, iterpool)); + + work_items = svn_wc__wq_merge(work_items, work_item, + scratch_pool); + } + } + + if (versioned_targets->nelts == 0) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__db_op_delete_many(db, versioned_targets, + !keep_local /* delete_dir_externals */, + work_items, + cancel_func, cancel_baton, + notify_func, notify_baton, + iterpool)); + + if (work_items != NULL) + { + /* Our only caller locked the wc, so for now assume it only passed + nodes from a single wc (asserted in svn_wc__db_op_delete_many) */ + local_abspath = APR_ARRAY_IDX(versioned_targets, 0, const char *); + + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + iterpool)); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_delete4(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t keep_local, + svn_boolean_t delete_unversioned_target, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + apr_pool_t *pool = scratch_pool; + svn_wc__db_t *db = wc_ctx->db; + svn_error_t *err; + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_boolean_t conflicted; + svn_skel_t *work_items = NULL; + const char *repos_relpath; + + err = svn_wc__db_read_info(&status, &kind, NULL, &repos_relpath, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, &conflicted, + NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, pool, pool); + + if (delete_unversioned_target && + err != NULL && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + + if (!keep_local) + SVN_ERR(erase_unversioned_from_wc(local_abspath, FALSE, + cancel_func, cancel_baton, + pool)); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + switch (status) + { + /* svn_wc__db_status_server_excluded handled by svn_wc__db_op_delete */ + case svn_wc__db_status_excluded: + case svn_wc__db_status_not_present: + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("'%s' cannot be deleted"), + svn_dirent_local_style(local_abspath, pool)); + + /* Explicitly ignore other statii */ + default: + break; + } + + if (status == svn_wc__db_status_normal + && kind == svn_node_dir) + { + svn_boolean_t is_wcroot; + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, pool)); + + if (is_wcroot) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' is the root of a working copy and " + "cannot be deleted"), + svn_dirent_local_style(local_abspath, pool)); + } + if (repos_relpath && !repos_relpath[0]) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' represents the repository root " + "and cannot be deleted"), + svn_dirent_local_style(local_abspath, pool)); + + /* Verify if we have a write lock on the parent of this node as we might + be changing the childlist of that directory. */ + SVN_ERR(svn_wc__write_check(db, svn_dirent_dirname(local_abspath, pool), + pool)); + + /* Prepare the on-disk delete */ + if (!keep_local) + { + SVN_ERR(create_delete_wq_items(&work_items, db, local_abspath, kind, + conflicted, + scratch_pool, scratch_pool)); + } + + SVN_ERR(svn_wc__db_op_delete(db, local_abspath, + NULL /*moved_to_abspath */, + !keep_local /* delete_dir_externals */, + NULL, work_items, + cancel_func, cancel_baton, + notify_func, notify_baton, + pool)); + + if (work_items) + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__internal_remove_from_revision_control(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t destroy_wf, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_boolean_t left_something = FALSE; + svn_boolean_t is_root; + svn_error_t *err = NULL; + + SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool)); + + SVN_ERR(svn_wc__write_check(db, is_root ? local_abspath + : svn_dirent_dirname(local_abspath, + scratch_pool), + scratch_pool)); + + SVN_ERR(svn_wc__db_op_remove_node(&left_something, + db, local_abspath, + destroy_wf /* destroy_wc */, + destroy_wf /* destroy_changes */, + SVN_INVALID_REVNUM, + svn_wc__db_status_not_present, + svn_node_none, + NULL, NULL, + cancel_func, cancel_baton, + scratch_pool)); + + SVN_ERR(svn_wc__wq_run(db, local_abspath, + cancel_func, cancel_baton, + scratch_pool)); + + if (is_root) + { + /* Destroy the administrative area */ + SVN_ERR(svn_wc__adm_destroy(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + + /* And if we didn't leave something interesting, remove the directory */ + if (!left_something && destroy_wf) + err = svn_io_dir_remove_nonrecursive(local_abspath, scratch_pool); + } + + if (left_something || err) + return svn_error_create(SVN_ERR_WC_LEFT_LOCAL_MOD, err, NULL); + + return SVN_NO_ERROR; +} + +/* Implements svn_wc_status_func4_t for svn_wc_remove_from_revision_control2 */ +static svn_error_t * +remove_from_revision_status_callback(void *baton, + const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + /* For legacy reasons we only check the file contents for changes */ + if (status->versioned + && status->kind == svn_node_file + && (status->text_status == svn_wc_status_modified + || status->text_status == svn_wc_status_conflicted)) + { + return svn_error_createf(SVN_ERR_WC_LEFT_LOCAL_MOD, NULL, + _("File '%s' has local modifications"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_remove_from_revision_control2(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t destroy_wf, + svn_boolean_t instant_error, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + if (instant_error) + { + SVN_ERR(svn_wc_walk_status(wc_ctx, local_abspath, svn_depth_infinity, + FALSE, FALSE, FALSE, NULL, + remove_from_revision_status_callback, NULL, + cancel_func, cancel_baton, + scratch_pool)); + } + return svn_error_trace( + svn_wc__internal_remove_from_revision_control(wc_ctx->db, + local_abspath, + destroy_wf, + cancel_func, + cancel_baton, + scratch_pool)); +} + diff --git a/subversion/libsvn_wc/deprecated.c b/subversion/libsvn_wc/deprecated.c new file mode 100644 index 000000000000..79cdb3030cbc --- /dev/null +++ b/subversion/libsvn_wc/deprecated.c @@ -0,0 +1,4582 @@ +/* + * deprecated.c: holding file for all deprecated APIs. + * "we can't lose 'em, but we can shun 'em!" + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* We define this here to remove any further warnings about the usage of + deprecated functions in this file. */ +#define SVN_DEPRECATED + +#include <apr_md5.h> + +#include "svn_wc.h" +#include "svn_subst.h" +#include "svn_pools.h" +#include "svn_props.h" +#include "svn_hash.h" +#include "svn_time.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" + +#include "private/svn_subr_private.h" +#include "private/svn_wc_private.h" + +#include "wc.h" +#include "entries.h" +#include "lock.h" +#include "props.h" +#include "translate.h" +#include "workqueue.h" + +#include "svn_private_config.h" + +/* baton for traversal_info_update */ +struct traversal_info_update_baton +{ + struct svn_wc_traversal_info_t *traversal; + svn_wc__db_t *db; +}; + +/* Helper for updating svn_wc_traversal_info_t structures + * Implements svn_wc_external_update_t */ +static svn_error_t * +traversal_info_update(void *baton, + const char *local_abspath, + const svn_string_t *old_val, + const svn_string_t *new_val, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + const char *dup_path; + svn_wc_adm_access_t *adm_access; + struct traversal_info_update_baton *ub = baton; + apr_pool_t *dup_pool = ub->traversal->pool; + const char *dup_val = NULL; + + /* We make the abspath relative by retrieving the access baton + for the specific directory */ + adm_access = svn_wc__adm_retrieve_internal2(ub->db, local_abspath, + scratch_pool); + + if (adm_access) + dup_path = apr_pstrdup(dup_pool, svn_wc_adm_access_path(adm_access)); + else + dup_path = apr_pstrdup(dup_pool, local_abspath); + + if (old_val) + { + dup_val = apr_pstrmemdup(dup_pool, old_val->data, old_val->len); + + svn_hash_sets(ub->traversal->externals_old, dup_path, dup_val); + } + + if (new_val) + { + /* In most cases the value is identical */ + if (old_val != new_val) + dup_val = apr_pstrmemdup(dup_pool, new_val->data, new_val->len); + + svn_hash_sets(ub->traversal->externals_new, dup_path, dup_val); + } + + svn_hash_sets(ub->traversal->depths, dup_path, svn_depth_to_word(depth)); + + return SVN_NO_ERROR; +} + +/* Helper for functions that used to gather traversal_info */ +static svn_error_t * +gather_traversal_info(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *path, + svn_depth_t depth, + struct svn_wc_traversal_info_t *traversal_info, + svn_boolean_t gather_as_old, + svn_boolean_t gather_as_new, + apr_pool_t *scratch_pool) +{ + apr_hash_t *externals; + apr_hash_t *ambient_depths; + apr_hash_index_t *hi; + + SVN_ERR(svn_wc__externals_gather_definitions(&externals, &ambient_depths, + wc_ctx, local_abspath, + depth, + scratch_pool, scratch_pool)); + + for (hi = apr_hash_first(scratch_pool, externals); + hi; + hi = apr_hash_next(hi)) + { + const char *node_abspath = svn__apr_hash_index_key(hi); + const char *relpath; + + relpath = svn_dirent_join(path, + svn_dirent_skip_ancestor(local_abspath, + node_abspath), + traversal_info->pool); + + if (gather_as_old) + svn_hash_sets(traversal_info->externals_old, relpath, + svn__apr_hash_index_val(hi)); + + if (gather_as_new) + svn_hash_sets(traversal_info->externals_new, relpath, + svn__apr_hash_index_val(hi)); + + svn_hash_sets(traversal_info->depths, relpath, + svn_hash_gets(ambient_depths, node_abspath)); + } + + return SVN_NO_ERROR; +} + + +/*** From adm_crawler.c ***/ + +svn_error_t * +svn_wc_crawl_revisions4(const char *path, + svn_wc_adm_access_t *adm_access, + const svn_ra_reporter3_t *reporter, + void *report_baton, + svn_boolean_t restore_files, + svn_depth_t depth, + svn_boolean_t honor_depth_exclude, + svn_boolean_t depth_compatibility_trick, + svn_boolean_t use_commit_times, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + svn_wc__db_t *wc_db = svn_wc__adm_get_db(adm_access); + const char *local_abspath; + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, wc_db, pool)); + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + + SVN_ERR(svn_wc_crawl_revisions5(wc_ctx, + local_abspath, + reporter, + report_baton, + restore_files, + depth, + honor_depth_exclude, + depth_compatibility_trick, + use_commit_times, + NULL /* cancel_func */, + NULL /* cancel_baton */, + notify_func, + notify_baton, + pool)); + + if (traversal_info) + SVN_ERR(gather_traversal_info(wc_ctx, local_abspath, path, depth, + traversal_info, TRUE, FALSE, pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +/*** Compatibility wrapper: turns an svn_ra_reporter2_t into an + svn_ra_reporter3_t. + + This code looks like it duplicates code in libsvn_ra/ra_loader.c, + but it does not. That code makes an new thing look like an old + thing; this code makes an old thing look like a new thing. ***/ + +struct wrap_3to2_report_baton { + const svn_ra_reporter2_t *reporter; + void *baton; +}; + +/* */ +static svn_error_t *wrap_3to2_set_path(void *report_baton, + const char *path, + svn_revnum_t revision, + svn_depth_t depth, + svn_boolean_t start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + struct wrap_3to2_report_baton *wrb = report_baton; + + return wrb->reporter->set_path(wrb->baton, path, revision, start_empty, + lock_token, pool); +} + +/* */ +static svn_error_t *wrap_3to2_delete_path(void *report_baton, + const char *path, + apr_pool_t *pool) +{ + struct wrap_3to2_report_baton *wrb = report_baton; + + return wrb->reporter->delete_path(wrb->baton, path, pool); +} + +/* */ +static svn_error_t *wrap_3to2_link_path(void *report_baton, + const char *path, + const char *url, + svn_revnum_t revision, + svn_depth_t depth, + svn_boolean_t start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + struct wrap_3to2_report_baton *wrb = report_baton; + + return wrb->reporter->link_path(wrb->baton, path, url, revision, + start_empty, lock_token, pool); +} + +/* */ +static svn_error_t *wrap_3to2_finish_report(void *report_baton, + apr_pool_t *pool) +{ + struct wrap_3to2_report_baton *wrb = report_baton; + + return wrb->reporter->finish_report(wrb->baton, pool); +} + +/* */ +static svn_error_t *wrap_3to2_abort_report(void *report_baton, + apr_pool_t *pool) +{ + struct wrap_3to2_report_baton *wrb = report_baton; + + return wrb->reporter->abort_report(wrb->baton, pool); +} + +static const svn_ra_reporter3_t wrap_3to2_reporter = { + wrap_3to2_set_path, + wrap_3to2_delete_path, + wrap_3to2_link_path, + wrap_3to2_finish_report, + wrap_3to2_abort_report +}; + +svn_error_t * +svn_wc_crawl_revisions3(const char *path, + svn_wc_adm_access_t *adm_access, + const svn_ra_reporter3_t *reporter, + void *report_baton, + svn_boolean_t restore_files, + svn_depth_t depth, + svn_boolean_t depth_compatibility_trick, + svn_boolean_t use_commit_times, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + return svn_wc_crawl_revisions4(path, + adm_access, + reporter, report_baton, + restore_files, + depth, + FALSE, + depth_compatibility_trick, + use_commit_times, + notify_func, + notify_baton, + traversal_info, + pool); +} + +svn_error_t * +svn_wc_crawl_revisions2(const char *path, + svn_wc_adm_access_t *adm_access, + const svn_ra_reporter2_t *reporter, + void *report_baton, + svn_boolean_t restore_files, + svn_boolean_t recurse, + svn_boolean_t use_commit_times, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + struct wrap_3to2_report_baton wrb; + wrb.reporter = reporter; + wrb.baton = report_baton; + + return svn_wc_crawl_revisions3(path, + adm_access, + &wrap_3to2_reporter, &wrb, + restore_files, + SVN_DEPTH_INFINITY_OR_FILES(recurse), + FALSE, + use_commit_times, + notify_func, + notify_baton, + traversal_info, + pool); +} + + +/* Baton for compat_call_notify_func below. */ +struct compat_notify_baton_t { + /* Wrapped func/baton. */ + svn_wc_notify_func_t func; + void *baton; +}; + + +/* Implements svn_wc_notify_func2_t. Call BATON->func (BATON is of type + svn_wc__compat_notify_baton_t), passing BATON->baton and the appropriate + arguments from NOTIFY. */ +static void +compat_call_notify_func(void *baton, + const svn_wc_notify_t *n, + apr_pool_t *pool) +{ + struct compat_notify_baton_t *nb = baton; + + if (nb->func) + (*nb->func)(nb->baton, n->path, n->action, n->kind, n->mime_type, + n->content_state, n->prop_state, n->revision); +} + + +/*** Compatibility wrapper: turns an svn_ra_reporter_t into an + svn_ra_reporter2_t. + + This code looks like it duplicates code in libsvn_ra/ra_loader.c, + but it does not. That code makes an new thing look like an old + thing; this code makes an old thing look like a new thing. ***/ + +struct wrap_2to1_report_baton { + const svn_ra_reporter_t *reporter; + void *baton; +}; + +/* */ +static svn_error_t *wrap_2to1_set_path(void *report_baton, + const char *path, + svn_revnum_t revision, + svn_boolean_t start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + struct wrap_2to1_report_baton *wrb = report_baton; + + return wrb->reporter->set_path(wrb->baton, path, revision, start_empty, + pool); +} + +/* */ +static svn_error_t *wrap_2to1_delete_path(void *report_baton, + const char *path, + apr_pool_t *pool) +{ + struct wrap_2to1_report_baton *wrb = report_baton; + + return wrb->reporter->delete_path(wrb->baton, path, pool); +} + +/* */ +static svn_error_t *wrap_2to1_link_path(void *report_baton, + const char *path, + const char *url, + svn_revnum_t revision, + svn_boolean_t start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + struct wrap_2to1_report_baton *wrb = report_baton; + + return wrb->reporter->link_path(wrb->baton, path, url, revision, + start_empty, pool); +} + +/* */ +static svn_error_t *wrap_2to1_finish_report(void *report_baton, + apr_pool_t *pool) +{ + struct wrap_2to1_report_baton *wrb = report_baton; + + return wrb->reporter->finish_report(wrb->baton, pool); +} + +/* */ +static svn_error_t *wrap_2to1_abort_report(void *report_baton, + apr_pool_t *pool) +{ + struct wrap_2to1_report_baton *wrb = report_baton; + + return wrb->reporter->abort_report(wrb->baton, pool); +} + +static const svn_ra_reporter2_t wrap_2to1_reporter = { + wrap_2to1_set_path, + wrap_2to1_delete_path, + wrap_2to1_link_path, + wrap_2to1_finish_report, + wrap_2to1_abort_report +}; + +svn_error_t * +svn_wc_crawl_revisions(const char *path, + svn_wc_adm_access_t *adm_access, + const svn_ra_reporter_t *reporter, + void *report_baton, + svn_boolean_t restore_files, + svn_boolean_t recurse, + svn_boolean_t use_commit_times, + svn_wc_notify_func_t notify_func, + void *notify_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + struct wrap_2to1_report_baton wrb; + struct compat_notify_baton_t nb; + + wrb.reporter = reporter; + wrb.baton = report_baton; + + nb.func = notify_func; + nb.baton = notify_baton; + + return svn_wc_crawl_revisions2(path, adm_access, &wrap_2to1_reporter, &wrb, + restore_files, recurse, use_commit_times, + compat_call_notify_func, &nb, + traversal_info, + pool); +} + +svn_error_t * +svn_wc_transmit_text_deltas2(const char **tempfile, + unsigned char digest[], + const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t fulltext, + const svn_delta_editor_t *editor, + void *file_baton, + apr_pool_t *pool) +{ + const char *local_abspath; + svn_wc_context_t *wc_ctx; + const svn_checksum_t *new_text_base_md5_checksum; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), + pool)); + + SVN_ERR(svn_wc__internal_transmit_text_deltas(tempfile, + (digest + ? &new_text_base_md5_checksum + : NULL), + NULL, wc_ctx->db, + local_abspath, fulltext, + editor, file_baton, + pool, pool)); + + if (digest) + memcpy(digest, new_text_base_md5_checksum->digest, APR_MD5_DIGESTSIZE); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_transmit_text_deltas(const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t fulltext, + const svn_delta_editor_t *editor, + void *file_baton, + const char **tempfile, + apr_pool_t *pool) +{ + return svn_wc_transmit_text_deltas2(tempfile, NULL, path, adm_access, + fulltext, editor, file_baton, pool); +} + +svn_error_t * +svn_wc_transmit_prop_deltas(const char *path, + svn_wc_adm_access_t *adm_access, + const svn_wc_entry_t *entry, + const svn_delta_editor_t *editor, + void *baton, + const char **tempfile, + apr_pool_t *pool) +{ + const char *local_abspath; + svn_wc_context_t *wc_ctx; + + if (tempfile) + *tempfile = NULL; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), + pool)); + + SVN_ERR(svn_wc_transmit_prop_deltas2(wc_ctx, local_abspath, editor, baton, + pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +/*** From adm_files.c ***/ +svn_error_t * +svn_wc_ensure_adm3(const char *path, + const char *uuid, + const char *url, + const char *repos, + svn_revnum_t revision, + svn_depth_t depth, + apr_pool_t *pool) +{ + const char *local_abspath; + svn_wc_context_t *wc_ctx; + + if (uuid == NULL) + return svn_error_create(SVN_ERR_BAD_UUID, NULL, NULL); + if (repos == NULL) + return svn_error_create(SVN_ERR_BAD_URL, NULL, NULL); + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc_context_create(&wc_ctx, NULL /* config */, pool, pool)); + + SVN_ERR(svn_wc_ensure_adm4(wc_ctx, local_abspath, url, repos, uuid, revision, + depth, pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_ensure_adm2(const char *path, + const char *uuid, + const char *url, + const char *repos, + svn_revnum_t revision, + apr_pool_t *pool) +{ + return svn_wc_ensure_adm3(path, uuid, url, repos, revision, + svn_depth_infinity, pool); +} + + +svn_error_t * +svn_wc_ensure_adm(const char *path, + const char *uuid, + const char *url, + svn_revnum_t revision, + apr_pool_t *pool) +{ + return svn_wc_ensure_adm2(path, uuid, url, NULL, revision, pool); +} + +svn_error_t * +svn_wc_create_tmp_file(apr_file_t **fp, + const char *path, + svn_boolean_t delete_on_close, + apr_pool_t *pool) +{ + return svn_wc_create_tmp_file2(fp, NULL, path, + delete_on_close + ? svn_io_file_del_on_close + : svn_io_file_del_none, + pool); +} + +svn_error_t * +svn_wc_create_tmp_file2(apr_file_t **fp, + const char **new_name, + const char *path, + svn_io_file_del_t delete_when, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + const char *local_abspath; + const char *temp_dir; + svn_error_t *err; + + SVN_ERR_ASSERT(fp || new_name); + + SVN_ERR(svn_wc_context_create(&wc_ctx, NULL /* config */, pool, pool)); + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + err = svn_wc__get_tmpdir(&temp_dir, wc_ctx, local_abspath, pool, pool); + err = svn_error_compose_create(err, svn_wc_context_destroy(wc_ctx)); + if (err) + return svn_error_trace(err); + + SVN_ERR(svn_io_open_unique_file3(fp, new_name, temp_dir, + delete_when, pool, pool)); + + return SVN_NO_ERROR; +} + + +/*** From adm_ops.c ***/ +svn_error_t * +svn_wc_get_pristine_contents(svn_stream_t **contents, + const char *path, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_context_t *wc_ctx; + const char *local_abspath; + + SVN_ERR(svn_wc_context_create(&wc_ctx, NULL, scratch_pool, scratch_pool)); + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool)); + + SVN_ERR(svn_wc_get_pristine_contents2(contents, + wc_ctx, + local_abspath, + result_pool, + scratch_pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + + +svn_error_t * +svn_wc_queue_committed2(svn_wc_committed_queue_t *queue, + const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t recurse, + const apr_array_header_t *wcprop_changes, + svn_boolean_t remove_lock, + svn_boolean_t remove_changelist, + const svn_checksum_t *md5_checksum, + apr_pool_t *scratch_pool) +{ + svn_wc_context_t *wc_ctx; + const char *local_abspath; + const svn_checksum_t *sha1_checksum = NULL; + + SVN_ERR(svn_wc_context_create(&wc_ctx, NULL, scratch_pool, scratch_pool)); + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool)); + + if (md5_checksum != NULL) + { + svn_error_t *err; + err = svn_wc__db_pristine_get_sha1(&sha1_checksum, wc_ctx->db, + local_abspath, md5_checksum, + svn_wc__get_committed_queue_pool(queue), + scratch_pool); + + /* Don't fail on SHA1 not found */ + if (err && err->apr_err == SVN_ERR_WC_DB_ERROR) + { + svn_error_clear(err); + sha1_checksum = NULL; + } + else + SVN_ERR(err); + } + + SVN_ERR(svn_wc_queue_committed3(queue, wc_ctx, local_abspath, recurse, + wcprop_changes, + remove_lock, remove_changelist, + sha1_checksum, scratch_pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_queue_committed(svn_wc_committed_queue_t **queue, + const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t recurse, + const apr_array_header_t *wcprop_changes, + svn_boolean_t remove_lock, + svn_boolean_t remove_changelist, + const unsigned char *digest, + apr_pool_t *pool) +{ + const svn_checksum_t *md5_checksum; + + if (digest) + md5_checksum = svn_checksum__from_digest_md5( + digest, svn_wc__get_committed_queue_pool(*queue)); + else + md5_checksum = NULL; + + return svn_wc_queue_committed2(*queue, path, adm_access, recurse, + wcprop_changes, remove_lock, + remove_changelist, md5_checksum, pool); +} + +svn_error_t * +svn_wc_process_committed_queue(svn_wc_committed_queue_t *queue, + svn_wc_adm_access_t *adm_access, + svn_revnum_t new_revnum, + const char *rev_date, + const char *rev_author, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, + svn_wc__adm_get_db(adm_access), + pool)); + SVN_ERR(svn_wc_process_committed_queue2(queue, wc_ctx, new_revnum, + rev_date, rev_author, + NULL, NULL, pool)); + SVN_ERR(svn_wc_context_destroy(wc_ctx)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_process_committed4(const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t recurse, + svn_revnum_t new_revnum, + const char *rev_date, + const char *rev_author, + const apr_array_header_t *wcprop_changes, + svn_boolean_t remove_lock, + svn_boolean_t remove_changelist, + const unsigned char *digest, + apr_pool_t *pool) +{ + svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); + const char *local_abspath; + const svn_checksum_t *md5_checksum; + const svn_checksum_t *sha1_checksum = NULL; + apr_time_t new_date; + apr_hash_t *wcprop_changes_hash; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + + if (rev_date) + SVN_ERR(svn_time_from_cstring(&new_date, rev_date, pool)); + else + new_date = 0; + + if (digest) + md5_checksum = svn_checksum__from_digest_md5(digest, pool); + else + md5_checksum = NULL; + + if (md5_checksum != NULL) + { + svn_error_t *err; + err = svn_wc__db_pristine_get_sha1(&sha1_checksum, db, + local_abspath, md5_checksum, + pool, pool); + + if (err && err->apr_err == SVN_ERR_WC_DB_ERROR) + { + svn_error_clear(err); + sha1_checksum = NULL; + } + else + SVN_ERR(err); + } + + wcprop_changes_hash = svn_wc__prop_array_to_hash(wcprop_changes, pool); + SVN_ERR(svn_wc__process_committed_internal(db, local_abspath, recurse, TRUE, + new_revnum, new_date, rev_author, + wcprop_changes_hash, + !remove_lock, !remove_changelist, + sha1_checksum, NULL, pool)); + + /* Run the log file(s) we just created. */ + return svn_error_trace(svn_wc__wq_run(db, local_abspath, NULL, NULL, pool)); +} + + +svn_error_t * +svn_wc_process_committed3(const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t recurse, + svn_revnum_t new_revnum, + const char *rev_date, + const char *rev_author, + const apr_array_header_t *wcprop_changes, + svn_boolean_t remove_lock, + const unsigned char *digest, + apr_pool_t *pool) +{ + return svn_wc_process_committed4(path, adm_access, recurse, new_revnum, + rev_date, rev_author, wcprop_changes, + remove_lock, FALSE, digest, pool); +} + +svn_error_t * +svn_wc_process_committed2(const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t recurse, + svn_revnum_t new_revnum, + const char *rev_date, + const char *rev_author, + const apr_array_header_t *wcprop_changes, + svn_boolean_t remove_lock, + apr_pool_t *pool) +{ + return svn_wc_process_committed3(path, adm_access, recurse, new_revnum, + rev_date, rev_author, wcprop_changes, + remove_lock, NULL, pool); +} + +svn_error_t * +svn_wc_process_committed(const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t recurse, + svn_revnum_t new_revnum, + const char *rev_date, + const char *rev_author, + const apr_array_header_t *wcprop_changes, + apr_pool_t *pool) +{ + return svn_wc_process_committed2(path, adm_access, recurse, new_revnum, + rev_date, rev_author, wcprop_changes, + FALSE, pool); +} + +svn_error_t * +svn_wc_maybe_set_repos_root(svn_wc_adm_access_t *adm_access, + const char *path, + const char *repos, + apr_pool_t *pool) +{ + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_delete3(const char *path, + svn_wc_adm_access_t *adm_access, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_boolean_t keep_local, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + svn_wc__db_t *wc_db = svn_wc__adm_get_db(adm_access); + svn_wc_adm_access_t *dir_access; + const char *local_abspath; + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, wc_db, pool)); + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + + /* Open access batons for everything below path, because we used to open + these before. */ + SVN_ERR(svn_wc_adm_probe_try3(&dir_access, adm_access, path, + TRUE, -1, cancel_func, cancel_baton, pool)); + + SVN_ERR(svn_wc_delete4(wc_ctx, + local_abspath, + keep_local, + TRUE, + cancel_func, cancel_baton, + notify_func, notify_baton, + pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_delete2(const char *path, + svn_wc_adm_access_t *adm_access, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + return svn_wc_delete3(path, adm_access, cancel_func, cancel_baton, + notify_func, notify_baton, FALSE, pool); +} + +svn_error_t * +svn_wc_delete(const char *path, + svn_wc_adm_access_t *adm_access, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + struct compat_notify_baton_t nb; + + nb.func = notify_func; + nb.baton = notify_baton; + + return svn_wc_delete2(path, adm_access, cancel_func, cancel_baton, + compat_call_notify_func, &nb, pool); +} + +svn_error_t * +svn_wc_add_from_disk(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_wc_add_from_disk2(wc_ctx, local_abspath, NULL, + notify_func, notify_baton, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_add3(const char *path, + svn_wc_adm_access_t *parent_access, + svn_depth_t depth, + const char *copyfrom_url, + svn_revnum_t copyfrom_rev, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + svn_wc__db_t *wc_db = svn_wc__adm_get_db(parent_access); + const char *local_abspath; + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, wc_db, pool)); + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + + SVN_ERR(svn_wc_add4(wc_ctx, local_abspath, + depth, copyfrom_url, + copyfrom_rev, + cancel_func, cancel_baton, + notify_func, notify_baton, pool)); + + /* Make sure the caller gets the new access baton in the set. */ + if (svn_wc__adm_retrieve_internal2(wc_db, local_abspath, pool) == NULL) + { + svn_node_kind_t kind; + + SVN_ERR(svn_wc__db_read_kind(&kind, wc_db, local_abspath, + FALSE /* allow_missing */, + TRUE /* show_deleted */, + FALSE /* show_hidden */, pool)); + if (kind == svn_node_dir) + { + svn_wc_adm_access_t *adm_access; + + /* Open the access baton in adm_access' pool to give it the same + lifetime */ + SVN_ERR(svn_wc_adm_open3(&adm_access, parent_access, path, TRUE, + copyfrom_url ? -1 : 0, + cancel_func, cancel_baton, + svn_wc_adm_access_pool(parent_access))); + } + } + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + + +svn_error_t * +svn_wc_add2(const char *path, + svn_wc_adm_access_t *parent_access, + const char *copyfrom_url, + svn_revnum_t copyfrom_rev, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + return svn_wc_add3(path, parent_access, svn_depth_infinity, + copyfrom_url, copyfrom_rev, + cancel_func, cancel_baton, + notify_func, notify_baton, pool); +} + +svn_error_t * +svn_wc_add(const char *path, + svn_wc_adm_access_t *parent_access, + const char *copyfrom_url, + svn_revnum_t copyfrom_rev, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + struct compat_notify_baton_t nb; + + nb.func = notify_func; + nb.baton = notify_baton; + + return svn_wc_add2(path, parent_access, copyfrom_url, copyfrom_rev, + cancel_func, cancel_baton, + compat_call_notify_func, &nb, pool); +} + +svn_error_t * +svn_wc_revert3(const char *path, + svn_wc_adm_access_t *parent_access, + svn_depth_t depth, + svn_boolean_t use_commit_times, + const apr_array_header_t *changelist_filter, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + svn_wc__db_t *wc_db = svn_wc__adm_get_db(parent_access); + const char *local_abspath; + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, wc_db, pool)); + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + + SVN_ERR(svn_wc_revert4(wc_ctx, + local_abspath, + depth, + use_commit_times, + changelist_filter, + cancel_func, cancel_baton, + notify_func, notify_baton, + pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_revert2(const char *path, + svn_wc_adm_access_t *parent_access, + svn_boolean_t recursive, + svn_boolean_t use_commit_times, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + return svn_wc_revert3(path, parent_access, + SVN_DEPTH_INFINITY_OR_EMPTY(recursive), + use_commit_times, NULL, cancel_func, cancel_baton, + notify_func, notify_baton, pool); +} + +svn_error_t * +svn_wc_revert(const char *path, + svn_wc_adm_access_t *parent_access, + svn_boolean_t recursive, + svn_boolean_t use_commit_times, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + struct compat_notify_baton_t nb; + + nb.func = notify_func; + nb.baton = notify_baton; + + return svn_wc_revert2(path, parent_access, recursive, use_commit_times, + cancel_func, cancel_baton, + compat_call_notify_func, &nb, pool); +} + +svn_error_t * +svn_wc_remove_from_revision_control(svn_wc_adm_access_t *adm_access, + const char *name, + svn_boolean_t destroy_wf, + svn_boolean_t instant_error, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + svn_wc__db_t *wc_db = svn_wc__adm_get_db(adm_access); + const char *local_abspath = svn_dirent_join( + svn_wc__adm_access_abspath(adm_access), + name, + pool); + + /* name must be an entry in adm_access, fail if not */ + SVN_ERR_ASSERT(strcmp(svn_dirent_basename(name, NULL), name) == 0); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, wc_db, pool)); + + SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, + local_abspath, + destroy_wf, + instant_error, + cancel_func, cancel_baton, + pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_resolved_conflict4(const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t resolve_text, + svn_boolean_t resolve_props, + svn_boolean_t resolve_tree, + svn_depth_t depth, + svn_wc_conflict_choice_t conflict_choice, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + svn_wc__db_t *wc_db = svn_wc__adm_get_db(adm_access); + const char *local_abspath; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, wc_db, pool)); + + SVN_ERR(svn_wc_resolved_conflict5(wc_ctx, + local_abspath, + depth, + resolve_text, + resolve_props ? "" : NULL, + resolve_tree, + conflict_choice, + cancel_func, + cancel_baton, + notify_func, + notify_baton, + pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); + +} + +svn_error_t * +svn_wc_resolved_conflict(const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t resolve_text, + svn_boolean_t resolve_props, + svn_boolean_t recurse, + svn_wc_notify_func_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + struct compat_notify_baton_t nb; + + nb.func = notify_func; + nb.baton = notify_baton; + + return svn_wc_resolved_conflict2(path, adm_access, + resolve_text, resolve_props, recurse, + compat_call_notify_func, &nb, + NULL, NULL, pool); + +} + +svn_error_t * +svn_wc_resolved_conflict2(const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t resolve_text, + svn_boolean_t resolve_props, + svn_boolean_t recurse, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + return svn_wc_resolved_conflict3(path, adm_access, resolve_text, + resolve_props, + SVN_DEPTH_INFINITY_OR_EMPTY(recurse), + svn_wc_conflict_choose_merged, + notify_func, notify_baton, cancel_func, + cancel_baton, pool); +} + +svn_error_t * +svn_wc_resolved_conflict3(const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t resolve_text, + svn_boolean_t resolve_props, + svn_depth_t depth, + svn_wc_conflict_choice_t conflict_choice, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + return svn_wc_resolved_conflict4(path, adm_access, resolve_text, + resolve_props, FALSE, depth, + svn_wc_conflict_choose_merged, + notify_func, notify_baton, cancel_func, + cancel_baton, pool); +} + +svn_error_t * +svn_wc_add_lock(const char *path, + const svn_lock_t *lock, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + const char *local_abspath; + svn_wc_context_t *wc_ctx; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), + pool)); + + SVN_ERR(svn_wc_add_lock2(wc_ctx, local_abspath, lock, pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_remove_lock(const char *path, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + const char *local_abspath; + svn_wc_context_t *wc_ctx; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), + pool)); + + SVN_ERR(svn_wc_remove_lock2(wc_ctx, local_abspath, pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); + +} + +svn_error_t * +svn_wc_get_ancestry(char **url, + svn_revnum_t *rev, + const char *path, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + const char *local_abspath; + const svn_wc_entry_t *entry; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + + SVN_ERR(svn_wc__get_entry(&entry, svn_wc__adm_get_db(adm_access), + local_abspath, FALSE, + svn_node_unknown, + pool, pool)); + + if (url) + *url = apr_pstrdup(pool, entry->url); + + if (rev) + *rev = entry->revision; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_set_changelist(const char *path, + const char *changelist, + svn_wc_adm_access_t *adm_access, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + const char *local_abspath; + svn_wc_context_t *wc_ctx; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), + pool)); + + SVN_ERR(svn_wc_set_changelist2(wc_ctx, local_abspath, changelist, + svn_depth_empty, NULL, + cancel_func, cancel_baton, notify_func, + notify_baton, pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + + +/*** From diff.c ***/ +/* Used to wrap svn_wc_diff_callbacks_t. */ +struct diff_callbacks_wrapper_baton { + const svn_wc_diff_callbacks_t *callbacks; + void *baton; +}; + +/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t. */ +static svn_error_t * +wrap_3to1_file_changed(svn_wc_adm_access_t *adm_access, + svn_wc_notify_state_t *contentstate, + svn_wc_notify_state_t *propstate, + svn_boolean_t *tree_conflicted, + const char *path, + const char *tmpfile1, + const char *tmpfile2, + svn_revnum_t rev1, + svn_revnum_t rev2, + const char *mimetype1, + const char *mimetype2, + const apr_array_header_t *propchanges, + apr_hash_t *originalprops, + void *diff_baton) +{ + struct diff_callbacks_wrapper_baton *b = diff_baton; + + if (tree_conflicted) + *tree_conflicted = FALSE; + + if (tmpfile2 != NULL) + SVN_ERR(b->callbacks->file_changed(adm_access, contentstate, path, + tmpfile1, tmpfile2, + rev1, rev2, mimetype1, mimetype2, + b->baton)); + if (propchanges->nelts > 0) + SVN_ERR(b->callbacks->props_changed(adm_access, propstate, path, + propchanges, originalprops, + b->baton)); + + return SVN_NO_ERROR; +} + +/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t. */ +static svn_error_t * +wrap_3to1_file_added(svn_wc_adm_access_t *adm_access, + svn_wc_notify_state_t *contentstate, + svn_wc_notify_state_t *propstate, + svn_boolean_t *tree_conflicted, + const char *path, + const char *tmpfile1, + const char *tmpfile2, + svn_revnum_t rev1, + svn_revnum_t rev2, + const char *mimetype1, + const char *mimetype2, + const apr_array_header_t *propchanges, + apr_hash_t *originalprops, + void *diff_baton) +{ + struct diff_callbacks_wrapper_baton *b = diff_baton; + + if (tree_conflicted) + *tree_conflicted = FALSE; + + SVN_ERR(b->callbacks->file_added(adm_access, contentstate, path, + tmpfile1, tmpfile2, rev1, rev2, + mimetype1, mimetype2, b->baton)); + if (propchanges->nelts > 0) + SVN_ERR(b->callbacks->props_changed(adm_access, propstate, path, + propchanges, originalprops, + b->baton)); + + return SVN_NO_ERROR; +} + +/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t. */ +static svn_error_t * +wrap_3to1_file_deleted(svn_wc_adm_access_t *adm_access, + svn_wc_notify_state_t *state, + svn_boolean_t *tree_conflicted, + const char *path, + const char *tmpfile1, + const char *tmpfile2, + const char *mimetype1, + const char *mimetype2, + apr_hash_t *originalprops, + void *diff_baton) +{ + struct diff_callbacks_wrapper_baton *b = diff_baton; + + if (tree_conflicted) + *tree_conflicted = FALSE; + + SVN_ERR_ASSERT(originalprops); + + return b->callbacks->file_deleted(adm_access, state, path, + tmpfile1, tmpfile2, mimetype1, mimetype2, + b->baton); +} + +/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t. */ +static svn_error_t * +wrap_3to1_dir_added(svn_wc_adm_access_t *adm_access, + svn_wc_notify_state_t *state, + svn_boolean_t *tree_conflicted, + const char *path, + svn_revnum_t rev, + void *diff_baton) +{ + struct diff_callbacks_wrapper_baton *b = diff_baton; + + if (tree_conflicted) + *tree_conflicted = FALSE; + + return b->callbacks->dir_added(adm_access, state, path, rev, b->baton); +} + +/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t. */ +static svn_error_t * +wrap_3to1_dir_deleted(svn_wc_adm_access_t *adm_access, + svn_wc_notify_state_t *state, + svn_boolean_t *tree_conflicted, + const char *path, + void *diff_baton) +{ + struct diff_callbacks_wrapper_baton *b = diff_baton; + + if (tree_conflicted) + *tree_conflicted = FALSE; + + return b->callbacks->dir_deleted(adm_access, state, path, b->baton); +} + +/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t. */ +static svn_error_t * +wrap_3to1_dir_props_changed(svn_wc_adm_access_t *adm_access, + svn_wc_notify_state_t *state, + svn_boolean_t *tree_conflicted, + const char *path, + const apr_array_header_t *propchanges, + apr_hash_t *originalprops, + void *diff_baton) +{ + struct diff_callbacks_wrapper_baton *b = diff_baton; + + if (tree_conflicted) + *tree_conflicted = FALSE; + + return b->callbacks->props_changed(adm_access, state, path, propchanges, + originalprops, b->baton); +} + +/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t + and svn_wc_diff_callbacks2_t. */ +static svn_error_t * +wrap_3to1or2_dir_opened(svn_wc_adm_access_t *adm_access, + svn_boolean_t *tree_conflicted, + const char *path, + svn_revnum_t rev, + void *diff_baton) +{ + if (tree_conflicted) + *tree_conflicted = FALSE; + /* Do nothing. */ + return SVN_NO_ERROR; +} + +/* An svn_wc_diff_callbacks3_t function for wrapping svn_wc_diff_callbacks_t + and svn_wc_diff_callbacks2_t. */ +static svn_error_t * +wrap_3to1or2_dir_closed(svn_wc_adm_access_t *adm_access, + svn_wc_notify_state_t *propstate, + svn_wc_notify_state_t *contentstate, + svn_boolean_t *tree_conflicted, + const char *path, + void *diff_baton) +{ + if (contentstate) + *contentstate = svn_wc_notify_state_unknown; + if (propstate) + *propstate = svn_wc_notify_state_unknown; + if (tree_conflicted) + *tree_conflicted = FALSE; + /* Do nothing. */ + return SVN_NO_ERROR; +} + +/* Used to wrap svn_diff_callbacks_t as an svn_wc_diff_callbacks3_t. */ +static struct svn_wc_diff_callbacks3_t diff_callbacks_wrapper = { + wrap_3to1_file_changed, + wrap_3to1_file_added, + wrap_3to1_file_deleted, + wrap_3to1_dir_added, + wrap_3to1_dir_deleted, + wrap_3to1_dir_props_changed, + wrap_3to1or2_dir_opened, + wrap_3to1or2_dir_closed +}; + + + +/* Used to wrap svn_wc_diff_callbacks2_t. */ +struct diff_callbacks2_wrapper_baton { + const svn_wc_diff_callbacks2_t *callbacks2; + void *baton; +}; + +/* An svn_wc_diff_callbacks3_t function for wrapping + * svn_wc_diff_callbacks2_t. */ +static svn_error_t * +wrap_3to2_file_changed(svn_wc_adm_access_t *adm_access, + svn_wc_notify_state_t *contentstate, + svn_wc_notify_state_t *propstate, + svn_boolean_t *tree_conflicted, + const char *path, + const char *tmpfile1, + const char *tmpfile2, + svn_revnum_t rev1, + svn_revnum_t rev2, + const char *mimetype1, + const char *mimetype2, + const apr_array_header_t *propchanges, + apr_hash_t *originalprops, + void *diff_baton) +{ + struct diff_callbacks2_wrapper_baton *b = diff_baton; + + if (tree_conflicted) + *tree_conflicted = FALSE; + + return b->callbacks2->file_changed(adm_access, contentstate, propstate, + path, tmpfile1, tmpfile2, + rev1, rev2, mimetype1, mimetype2, + propchanges, originalprops, b->baton); +} + +/* An svn_wc_diff_callbacks3_t function for wrapping + * svn_wc_diff_callbacks2_t. */ +static svn_error_t * +wrap_3to2_file_added(svn_wc_adm_access_t *adm_access, + svn_wc_notify_state_t *contentstate, + svn_wc_notify_state_t *propstate, + svn_boolean_t *tree_conflicted, + const char *path, + const char *tmpfile1, + const char *tmpfile2, + svn_revnum_t rev1, + svn_revnum_t rev2, + const char *mimetype1, + const char *mimetype2, + const apr_array_header_t *propchanges, + apr_hash_t *originalprops, + void *diff_baton) +{ + struct diff_callbacks2_wrapper_baton *b = diff_baton; + + if (tree_conflicted) + *tree_conflicted = FALSE; + + return b->callbacks2->file_added(adm_access, contentstate, propstate, path, + tmpfile1, tmpfile2, rev1, rev2, + mimetype1, mimetype2, propchanges, + originalprops, b->baton); +} + +/* An svn_wc_diff_callbacks3_t function for wrapping + * svn_wc_diff_callbacks2_t. */ +static svn_error_t * +wrap_3to2_file_deleted(svn_wc_adm_access_t *adm_access, + svn_wc_notify_state_t *state, + svn_boolean_t *tree_conflicted, + const char *path, + const char *tmpfile1, + const char *tmpfile2, + const char *mimetype1, + const char *mimetype2, + apr_hash_t *originalprops, + void *diff_baton) +{ + struct diff_callbacks2_wrapper_baton *b = diff_baton; + + if (tree_conflicted) + *tree_conflicted = FALSE; + + return b->callbacks2->file_deleted(adm_access, state, path, + tmpfile1, tmpfile2, mimetype1, mimetype2, + originalprops, b->baton); +} + +/* An svn_wc_diff_callbacks3_t function for wrapping + * svn_wc_diff_callbacks2_t. */ +static svn_error_t * +wrap_3to2_dir_added(svn_wc_adm_access_t *adm_access, + svn_wc_notify_state_t *state, + svn_boolean_t *tree_conflicted, + const char *path, + svn_revnum_t rev, + void *diff_baton) +{ + struct diff_callbacks2_wrapper_baton *b = diff_baton; + + if (tree_conflicted) + *tree_conflicted = FALSE; + + return b->callbacks2->dir_added(adm_access, state, path, rev, b->baton); +} + +/* An svn_wc_diff_callbacks3_t function for wrapping + * svn_wc_diff_callbacks2_t. */ +static svn_error_t * +wrap_3to2_dir_deleted(svn_wc_adm_access_t *adm_access, + svn_wc_notify_state_t *state, + svn_boolean_t *tree_conflicted, + const char *path, + void *diff_baton) +{ + struct diff_callbacks2_wrapper_baton *b = diff_baton; + + if (tree_conflicted) + *tree_conflicted = FALSE; + + return b->callbacks2->dir_deleted(adm_access, state, path, b->baton); +} + +/* An svn_wc_diff_callbacks3_t function for wrapping + * svn_wc_diff_callbacks2_t. */ +static svn_error_t * +wrap_3to2_dir_props_changed(svn_wc_adm_access_t *adm_access, + svn_wc_notify_state_t *state, + svn_boolean_t *tree_conflicted, + const char *path, + const apr_array_header_t *propchanges, + apr_hash_t *originalprops, + void *diff_baton) +{ + struct diff_callbacks2_wrapper_baton *b = diff_baton; + + if (tree_conflicted) + *tree_conflicted = FALSE; + + return b->callbacks2->dir_props_changed(adm_access, state, path, propchanges, + originalprops, b->baton); +} + +/* Used to wrap svn_diff_callbacks2_t as an svn_wc_diff_callbacks3_t. */ +static struct svn_wc_diff_callbacks3_t diff_callbacks2_wrapper = { + wrap_3to2_file_changed, + wrap_3to2_file_added, + wrap_3to2_file_deleted, + wrap_3to2_dir_added, + wrap_3to2_dir_deleted, + wrap_3to2_dir_props_changed, + wrap_3to1or2_dir_opened, + wrap_3to1or2_dir_closed +}; + + + +/* Used to wrap svn_wc_diff_callbacks3_t. */ +struct diff_callbacks3_wrapper_baton { + const svn_wc_diff_callbacks3_t *callbacks3; + svn_wc__db_t *db; + void *baton; + const char *anchor; + const char *anchor_abspath; +}; + +static svn_error_t * +wrap_4to3_file_opened(svn_boolean_t *tree_conflicted, + svn_boolean_t *skip, + const char *path, + svn_revnum_t rev, + void *diff_baton, + apr_pool_t *scratch_pool) +{ + return SVN_NO_ERROR; +} + +/* An svn_wc_diff_callbacks4_t function for wrapping + * svn_wc_diff_callbacks3_t. */ +static svn_error_t * +wrap_4to3_file_changed(svn_wc_notify_state_t *contentstate, + svn_wc_notify_state_t *propstate, + svn_boolean_t *tree_conflicted, + const char *path, + const char *tmpfile1, + const char *tmpfile2, + svn_revnum_t rev1, + svn_revnum_t rev2, + const char *mimetype1, + const char *mimetype2, + const apr_array_header_t *propchanges, + apr_hash_t *originalprops, + void *diff_baton, + apr_pool_t *scratch_pool) +{ + struct diff_callbacks3_wrapper_baton *b = diff_baton; + svn_wc_adm_access_t *adm_access; + const char *dir = svn_relpath_dirname(path, scratch_pool); + + adm_access = svn_wc__adm_retrieve_internal2( + b->db, + svn_dirent_join(b->anchor_abspath, dir, scratch_pool), + scratch_pool); + + return b->callbacks3->file_changed(adm_access, contentstate, propstate, + tree_conflicted, + svn_dirent_join(b->anchor, path, + scratch_pool), + tmpfile1, tmpfile2, + rev1, rev2, mimetype1, mimetype2, + propchanges, originalprops, b->baton); +} + +/* An svn_wc_diff_callbacks4_t function for wrapping + * svn_wc_diff_callbacks3_t. */ +static svn_error_t * +wrap_4to3_file_added(svn_wc_notify_state_t *contentstate, + svn_wc_notify_state_t *propstate, + svn_boolean_t *tree_conflicted, + const char *path, + const char *tmpfile1, + const char *tmpfile2, + svn_revnum_t rev1, + svn_revnum_t rev2, + const char *mimetype1, + const char *mimetype2, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + const apr_array_header_t *propchanges, + apr_hash_t *originalprops, + void *diff_baton, + apr_pool_t *scratch_pool) +{ + struct diff_callbacks3_wrapper_baton *b = diff_baton; + svn_wc_adm_access_t *adm_access; + const char *dir = svn_relpath_dirname(path, scratch_pool); + + adm_access = svn_wc__adm_retrieve_internal2( + b->db, + svn_dirent_join(b->anchor_abspath, dir, scratch_pool), + scratch_pool); + + return b->callbacks3->file_added(adm_access, contentstate, propstate, + tree_conflicted, + svn_dirent_join(b->anchor, path, + scratch_pool), + tmpfile1, tmpfile2, + rev1, rev2, mimetype1, mimetype2, + propchanges, originalprops, b->baton); +} + +/* An svn_wc_diff_callbacks4_t function for wrapping + * svn_wc_diff_callbacks3_t. */ +static svn_error_t * +wrap_4to3_file_deleted(svn_wc_notify_state_t *state, + svn_boolean_t *tree_conflicted, + const char *path, + const char *tmpfile1, + const char *tmpfile2, + const char *mimetype1, + const char *mimetype2, + apr_hash_t *originalprops, + void *diff_baton, + apr_pool_t *scratch_pool) +{ + struct diff_callbacks3_wrapper_baton *b = diff_baton; + svn_wc_adm_access_t *adm_access; + const char *dir = svn_relpath_dirname(path, scratch_pool); + + adm_access = svn_wc__adm_retrieve_internal2( + b->db, + svn_dirent_join(b->anchor_abspath, dir, scratch_pool), + scratch_pool); + + return b->callbacks3->file_deleted(adm_access, state, tree_conflicted, + svn_dirent_join(b->anchor, path, + scratch_pool), + tmpfile1, tmpfile2, + mimetype1, mimetype2, originalprops, + b->baton); +} + +/* An svn_wc_diff_callbacks4_t function for wrapping + * svn_wc_diff_callbacks3_t. */ +static svn_error_t * +wrap_4to3_dir_added(svn_wc_notify_state_t *state, + svn_boolean_t *tree_conflicted, + svn_boolean_t *skip, + svn_boolean_t *skip_children, + const char *path, + svn_revnum_t rev, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + void *diff_baton, + apr_pool_t *scratch_pool) +{ + struct diff_callbacks3_wrapper_baton *b = diff_baton; + svn_wc_adm_access_t *adm_access; + + adm_access = svn_wc__adm_retrieve_internal2( + b->db, + svn_dirent_join(b->anchor_abspath, path, scratch_pool), + scratch_pool); + + return b->callbacks3->dir_added(adm_access, state, tree_conflicted, + svn_dirent_join(b->anchor, path, + scratch_pool), + rev, b->baton); +} + +/* An svn_wc_diff_callbacks4_t function for wrapping + * svn_wc_diff_callbacks3_t. */ +static svn_error_t * +wrap_4to3_dir_deleted(svn_wc_notify_state_t *state, + svn_boolean_t *tree_conflicted, + const char *path, + void *diff_baton, + apr_pool_t *scratch_pool) +{ + struct diff_callbacks3_wrapper_baton *b = diff_baton; + svn_wc_adm_access_t *adm_access; + + adm_access = svn_wc__adm_retrieve_internal2( + b->db, + svn_dirent_join(b->anchor_abspath, path, scratch_pool), + scratch_pool); + + return b->callbacks3->dir_deleted(adm_access, state, tree_conflicted, + svn_dirent_join(b->anchor, path, + scratch_pool), + b->baton); +} + +/* An svn_wc_diff_callbacks4_t function for wrapping + * svn_wc_diff_callbacks3_t. */ +static svn_error_t * +wrap_4to3_dir_props_changed(svn_wc_notify_state_t *propstate, + svn_boolean_t *tree_conflicted, + const char *path, + svn_boolean_t dir_was_added, + const apr_array_header_t *propchanges, + apr_hash_t *original_props, + void *diff_baton, + apr_pool_t *scratch_pool) +{ + struct diff_callbacks3_wrapper_baton *b = diff_baton; + svn_wc_adm_access_t *adm_access; + + adm_access = svn_wc__adm_retrieve_internal2( + b->db, + svn_dirent_join(b->anchor_abspath, path, scratch_pool), + scratch_pool); + + return b->callbacks3->dir_props_changed(adm_access, propstate, + tree_conflicted, + svn_dirent_join(b->anchor, path, + scratch_pool), + propchanges, original_props, + b->baton); +} + +/* An svn_wc_diff_callbacks4_t function for wrapping + * svn_wc_diff_callbacks3_t. */ +static svn_error_t * +wrap_4to3_dir_opened(svn_boolean_t *tree_conflicted, + svn_boolean_t *skip, + svn_boolean_t *skip_children, + const char *path, + svn_revnum_t rev, + void *diff_baton, + apr_pool_t *scratch_pool) +{ + struct diff_callbacks3_wrapper_baton *b = diff_baton; + svn_wc_adm_access_t *adm_access; + + adm_access = svn_wc__adm_retrieve_internal2( + b->db, + svn_dirent_join(b->anchor_abspath, path, scratch_pool), + scratch_pool); + if (skip_children) + *skip_children = FALSE; + + return b->callbacks3->dir_opened(adm_access, tree_conflicted, + svn_dirent_join(b->anchor, path, + scratch_pool), + rev, b->baton); +} + +/* An svn_wc_diff_callbacks4_t function for wrapping + * svn_wc_diff_callbacks3_t. */ +static svn_error_t * +wrap_4to3_dir_closed(svn_wc_notify_state_t *contentstate, + svn_wc_notify_state_t *propstate, + svn_boolean_t *tree_conflicted, + const char *path, + svn_boolean_t dir_was_added, + void *diff_baton, + apr_pool_t *scratch_pool) +{ + struct diff_callbacks3_wrapper_baton *b = diff_baton; + svn_wc_adm_access_t *adm_access; + + adm_access = svn_wc__adm_retrieve_internal2( + b->db, + svn_dirent_join(b->anchor_abspath, path, scratch_pool), + scratch_pool); + + return b->callbacks3->dir_closed(adm_access, contentstate, propstate, + tree_conflicted, + svn_dirent_join(b->anchor, path, + scratch_pool), + b->baton); +} + + +/* Used to wrap svn_diff_callbacks3_t as an svn_wc_diff_callbacks4_t. */ +static struct svn_wc_diff_callbacks4_t diff_callbacks3_wrapper = { + wrap_4to3_file_opened, + wrap_4to3_file_changed, + wrap_4to3_file_added, + wrap_4to3_file_deleted, + wrap_4to3_dir_deleted, + wrap_4to3_dir_opened, + wrap_4to3_dir_added, + wrap_4to3_dir_props_changed, + wrap_4to3_dir_closed +}; + + +svn_error_t * +svn_wc_get_diff_editor6(const svn_delta_editor_t **editor, + void **edit_baton, + svn_wc_context_t *wc_ctx, + const char *anchor_abspath, + const char *target, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t show_copies_as_adds, + svn_boolean_t use_git_diff_format, + svn_boolean_t use_text_base, + svn_boolean_t reverse_order, + svn_boolean_t server_performs_filtering, + const apr_array_header_t *changelist_filter, + const svn_wc_diff_callbacks4_t *callbacks, + void *callback_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__get_diff_editor(editor, edit_baton, + wc_ctx, + anchor_abspath, target, + depth, + ignore_ancestry, show_copies_as_adds, + use_git_diff_format, use_text_base, + reverse_order, server_performs_filtering, + changelist_filter, + callbacks, callback_baton, + cancel_func, cancel_baton, + result_pool, scratch_pool)); +} + + +svn_error_t * +svn_wc_get_diff_editor5(svn_wc_adm_access_t *anchor, + const char *target, + const svn_wc_diff_callbacks3_t *callbacks, + void *callback_baton, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t use_text_base, + svn_boolean_t reverse_order, + svn_cancel_func_t cancel_func, + void *cancel_baton, + const apr_array_header_t *changelist_filter, + const svn_delta_editor_t **editor, + void **edit_baton, + apr_pool_t *pool) +{ + struct diff_callbacks3_wrapper_baton *b = apr_palloc(pool, sizeof(*b)); + svn_wc_context_t *wc_ctx; + svn_wc__db_t *db = svn_wc__adm_get_db(anchor); + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, db, pool)); + + b->callbacks3 = callbacks; + b->baton = callback_baton; + b->db = db; + b->anchor = svn_wc_adm_access_path(anchor); + b->anchor_abspath = svn_wc__adm_access_abspath(anchor); + + SVN_ERR(svn_wc_get_diff_editor6(editor, + edit_baton, + wc_ctx, + b->anchor_abspath, + target, + depth, + ignore_ancestry, + FALSE, + FALSE, + use_text_base, + reverse_order, + FALSE, + changelist_filter, + &diff_callbacks3_wrapper, + b, + cancel_func, + cancel_baton, + pool, + pool)); + + /* Can't destroy wc_ctx. It is used by the diff editor */ + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_get_diff_editor4(svn_wc_adm_access_t *anchor, + const char *target, + const svn_wc_diff_callbacks2_t *callbacks, + void *callback_baton, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t use_text_base, + svn_boolean_t reverse_order, + svn_cancel_func_t cancel_func, + void *cancel_baton, + const apr_array_header_t *changelist_filter, + const svn_delta_editor_t **editor, + void **edit_baton, + apr_pool_t *pool) +{ + struct diff_callbacks2_wrapper_baton *b = apr_palloc(pool, sizeof(*b)); + b->callbacks2 = callbacks; + b->baton = callback_baton; + return svn_wc_get_diff_editor5(anchor, + target, + &diff_callbacks2_wrapper, + b, + depth, + ignore_ancestry, + use_text_base, + reverse_order, + cancel_func, + cancel_baton, + changelist_filter, + editor, + edit_baton, + pool); +} + +svn_error_t * +svn_wc_get_diff_editor3(svn_wc_adm_access_t *anchor, + const char *target, + const svn_wc_diff_callbacks2_t *callbacks, + void *callback_baton, + svn_boolean_t recurse, + svn_boolean_t ignore_ancestry, + svn_boolean_t use_text_base, + svn_boolean_t reverse_order, + svn_cancel_func_t cancel_func, + void *cancel_baton, + const svn_delta_editor_t **editor, + void **edit_baton, + apr_pool_t *pool) +{ + return svn_wc_get_diff_editor4(anchor, + target, + callbacks, + callback_baton, + SVN_DEPTH_INFINITY_OR_FILES(recurse), + ignore_ancestry, + use_text_base, + reverse_order, + cancel_func, + cancel_baton, + NULL, + editor, + edit_baton, + pool); +} + +svn_error_t * +svn_wc_get_diff_editor2(svn_wc_adm_access_t *anchor, + const char *target, + const svn_wc_diff_callbacks_t *callbacks, + void *callback_baton, + svn_boolean_t recurse, + svn_boolean_t ignore_ancestry, + svn_boolean_t use_text_base, + svn_boolean_t reverse_order, + svn_cancel_func_t cancel_func, + void *cancel_baton, + const svn_delta_editor_t **editor, + void **edit_baton, + apr_pool_t *pool) +{ + struct diff_callbacks_wrapper_baton *b = apr_palloc(pool, sizeof(*b)); + b->callbacks = callbacks; + b->baton = callback_baton; + return svn_wc_get_diff_editor5(anchor, target, &diff_callbacks_wrapper, b, + SVN_DEPTH_INFINITY_OR_FILES(recurse), + ignore_ancestry, use_text_base, + reverse_order, cancel_func, cancel_baton, + NULL, editor, edit_baton, pool); +} + +svn_error_t * +svn_wc_get_diff_editor(svn_wc_adm_access_t *anchor, + const char *target, + const svn_wc_diff_callbacks_t *callbacks, + void *callback_baton, + svn_boolean_t recurse, + svn_boolean_t use_text_base, + svn_boolean_t reverse_order, + svn_cancel_func_t cancel_func, + void *cancel_baton, + const svn_delta_editor_t **editor, + void **edit_baton, + apr_pool_t *pool) +{ + return svn_wc_get_diff_editor2(anchor, target, callbacks, callback_baton, + recurse, FALSE, use_text_base, reverse_order, + cancel_func, cancel_baton, + editor, edit_baton, pool); +} + +svn_error_t * +svn_wc_diff5(svn_wc_adm_access_t *anchor, + const char *target, + const svn_wc_diff_callbacks3_t *callbacks, + void *callback_baton, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + const apr_array_header_t *changelist_filter, + apr_pool_t *pool) +{ + struct diff_callbacks3_wrapper_baton *b = apr_palloc(pool, sizeof(*b)); + svn_wc_context_t *wc_ctx; + svn_wc__db_t *db = svn_wc__adm_get_db(anchor); + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, db, pool)); + + b->callbacks3 = callbacks; + b->baton = callback_baton; + b->anchor = svn_wc_adm_access_path(anchor); + b->anchor_abspath = svn_wc__adm_access_abspath(anchor); + + SVN_ERR(svn_wc_diff6(wc_ctx, + svn_dirent_join(b->anchor_abspath, target, pool), + &diff_callbacks3_wrapper, + b, + depth, + ignore_ancestry, + FALSE, + FALSE, + changelist_filter, + NULL, NULL, + pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_diff4(svn_wc_adm_access_t *anchor, + const char *target, + const svn_wc_diff_callbacks2_t *callbacks, + void *callback_baton, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + const apr_array_header_t *changelist_filter, + apr_pool_t *pool) +{ + struct diff_callbacks2_wrapper_baton *b = apr_palloc(pool, sizeof(*b)); + b->callbacks2 = callbacks; + b->baton = callback_baton; + + return svn_wc_diff5(anchor, target, &diff_callbacks2_wrapper, b, + depth, ignore_ancestry, changelist_filter, pool); +} + +svn_error_t * +svn_wc_diff3(svn_wc_adm_access_t *anchor, + const char *target, + const svn_wc_diff_callbacks2_t *callbacks, + void *callback_baton, + svn_boolean_t recurse, + svn_boolean_t ignore_ancestry, + apr_pool_t *pool) +{ + return svn_wc_diff4(anchor, target, callbacks, callback_baton, + SVN_DEPTH_INFINITY_OR_FILES(recurse), ignore_ancestry, + NULL, pool); +} + +svn_error_t * +svn_wc_diff2(svn_wc_adm_access_t *anchor, + const char *target, + const svn_wc_diff_callbacks_t *callbacks, + void *callback_baton, + svn_boolean_t recurse, + svn_boolean_t ignore_ancestry, + apr_pool_t *pool) +{ + struct diff_callbacks_wrapper_baton *b = apr_pcalloc(pool, sizeof(*b)); + b->callbacks = callbacks; + b->baton = callback_baton; + return svn_wc_diff5(anchor, target, &diff_callbacks_wrapper, b, + SVN_DEPTH_INFINITY_OR_FILES(recurse), ignore_ancestry, + NULL, pool); +} + +svn_error_t * +svn_wc_diff(svn_wc_adm_access_t *anchor, + const char *target, + const svn_wc_diff_callbacks_t *callbacks, + void *callback_baton, + svn_boolean_t recurse, + apr_pool_t *pool) +{ + return svn_wc_diff2(anchor, target, callbacks, callback_baton, + recurse, FALSE, pool); +} + +/*** From entries.c ***/ +svn_error_t * +svn_wc_walk_entries2(const char *path, + svn_wc_adm_access_t *adm_access, + const svn_wc_entry_callbacks_t *walk_callbacks, + void *walk_baton, + svn_boolean_t show_hidden, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_wc_entry_callbacks2_t walk_cb2 = { 0 }; + walk_cb2.found_entry = walk_callbacks->found_entry; + walk_cb2.handle_error = svn_wc__walker_default_error_handler; + return svn_wc_walk_entries3(path, adm_access, + &walk_cb2, walk_baton, svn_depth_infinity, + show_hidden, cancel_func, cancel_baton, pool); +} + +svn_error_t * +svn_wc_walk_entries(const char *path, + svn_wc_adm_access_t *adm_access, + const svn_wc_entry_callbacks_t *walk_callbacks, + void *walk_baton, + svn_boolean_t show_hidden, + apr_pool_t *pool) +{ + return svn_wc_walk_entries2(path, adm_access, walk_callbacks, + walk_baton, show_hidden, NULL, NULL, + pool); +} + +svn_error_t * +svn_wc_mark_missing_deleted(const char *path, + svn_wc_adm_access_t *parent, + apr_pool_t *pool) +{ + /* With a single DB a node will never be missing */ + return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL, + _("Unexpectedly found '%s': " + "path is marked 'missing'"), + svn_dirent_local_style(path, pool)); +} + + +/*** From props.c ***/ +svn_error_t * +svn_wc_parse_externals_description2(apr_array_header_t **externals_p, + const char *parent_directory, + const char *desc, + apr_pool_t *pool) +{ + apr_array_header_t *list; + apr_pool_t *subpool = svn_pool_create(pool); + + SVN_ERR(svn_wc_parse_externals_description3(externals_p ? &list : NULL, + parent_directory, desc, + TRUE, subpool)); + + if (externals_p) + { + int i; + + *externals_p = apr_array_make(pool, list->nelts, + sizeof(svn_wc_external_item_t *)); + for (i = 0; i < list->nelts; i++) + { + svn_wc_external_item2_t *item2 = APR_ARRAY_IDX(list, i, + svn_wc_external_item2_t *); + svn_wc_external_item_t *item = apr_palloc(pool, sizeof (*item)); + + if (item2->target_dir) + item->target_dir = apr_pstrdup(pool, item2->target_dir); + if (item2->url) + item->url = apr_pstrdup(pool, item2->url); + item->revision = item2->revision; + + APR_ARRAY_PUSH(*externals_p, svn_wc_external_item_t *) = item; + } + } + + svn_pool_destroy(subpool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_parse_externals_description(apr_hash_t **externals_p, + const char *parent_directory, + const char *desc, + apr_pool_t *pool) +{ + apr_array_header_t *list; + + SVN_ERR(svn_wc_parse_externals_description2(externals_p ? &list : NULL, + parent_directory, desc, pool)); + + /* Store all of the items into the hash if that was requested. */ + if (externals_p) + { + int i; + + *externals_p = apr_hash_make(pool); + for (i = 0; i < list->nelts; i++) + { + svn_wc_external_item_t *item; + item = APR_ARRAY_IDX(list, i, svn_wc_external_item_t *); + + svn_hash_sets(*externals_p, item->target_dir, item); + } + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_prop_set3(const char *name, + const svn_string_t *value, + const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t skip_checks, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + const char *local_abspath; + svn_error_t *err; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), + pool)); + + err = svn_wc_prop_set4(wc_ctx, local_abspath, + name, value, + svn_depth_empty, + skip_checks, NULL /* changelist_filter */, + NULL, NULL /* cancellation */, + notify_func, notify_baton, + pool); + + if (err && err->apr_err == SVN_ERR_WC_INVALID_SCHEDULE) + svn_error_clear(err); + else + SVN_ERR(err); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_prop_set2(const char *name, + const svn_string_t *value, + const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t skip_checks, + apr_pool_t *pool) +{ + return svn_wc_prop_set3(name, value, path, adm_access, skip_checks, + NULL, NULL, pool); +} + +svn_error_t * +svn_wc_prop_set(const char *name, + const svn_string_t *value, + const char *path, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + return svn_wc_prop_set2(name, value, path, adm_access, FALSE, pool); +} + +svn_error_t * +svn_wc_prop_list(apr_hash_t **props, + const char *path, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + const char *local_abspath; + svn_error_t *err; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), pool)); + + err = svn_wc_prop_list2(props, wc_ctx, local_abspath, pool, pool); + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + *props = apr_hash_make(pool); + svn_error_clear(err); + err = NULL; + } + + return svn_error_compose_create(err, svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_prop_get(const svn_string_t **value, + const char *name, + const char *path, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + + svn_wc_context_t *wc_ctx; + const char *local_abspath; + svn_error_t *err; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), pool)); + + err = svn_wc_prop_get2(value, wc_ctx, local_abspath, name, pool, pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + *value = NULL; + svn_error_clear(err); + err = NULL; + } + + return svn_error_compose_create(err, svn_wc_context_destroy(wc_ctx)); +} + +/* baton for conflict_func_1to2_wrapper */ +struct conflict_func_1to2_baton +{ + svn_wc_conflict_resolver_func_t inner_func; + void *inner_baton; +}; + + +/* Implements svn_wc_conflict_resolver_func2_t */ +static svn_error_t * +conflict_func_1to2_wrapper(svn_wc_conflict_result_t **result, + const svn_wc_conflict_description2_t *conflict, + void *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct conflict_func_1to2_baton *btn = baton; + svn_wc_conflict_description_t *cd = svn_wc__cd2_to_cd(conflict, + scratch_pool); + + return svn_error_trace(btn->inner_func(result, cd, btn->inner_baton, + result_pool)); +} + +svn_error_t * +svn_wc_merge_props2(svn_wc_notify_state_t *state, + const char *path, + svn_wc_adm_access_t *adm_access, + apr_hash_t *baseprops, + const apr_array_header_t *propchanges, + svn_boolean_t base_merge, + svn_boolean_t dry_run, + svn_wc_conflict_resolver_func_t conflict_func, + void *conflict_baton, + apr_pool_t *scratch_pool) +{ + const char *local_abspath; + svn_error_t *err; + svn_wc_context_t *wc_ctx; + struct conflict_func_1to2_baton conflict_wrapper; + + if (base_merge && !dry_run) + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + U_("base_merge=TRUE is no longer supported; " + "see notes/api-errata/1.7/wc006.txt")); + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool)); + + conflict_wrapper.inner_func = conflict_func; + conflict_wrapper.inner_baton = conflict_baton; + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, + svn_wc__adm_get_db(adm_access), + scratch_pool)); + + err = svn_wc_merge_props3(state, + wc_ctx, + local_abspath, + NULL /* left_version */, + NULL /* right_version */, + baseprops, + propchanges, + dry_run, + conflict_func ? conflict_func_1to2_wrapper + : NULL, + &conflict_wrapper, + NULL, NULL, + scratch_pool); + + if (err) + switch(err->apr_err) + { + case SVN_ERR_WC_PATH_NOT_FOUND: + case SVN_ERR_WC_PATH_UNEXPECTED_STATUS: + err->apr_err = SVN_ERR_UNVERSIONED_RESOURCE; + break; + } + return svn_error_trace( + svn_error_compose_create(err, + svn_wc_context_destroy(wc_ctx))); +} + +svn_error_t * +svn_wc_merge_props(svn_wc_notify_state_t *state, + const char *path, + svn_wc_adm_access_t *adm_access, + apr_hash_t *baseprops, + const apr_array_header_t *propchanges, + svn_boolean_t base_merge, + svn_boolean_t dry_run, + apr_pool_t *pool) +{ + return svn_wc_merge_props2(state, path, adm_access, baseprops, propchanges, + base_merge, dry_run, NULL, NULL, pool); +} + + +svn_error_t * +svn_wc_merge_prop_diffs(svn_wc_notify_state_t *state, + const char *path, + svn_wc_adm_access_t *adm_access, + const apr_array_header_t *propchanges, + svn_boolean_t base_merge, + svn_boolean_t dry_run, + apr_pool_t *pool) +{ + /* NOTE: Here, we use implementation knowledge. The public + svn_wc_merge_props2 doesn't allow NULL as baseprops argument, but we know + that it works. */ + return svn_wc_merge_props2(state, path, adm_access, NULL, propchanges, + base_merge, dry_run, NULL, NULL, pool); +} + +svn_error_t * +svn_wc_get_prop_diffs(apr_array_header_t **propchanges, + apr_hash_t **original_props, + const char *path, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + const char *local_abspath; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), pool)); + + SVN_ERR(svn_wc_get_prop_diffs2(propchanges, original_props, wc_ctx, + local_abspath, pool, pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + + +svn_error_t * +svn_wc_props_modified_p(svn_boolean_t *modified_p, + const char *path, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + const char *local_abspath; + svn_error_t *err; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), pool)); + + err = svn_wc_props_modified_p2(modified_p, + wc_ctx, + local_abspath, + pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + *modified_p = FALSE; + } + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + + +/*** From status.c ***/ + +struct status4_wrapper_baton +{ + svn_wc_status_func3_t old_func; + void *old_baton; + const char *anchor_abspath; + const char *anchor_relpath; + svn_wc_context_t *wc_ctx; +}; + +/* */ +static svn_error_t * +status4_wrapper_func(void *baton, + const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + struct status4_wrapper_baton *swb = baton; + svn_wc_status2_t *dup; + const char *path = local_abspath; + + SVN_ERR(svn_wc__status2_from_3(&dup, status, swb->wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + + if (swb->anchor_abspath != NULL) + { + path = svn_dirent_join( + swb->anchor_relpath, + svn_dirent_skip_ancestor(swb->anchor_abspath, local_abspath), + scratch_pool); + } + + return (*swb->old_func)(swb->old_baton, path, dup, scratch_pool); +} + + +svn_error_t * +svn_wc_get_status_editor5(const svn_delta_editor_t **editor, + void **edit_baton, + void **set_locks_baton, + svn_revnum_t *edit_revision, + svn_wc_context_t *wc_ctx, + const char *anchor_abspath, + const char *target_basename, + svn_depth_t depth, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + svn_boolean_t depth_as_sticky, + svn_boolean_t server_performs_filtering, + const apr_array_header_t *ignore_patterns, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__get_status_editor(editor, edit_baton, + set_locks_baton, + edit_revision, + wc_ctx, + anchor_abspath, + target_basename, + depth, + get_all, no_ignore, + depth_as_sticky, + server_performs_filtering, + ignore_patterns, + status_func, status_baton, + cancel_func, cancel_baton, + result_pool, + scratch_pool)); +} + + +svn_error_t * +svn_wc_get_status_editor4(const svn_delta_editor_t **editor, + void **edit_baton, + void **set_locks_baton, + svn_revnum_t *edit_revision, + svn_wc_adm_access_t *anchor, + const char *target, + svn_depth_t depth, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + const apr_array_header_t *ignore_patterns, + svn_wc_status_func3_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + struct status4_wrapper_baton *swb = apr_palloc(pool, sizeof(*swb)); + svn_wc__db_t *wc_db; + svn_wc_context_t *wc_ctx; + const char *anchor_abspath; + + swb->old_func = status_func; + swb->old_baton = status_baton; + + wc_db = svn_wc__adm_get_db(anchor); + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + wc_db, pool)); + + swb->wc_ctx = wc_ctx; + + anchor_abspath = svn_wc__adm_access_abspath(anchor); + + if (!svn_dirent_is_absolute(svn_wc_adm_access_path(anchor))) + { + swb->anchor_abspath = anchor_abspath; + swb->anchor_relpath = svn_wc_adm_access_path(anchor); + } + else + { + swb->anchor_abspath = NULL; + swb->anchor_relpath = NULL; + } + + /* Before subversion 1.7 status always handled depth as sticky. 1.7 made + the output of svn status by default match the result of what would be + updated by a similar svn update. (Following the documentation) */ + + SVN_ERR(svn_wc_get_status_editor5(editor, edit_baton, set_locks_baton, + edit_revision, wc_ctx, anchor_abspath, + target, depth, get_all, + no_ignore, + (depth != svn_depth_unknown) /*as_sticky*/, + FALSE /* server_performs_filtering */, + ignore_patterns, + status4_wrapper_func, swb, + cancel_func, cancel_baton, + pool, pool)); + + if (traversal_info) + { + const char *local_path = svn_wc_adm_access_path(anchor); + const char *local_abspath = anchor_abspath; + if (*target) + { + local_path = svn_dirent_join(local_path, target, pool); + local_abspath = svn_dirent_join(local_abspath, target, pool); + } + + SVN_ERR(gather_traversal_info(wc_ctx, local_abspath, local_path, depth, + traversal_info, TRUE, TRUE, + pool)); + } + + /* We can't destroy wc_ctx here, because the editor needs it while it's + driven. */ + return SVN_NO_ERROR; +} + +struct status_editor3_compat_baton +{ + svn_wc_status_func2_t old_func; + void *old_baton; +}; + +/* */ +static svn_error_t * +status_editor3_compat_func(void *baton, + const char *path, + svn_wc_status2_t *status, + apr_pool_t *pool) +{ + struct status_editor3_compat_baton *secb = baton; + + secb->old_func(secb->old_baton, path, status); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_get_status_editor3(const svn_delta_editor_t **editor, + void **edit_baton, + void **set_locks_baton, + svn_revnum_t *edit_revision, + svn_wc_adm_access_t *anchor, + const char *target, + svn_depth_t depth, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + const apr_array_header_t *ignore_patterns, + svn_wc_status_func2_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + /* This baton must live beyond this function. Alloc on heap. */ + struct status_editor3_compat_baton *secb = apr_palloc(pool, sizeof(*secb)); + + secb->old_func = status_func; + secb->old_baton = status_baton; + + return svn_wc_get_status_editor4(editor, edit_baton, set_locks_baton, + edit_revision, anchor, target, depth, + get_all, no_ignore, ignore_patterns, + status_editor3_compat_func, secb, + cancel_func, cancel_baton, traversal_info, + pool); +} + +svn_error_t * +svn_wc_get_status_editor2(const svn_delta_editor_t **editor, + void **edit_baton, + void **set_locks_baton, + svn_revnum_t *edit_revision, + svn_wc_adm_access_t *anchor, + const char *target, + apr_hash_t *config, + svn_boolean_t recurse, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + svn_wc_status_func2_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + apr_array_header_t *ignores; + + SVN_ERR(svn_wc_get_default_ignores(&ignores, config, pool)); + return svn_wc_get_status_editor3(editor, + edit_baton, + set_locks_baton, + edit_revision, + anchor, + target, + SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse), + get_all, + no_ignore, + ignores, + status_func, + status_baton, + cancel_func, + cancel_baton, + traversal_info, + pool); +} + + +/* Helpers for deprecated svn_wc_status_editor(), of type + svn_wc_status_func2_t. */ +struct old_status_func_cb_baton +{ + svn_wc_status_func_t original_func; + void *original_baton; +}; + +/* */ +static void old_status_func_cb(void *baton, + const char *path, + svn_wc_status2_t *status) +{ + struct old_status_func_cb_baton *b = baton; + svn_wc_status_t *stat = (svn_wc_status_t *) status; + + b->original_func(b->original_baton, path, stat); +} + +svn_error_t * +svn_wc_get_status_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_revnum_t *edit_revision, + svn_wc_adm_access_t *anchor, + const char *target, + apr_hash_t *config, + svn_boolean_t recurse, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + svn_wc_status_func_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + struct old_status_func_cb_baton *b = apr_pcalloc(pool, sizeof(*b)); + apr_array_header_t *ignores; + b->original_func = status_func; + b->original_baton = status_baton; + SVN_ERR(svn_wc_get_default_ignores(&ignores, config, pool)); + return svn_wc_get_status_editor3(editor, edit_baton, NULL, edit_revision, + anchor, target, + SVN_DEPTH_INFINITY_OR_IMMEDIATES(recurse), + get_all, no_ignore, ignores, + old_status_func_cb, b, + cancel_func, cancel_baton, + traversal_info, pool); +} + +svn_error_t * +svn_wc_status(svn_wc_status_t **status, + const char *path, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + svn_wc_status2_t *stat2; + + SVN_ERR(svn_wc_status2(&stat2, path, adm_access, pool)); + *status = (svn_wc_status_t *) stat2; + return SVN_NO_ERROR; +} + + +static svn_wc_conflict_description_t * +conflict_description_dup(const svn_wc_conflict_description_t *conflict, + apr_pool_t *pool) +{ + svn_wc_conflict_description_t *new_conflict; + + new_conflict = apr_pcalloc(pool, sizeof(*new_conflict)); + + /* Shallow copy all members. */ + *new_conflict = *conflict; + + if (conflict->path) + new_conflict->path = apr_pstrdup(pool, conflict->path); + if (conflict->property_name) + new_conflict->property_name = apr_pstrdup(pool, conflict->property_name); + if (conflict->mime_type) + new_conflict->mime_type = apr_pstrdup(pool, conflict->mime_type); + /* NOTE: We cannot make a deep copy of adm_access. */ + if (conflict->base_file) + new_conflict->base_file = apr_pstrdup(pool, conflict->base_file); + if (conflict->their_file) + new_conflict->their_file = apr_pstrdup(pool, conflict->their_file); + if (conflict->my_file) + new_conflict->my_file = apr_pstrdup(pool, conflict->my_file); + if (conflict->merged_file) + new_conflict->merged_file = apr_pstrdup(pool, conflict->merged_file); + if (conflict->src_left_version) + new_conflict->src_left_version = + svn_wc_conflict_version_dup(conflict->src_left_version, pool); + if (conflict->src_right_version) + new_conflict->src_right_version = + svn_wc_conflict_version_dup(conflict->src_right_version, pool); + + return new_conflict; +} + + +svn_wc_status2_t * +svn_wc_dup_status2(const svn_wc_status2_t *orig_stat, + apr_pool_t *pool) +{ + svn_wc_status2_t *new_stat = apr_palloc(pool, sizeof(*new_stat)); + + /* Shallow copy all members. */ + *new_stat = *orig_stat; + + /* Now go back and dup the deep items into this pool. */ + if (orig_stat->entry) + new_stat->entry = svn_wc_entry_dup(orig_stat->entry, pool); + + if (orig_stat->repos_lock) + new_stat->repos_lock = svn_lock_dup(orig_stat->repos_lock, pool); + + if (orig_stat->url) + new_stat->url = apr_pstrdup(pool, orig_stat->url); + + if (orig_stat->ood_last_cmt_author) + new_stat->ood_last_cmt_author + = apr_pstrdup(pool, orig_stat->ood_last_cmt_author); + + if (orig_stat->tree_conflict) + new_stat->tree_conflict + = conflict_description_dup(orig_stat->tree_conflict, pool); + + /* Return the new hotness. */ + return new_stat; +} + +svn_wc_status_t * +svn_wc_dup_status(const svn_wc_status_t *orig_stat, + apr_pool_t *pool) +{ + svn_wc_status_t *new_stat = apr_palloc(pool, sizeof(*new_stat)); + + /* Shallow copy all members. */ + *new_stat = *orig_stat; + + /* Now go back and dup the deep item into this pool. */ + if (orig_stat->entry) + new_stat->entry = svn_wc_entry_dup(orig_stat->entry, pool); + + /* Return the new hotness. */ + return new_stat; +} + +svn_error_t * +svn_wc_get_ignores(apr_array_header_t **patterns, + apr_hash_t *config, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + const char *local_abspath; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, + svn_wc_adm_access_path(adm_access), pool)); + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), + pool)); + + SVN_ERR(svn_wc_get_ignores2(patterns, wc_ctx, local_abspath, config, pool, + pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_status2(svn_wc_status2_t **status, + const char *path, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + const char *local_abspath; + svn_wc_context_t *wc_ctx; + svn_wc_status3_t *stat3; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), + pool)); + + SVN_ERR(svn_wc_status3(&stat3, wc_ctx, local_abspath, pool, pool)); + SVN_ERR(svn_wc__status2_from_3(status, stat3, wc_ctx, local_abspath, + pool, pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + + +/*** From update_editor.c ***/ + +svn_error_t * +svn_wc_add_repos_file3(const char *dst_path, + svn_wc_adm_access_t *adm_access, + svn_stream_t *new_base_contents, + svn_stream_t *new_contents, + apr_hash_t *new_base_props, + apr_hash_t *new_props, + const char *copyfrom_url, + svn_revnum_t copyfrom_rev, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + const char *local_abspath; + svn_wc_context_t *wc_ctx; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, dst_path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), + pool)); + + SVN_ERR(svn_wc_add_repos_file4(wc_ctx, + local_abspath, + new_base_contents, + new_contents, + new_base_props, + new_props, + copyfrom_url, + copyfrom_rev, + cancel_func, cancel_baton, + pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_add_repos_file2(const char *dst_path, + svn_wc_adm_access_t *adm_access, + const char *new_text_base_path, + const char *new_text_path, + apr_hash_t *new_base_props, + apr_hash_t *new_props, + const char *copyfrom_url, + svn_revnum_t copyfrom_rev, + apr_pool_t *pool) +{ + svn_stream_t *new_base_contents; + svn_stream_t *new_contents = NULL; + + SVN_ERR(svn_stream_open_readonly(&new_base_contents, new_text_base_path, + pool, pool)); + + if (new_text_path) + { + /* NOTE: the specified path may *not* be under version control. + It is most likely sitting in .svn/tmp/. Thus, we cannot use the + typical WC functions to access "special", "keywords" or "EOL" + information. We need to look at the properties given to us. */ + + /* If the new file is special, then we can simply open the given + contents since it is already in normal form. */ + if (svn_hash_gets(new_props, SVN_PROP_SPECIAL) != NULL) + { + SVN_ERR(svn_stream_open_readonly(&new_contents, new_text_path, + pool, pool)); + } + else + { + /* The new text contents need to be detrans'd into normal form. */ + svn_subst_eol_style_t eol_style; + const char *eol_str; + apr_hash_t *keywords = NULL; + svn_string_t *list; + + list = svn_hash_gets(new_props, SVN_PROP_KEYWORDS); + if (list != NULL) + { + /* Since we are detranslating, all of the keyword values + can be "". */ + SVN_ERR(svn_subst_build_keywords2(&keywords, + list->data, + "", "", 0, "", + pool)); + if (apr_hash_count(keywords) == 0) + keywords = NULL; + } + + svn_subst_eol_style_from_value(&eol_style, &eol_str, + svn_hash_gets(new_props, + SVN_PROP_EOL_STYLE)); + + if (svn_subst_translation_required(eol_style, eol_str, keywords, + FALSE, FALSE)) + { + SVN_ERR(svn_subst_stream_detranslated(&new_contents, + new_text_path, + eol_style, eol_str, + FALSE, + keywords, + FALSE, + pool)); + } + else + { + SVN_ERR(svn_stream_open_readonly(&new_contents, new_text_path, + pool, pool)); + } + } + } + + SVN_ERR(svn_wc_add_repos_file3(dst_path, adm_access, + new_base_contents, new_contents, + new_base_props, new_props, + copyfrom_url, copyfrom_rev, + NULL, NULL, NULL, NULL, + pool)); + + /* The API contract states that the text files will be removed upon + successful completion. add_repos_file3() does not remove the files + since it only has streams on them. Toss 'em now. */ + svn_error_clear(svn_io_remove_file(new_text_base_path, pool)); + if (new_text_path) + svn_error_clear(svn_io_remove_file(new_text_path, pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_add_repos_file(const char *dst_path, + svn_wc_adm_access_t *adm_access, + const char *new_text_path, + apr_hash_t *new_props, + const char *copyfrom_url, + svn_revnum_t copyfrom_rev, + apr_pool_t *pool) +{ + return svn_wc_add_repos_file2(dst_path, adm_access, + new_text_path, NULL, + new_props, NULL, + copyfrom_url, copyfrom_rev, + pool); +} + +svn_error_t * +svn_wc_get_actual_target(const char *path, + const char **anchor, + const char **target, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + + SVN_ERR(svn_wc_context_create(&wc_ctx, NULL, pool, pool)); + SVN_ERR(svn_wc_get_actual_target2(anchor, target, wc_ctx, path, pool, pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +/* This function has no internal variant as its behavior on switched + non-directories is not what you would expect. But this happens to + be the legacy behavior of this function. */ +svn_error_t * +svn_wc_is_wc_root2(svn_boolean_t *wc_root, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_boolean_t is_root; + svn_boolean_t is_switched; + svn_node_kind_t kind; + svn_error_t *err; + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + err = svn_wc__db_is_switched(&is_root, &is_switched, &kind, + wc_ctx->db, local_abspath, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND && + err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) + return svn_error_trace(err); + + return svn_error_create(SVN_ERR_ENTRY_NOT_FOUND, err, err->message); + } + + *wc_root = is_root || (kind == svn_node_dir && is_switched); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_is_wc_root(svn_boolean_t *wc_root, + const char *path, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + const char *local_abspath; + svn_error_t *err; + + /* Subversion <= 1.6 said that '.' or a drive root is a WC root. */ + if (svn_path_is_empty(path) || svn_dirent_is_root(path, strlen(path))) + { + *wc_root = TRUE; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), + pool)); + + err = svn_wc_is_wc_root2(wc_root, wc_ctx, local_abspath, pool); + + if (err + && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY + || err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)) + { + /* Subversion <= 1.6 said that an unversioned path is a WC root. */ + svn_error_clear(err); + *wc_root = TRUE; + } + else + SVN_ERR(err); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + + +svn_error_t * +svn_wc_get_update_editor4(const svn_delta_editor_t **editor, + void **edit_baton, + svn_revnum_t *target_revision, + svn_wc_context_t *wc_ctx, + const char *anchor_abspath, + const char *target_basename, + svn_boolean_t use_commit_times, + svn_depth_t depth, + svn_boolean_t depth_is_sticky, + svn_boolean_t allow_unver_obstructions, + svn_boolean_t adds_as_modification, + svn_boolean_t server_performs_filtering, + svn_boolean_t clean_checkout, + const char *diff3_cmd, + const apr_array_header_t *preserved_exts, + svn_wc_dirents_func_t fetch_dirents_func, + void *fetch_dirents_baton, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_wc_external_update_t external_func, + void *external_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__get_update_editor(editor, edit_baton, + target_revision, + wc_ctx, + anchor_abspath, + target_basename, NULL, + use_commit_times, + depth, depth_is_sticky, + allow_unver_obstructions, + adds_as_modification, + server_performs_filtering, + clean_checkout, + diff3_cmd, + preserved_exts, + fetch_dirents_func, fetch_dirents_baton, + conflict_func, conflict_baton, + external_func, external_baton, + cancel_func, cancel_baton, + notify_func, notify_baton, + result_pool, scratch_pool)); +} + + +svn_error_t * +svn_wc_get_update_editor3(svn_revnum_t *target_revision, + svn_wc_adm_access_t *anchor, + const char *target, + svn_boolean_t use_commit_times, + svn_depth_t depth, + svn_boolean_t depth_is_sticky, + svn_boolean_t allow_unver_obstructions, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_conflict_resolver_func_t conflict_func, + void *conflict_baton, + svn_wc_get_file_t fetch_func, + void *fetch_baton, + const char *diff3_cmd, + const apr_array_header_t *preserved_exts, + const svn_delta_editor_t **editor, + void **edit_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + svn_wc__db_t *db = svn_wc__adm_get_db(anchor); + svn_wc_external_update_t external_func = NULL; + struct traversal_info_update_baton *eb = NULL; + struct conflict_func_1to2_baton *cfw = NULL; + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, db, pool)); + + if (traversal_info) + { + eb = apr_palloc(pool, sizeof(*eb)); + eb->db = db; + eb->traversal = traversal_info; + external_func = traversal_info_update; + } + + if (conflict_func) + { + cfw = apr_pcalloc(pool, sizeof(*cfw)); + cfw->inner_func = conflict_func; + cfw->inner_baton = conflict_baton; + } + + if (diff3_cmd) + SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool)); + + SVN_ERR(svn_wc_get_update_editor4(editor, edit_baton, + target_revision, + wc_ctx, + svn_wc__adm_access_abspath(anchor), + target, + use_commit_times, + depth, depth_is_sticky, + allow_unver_obstructions, + TRUE /* adds_as_modification */, + FALSE /* server_performs_filtering */, + FALSE /* clean_checkout */, + diff3_cmd, + preserved_exts, + NULL, NULL, /* fetch_dirents_func, baton */ + conflict_func ? conflict_func_1to2_wrapper + : NULL, + cfw, + external_func, eb, + cancel_func, cancel_baton, + notify_func, notify_baton, + pool, pool)); + + /* We can't destroy wc_ctx here, because the editor needs it while it's + driven. */ + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_get_update_editor2(svn_revnum_t *target_revision, + svn_wc_adm_access_t *anchor, + const char *target, + svn_boolean_t use_commit_times, + svn_boolean_t recurse, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + const char *diff3_cmd, + const svn_delta_editor_t **editor, + void **edit_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + return svn_wc_get_update_editor3(target_revision, anchor, target, + use_commit_times, + SVN_DEPTH_INFINITY_OR_FILES(recurse), FALSE, + FALSE, notify_func, notify_baton, + cancel_func, cancel_baton, NULL, NULL, + NULL, NULL, + diff3_cmd, NULL, editor, edit_baton, + traversal_info, pool); +} + +svn_error_t * +svn_wc_get_update_editor(svn_revnum_t *target_revision, + svn_wc_adm_access_t *anchor, + const char *target, + svn_boolean_t use_commit_times, + svn_boolean_t recurse, + svn_wc_notify_func_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + const char *diff3_cmd, + const svn_delta_editor_t **editor, + void **edit_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + /* This baton must live beyond this function. Alloc on heap. */ + struct compat_notify_baton_t *nb = apr_palloc(pool, sizeof(*nb)); + + nb->func = notify_func; + nb->baton = notify_baton; + + return svn_wc_get_update_editor3(target_revision, anchor, target, + use_commit_times, + SVN_DEPTH_INFINITY_OR_FILES(recurse), FALSE, + FALSE, compat_call_notify_func, nb, + cancel_func, cancel_baton, NULL, NULL, + NULL, NULL, + diff3_cmd, NULL, editor, edit_baton, + traversal_info, pool); +} + + +svn_error_t * +svn_wc_get_switch_editor4(const svn_delta_editor_t **editor, + void **edit_baton, + svn_revnum_t *target_revision, + svn_wc_context_t *wc_ctx, + const char *anchor_abspath, + const char *target_basename, + const char *switch_url, + svn_boolean_t use_commit_times, + svn_depth_t depth, + svn_boolean_t depth_is_sticky, + svn_boolean_t allow_unver_obstructions, + svn_boolean_t server_performs_filtering, + const char *diff3_cmd, + const apr_array_header_t *preserved_exts, + svn_wc_dirents_func_t fetch_dirents_func, + void *fetch_dirents_baton, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_wc_external_update_t external_func, + void *external_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__get_switch_editor(editor, edit_baton, + target_revision, + wc_ctx, + anchor_abspath, target_basename, + switch_url, NULL, + use_commit_times, + depth, depth_is_sticky, + allow_unver_obstructions, + server_performs_filtering, + diff3_cmd, + preserved_exts, + fetch_dirents_func, fetch_dirents_baton, + conflict_func, conflict_baton, + external_func, external_baton, + cancel_func, cancel_baton, + notify_func, notify_baton, + result_pool, scratch_pool)); +} + + +svn_error_t * +svn_wc_get_switch_editor3(svn_revnum_t *target_revision, + svn_wc_adm_access_t *anchor, + const char *target, + const char *switch_url, + svn_boolean_t use_commit_times, + svn_depth_t depth, + svn_boolean_t depth_is_sticky, + svn_boolean_t allow_unver_obstructions, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_conflict_resolver_func_t conflict_func, + void *conflict_baton, + const char *diff3_cmd, + const apr_array_header_t *preserved_exts, + const svn_delta_editor_t **editor, + void **edit_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + svn_wc__db_t *db = svn_wc__adm_get_db(anchor); + svn_wc_external_update_t external_func = NULL; + struct traversal_info_update_baton *eb = NULL; + struct conflict_func_1to2_baton *cfw = NULL; + + SVN_ERR_ASSERT(switch_url && svn_uri_is_canonical(switch_url, pool)); + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, db, pool)); + + if (traversal_info) + { + eb = apr_palloc(pool, sizeof(*eb)); + eb->db = db; + eb->traversal = traversal_info; + external_func = traversal_info_update; + } + + if (conflict_func) + { + cfw = apr_pcalloc(pool, sizeof(*cfw)); + cfw->inner_func = conflict_func; + cfw->inner_baton = conflict_baton; + } + + if (diff3_cmd) + SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool)); + + SVN_ERR(svn_wc_get_switch_editor4(editor, edit_baton, + target_revision, + wc_ctx, + svn_wc__adm_access_abspath(anchor), + target, switch_url, + use_commit_times, + depth, depth_is_sticky, + allow_unver_obstructions, + FALSE /* server_performs_filtering */, + diff3_cmd, + preserved_exts, + NULL, NULL, /* fetch_dirents_func, baton */ + conflict_func ? conflict_func_1to2_wrapper + : NULL, + cfw, + external_func, eb, + cancel_func, cancel_baton, + notify_func, notify_baton, + pool, pool)); + + /* We can't destroy wc_ctx here, because the editor needs it while it's + driven. */ + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_get_switch_editor2(svn_revnum_t *target_revision, + svn_wc_adm_access_t *anchor, + const char *target, + const char *switch_url, + svn_boolean_t use_commit_times, + svn_boolean_t recurse, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + const char *diff3_cmd, + const svn_delta_editor_t **editor, + void **edit_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + SVN_ERR_ASSERT(switch_url); + + return svn_wc_get_switch_editor3(target_revision, anchor, target, + switch_url, use_commit_times, + SVN_DEPTH_INFINITY_OR_FILES(recurse), FALSE, + FALSE, notify_func, notify_baton, + cancel_func, cancel_baton, + NULL, NULL, diff3_cmd, + NULL, editor, edit_baton, traversal_info, + pool); +} + +svn_error_t * +svn_wc_get_switch_editor(svn_revnum_t *target_revision, + svn_wc_adm_access_t *anchor, + const char *target, + const char *switch_url, + svn_boolean_t use_commit_times, + svn_boolean_t recurse, + svn_wc_notify_func_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + const char *diff3_cmd, + const svn_delta_editor_t **editor, + void **edit_baton, + svn_wc_traversal_info_t *traversal_info, + apr_pool_t *pool) +{ + /* This baton must live beyond this function. Alloc on heap. */ + struct compat_notify_baton_t *nb = apr_palloc(pool, sizeof(*nb)); + + nb->func = notify_func; + nb->baton = notify_baton; + + return svn_wc_get_switch_editor3(target_revision, anchor, target, + switch_url, use_commit_times, + SVN_DEPTH_INFINITY_OR_FILES(recurse), FALSE, + FALSE, compat_call_notify_func, nb, + cancel_func, cancel_baton, + NULL, NULL, diff3_cmd, + NULL, editor, edit_baton, traversal_info, + pool); +} + + +svn_error_t * +svn_wc_external_item_create(const svn_wc_external_item2_t **item, + apr_pool_t *pool) +{ + *item = apr_pcalloc(pool, sizeof(svn_wc_external_item2_t)); + return SVN_NO_ERROR; +} + +svn_wc_external_item_t * +svn_wc_external_item_dup(const svn_wc_external_item_t *item, + apr_pool_t *pool) +{ + svn_wc_external_item_t *new_item = apr_palloc(pool, sizeof(*new_item)); + + *new_item = *item; + + if (new_item->target_dir) + new_item->target_dir = apr_pstrdup(pool, new_item->target_dir); + + if (new_item->url) + new_item->url = apr_pstrdup(pool, new_item->url); + + return new_item; +} + + +svn_wc_traversal_info_t * +svn_wc_init_traversal_info(apr_pool_t *pool) +{ + svn_wc_traversal_info_t *ti = apr_palloc(pool, sizeof(*ti)); + + ti->pool = pool; + ti->externals_old = apr_hash_make(pool); + ti->externals_new = apr_hash_make(pool); + ti->depths = apr_hash_make(pool); + + return ti; +} + + +void +svn_wc_edited_externals(apr_hash_t **externals_old, + apr_hash_t **externals_new, + svn_wc_traversal_info_t *traversal_info) +{ + *externals_old = traversal_info->externals_old; + *externals_new = traversal_info->externals_new; +} + + +void +svn_wc_traversed_depths(apr_hash_t **depths, + svn_wc_traversal_info_t *traversal_info) +{ + *depths = traversal_info->depths; +} + + +/*** From lock.c ***/ + +/* To preserve API compatibility with Subversion 1.0.0 */ +svn_error_t * +svn_wc_adm_open(svn_wc_adm_access_t **adm_access, + svn_wc_adm_access_t *associated, + const char *path, + svn_boolean_t write_lock, + svn_boolean_t tree_lock, + apr_pool_t *pool) +{ + return svn_wc_adm_open3(adm_access, associated, path, write_lock, + (tree_lock ? -1 : 0), NULL, NULL, pool); +} + +svn_error_t * +svn_wc_adm_open2(svn_wc_adm_access_t **adm_access, + svn_wc_adm_access_t *associated, + const char *path, + svn_boolean_t write_lock, + int levels_to_lock, + apr_pool_t *pool) +{ + return svn_wc_adm_open3(adm_access, associated, path, write_lock, + levels_to_lock, NULL, NULL, pool); +} + +svn_error_t * +svn_wc_adm_probe_open(svn_wc_adm_access_t **adm_access, + svn_wc_adm_access_t *associated, + const char *path, + svn_boolean_t write_lock, + svn_boolean_t tree_lock, + apr_pool_t *pool) +{ + return svn_wc_adm_probe_open3(adm_access, associated, path, + write_lock, (tree_lock ? -1 : 0), + NULL, NULL, pool); +} + + +svn_error_t * +svn_wc_adm_probe_open2(svn_wc_adm_access_t **adm_access, + svn_wc_adm_access_t *associated, + const char *path, + svn_boolean_t write_lock, + int levels_to_lock, + apr_pool_t *pool) +{ + return svn_wc_adm_probe_open3(adm_access, associated, path, write_lock, + levels_to_lock, NULL, NULL, pool); +} + +svn_error_t * +svn_wc_adm_probe_try2(svn_wc_adm_access_t **adm_access, + svn_wc_adm_access_t *associated, + const char *path, + svn_boolean_t write_lock, + int levels_to_lock, + apr_pool_t *pool) +{ + return svn_wc_adm_probe_try3(adm_access, associated, path, write_lock, + levels_to_lock, NULL, NULL, pool); +} + +svn_error_t * +svn_wc_adm_probe_try(svn_wc_adm_access_t **adm_access, + svn_wc_adm_access_t *associated, + const char *path, + svn_boolean_t write_lock, + svn_boolean_t tree_lock, + apr_pool_t *pool) +{ + return svn_wc_adm_probe_try3(adm_access, associated, path, write_lock, + (tree_lock ? -1 : 0), NULL, NULL, pool); +} + +svn_error_t * +svn_wc_adm_close(svn_wc_adm_access_t *adm_access) +{ + /* This is the only pool we have access to. */ + apr_pool_t *scratch_pool = svn_wc_adm_access_pool(adm_access); + + return svn_wc_adm_close2(adm_access, scratch_pool); +} + +svn_error_t * +svn_wc_locked(svn_boolean_t *locked, + const char *path, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + const char *local_abspath; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc_context_create(&wc_ctx, NULL, pool, pool)); + + SVN_ERR(svn_wc_locked2(NULL, locked, wc_ctx, local_abspath, pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_check_wc(const char *path, + int *wc_format, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + const char *local_abspath; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc_context_create(&wc_ctx, NULL, pool, pool)); + + SVN_ERR(svn_wc_check_wc2(wc_format, wc_ctx, local_abspath, pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + + +/*** From translate.c ***/ + +svn_error_t * +svn_wc_translated_file(const char **xlated_p, + const char *vfile, + svn_wc_adm_access_t *adm_access, + svn_boolean_t force_repair, + apr_pool_t *pool) +{ + return svn_wc_translated_file2(xlated_p, vfile, vfile, adm_access, + SVN_WC_TRANSLATE_TO_NF + | (force_repair ? + SVN_WC_TRANSLATE_FORCE_EOL_REPAIR : 0), + pool); +} + +svn_error_t * +svn_wc_translated_stream(svn_stream_t **stream, + const char *path, + const char *versioned_file, + svn_wc_adm_access_t *adm_access, + apr_uint32_t flags, + apr_pool_t *pool) +{ + const char *local_abspath; + const char *versioned_abspath; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_dirent_get_absolute(&versioned_abspath, versioned_file, pool)); + + return svn_error_trace( + svn_wc__internal_translated_stream(stream, svn_wc__adm_get_db(adm_access), + local_abspath, versioned_abspath, flags, + pool, pool)); +} + +svn_error_t * +svn_wc_translated_file2(const char **xlated_path, + const char *src, + const char *versioned_file, + svn_wc_adm_access_t *adm_access, + apr_uint32_t flags, + apr_pool_t *pool) +{ + const char *versioned_abspath; + const char *root; + const char *tmp_root; + const char *src_abspath; + + SVN_ERR(svn_dirent_get_absolute(&versioned_abspath, versioned_file, pool)); + SVN_ERR(svn_dirent_get_absolute(&src_abspath, src, pool)); + + SVN_ERR(svn_wc__internal_translated_file(xlated_path, src_abspath, + svn_wc__adm_get_db(adm_access), + versioned_abspath, + flags, NULL, NULL, pool, pool)); + + if (strcmp(*xlated_path, src_abspath) == 0) + *xlated_path = src; + else if (! svn_dirent_is_absolute(versioned_file)) + { + SVN_ERR(svn_io_temp_dir(&tmp_root, pool)); + if (! svn_dirent_is_child(tmp_root, *xlated_path, pool)) + { + SVN_ERR(svn_dirent_get_absolute(&root, "", pool)); + + if (svn_dirent_is_child(root, *xlated_path, pool)) + *xlated_path = svn_dirent_is_child(root, *xlated_path, pool); + } + } + + return SVN_NO_ERROR; +} + +/*** From relocate.c ***/ +svn_error_t * +svn_wc_relocate3(const char *path, + svn_wc_adm_access_t *adm_access, + const char *from, + const char *to, + svn_boolean_t recurse, + svn_wc_relocation_validator3_t validator, + void *validator_baton, + apr_pool_t *pool) +{ + const char *local_abspath; + svn_wc_context_t *wc_ctx; + + if (! recurse) + SVN_ERR(svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Non-recursive relocation not supported"))); + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), + pool)); + + SVN_ERR(svn_wc_relocate4(wc_ctx, local_abspath, from, to, + validator, validator_baton, pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +/* Compatibility baton and wrapper. */ +struct compat2_baton { + svn_wc_relocation_validator2_t validator; + void *baton; +}; + +/* Compatibility baton and wrapper. */ +struct compat_baton { + svn_wc_relocation_validator_t validator; + void *baton; +}; + +/* This implements svn_wc_relocate_validator3_t. */ +static svn_error_t * +compat2_validator(void *baton, + const char *uuid, + const char *url, + const char *root_url, + apr_pool_t *pool) +{ + struct compat2_baton *cb = baton; + /* The old callback type doesn't set root_url. */ + return cb->validator(cb->baton, uuid, + (root_url ? root_url : url), (root_url != NULL), + pool); +} + +/* This implements svn_wc_relocate_validator3_t. */ +static svn_error_t * +compat_validator(void *baton, + const char *uuid, + const char *url, + const char *root_url, + apr_pool_t *pool) +{ + struct compat_baton *cb = baton; + /* The old callback type doesn't allow uuid to be NULL. */ + if (uuid) + return cb->validator(cb->baton, uuid, url); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_relocate2(const char *path, + svn_wc_adm_access_t *adm_access, + const char *from, + const char *to, + svn_boolean_t recurse, + svn_wc_relocation_validator2_t validator, + void *validator_baton, + apr_pool_t *pool) +{ + struct compat2_baton cb; + + cb.validator = validator; + cb.baton = validator_baton; + + return svn_wc_relocate3(path, adm_access, from, to, recurse, + compat2_validator, &cb, pool); +} + +svn_error_t * +svn_wc_relocate(const char *path, + svn_wc_adm_access_t *adm_access, + const char *from, + const char *to, + svn_boolean_t recurse, + svn_wc_relocation_validator_t validator, + void *validator_baton, + apr_pool_t *pool) +{ + struct compat_baton cb; + + cb.validator = validator; + cb.baton = validator_baton; + + return svn_wc_relocate3(path, adm_access, from, to, recurse, + compat_validator, &cb, pool); +} + + +/*** From log.c ***/ + +svn_error_t * +svn_wc_cleanup2(const char *path, + const char *diff3_cmd, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + const char *local_abspath; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc_context_create(&wc_ctx, NULL, pool, pool)); + + SVN_ERR(svn_wc_cleanup3(wc_ctx, local_abspath, cancel_func, + cancel_baton, pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_cleanup(const char *path, + svn_wc_adm_access_t *optional_adm_access, + const char *diff3_cmd, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + return svn_wc_cleanup2(path, diff3_cmd, cancel_func, cancel_baton, pool); +} + +/*** From questions.c ***/ + +svn_error_t * +svn_wc_has_binary_prop(svn_boolean_t *has_binary_prop, + const char *path, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); + const char *local_abspath; + const svn_string_t *value; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + + SVN_ERR(svn_wc__internal_propget(&value, db, local_abspath, + SVN_PROP_MIME_TYPE, + pool, pool)); + + if (value && (svn_mime_type_is_binary(value->data))) + *has_binary_prop = TRUE; + else + *has_binary_prop = FALSE; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_conflicted_p2(svn_boolean_t *text_conflicted_p, + svn_boolean_t *prop_conflicted_p, + svn_boolean_t *tree_conflicted_p, + const char *path, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + const char *local_abspath; + svn_wc_context_t *wc_ctx; + svn_error_t *err; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, + svn_wc__adm_get_db(adm_access), + pool)); + + err = svn_wc_conflicted_p3(text_conflicted_p, prop_conflicted_p, + tree_conflicted_p, wc_ctx, local_abspath, pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + + if (text_conflicted_p) + *text_conflicted_p = FALSE; + if (prop_conflicted_p) + *prop_conflicted_p = FALSE; + if (tree_conflicted_p) + *tree_conflicted_p = FALSE; + } + else if (err) + return err; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_conflicted_p(svn_boolean_t *text_conflicted_p, + svn_boolean_t *prop_conflicted_p, + const char *dir_path, + const svn_wc_entry_t *entry, + apr_pool_t *pool) +{ + svn_node_kind_t kind; + const char *path; + + *text_conflicted_p = FALSE; + *prop_conflicted_p = FALSE; + + if (entry->conflict_old) + { + path = svn_dirent_join(dir_path, entry->conflict_old, pool); + SVN_ERR(svn_io_check_path(path, &kind, pool)); + *text_conflicted_p = (kind == svn_node_file); + } + + if ((! *text_conflicted_p) && (entry->conflict_new)) + { + path = svn_dirent_join(dir_path, entry->conflict_new, pool); + SVN_ERR(svn_io_check_path(path, &kind, pool)); + *text_conflicted_p = (kind == svn_node_file); + } + + if ((! *text_conflicted_p) && (entry->conflict_wrk)) + { + path = svn_dirent_join(dir_path, entry->conflict_wrk, pool); + SVN_ERR(svn_io_check_path(path, &kind, pool)); + *text_conflicted_p = (kind == svn_node_file); + } + + if (entry->prejfile) + { + path = svn_dirent_join(dir_path, entry->prejfile, pool); + SVN_ERR(svn_io_check_path(path, &kind, pool)); + *prop_conflicted_p = (kind == svn_node_file); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_text_modified_p(svn_boolean_t *modified_p, + const char *filename, + svn_boolean_t force_comparison, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); + const char *local_abspath; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, filename, pool)); + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, db, pool)); + + SVN_ERR(svn_wc_text_modified_p2(modified_p, wc_ctx, local_abspath, + force_comparison, pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + + +/*** From copy.c ***/ +svn_error_t * +svn_wc_copy2(const char *src, + svn_wc_adm_access_t *dst_parent, + const char *dst_basename, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + svn_wc__db_t *wc_db = svn_wc__adm_get_db(dst_parent); + const char *src_abspath; + const char *dst_abspath; + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, wc_db, pool)); + SVN_ERR(svn_dirent_get_absolute(&src_abspath, src, pool)); + + dst_abspath = svn_dirent_join(svn_wc__adm_access_abspath(dst_parent), + dst_basename, pool); + + SVN_ERR(svn_wc_copy3(wc_ctx, + src_abspath, + dst_abspath, + FALSE /* metadata_only */, + cancel_func, cancel_baton, + notify_func, notify_baton, + pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_copy(const char *src_path, + svn_wc_adm_access_t *dst_parent, + const char *dst_basename, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func_t notify_func, + void *notify_baton, + apr_pool_t *pool) +{ + struct compat_notify_baton_t nb; + + nb.func = notify_func; + nb.baton = notify_baton; + + return svn_wc_copy2(src_path, dst_parent, dst_basename, cancel_func, + cancel_baton, compat_call_notify_func, + &nb, pool); +} + + +/*** From merge.c ***/ + +svn_error_t * +svn_wc_merge4(enum svn_wc_merge_outcome_t *merge_outcome, + svn_wc_context_t *wc_ctx, + const char *left_abspath, + const char *right_abspath, + const char *target_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + svn_boolean_t dry_run, + const char *diff3_cmd, + const apr_array_header_t *merge_options, + const apr_array_header_t *prop_diff, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc_merge5(merge_outcome, + NULL /* merge_props_outcome */, + wc_ctx, + left_abspath, + right_abspath, + target_abspath, + left_label, + right_label, + target_label, + left_version, + right_version, + dry_run, + diff3_cmd, + merge_options, + NULL /* original_props */, + prop_diff, + conflict_func, conflict_baton, + cancel_func, cancel_baton, + scratch_pool)); +} + +svn_error_t * +svn_wc_merge3(enum svn_wc_merge_outcome_t *merge_outcome, + const char *left, + const char *right, + const char *merge_target, + svn_wc_adm_access_t *adm_access, + const char *left_label, + const char *right_label, + const char *target_label, + svn_boolean_t dry_run, + const char *diff3_cmd, + const apr_array_header_t *merge_options, + const apr_array_header_t *prop_diff, + svn_wc_conflict_resolver_func_t conflict_func, + void *conflict_baton, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); + const char *left_abspath, *right_abspath, *target_abspath; + struct conflict_func_1to2_baton cfw; + + SVN_ERR(svn_dirent_get_absolute(&left_abspath, left, pool)); + SVN_ERR(svn_dirent_get_absolute(&right_abspath, right, pool)); + SVN_ERR(svn_dirent_get_absolute(&target_abspath, merge_target, pool)); + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL /* config */, db, pool)); + + cfw.inner_func = conflict_func; + cfw.inner_baton = conflict_baton; + + if (diff3_cmd) + SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool)); + + SVN_ERR(svn_wc_merge4(merge_outcome, + wc_ctx, + left_abspath, + right_abspath, + target_abspath, + left_label, + right_label, + target_label, + NULL, + NULL, + dry_run, + diff3_cmd, + merge_options, + prop_diff, + conflict_func ? conflict_func_1to2_wrapper : NULL, + &cfw, + NULL, NULL, + pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_merge2(enum svn_wc_merge_outcome_t *merge_outcome, + const char *left, + const char *right, + const char *merge_target, + svn_wc_adm_access_t *adm_access, + const char *left_label, + const char *right_label, + const char *target_label, + svn_boolean_t dry_run, + const char *diff3_cmd, + const apr_array_header_t *merge_options, + apr_pool_t *pool) +{ + return svn_wc_merge3(merge_outcome, + left, right, merge_target, adm_access, + left_label, right_label, target_label, + dry_run, diff3_cmd, merge_options, NULL, + NULL, NULL, pool); +} + +svn_error_t * +svn_wc_merge(const char *left, + const char *right, + const char *merge_target, + svn_wc_adm_access_t *adm_access, + const char *left_label, + const char *right_label, + const char *target_label, + svn_boolean_t dry_run, + enum svn_wc_merge_outcome_t *merge_outcome, + const char *diff3_cmd, + apr_pool_t *pool) +{ + return svn_wc_merge3(merge_outcome, + left, right, merge_target, adm_access, + left_label, right_label, target_label, + dry_run, diff3_cmd, NULL, NULL, NULL, + NULL, pool); +} + + +/*** From util.c ***/ + +svn_wc_conflict_version_t * +svn_wc_conflict_version_create(const char *repos_url, + const char *path_in_repos, + svn_revnum_t peg_rev, + svn_node_kind_t node_kind, + apr_pool_t *pool) +{ + return svn_wc_conflict_version_create2(repos_url, NULL, path_in_repos, + peg_rev, node_kind, pool); +} + +svn_wc_conflict_description_t * +svn_wc_conflict_description_create_text(const char *path, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + svn_wc_conflict_description_t *conflict; + + conflict = apr_pcalloc(pool, sizeof(*conflict)); + conflict->path = path; + conflict->node_kind = svn_node_file; + conflict->kind = svn_wc_conflict_kind_text; + conflict->access = adm_access; + conflict->action = svn_wc_conflict_action_edit; + conflict->reason = svn_wc_conflict_reason_edited; + return conflict; +} + +svn_wc_conflict_description_t * +svn_wc_conflict_description_create_prop(const char *path, + svn_wc_adm_access_t *adm_access, + svn_node_kind_t node_kind, + const char *property_name, + apr_pool_t *pool) +{ + svn_wc_conflict_description_t *conflict; + + conflict = apr_pcalloc(pool, sizeof(*conflict)); + conflict->path = path; + conflict->node_kind = node_kind; + conflict->kind = svn_wc_conflict_kind_property; + conflict->access = adm_access; + conflict->property_name = property_name; + return conflict; +} + +svn_wc_conflict_description_t * +svn_wc_conflict_description_create_tree( + const char *path, + svn_wc_adm_access_t *adm_access, + svn_node_kind_t node_kind, + svn_wc_operation_t operation, + svn_wc_conflict_version_t *src_left_version, + svn_wc_conflict_version_t *src_right_version, + apr_pool_t *pool) +{ + svn_wc_conflict_description_t *conflict; + + conflict = apr_pcalloc(pool, sizeof(*conflict)); + conflict->path = path; + conflict->node_kind = node_kind; + conflict->kind = svn_wc_conflict_kind_tree; + conflict->access = adm_access; + conflict->operation = operation; + conflict->src_left_version = src_left_version; + conflict->src_right_version = src_right_version; + return conflict; +} + + +/*** From revision_status.c ***/ + +svn_error_t * +svn_wc_revision_status(svn_wc_revision_status_t **result_p, + const char *wc_path, + const char *trail_url, + svn_boolean_t committed, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + const char *local_abspath; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, wc_path, pool)); + SVN_ERR(svn_wc_context_create(&wc_ctx, NULL /* config */, pool, pool)); + + SVN_ERR(svn_wc_revision_status2(result_p, wc_ctx, local_abspath, trail_url, + committed, cancel_func, cancel_baton, pool, + pool)); + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +/*** From crop.c ***/ +svn_error_t * +svn_wc_crop_tree(svn_wc_adm_access_t *anchor, + const char *target, + svn_depth_t depth, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_wc_context_t *wc_ctx; + svn_wc__db_t *db = svn_wc__adm_get_db(anchor); + const char *local_abspath; + + local_abspath = svn_dirent_join(svn_wc__adm_access_abspath(anchor), + target, pool); + + SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, db, pool)); + + if (depth == svn_depth_exclude) + { + SVN_ERR(svn_wc_exclude(wc_ctx, + local_abspath, + cancel_func, cancel_baton, + notify_func, notify_baton, + pool)); + } + else + { + SVN_ERR(svn_wc_crop_tree2(wc_ctx, + local_abspath, + depth, + cancel_func, cancel_baton, + notify_func, notify_baton, + pool)); + } + + return svn_error_trace(svn_wc_context_destroy(wc_ctx)); +} + +svn_error_t * +svn_wc_move(svn_wc_context_t *wc_ctx, + const char *src_abspath, + const char *dst_abspath, + svn_boolean_t metadata_only, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__move2(wc_ctx, src_abspath, dst_abspath, + metadata_only, + TRUE, /* allow_mixed_revisions */ + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); +} + +svn_error_t * +svn_wc_read_kind(svn_node_kind_t *kind, + svn_wc_context_t *wc_ctx, + const char *abspath, + svn_boolean_t show_hidden, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc_read_kind2(kind, + wc_ctx, abspath, + TRUE /* show_deleted */, + show_hidden, + scratch_pool)); + + /*if (db_kind == svn_node_dir) + *kind = svn_node_dir; + else if (db_kind == svn_node_file || db_kind == svn_node_symlink) + *kind = svn_node_file; + else + *kind = svn_node_none;*/ + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/diff.h b/subversion/libsvn_wc/diff.h new file mode 100644 index 000000000000..d16a9e5eeccb --- /dev/null +++ b/subversion/libsvn_wc/diff.h @@ -0,0 +1,144 @@ +/* + * lock.h: routines for diffing local files and directories. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_WC_DIFF_H +#define SVN_LIBSVN_WC_DIFF_H + +#include <apr_pools.h> +#include <apr_hash.h> + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_wc.h" + +#include "wc_db.h" +#include "private/svn_diff_tree.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Reports the file LOCAL_ABSPATH as ADDED file with relpath RELPATH to + PROCESSOR with as parent baton PROCESSOR_PARENT_BATON. + + The node is expected to have status svn_wc__db_status_normal, or + svn_wc__db_status_added. When DIFF_PRISTINE is TRUE, report the pristine + version of LOCAL_ABSPATH as ADDED. In this case an + svn_wc__db_status_deleted may shadow an added or deleted node. + + If CHANGELIST_HASH is not NULL and LOCAL_ABSPATH's changelist is not + in the changelist, don't report the node. + */ +svn_error_t * +svn_wc__diff_local_only_file(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + apr_hash_t *changelist_hash, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* Reports the directory LOCAL_ABSPATH and everything below it (limited by + DEPTH) as added with relpath RELPATH to PROCESSOR with as parent baton + PROCESSOR_PARENT_BATON. + + The node is expected to have status svn_wc__db_status_normal, or + svn_wc__db_status_added. When DIFF_PRISTINE is TRUE, report the pristine + version of LOCAL_ABSPATH as ADDED. In this case an + svn_wc__db_status_deleted may shadow an added or deleted node. + + If CHANGELIST_HASH is not NULL and LOCAL_ABSPATH's changelist is not + in the changelist, don't report the node. + */ +svn_error_t * +svn_wc__diff_local_only_dir(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_depth_t depth, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + apr_hash_t *changelist_hash, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* Reports the BASE-file LOCAL_ABSPATH as deleted to PROCESSOR with relpath + RELPATH, revision REVISION and parent baton PROCESSOR_PARENT_BATON. + + If REVISION is invalid, the revision as stored in BASE is used. + + The node is expected to have status svn_wc__db_status_normal in BASE. */ +svn_error_t * +svn_wc__diff_base_only_file(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + apr_pool_t *scratch_pool); + +/* Reports the BASE-directory LOCAL_ABSPATH and everything below it (limited + by DEPTH) as deleted to PROCESSOR with relpath RELPATH and parent baton + PROCESSOR_PARENT_BATON. + + If REVISION is invalid, the revision as stored in BASE is used. + + The node is expected to have status svn_wc__db_status_normal in BASE. */ +svn_error_t * +svn_wc__diff_base_only_dir(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + svn_depth_t depth, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* Diff the file PATH against the text base of its BASE layer. At this + * stage we are dealing with a file that does exist in the working copy. + */ +svn_error_t * +svn_wc__diff_base_working_diff(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + apr_hash_t *changelist_hash, + const svn_diff_tree_processor_t *processor, + void *processor_dir_baton, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_WC_DIFF_H */ diff --git a/subversion/libsvn_wc/diff_editor.c b/subversion/libsvn_wc/diff_editor.c new file mode 100644 index 000000000000..839241f9db62 --- /dev/null +++ b/subversion/libsvn_wc/diff_editor.c @@ -0,0 +1,2747 @@ +/* + * diff_editor.c -- The diff editor for comparing the working copy against the + * repository. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* + * This code uses an svn_delta_editor_t editor driven by + * svn_wc_crawl_revisions (like the update command) to retrieve the + * differences between the working copy and the requested repository + * version. Rather than updating the working copy, this new editor creates + * temporary files that contain the pristine repository versions. When the + * crawler closes the files the editor calls back to a client layer + * function to compare the working copy and the temporary file. There is + * only ever one temporary file in existence at any time. + * + * When the crawler closes a directory, the editor then calls back to the + * client layer to compare any remaining files that may have been modified + * locally. Added directories do not have corresponding temporary + * directories created, as they are not needed. + * + * The diff result from this editor is a combination of the restructuring + * operations from the repository with the local restructurings since checking + * out. + * + * ### TODO: Make sure that we properly support and report multi layered + * operations instead of only simple file replacements. + * + * ### TODO: Replacements where the node kind changes needs support. It + * mostly works when the change is in the repository, but not when it is + * in the working copy. + * + * ### TODO: Do we need to support copyfrom? + * + */ + +#include <apr_hash.h> +#include <apr_md5.h> + +#include <assert.h> + +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" +#include "svn_sorts.h" + +#include "private/svn_subr_private.h" +#include "private/svn_wc_private.h" +#include "private/svn_diff_tree.h" +#include "private/svn_editor.h" + +#include "wc.h" +#include "props.h" +#include "adm_files.h" +#include "translate.h" +#include "diff.h" + +#include "svn_private_config.h" + +/*-------------------------------------------------------------------------*/ + + +/* Overall crawler editor baton. + */ +struct edit_baton_t +{ + /* A wc db. */ + svn_wc__db_t *db; + + /* A diff tree processor, receiving the result of the diff. */ + const svn_diff_tree_processor_t *processor; + + /* A boolean indicating whether local additions should be reported before + remote deletes. The processor can transform adds in deletes and deletes + in adds, but it can't reorder the output. */ + svn_boolean_t local_before_remote; + + /* ANCHOR/TARGET represent the base of the hierarchy to be compared. */ + const char *target; + const char *anchor_abspath; + + /* Target revision */ + svn_revnum_t revnum; + + /* Was the root opened? */ + svn_boolean_t root_opened; + + /* How does this diff descend as seen from target? */ + svn_depth_t depth; + + /* Should this diff ignore node ancestry? */ + svn_boolean_t ignore_ancestry; + + /* Possibly diff repos against text-bases instead of working files. */ + svn_boolean_t diff_pristine; + + /* Hash whose keys are const char * changelist names. */ + apr_hash_t *changelist_hash; + + /* Cancel function/baton */ + svn_cancel_func_t cancel_func; + void *cancel_baton; + + apr_pool_t *pool; +}; + +/* Directory level baton. + */ +struct dir_baton_t +{ + /* Reference to parent directory baton (or NULL for the root) */ + struct dir_baton_t *parent_baton; + + /* The depth at which this directory should be diffed. */ + svn_depth_t depth; + + /* The name and path of this directory as if they would be/are in the + local working copy. */ + const char *name; + const char *relpath; + const char *local_abspath; + + /* TRUE if the file is added by the editor drive. */ + svn_boolean_t added; + /* TRUE if the node exists only on the repository side (op_depth 0 or added) */ + svn_boolean_t repos_only; + /* TRUE if the node is to be compared with an unrelated node*/ + svn_boolean_t ignoring_ancestry; + + /* Processor state */ + void *pdb; + svn_boolean_t skip; + svn_boolean_t skip_children; + + svn_diff_source_t *left_src; + svn_diff_source_t *right_src; + + apr_hash_t *local_info; + + /* A hash containing the basenames of the nodes reported deleted by the + repository (or NULL for no values). */ + apr_hash_t *deletes; + + /* Identifies those directory elements that get compared while running + the crawler. These elements should not be compared again when + recursively looking for local modifications. + + This hash maps the basename of the node to an unimportant value. + + If the directory's properties have been compared, an item with hash + key of "" will be present in the hash. */ + apr_hash_t *compared; + + /* The list of incoming BASE->repos propchanges. */ + apr_array_header_t *propchanges; + + /* Has a change on regular properties */ + svn_boolean_t has_propchange; + + /* The overall crawler editor baton. */ + struct edit_baton_t *eb; + + apr_pool_t *pool; + int users; +}; + +/* File level baton. + */ +struct file_baton_t +{ + struct dir_baton_t *parent_baton; + + /* The name and path of this file as if they would be/are in the + parent directory, diff session and local working copy. */ + const char *name; + const char *relpath; + const char *local_abspath; + + /* Processor state */ + void *pfb; + svn_boolean_t skip; + + /* TRUE if the file is added by the editor drive. */ + svn_boolean_t added; + /* TRUE if the node exists only on the repository side (op_depth 0 or added) */ + svn_boolean_t repos_only; + /* TRUE if the node is to be compared with an unrelated node*/ + svn_boolean_t ignoring_ancestry; + + const svn_diff_source_t *left_src; + const svn_diff_source_t *right_src; + + /* The list of incoming BASE->repos propchanges. */ + apr_array_header_t *propchanges; + + /* Has a change on regular properties */ + svn_boolean_t has_propchange; + + /* The current BASE checksum and props */ + const svn_checksum_t *base_checksum; + apr_hash_t *base_props; + + /* The resulting from apply_textdelta */ + const char *temp_file_path; + unsigned char result_digest[APR_MD5_DIGESTSIZE]; + + /* The overall crawler editor baton. */ + struct edit_baton_t *eb; + + apr_pool_t *pool; +}; + +/* Create a new edit baton. TARGET_PATH/ANCHOR are working copy paths + * that describe the root of the comparison. CALLBACKS/CALLBACK_BATON + * define the callbacks to compare files. DEPTH defines if and how to + * descend into subdirectories; see public doc string for exactly how. + * IGNORE_ANCESTRY defines whether to utilize node ancestry when + * calculating diffs. USE_TEXT_BASE defines whether to compare + * against working files or text-bases. REVERSE_ORDER defines which + * direction to perform the diff. + * + * CHANGELIST_FILTER is a list of const char * changelist names, used to + * filter diff output responses to only those items in one of the + * specified changelists, empty (or NULL altogether) if no changelist + * filtering is requested. + */ +static svn_error_t * +make_edit_baton(struct edit_baton_t **edit_baton, + svn_wc__db_t *db, + const char *anchor_abspath, + const char *target, + const svn_wc_diff_callbacks4_t *callbacks, + void *callback_baton, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t show_copies_as_adds, + svn_boolean_t use_text_base, + svn_boolean_t reverse_order, + const apr_array_header_t *changelist_filter, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + apr_hash_t *changelist_hash = NULL; + struct edit_baton_t *eb; + const svn_diff_tree_processor_t *processor; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath)); + + if (changelist_filter && changelist_filter->nelts) + SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, + pool)); + + SVN_ERR(svn_wc__wrap_diff_callbacks(&processor, + callbacks, callback_baton, TRUE, + pool, pool)); + + if (reverse_order) + processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool); + + /* --show-copies-as-adds implies --notice-ancestry */ + if (show_copies_as_adds) + ignore_ancestry = FALSE; + + if (! show_copies_as_adds) + processor = svn_diff__tree_processor_copy_as_changed_create(processor, + pool); + + eb = apr_pcalloc(pool, sizeof(*eb)); + eb->db = db; + eb->anchor_abspath = apr_pstrdup(pool, anchor_abspath); + eb->target = apr_pstrdup(pool, target); + eb->processor = processor; + eb->depth = depth; + eb->ignore_ancestry = ignore_ancestry; + eb->local_before_remote = reverse_order; + eb->diff_pristine = use_text_base; + eb->changelist_hash = changelist_hash; + eb->cancel_func = cancel_func; + eb->cancel_baton = cancel_baton; + eb->pool = pool; + + *edit_baton = eb; + return SVN_NO_ERROR; +} + +/* Create a new directory baton. PATH is the directory path, + * including anchor_path. ADDED is set if this directory is being + * added rather than replaced. PARENT_BATON is the baton of the + * parent directory, it will be null if this is the root of the + * comparison hierarchy. The directory and its parent may or may not + * exist in the working copy. EDIT_BATON is the overall crawler + * editor baton. + */ +static struct dir_baton_t * +make_dir_baton(const char *path, + struct dir_baton_t *parent_baton, + struct edit_baton_t *eb, + svn_boolean_t added, + svn_depth_t depth, + apr_pool_t *result_pool) +{ + apr_pool_t *dir_pool = svn_pool_create(parent_baton ? parent_baton->pool + : eb->pool); + struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db)); + + db->parent_baton = parent_baton; + + /* Allocate 1 string for using as 3 strings */ + db->local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool); + db->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, db->local_abspath); + db->name = svn_dirent_basename(db->relpath, NULL); + + db->eb = eb; + db->added = added; + db->depth = depth; + db->pool = dir_pool; + db->propchanges = apr_array_make(dir_pool, 8, sizeof(svn_prop_t)); + db->compared = apr_hash_make(dir_pool); + + if (parent_baton != NULL) + { + parent_baton->users++; + } + + db->users = 1; + + return db; +} + +/* Create a new file baton. PATH is the file path, including + * anchor_path. ADDED is set if this file is being added rather than + * replaced. PARENT_BATON is the baton of the parent directory. + * The directory and its parent may or may not exist in the working copy. + */ +static struct file_baton_t * +make_file_baton(const char *path, + svn_boolean_t added, + struct dir_baton_t *parent_baton, + apr_pool_t *result_pool) +{ + apr_pool_t *file_pool = svn_pool_create(result_pool); + struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb)); + struct edit_baton_t *eb = parent_baton->eb; + + fb->eb = eb; + fb->parent_baton = parent_baton; + fb->parent_baton->users++; + + /* Allocate 1 string for using as 3 strings */ + fb->local_abspath = svn_dirent_join(eb->anchor_abspath, path, file_pool); + fb->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, fb->local_abspath); + fb->name = svn_dirent_basename(fb->relpath, NULL); + + fb->added = added; + fb->pool = file_pool; + fb->propchanges = apr_array_make(file_pool, 8, sizeof(svn_prop_t)); + + return fb; +} + +/* Destroy DB when there are no more registered users */ +static svn_error_t * +maybe_done(struct dir_baton_t *db) +{ + db->users--; + + if (!db->users) + { + struct dir_baton_t *pb = db->parent_baton; + + svn_pool_clear(db->pool); + + if (pb != NULL) + SVN_ERR(maybe_done(pb)); + } + + return SVN_NO_ERROR; +} + +/* Standard check to see if a node is represented in the local working copy */ +#define NOT_PRESENT(status) \ + ((status) == svn_wc__db_status_not_present \ + || (status) == svn_wc__db_status_excluded \ + || (status) == svn_wc__db_status_server_excluded) + +svn_error_t * +svn_wc__diff_base_working_diff(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + apr_hash_t *changelist_hash, + const svn_diff_tree_processor_t *processor, + void *processor_dir_baton, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + void *file_baton = NULL; + svn_boolean_t skip = FALSE; + svn_wc__db_status_t status; + svn_revnum_t db_revision; + svn_boolean_t had_props; + svn_boolean_t props_mod; + svn_boolean_t files_same = FALSE; + svn_wc__db_status_t base_status; + const svn_checksum_t *working_checksum; + const svn_checksum_t *checksum; + svn_filesize_t recorded_size; + apr_time_t recorded_time; + const char *pristine_file; + const char *local_file; + svn_diff_source_t *left_src; + svn_diff_source_t *right_src; + apr_hash_t *base_props; + apr_hash_t *local_props; + apr_array_header_t *prop_changes; + const char *changelist; + + SVN_ERR(svn_wc__db_read_info(&status, NULL, &db_revision, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &working_checksum, NULL, + NULL, NULL, NULL, NULL, NULL, &recorded_size, + &recorded_time, &changelist, NULL, NULL, + &had_props, &props_mod, NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool)); + checksum = working_checksum; + + assert(status == svn_wc__db_status_normal + || status == svn_wc__db_status_added + || (status == svn_wc__db_status_deleted && diff_pristine)); + + /* If the item is not a member of a specified changelist (and there are + some specified changelists), skip it. */ + if (changelist_hash && !svn_hash_gets(changelist_hash, changelist)) + return SVN_NO_ERROR; + + + if (status != svn_wc__db_status_normal) + { + SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &db_revision, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &checksum, NULL, NULL, &had_props, + NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + recorded_size = SVN_INVALID_FILESIZE; + recorded_time = 0; + props_mod = TRUE; /* Requires compare */ + } + else if (diff_pristine) + files_same = TRUE; + else + { + const svn_io_dirent2_t *dirent; + + SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, + FALSE /* verify truename */, + TRUE /* ingore_enoent */, + scratch_pool, scratch_pool)); + + if (dirent->kind == svn_node_file + && dirent->filesize == recorded_size + && dirent->mtime == recorded_time) + { + files_same = TRUE; + } + } + + if (files_same && !props_mod) + return SVN_NO_ERROR; /* Cheap exit */ + + assert(checksum); + + if (!SVN_IS_VALID_REVNUM(revision)) + revision = db_revision; + + left_src = svn_diff__source_create(revision, scratch_pool); + right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); + + SVN_ERR(processor->file_opened(&file_baton, &skip, relpath, + left_src, + right_src, + NULL /* copyfrom_src */, + processor_dir_baton, + processor, + scratch_pool, scratch_pool)); + + if (skip) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, + db, local_abspath, checksum, + scratch_pool, scratch_pool)); + + if (diff_pristine) + SVN_ERR(svn_wc__db_pristine_get_path(&local_file, + db, local_abspath, + working_checksum, + scratch_pool, scratch_pool)); + else if (! (had_props || props_mod)) + local_file = local_abspath; + else if (files_same) + local_file = pristine_file; + else + SVN_ERR(svn_wc__internal_translated_file( + &local_file, local_abspath, + db, local_abspath, + SVN_WC_TRANSLATE_TO_NF + | SVN_WC_TRANSLATE_USE_GLOBAL_TMP, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + + if (! files_same) + SVN_ERR(svn_io_files_contents_same_p(&files_same, local_file, + pristine_file, scratch_pool)); + + if (had_props) + SVN_ERR(svn_wc__db_base_get_props(&base_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + base_props = apr_hash_make(scratch_pool); + + if (status == svn_wc__db_status_normal && (diff_pristine || !props_mod)) + local_props = base_props; + else if (diff_pristine) + SVN_ERR(svn_wc__db_read_pristine_props(&local_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(svn_wc__db_read_props(&local_props, db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_prop_diffs(&prop_changes, local_props, base_props, scratch_pool)); + + if (prop_changes->nelts || !files_same) + { + SVN_ERR(processor->file_changed(relpath, + left_src, + right_src, + pristine_file, + local_file, + base_props, + local_props, + ! files_same, + prop_changes, + file_baton, + processor, + scratch_pool)); + } + else + { + SVN_ERR(processor->file_closed(relpath, + left_src, + right_src, + file_baton, + processor, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +ensure_local_info(struct dir_baton_t *db, + apr_pool_t *scratch_pool) +{ + apr_hash_t *conflicts; + + if (db->local_info) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__db_read_children_info(&db->local_info, &conflicts, + db->eb->db, db->local_abspath, + db->pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Called when the directory is closed to compare any elements that have + * not yet been compared. This identifies local, working copy only + * changes. At this stage we are dealing with files/directories that do + * exist in the working copy. + * + * DIR_BATON is the baton for the directory. + */ +static svn_error_t * +walk_local_nodes_diff(struct edit_baton_t *eb, + const char *local_abspath, + const char *path, + svn_depth_t depth, + apr_hash_t *compared, + void *parent_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = eb->db; + svn_boolean_t in_anchor_not_target; + apr_pool_t *iterpool; + void *dir_baton = NULL; + svn_boolean_t skip = FALSE; + svn_boolean_t skip_children = FALSE; + svn_revnum_t revision; + svn_boolean_t props_mod; + svn_diff_source_t *left_src; + svn_diff_source_t *right_src; + + /* Everything we do below is useless if we are comparing to BASE. */ + if (eb->diff_pristine) + return SVN_NO_ERROR; + + /* Determine if this is the anchor directory if the anchor is different + to the target. When the target is a file, the anchor is the parent + directory and if this is that directory the non-target entries must be + skipped. */ + in_anchor_not_target = ((*path == '\0') && (*eb->target != '\0')); + + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_wc__db_read_info(NULL, NULL, &revision, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &props_mod, NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool)); + + left_src = svn_diff__source_create(revision, scratch_pool); + right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); + + if (compared) + { + dir_baton = parent_baton; + skip = TRUE; + } + else if (!in_anchor_not_target) + SVN_ERR(eb->processor->dir_opened(&dir_baton, &skip, &skip_children, + path, + left_src, + right_src, + NULL /* copyfrom_src */, + parent_baton, + eb->processor, + scratch_pool, scratch_pool)); + + + if (!skip_children && depth != svn_depth_empty) + { + apr_hash_t *nodes; + apr_hash_t *conflicts; + apr_array_header_t *children; + svn_depth_t depth_below_here = depth; + svn_boolean_t diff_files; + svn_boolean_t diff_dirs; + int i; + + if (depth_below_here == svn_depth_immediates) + depth_below_here = svn_depth_empty; + + diff_files = (depth == svn_depth_unknown + || depth >= svn_depth_files); + diff_dirs = (depth == svn_depth_unknown + || depth >= svn_depth_immediates); + + SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, + db, local_abspath, + scratch_pool, iterpool)); + + children = svn_sort__hash(nodes, svn_sort_compare_items_lexically, + scratch_pool); + + for (i = 0; i < children->nelts; i++) + { + svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, + svn_sort__item_t); + const char *name = item->key; + struct svn_wc__db_info_t *info = item->value; + + const char *child_abspath; + const char *child_relpath; + svn_boolean_t repos_only; + svn_boolean_t local_only; + svn_node_kind_t base_kind; + + if (eb->cancel_func) + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + /* In the anchor directory, if the anchor is not the target then all + entries other than the target should not be diff'd. Running diff + on one file in a directory should not diff other files in that + directory. */ + if (in_anchor_not_target && strcmp(eb->target, name)) + continue; + + if (compared && svn_hash_gets(compared, name)) + continue; + + if (NOT_PRESENT(info->status)) + continue; + + assert(info->status == svn_wc__db_status_normal + || info->status == svn_wc__db_status_added + || info->status == svn_wc__db_status_deleted); + + svn_pool_clear(iterpool); + child_abspath = svn_dirent_join(local_abspath, name, iterpool); + child_relpath = svn_relpath_join(path, name, iterpool); + + repos_only = FALSE; + local_only = FALSE; + + if (!info->have_base) + { + local_only = TRUE; /* Only report additions */ + } + else if (info->status == svn_wc__db_status_normal) + { + /* Simple diff */ + base_kind = info->kind; + } + else if (info->status == svn_wc__db_status_deleted + && (!eb->diff_pristine || !info->have_more_work)) + { + svn_wc__db_status_t base_status; + repos_only = TRUE; + SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, child_abspath, + iterpool, iterpool)); + + if (NOT_PRESENT(base_status)) + continue; + } + else + { + /* working status is either added or deleted */ + svn_wc__db_status_t base_status; + + SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, child_abspath, + iterpool, iterpool)); + + if (NOT_PRESENT(base_status)) + local_only = TRUE; + else if (base_kind != info->kind || !eb->ignore_ancestry) + { + repos_only = TRUE; + local_only = TRUE; + } + } + + if (eb->local_before_remote && local_only) + { + if (info->kind == svn_node_file && diff_files) + SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, + child_relpath, + eb->processor, dir_baton, + eb->changelist_hash, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + else if (info->kind == svn_node_dir && diff_dirs) + SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, + child_relpath, + depth_below_here, + eb->processor, dir_baton, + eb->changelist_hash, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + } + + if (repos_only) + { + /* Report repository form deleted */ + if (base_kind == svn_node_file && diff_files) + SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath, + child_relpath, eb->revnum, + eb->processor, dir_baton, + iterpool)); + else if (base_kind == svn_node_dir && diff_dirs) + SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath, + child_relpath, eb->revnum, + depth_below_here, + eb->processor, dir_baton, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + } + else if (!local_only) /* Not local only nor remote only */ + { + /* Diff base against actual */ + if (info->kind == svn_node_file && diff_files) + { + if (info->status != svn_wc__db_status_normal + || !eb->diff_pristine) + { + SVN_ERR(svn_wc__diff_base_working_diff( + db, child_abspath, + child_relpath, + eb->revnum, + eb->changelist_hash, + eb->processor, dir_baton, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + scratch_pool)); + } + } + else if (info->kind == svn_node_dir && diff_dirs) + SVN_ERR(walk_local_nodes_diff(eb, child_abspath, + child_relpath, + depth_below_here, + NULL /* compared */, + dir_baton, + scratch_pool)); + } + + if (!eb->local_before_remote && local_only) + { + if (info->kind == svn_node_file && diff_files) + SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, + child_relpath, + eb->processor, dir_baton, + eb->changelist_hash, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + else if (info->kind == svn_node_dir && diff_dirs) + SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, + child_relpath, depth_below_here, + eb->processor, dir_baton, + eb->changelist_hash, + eb->diff_pristine, + eb->cancel_func, + eb->cancel_baton, + iterpool)); + } + } + } + + if (compared) + return SVN_NO_ERROR; + + /* Check for local property mods on this directory, if we haven't + already reported them and we aren't changelist-filted. + ### it should be noted that we do not currently allow directories + ### to be part of changelists, so if a changelist is provided, the + ### changelist check will always fail. */ + if (! skip + && ! eb->changelist_hash + && ! in_anchor_not_target + && props_mod) + { + apr_array_header_t *propchanges; + apr_hash_t *left_props; + apr_hash_t *right_props; + + SVN_ERR(svn_wc__internal_propdiff(&propchanges, &left_props, + db, local_abspath, + scratch_pool, scratch_pool)); + + right_props = svn_prop__patch(left_props, propchanges, scratch_pool); + + SVN_ERR(eb->processor->dir_changed(path, + left_src, + right_src, + left_props, + right_props, + propchanges, + dir_baton, + eb->processor, + scratch_pool)); + } + else if (! skip) + SVN_ERR(eb->processor->dir_closed(path, + left_src, + right_src, + dir_baton, + eb->processor, + scratch_pool)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__diff_local_only_file(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + apr_hash_t *changelist_hash, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_diff_source_t *right_src; + svn_diff_source_t *copyfrom_src = NULL; + svn_wc__db_status_t status; + svn_node_kind_t kind; + const svn_checksum_t *checksum; + const char *original_repos_relpath; + svn_revnum_t original_revision; + const char *changelist; + svn_boolean_t had_props; + svn_boolean_t props_mod; + apr_hash_t *pristine_props; + apr_hash_t *right_props = NULL; + const char *pristine_file; + const char *translated_file; + svn_revnum_t revision; + void *file_baton = NULL; + svn_boolean_t skip = FALSE; + svn_boolean_t file_mod = TRUE; + + SVN_ERR(svn_wc__db_read_info(&status, &kind, &revision, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &checksum, NULL, + &original_repos_relpath, NULL, NULL, + &original_revision, NULL, NULL, NULL, + &changelist, NULL, NULL, &had_props, + &props_mod, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + assert(kind == svn_node_file + && (status == svn_wc__db_status_normal + || status == svn_wc__db_status_added + || (status == svn_wc__db_status_deleted && diff_pristine))); + + + if (changelist && changelist_hash + && !svn_hash_gets(changelist_hash, changelist)) + return SVN_NO_ERROR; + + if (status == svn_wc__db_status_deleted) + { + assert(diff_pristine); + + SVN_ERR(svn_wc__db_read_pristine_info(&status, &kind, NULL, NULL, NULL, + NULL, &checksum, NULL, &had_props, + &pristine_props, + db, local_abspath, + scratch_pool, scratch_pool)); + props_mod = FALSE; + } + else if (!had_props) + pristine_props = apr_hash_make(scratch_pool); + else + SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, + db, local_abspath, + scratch_pool, scratch_pool)); + + if (original_repos_relpath) + { + copyfrom_src = svn_diff__source_create(original_revision, scratch_pool); + copyfrom_src->repos_relpath = original_repos_relpath; + } + + if (props_mod || !SVN_IS_VALID_REVNUM(revision)) + right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); + else + { + if (diff_pristine) + file_mod = FALSE; + else + SVN_ERR(svn_wc__internal_file_modified_p(&file_mod, db, local_abspath, + FALSE, scratch_pool)); + + if (!file_mod) + right_src = svn_diff__source_create(revision, scratch_pool); + else + right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); + } + + SVN_ERR(processor->file_opened(&file_baton, &skip, + relpath, + NULL /* left_source */, + right_src, + copyfrom_src, + processor_parent_baton, + processor, + scratch_pool, scratch_pool)); + + if (skip) + return SVN_NO_ERROR; + + if (props_mod && !diff_pristine) + SVN_ERR(svn_wc__db_read_props(&right_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + right_props = svn_prop_hash_dup(pristine_props, scratch_pool); + + if (checksum) + SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, db, local_abspath, + checksum, scratch_pool, scratch_pool)); + else + pristine_file = NULL; + + if (diff_pristine) + { + translated_file = pristine_file; /* No translation needed */ + } + else + { + SVN_ERR(svn_wc__internal_translated_file( + &translated_file, local_abspath, db, local_abspath, + SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + } + + SVN_ERR(processor->file_added(relpath, + copyfrom_src, + right_src, + copyfrom_src + ? pristine_file + : NULL, + translated_file, + copyfrom_src + ? pristine_props + : NULL, + right_props, + file_baton, + processor, + scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__diff_local_only_dir(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_depth_t depth, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + apr_hash_t *changelist_hash, + svn_boolean_t diff_pristine, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const apr_array_header_t *children; + int i; + apr_pool_t *iterpool; + void *pdb = NULL; + svn_boolean_t skip = FALSE; + svn_boolean_t skip_children = FALSE; + svn_diff_source_t *right_src = svn_diff__source_create(SVN_INVALID_REVNUM, + scratch_pool); + svn_depth_t depth_below_here = depth; + apr_hash_t *nodes; + apr_hash_t *conflicts; + + /* Report the addition of the directory's contents. */ + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(processor->dir_opened(&pdb, &skip, &skip_children, + relpath, + NULL, + right_src, + NULL /* copyfrom_src */, + processor_parent_baton, + processor, + scratch_pool, iterpool)); + + SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, db, local_abspath, + scratch_pool, iterpool)); + + if (depth_below_here == svn_depth_immediates) + depth_below_here = svn_depth_empty; + + children = svn_sort__hash(nodes, svn_sort_compare_items_lexically, + scratch_pool); + + for (i = 0; i < children->nelts; i++) + { + svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, svn_sort__item_t); + const char *name = item->key; + struct svn_wc__db_info_t *info = item->value; + const char *child_abspath; + const char *child_relpath; + + svn_pool_clear(iterpool); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + child_abspath = svn_dirent_join(local_abspath, name, iterpool); + + if (NOT_PRESENT(info->status)) + { + continue; + } + + /* If comparing against WORKING, skip entries that are + schedule-deleted - they don't really exist. */ + if (!diff_pristine && info->status == svn_wc__db_status_deleted) + continue; + + child_relpath = svn_relpath_join(relpath, name, iterpool); + + switch (info->kind) + { + case svn_node_file: + case svn_node_symlink: + SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, + child_relpath, + processor, pdb, + changelist_hash, + diff_pristine, + cancel_func, cancel_baton, + scratch_pool)); + break; + + case svn_node_dir: + if (depth > svn_depth_files || depth == svn_depth_unknown) + { + SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, + child_relpath, depth_below_here, + processor, pdb, + changelist_hash, + diff_pristine, + cancel_func, cancel_baton, + iterpool)); + } + break; + + default: + break; + } + } + + if (!skip) + { + apr_hash_t *right_props; + if (diff_pristine) + SVN_ERR(svn_wc__db_read_pristine_props(&right_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(svn_wc__get_actual_props(&right_props, db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(processor->dir_added(relpath, + NULL /* copyfrom_src */, + right_src, + NULL, + right_props, + pdb, + processor, + iterpool)); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Reports local changes. */ +static svn_error_t * +handle_local_only(struct dir_baton_t *pb, + const char *name, + apr_pool_t *scratch_pool) +{ + struct edit_baton_t *eb = pb->eb; + const struct svn_wc__db_info_t *info; + svn_boolean_t repos_delete = (pb->deletes + && svn_hash_gets(pb->deletes, name)); + + assert(!strchr(name, '/')); + assert(!pb->added || eb->ignore_ancestry); + + if (pb->skip_children) + return SVN_NO_ERROR; + + SVN_ERR(ensure_local_info(pb, scratch_pool)); + + info = svn_hash_gets(pb->local_info, name); + + if (info == NULL || NOT_PRESENT(info->status)) + return SVN_NO_ERROR; + + switch (info->status) + { + case svn_wc__db_status_incomplete: + return SVN_NO_ERROR; /* Not local only */ + + case svn_wc__db_status_normal: + if (!repos_delete) + return SVN_NO_ERROR; /* Local and remote */ + svn_hash_sets(pb->deletes, name, NULL); + break; + + case svn_wc__db_status_deleted: + if (!(eb->diff_pristine && repos_delete)) + return SVN_NO_ERROR; + break; + + case svn_wc__db_status_added: + default: + break; + } + + if (info->kind == svn_node_dir) + { + svn_depth_t depth ; + + if (pb->depth == svn_depth_infinity || pb->depth == svn_depth_unknown) + depth = pb->depth; + else + depth = svn_depth_empty; + + SVN_ERR(svn_wc__diff_local_only_dir( + eb->db, + svn_dirent_join(pb->local_abspath, name, scratch_pool), + svn_relpath_join(pb->relpath, name, scratch_pool), + repos_delete ? svn_depth_infinity : depth, + eb->processor, pb->pdb, + eb->changelist_hash, + eb->diff_pristine, + eb->cancel_func, eb->cancel_baton, + scratch_pool)); + } + else + SVN_ERR(svn_wc__diff_local_only_file( + eb->db, + svn_dirent_join(pb->local_abspath, name, scratch_pool), + svn_relpath_join(pb->relpath, name, scratch_pool), + eb->processor, pb->pdb, + eb->changelist_hash, + eb->diff_pristine, + eb->cancel_func, eb->cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Reports a file LOCAL_ABSPATH in BASE as deleted */ +svn_error_t * +svn_wc__diff_base_only_file(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + const svn_checksum_t *checksum; + apr_hash_t *props; + void *file_baton = NULL; + svn_boolean_t skip = FALSE; + svn_diff_source_t *left_src; + const char *pristine_file; + + SVN_ERR(svn_wc__db_base_get_info(&status, &kind, + SVN_IS_VALID_REVNUM(revision) + ? NULL : &revision, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &checksum, NULL, NULL, NULL, &props, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR_ASSERT(status == svn_wc__db_status_normal + && kind == svn_node_file + && checksum); + + left_src = svn_diff__source_create(revision, scratch_pool); + + SVN_ERR(processor->file_opened(&file_baton, &skip, + relpath, + left_src, + NULL /* right_src */, + NULL /* copyfrom_source */, + processor_parent_baton, + processor, + scratch_pool, scratch_pool)); + + if (skip) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, + db, local_abspath, checksum, + scratch_pool, scratch_pool)); + + SVN_ERR(processor->file_deleted(relpath, + left_src, + pristine_file, + props, + file_baton, + processor, + scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__diff_base_only_dir(svn_wc__db_t *db, + const char *local_abspath, + const char *relpath, + svn_revnum_t revision, + svn_depth_t depth, + const svn_diff_tree_processor_t *processor, + void *processor_parent_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + void *dir_baton = NULL; + svn_boolean_t skip = FALSE; + svn_boolean_t skip_children = FALSE; + svn_diff_source_t *left_src; + svn_revnum_t report_rev = revision; + + if (!SVN_IS_VALID_REVNUM(report_rev)) + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &report_rev, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + left_src = svn_diff__source_create(report_rev, scratch_pool); + + SVN_ERR(processor->dir_opened(&dir_baton, &skip, &skip_children, + relpath, + left_src, + NULL /* right_src */, + NULL /* copyfrom_src */, + processor_parent_baton, + processor, + scratch_pool, scratch_pool)); + + if (!skip_children && (depth == svn_depth_unknown || depth > svn_depth_empty)) + { + apr_hash_t *nodes; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_array_header_t *children; + int i; + + SVN_ERR(svn_wc__db_base_get_children_info(&nodes, db, local_abspath, + scratch_pool, iterpool)); + + children = svn_sort__hash(nodes, svn_sort_compare_items_lexically, + scratch_pool); + + for (i = 0; i < children->nelts; i++) + { + svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, + svn_sort__item_t); + const char *name = item->key; + struct svn_wc__db_base_info_t *info = item->value; + const char *child_abspath; + const char *child_relpath; + + if (info->status != svn_wc__db_status_normal) + continue; + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + svn_pool_clear(iterpool); + + child_abspath = svn_dirent_join(local_abspath, name, iterpool); + child_relpath = svn_relpath_join(relpath, name, iterpool); + + switch (info->kind) + { + case svn_node_file: + case svn_node_symlink: + SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath, + child_relpath, + revision, + processor, dir_baton, + iterpool)); + break; + case svn_node_dir: + if (depth > svn_depth_files || depth == svn_depth_unknown) + { + svn_depth_t depth_below_here = depth; + + if (depth_below_here == svn_depth_immediates) + depth_below_here = svn_depth_empty; + + SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath, + child_relpath, + revision, + depth_below_here, + processor, dir_baton, + cancel_func, + cancel_baton, + iterpool)); + } + break; + + default: + break; + } + } + } + + if (!skip) + { + apr_hash_t *props; + SVN_ERR(svn_wc__db_base_get_props(&props, db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(processor->dir_deleted(relpath, + left_src, + props, + dir_baton, + processor, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +set_target_revision(void *edit_baton, + svn_revnum_t target_revision, + apr_pool_t *pool) +{ + struct edit_baton_t *eb = edit_baton; + eb->revnum = target_revision; + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. The root of the comparison hierarchy */ +static svn_error_t * +open_root(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *dir_pool, + void **root_baton) +{ + struct edit_baton_t *eb = edit_baton; + struct dir_baton_t *db; + + eb->root_opened = TRUE; + db = make_dir_baton("", NULL, eb, FALSE, eb->depth, dir_pool); + *root_baton = db; + + if (eb->target[0] == '\0') + { + db->left_src = svn_diff__source_create(eb->revnum, db->pool); + db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool); + + SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, + &db->skip_children, + "", + db->left_src, + db->right_src, + NULL /* copyfrom_source */, + NULL /* parent_baton */, + eb->processor, + db->pool, db->pool)); + } + else + db->skip = TRUE; /* Skip this, but not the children */ + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t base_revision, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton_t *pb = parent_baton; + const char *name = svn_dirent_basename(path, pb->pool); + + if (!pb->deletes) + pb->deletes = apr_hash_make(pb->pool); + + svn_hash_sets(pb->deletes, name, ""); + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +add_directory(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *dir_pool, + void **child_baton) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct dir_baton_t *db; + svn_depth_t subdir_depth = (pb->depth == svn_depth_immediates) + ? svn_depth_empty : pb->depth; + + db = make_dir_baton(path, pb, pb->eb, TRUE, subdir_depth, + dir_pool); + *child_baton = db; + + if (pb->repos_only || !eb->ignore_ancestry) + db->repos_only = TRUE; + else + { + struct svn_wc__db_info_t *info; + SVN_ERR(ensure_local_info(pb, dir_pool)); + + info = svn_hash_gets(pb->local_info, db->name); + + if (!info || info->kind != svn_node_dir || NOT_PRESENT(info->status)) + db->repos_only = TRUE; + + if (!db->repos_only && info->status != svn_wc__db_status_added) + db->repos_only = TRUE; + + if (!db->repos_only) + { + db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool); + db->ignoring_ancestry = TRUE; + + svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, db->name), ""); + } + } + + db->left_src = svn_diff__source_create(eb->revnum, db->pool); + + if (eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, db->name, dir_pool)); + + SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, &db->skip_children, + db->relpath, + db->left_src, + db->right_src, + NULL /* copyfrom src */, + pb->pdb, + eb->processor, + db->pool, db->pool)); + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +open_directory(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *dir_pool, + void **child_baton) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct dir_baton_t *db; + svn_depth_t subdir_depth = (pb->depth == svn_depth_immediates) + ? svn_depth_empty : pb->depth; + + /* Allocate path from the parent pool since the memory is used in the + parent's compared hash */ + db = make_dir_baton(path, pb, pb->eb, FALSE, subdir_depth, dir_pool); + *child_baton = db; + + if (pb->repos_only) + db->repos_only = TRUE; + else + { + struct svn_wc__db_info_t *info; + SVN_ERR(ensure_local_info(pb, dir_pool)); + + info = svn_hash_gets(pb->local_info, db->name); + + if (!info || info->kind != svn_node_dir || NOT_PRESENT(info->status)) + db->repos_only = TRUE; + + if (!db->repos_only) + switch (info->status) + { + case svn_wc__db_status_normal: + break; + case svn_wc__db_status_deleted: + db->repos_only = TRUE; + + if (!info->have_more_work) + svn_hash_sets(pb->compared, + apr_pstrdup(pb->pool, db->name), ""); + break; + case svn_wc__db_status_added: + if (eb->ignore_ancestry) + db->ignoring_ancestry = TRUE; + else + db->repos_only = TRUE; + break; + default: + SVN_ERR_MALFUNCTION(); + } + + if (!db->repos_only) + { + db->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, db->pool); + svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, db->name), ""); + } + } + + db->left_src = svn_diff__source_create(eb->revnum, db->pool); + + if (eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, db->name, dir_pool)); + + SVN_ERR(eb->processor->dir_opened(&db->pdb, &db->skip, &db->skip_children, + db->relpath, + db->left_src, + db->right_src, + NULL /* copyfrom src */, + pb->pdb, + eb->processor, + db->pool, db->pool)); + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. When a directory is closed, all the + * directory elements that have been added or replaced will already have been + * diff'd. However there may be other elements in the working copy + * that have not yet been considered. */ +static svn_error_t * +close_directory(void *dir_baton, + apr_pool_t *pool) +{ + struct dir_baton_t *db = dir_baton; + struct dir_baton_t *pb = db->parent_baton; + struct edit_baton_t *eb = db->eb; + apr_pool_t *scratch_pool = db->pool; + svn_boolean_t reported_closed = FALSE; + + if (!db->skip_children && db->deletes && apr_hash_count(db->deletes)) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_array_header_t *children; + int i; + children = svn_sort__hash(db->deletes, svn_sort_compare_items_lexically, + scratch_pool); + + for (i = 0; i < children->nelts; i++) + { + svn_sort__item_t *item = &APR_ARRAY_IDX(children, i, + svn_sort__item_t); + const char *name = item->key; + + svn_pool_clear(iterpool); + SVN_ERR(handle_local_only(db, name, iterpool)); + + svn_hash_sets(db->compared, name, ""); + } + + svn_pool_destroy(iterpool); + } + + /* Report local modifications for this directory. Skip added + directories since they can only contain added elements, all of + which have already been diff'd. */ + if (!db->repos_only && !db->skip_children) + { + SVN_ERR(walk_local_nodes_diff(eb, + db->local_abspath, + db->relpath, + db->depth, + db->compared, + db->pdb, + scratch_pool)); + } + + /* Report the property changes on the directory itself, if necessary. */ + if (db->skip) + { + /* Diff processor requested no directory details */ + } + else if (db->propchanges->nelts > 0 || db->repos_only) + { + apr_hash_t *repos_props; + + if (db->added) + { + repos_props = apr_hash_make(scratch_pool); + } + else + { + SVN_ERR(svn_wc__db_base_get_props(&repos_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + } + + /* Add received property changes and entry props */ + if (db->propchanges->nelts) + repos_props = svn_prop__patch(repos_props, db->propchanges, + scratch_pool); + + if (db->repos_only) + { + SVN_ERR(eb->processor->dir_deleted(db->relpath, + db->left_src, + repos_props, + db->pdb, + eb->processor, + scratch_pool)); + reported_closed = TRUE; + } + else + { + apr_hash_t *local_props; + apr_array_header_t *prop_changes; + + if (eb->diff_pristine) + SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + &local_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(svn_wc__db_read_props(&local_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_prop_diffs(&prop_changes, local_props, repos_props, + scratch_pool)); + + /* ### as a good diff processor we should now only report changes + if there are non-entry changes, but for now we stick to + compatibility */ + + if (prop_changes->nelts) + { + SVN_ERR(eb->processor->dir_changed(db->relpath, + db->left_src, + db->right_src, + repos_props, + local_props, + prop_changes, + db->pdb, + eb->processor, + scratch_pool)); + reported_closed = TRUE; + } + } + } + + /* Mark this directory as compared in the parent directory's baton, + unless this is the root of the comparison. */ + if (!reported_closed && !db->skip) + SVN_ERR(eb->processor->dir_closed(db->relpath, + db->left_src, + db->right_src, + db->pdb, + eb->processor, + scratch_pool)); + + if (pb && !eb->local_before_remote && !db->repos_only && !db->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, db->name, scratch_pool)); + + SVN_ERR(maybe_done(db)); /* destroys scratch_pool */ + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +add_file(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *file_pool, + void **file_baton) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct file_baton_t *fb; + + fb = make_file_baton(path, TRUE, pb, file_pool); + *file_baton = fb; + + if (pb->skip_children) + { + fb->skip = TRUE; + return SVN_NO_ERROR; + } + else if (pb->repos_only || !eb->ignore_ancestry) + fb->repos_only = TRUE; + else + { + struct svn_wc__db_info_t *info; + SVN_ERR(ensure_local_info(pb, file_pool)); + + info = svn_hash_gets(pb->local_info, fb->name); + + if (!info || info->kind != svn_node_file || NOT_PRESENT(info->status)) + fb->repos_only = TRUE; + + if (!fb->repos_only && info->status != svn_wc__db_status_added) + fb->repos_only = TRUE; + + if (!fb->repos_only) + { + /* Add this path to the parent directory's list of elements that + have been compared. */ + fb->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, fb->pool); + fb->ignoring_ancestry = TRUE; + + svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, fb->name), ""); + } + } + + fb->left_src = svn_diff__source_create(eb->revnum, fb->pool); + + SVN_ERR(eb->processor->file_opened(&fb->pfb, &fb->skip, + fb->relpath, + fb->left_src, + fb->right_src, + NULL /* copyfrom src */, + pb->pdb, + eb->processor, + fb->pool, fb->pool)); + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +open_file(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *file_pool, + void **file_baton) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct file_baton_t *fb; + + fb = make_file_baton(path, FALSE, pb, file_pool); + *file_baton = fb; + + if (pb->skip_children) + fb->skip = TRUE; + else if (pb->repos_only) + fb->repos_only = TRUE; + else + { + struct svn_wc__db_info_t *info; + SVN_ERR(ensure_local_info(pb, file_pool)); + + info = svn_hash_gets(pb->local_info, fb->name); + + if (!info || info->kind != svn_node_file || NOT_PRESENT(info->status)) + fb->repos_only = TRUE; + + if (!fb->repos_only) + switch (info->status) + { + case svn_wc__db_status_normal: + break; + case svn_wc__db_status_deleted: + fb->repos_only = TRUE; + if (!info->have_more_work) + svn_hash_sets(pb->compared, + apr_pstrdup(pb->pool, fb->name), ""); + break; + case svn_wc__db_status_added: + if (eb->ignore_ancestry) + fb->ignoring_ancestry = TRUE; + else + fb->repos_only = TRUE; + break; + default: + SVN_ERR_MALFUNCTION(); + } + + if (!fb->repos_only) + { + /* Add this path to the parent directory's list of elements that + have been compared. */ + fb->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, fb->pool); + svn_hash_sets(pb->compared, apr_pstrdup(pb->pool, fb->name), ""); + } + } + + fb->left_src = svn_diff__source_create(eb->revnum, fb->pool); + + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &fb->base_checksum, NULL, + NULL, NULL, &fb->base_props, NULL, + eb->db, fb->local_abspath, + fb->pool, fb->pool)); + + SVN_ERR(eb->processor->file_opened(&fb->pfb, &fb->skip, + fb->relpath, + fb->left_src, + fb->right_src, + NULL /* copyfrom src */, + pb->pdb, + eb->processor, + fb->pool, fb->pool)); + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +apply_textdelta(void *file_baton, + const char *base_checksum_hex, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct file_baton_t *fb = file_baton; + struct edit_baton_t *eb = fb->eb; + svn_stream_t *source; + svn_stream_t *temp_stream; + svn_checksum_t *repos_checksum = NULL; + + if (fb->skip) + { + *handler = svn_delta_noop_window_handler; + *handler_baton = NULL; + return SVN_NO_ERROR; + } + + if (base_checksum_hex && fb->base_checksum) + { + const svn_checksum_t *base_md5; + SVN_ERR(svn_checksum_parse_hex(&repos_checksum, svn_checksum_md5, + base_checksum_hex, pool)); + + SVN_ERR(svn_wc__db_pristine_get_md5(&base_md5, + eb->db, eb->anchor_abspath, + fb->base_checksum, + pool, pool)); + + if (! svn_checksum_match(repos_checksum, base_md5)) + { + /* ### I expect that there are some bad drivers out there + ### that used to give bad results. We could look in + ### working to see if the expected checksum matches and + ### then return the pristine of that... But that only moves + ### the problem */ + + /* If needed: compare checksum obtained via md5 of working. + And if they match set fb->base_checksum and fb->base_props */ + + return svn_checksum_mismatch_err( + base_md5, + repos_checksum, + pool, + _("Checksum mismatch for '%s'"), + svn_dirent_local_style(fb->local_abspath, + pool)); + } + + SVN_ERR(svn_wc__db_pristine_read(&source, NULL, + eb->db, fb->local_abspath, + fb->base_checksum, + pool, pool)); + } + else if (fb->base_checksum) + { + SVN_ERR(svn_wc__db_pristine_read(&source, NULL, + eb->db, fb->local_abspath, + fb->base_checksum, + pool, pool)); + } + else + source = svn_stream_empty(pool); + + /* This is the file that will contain the pristine repository version. */ + SVN_ERR(svn_stream_open_unique(&temp_stream, &fb->temp_file_path, NULL, + svn_io_file_del_on_pool_cleanup, + fb->pool, fb->pool)); + + svn_txdelta_apply(source, temp_stream, + fb->result_digest, + fb->local_abspath /* error_info */, + fb->pool, + handler, handler_baton); + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. When the file is closed we have a temporary + * file containing a pristine version of the repository file. This can + * be compared against the working copy. + * + * Ignore TEXT_CHECKSUM. + */ +static svn_error_t * +close_file(void *file_baton, + const char *expected_md5_digest, + apr_pool_t *pool) +{ + struct file_baton_t *fb = file_baton; + struct dir_baton_t *pb = fb->parent_baton; + struct edit_baton_t *eb = fb->eb; + apr_pool_t *scratch_pool = fb->pool; + + /* The repository information; constructed from BASE + Changes */ + const char *repos_file; + apr_hash_t *repos_props; + + if (!fb->skip && expected_md5_digest != NULL) + { + svn_checksum_t *expected_checksum; + const svn_checksum_t *result_checksum; + + if (fb->temp_file_path) + result_checksum = svn_checksum__from_digest_md5(fb->result_digest, + scratch_pool); + else + result_checksum = fb->base_checksum; + + SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, + expected_md5_digest, scratch_pool)); + + if (result_checksum->kind != svn_checksum_md5) + SVN_ERR(svn_wc__db_pristine_get_md5(&result_checksum, + eb->db, fb->local_abspath, + result_checksum, + scratch_pool, scratch_pool)); + + if (!svn_checksum_match(expected_checksum, result_checksum)) + return svn_checksum_mismatch_err( + expected_checksum, + result_checksum, + pool, + _("Checksum mismatch for '%s'"), + svn_dirent_local_style(fb->local_abspath, + scratch_pool)); + } + + if (eb->local_before_remote && !fb->repos_only && !fb->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, fb->name, scratch_pool)); + + { + apr_hash_t *prop_base; + + if (fb->added) + prop_base = apr_hash_make(scratch_pool); + else + prop_base = fb->base_props; + + /* includes entry props */ + repos_props = svn_prop__patch(prop_base, fb->propchanges, scratch_pool); + + repos_file = fb->temp_file_path; + if (! repos_file) + { + assert(fb->base_checksum); + SVN_ERR(svn_wc__db_pristine_get_path(&repos_file, + eb->db, eb->anchor_abspath, + fb->base_checksum, + scratch_pool, scratch_pool)); + } + } + + if (fb->skip) + { + /* Diff processor requested skipping information */ + } + else if (fb->repos_only) + { + SVN_ERR(eb->processor->file_deleted(fb->relpath, + fb->left_src, + fb->temp_file_path, + repos_props, + fb->pfb, + eb->processor, + scratch_pool)); + } + else + { + /* Produce a diff of actual or pristine against repos */ + apr_hash_t *local_props; + apr_array_header_t *prop_changes; + const char *localfile; + + /* pb->local_info contains some information that might allow optimizing + this a bit */ + + if (eb->diff_pristine) + { + const svn_checksum_t *checksum; + SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, NULL, NULL, NULL, + NULL, &checksum, NULL, NULL, + &local_props, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool)); + assert(checksum); + SVN_ERR(svn_wc__db_pristine_get_path(&localfile, + eb->db, eb->anchor_abspath, + checksum, + scratch_pool, scratch_pool)); + } + else + { + SVN_ERR(svn_wc__db_read_props(&local_props, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool)); + + /* a detranslated version of the working file */ + SVN_ERR(svn_wc__internal_translated_file( + &localfile, fb->local_abspath, eb->db, fb->local_abspath, + SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_USE_GLOBAL_TMP, + eb->cancel_func, eb->cancel_baton, + scratch_pool, scratch_pool)); + } + + SVN_ERR(svn_prop_diffs(&prop_changes, local_props, repos_props, + scratch_pool)); + + + /* ### as a good diff processor we should now only report changes, and + report file_closed() in other cases */ + SVN_ERR(eb->processor->file_changed(fb->relpath, + fb->left_src, + fb->right_src, + repos_file /* left file */, + localfile /* right file */, + repos_props /* left_props */, + local_props /* right props */, + TRUE /* ### file_modified */, + prop_changes, + fb->pfb, + eb->processor, + scratch_pool)); + } + + if (!eb->local_before_remote && !fb->repos_only && !fb->ignoring_ancestry) + SVN_ERR(handle_local_only(pb, fb->name, scratch_pool)); + + svn_pool_destroy(fb->pool); /* destroys scratch_pool and fb */ + SVN_ERR(maybe_done(pb)); + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +change_file_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct file_baton_t *fb = file_baton; + svn_prop_t *propchange; + svn_prop_kind_t propkind; + + propkind = svn_property_kind2(name); + if (propkind == svn_prop_wc_kind) + return SVN_NO_ERROR; + else if (propkind == svn_prop_regular_kind) + fb->has_propchange = TRUE; + + propchange = apr_array_push(fb->propchanges); + propchange->name = apr_pstrdup(fb->pool, name); + propchange->value = value ? svn_string_dup(value, fb->pool) : NULL; + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +change_dir_prop(void *dir_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct dir_baton_t *db = dir_baton; + svn_prop_t *propchange; + svn_prop_kind_t propkind; + + propkind = svn_property_kind2(name); + if (propkind == svn_prop_wc_kind) + return SVN_NO_ERROR; + else if (propkind == svn_prop_regular_kind) + db->has_propchange = TRUE; + + propchange = apr_array_push(db->propchanges); + propchange->name = apr_pstrdup(db->pool, name); + propchange->value = value ? svn_string_dup(value, db->pool) : NULL; + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +close_edit(void *edit_baton, + apr_pool_t *pool) +{ + struct edit_baton_t *eb = edit_baton; + + if (!eb->root_opened) + { + SVN_ERR(walk_local_nodes_diff(eb, + eb->anchor_abspath, + "", + eb->depth, + NULL /* compared */, + NULL /* No parent_baton */, + eb->pool)); + } + + return SVN_NO_ERROR; +} + +/* Public Interface */ + + +/* Create a diff editor and baton. */ +svn_error_t * +svn_wc__get_diff_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_wc_context_t *wc_ctx, + const char *anchor_abspath, + const char *target, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t show_copies_as_adds, + svn_boolean_t use_git_diff_format, + svn_boolean_t use_text_base, + svn_boolean_t reverse_order, + svn_boolean_t server_performs_filtering, + const apr_array_header_t *changelist_filter, + const svn_wc_diff_callbacks4_t *callbacks, + void *callback_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct edit_baton_t *eb; + void *inner_baton; + svn_delta_editor_t *tree_editor; + const svn_delta_editor_t *inner_editor; + struct svn_wc__shim_fetch_baton_t *sfb; + svn_delta_shim_callbacks_t *shim_callbacks = + svn_delta_shim_callbacks_default(result_pool); + + SVN_ERR_ASSERT(svn_dirent_is_absolute(anchor_abspath)); + + /* --git implies --show-copies-as-adds */ + if (use_git_diff_format) + show_copies_as_adds = TRUE; + + SVN_ERR(make_edit_baton(&eb, + wc_ctx->db, + anchor_abspath, target, + callbacks, callback_baton, + depth, ignore_ancestry, show_copies_as_adds, + use_text_base, reverse_order, changelist_filter, + cancel_func, cancel_baton, + result_pool)); + + tree_editor = svn_delta_default_editor(eb->pool); + + tree_editor->set_target_revision = set_target_revision; + tree_editor->open_root = open_root; + tree_editor->delete_entry = delete_entry; + tree_editor->add_directory = add_directory; + tree_editor->open_directory = open_directory; + tree_editor->close_directory = close_directory; + tree_editor->add_file = add_file; + tree_editor->open_file = open_file; + tree_editor->apply_textdelta = apply_textdelta; + tree_editor->change_file_prop = change_file_prop; + tree_editor->change_dir_prop = change_dir_prop; + tree_editor->close_file = close_file; + tree_editor->close_edit = close_edit; + + inner_editor = tree_editor; + inner_baton = eb; + + if (!server_performs_filtering + && depth == svn_depth_unknown) + SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor, + &inner_baton, + wc_ctx->db, + anchor_abspath, + target, + inner_editor, + inner_baton, + result_pool)); + + SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, + cancel_baton, + inner_editor, + inner_baton, + editor, + edit_baton, + result_pool)); + + sfb = apr_palloc(result_pool, sizeof(*sfb)); + sfb->db = wc_ctx->db; + sfb->base_abspath = eb->anchor_abspath; + sfb->fetch_base = TRUE; + + shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func; + shim_callbacks->fetch_props_func = svn_wc__fetch_props_func; + shim_callbacks->fetch_base_func = svn_wc__fetch_base_func; + shim_callbacks->fetch_baton = sfb; + + + SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, + NULL, NULL, shim_callbacks, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Wrapping svn_wc_diff_callbacks4_t as svn_diff_tree_processor_t */ + +/* baton for the svn_diff_tree_processor_t wrapper */ +typedef struct wc_diff_wrap_baton_t +{ + const svn_wc_diff_callbacks4_t *callbacks; + void *callback_baton; + + svn_boolean_t walk_deleted_dirs; + + apr_pool_t *result_pool; + const char *empty_file; + +} wc_diff_wrap_baton_t; + +static svn_error_t * +wrap_ensure_empty_file(wc_diff_wrap_baton_t *wb, + apr_pool_t *scratch_pool) +{ + if (wb->empty_file) + return SVN_NO_ERROR; + + /* Create a unique file in the tempdir */ + SVN_ERR(svn_io_open_unique_file3(NULL, &wb->empty_file, NULL, + svn_io_file_del_on_pool_cleanup, + wb->result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_opened(void **new_dir_baton, + svn_boolean_t *skip, + svn_boolean_t *skip_children, + const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + const svn_diff_source_t *copyfrom_source, + void *parent_dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + + assert(left_source || right_source); + assert(!copyfrom_source || !right_source); + + /* Maybe store state and tree_conflicted in baton? */ + if (left_source != NULL) + { + /* Open for change or delete */ + SVN_ERR(wb->callbacks->dir_opened(&tree_conflicted, skip, skip_children, + relpath, + right_source + ? right_source->revision + : (left_source + ? left_source->revision + : SVN_INVALID_REVNUM), + wb->callback_baton, + scratch_pool)); + + if (! right_source && !wb->walk_deleted_dirs) + *skip_children = TRUE; + } + else /* left_source == NULL -> Add */ + { + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + SVN_ERR(wb->callbacks->dir_added(&state, &tree_conflicted, + skip, skip_children, + relpath, + right_source->revision, + copyfrom_source + ? copyfrom_source->repos_relpath + : NULL, + copyfrom_source + ? copyfrom_source->revision + : SVN_INVALID_REVNUM, + wb->callback_baton, + scratch_pool)); + } + + *new_dir_baton = NULL; + + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_added(const char *relpath, + const svn_diff_source_t *right_source, + const svn_diff_source_t *copyfrom_source, + /*const*/ apr_hash_t *copyfrom_props, + /*const*/ apr_hash_t *right_props, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_unknown; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown; + apr_hash_t *pristine_props = copyfrom_props; + apr_array_header_t *prop_changes = NULL; + + if (right_props && apr_hash_count(right_props)) + { + if (!pristine_props) + pristine_props = apr_hash_make(scratch_pool); + + SVN_ERR(svn_prop_diffs(&prop_changes, right_props, pristine_props, + scratch_pool)); + + SVN_ERR(wb->callbacks->dir_props_changed(&prop_state, + &tree_conflicted, + relpath, + TRUE /* dir_was_added */, + prop_changes, pristine_props, + wb->callback_baton, + scratch_pool)); + } + + SVN_ERR(wb->callbacks->dir_closed(&state, &prop_state, + &tree_conflicted, + relpath, + TRUE /* dir_was_added */, + wb->callback_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_deleted(const char *relpath, + const svn_diff_source_t *left_source, + /*const*/ apr_hash_t *left_props, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + + SVN_ERR(wb->callbacks->dir_deleted(&state, &tree_conflicted, + relpath, + wb->callback_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_closed(const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + + /* No previous implementations provided these arguments, so we + are not providing them either */ + SVN_ERR(wb->callbacks->dir_closed(NULL, NULL, NULL, + relpath, + FALSE /* added */, + wb->callback_baton, + scratch_pool)); + +return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_dir_changed(const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + /*const*/ apr_hash_t *left_props, + /*const*/ apr_hash_t *right_props, + const apr_array_header_t *prop_changes, + void *dir_baton, + const struct svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable; + + assert(left_source && right_source); + + SVN_ERR(wb->callbacks->dir_props_changed(&prop_state, &tree_conflicted, + relpath, + FALSE /* dir_was_added */, + prop_changes, + left_props, + wb->callback_baton, + scratch_pool)); + + /* And call dir_closed, etc */ + SVN_ERR(wrap_dir_closed(relpath, left_source, right_source, + dir_baton, processor, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_file_opened(void **new_file_baton, + svn_boolean_t *skip, + const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + const svn_diff_source_t *copyfrom_source, + void *dir_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + + if (left_source) /* If ! added */ + SVN_ERR(wb->callbacks->file_opened(&tree_conflicted, skip, relpath, + right_source + ? right_source->revision + : (left_source + ? left_source->revision + : SVN_INVALID_REVNUM), + wb->callback_baton, scratch_pool)); + + /* No old implementation used the output arguments for notify */ + + *new_file_baton = NULL; + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_file_added(const char *relpath, + const svn_diff_source_t *copyfrom_source, + const svn_diff_source_t *right_source, + const char *copyfrom_file, + const char *right_file, + /*const*/ apr_hash_t *copyfrom_props, + /*const*/ apr_hash_t *right_props, + void *file_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable; + apr_array_header_t *prop_changes; + + if (! copyfrom_props) + copyfrom_props = apr_hash_make(scratch_pool); + + SVN_ERR(svn_prop_diffs(&prop_changes, right_props, copyfrom_props, + scratch_pool)); + + if (! copyfrom_source) + SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool)); + + SVN_ERR(wb->callbacks->file_added(&state, &prop_state, &tree_conflicted, + relpath, + copyfrom_source + ? copyfrom_file + : wb->empty_file, + right_file, + 0, + right_source->revision, + copyfrom_props + ? svn_prop_get_value(copyfrom_props, + SVN_PROP_MIME_TYPE) + : NULL, + right_props + ? svn_prop_get_value(right_props, + SVN_PROP_MIME_TYPE) + : NULL, + copyfrom_source + ? copyfrom_source->repos_relpath + : NULL, + copyfrom_source + ? copyfrom_source->revision + : SVN_INVALID_REVNUM, + prop_changes, copyfrom_props, + wb->callback_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +wrap_file_deleted(const char *relpath, + const svn_diff_source_t *left_source, + const char *left_file, + apr_hash_t *left_props, + void *file_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + + SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool)); + + SVN_ERR(wb->callbacks->file_deleted(&state, &tree_conflicted, + relpath, + left_file, wb->empty_file, + left_props + ? svn_prop_get_value(left_props, + SVN_PROP_MIME_TYPE) + : NULL, + NULL, + left_props, + wb->callback_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* svn_diff_tree_processor_t function */ +static svn_error_t * +wrap_file_changed(const char *relpath, + const svn_diff_source_t *left_source, + const svn_diff_source_t *right_source, + const char *left_file, + const char *right_file, + /*const*/ apr_hash_t *left_props, + /*const*/ apr_hash_t *right_props, + svn_boolean_t file_modified, + const apr_array_header_t *prop_changes, + void *file_baton, + const svn_diff_tree_processor_t *processor, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wb = processor->baton; + svn_boolean_t tree_conflicted = FALSE; + svn_wc_notify_state_t state = svn_wc_notify_state_inapplicable; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_inapplicable; + + SVN_ERR(wrap_ensure_empty_file(wb, scratch_pool)); + + assert(left_source && right_source); + + SVN_ERR(wb->callbacks->file_changed(&state, &prop_state, &tree_conflicted, + relpath, + file_modified ? left_file : NULL, + file_modified ? right_file : NULL, + left_source->revision, + right_source->revision, + left_props + ? svn_prop_get_value(left_props, + SVN_PROP_MIME_TYPE) + : NULL, + right_props + ? svn_prop_get_value(right_props, + SVN_PROP_MIME_TYPE) + : NULL, + prop_changes, + left_props, + wb->callback_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__wrap_diff_callbacks(const svn_diff_tree_processor_t **diff_processor, + const svn_wc_diff_callbacks4_t *callbacks, + void *callback_baton, + svn_boolean_t walk_deleted_dirs, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_diff_wrap_baton_t *wrap_baton; + svn_diff_tree_processor_t *processor; + + wrap_baton = apr_pcalloc(result_pool, sizeof(*wrap_baton)); + + wrap_baton->result_pool = result_pool; + wrap_baton->callbacks = callbacks; + wrap_baton->callback_baton = callback_baton; + wrap_baton->empty_file = NULL; + wrap_baton->walk_deleted_dirs = walk_deleted_dirs; + + processor = svn_diff__tree_processor_create(wrap_baton, result_pool); + + processor->dir_opened = wrap_dir_opened; + processor->dir_added = wrap_dir_added; + processor->dir_deleted = wrap_dir_deleted; + processor->dir_changed = wrap_dir_changed; + processor->dir_closed = wrap_dir_closed; + + processor->file_opened = wrap_file_opened; + processor->file_added = wrap_file_added; + processor->file_deleted = wrap_file_deleted; + processor->file_changed = wrap_file_changed; + /*processor->file_closed = wrap_file_closed*/; /* Not needed */ + + *diff_processor = processor; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/diff_local.c b/subversion/libsvn_wc/diff_local.c new file mode 100644 index 000000000000..ad87c76a6033 --- /dev/null +++ b/subversion/libsvn_wc/diff_local.c @@ -0,0 +1,541 @@ +/* + * diff_pristine.c -- A simple diff walker which compares local files against + * their pristine versions. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This is the simple working copy diff algorithm which is used when you + * just use 'svn diff PATH'. It shows what is modified in your working copy + * since a node was checked out or copied but doesn't show most kinds of + * restructuring operations. + * + * You can look at this as another form of the status walker. + */ + +#include <apr_hash.h> + +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" + +#include "private/svn_wc_private.h" +#include "private/svn_diff_tree.h" + +#include "wc.h" +#include "props.h" +#include "translate.h" +#include "diff.h" + +#include "svn_private_config.h" + +/*-------------------------------------------------------------------------*/ + +/* Baton containing the state of a directory + reported open via a diff processor */ +struct node_state_t +{ + struct node_state_t *parent; + + apr_pool_t *pool; + + const char *local_abspath; + const char *relpath; + void *baton; + + svn_diff_source_t *left_src; + svn_diff_source_t *right_src; + svn_diff_source_t *copy_src; + + svn_boolean_t skip; + svn_boolean_t skip_children; + + apr_hash_t *left_props; + apr_hash_t *right_props; + const apr_array_header_t *propchanges; +}; + +/* The diff baton */ +struct diff_baton +{ + /* A wc db. */ + svn_wc__db_t *db; + + /* Report editor paths relative from this directory */ + const char *anchor_abspath; + + struct node_state_t *cur; + + const svn_diff_tree_processor_t *processor; + + /* Should this diff ignore node ancestry? */ + svn_boolean_t ignore_ancestry; + + /* Should this diff not compare copied files with their source? */ + svn_boolean_t show_copies_as_adds; + + /* Hash whose keys are const char * changelist names. */ + apr_hash_t *changelist_hash; + + /* Cancel function/baton */ + svn_cancel_func_t cancel_func; + void *cancel_baton; + + apr_pool_t *pool; +}; + +/* Recursively opens directories on the stack in EB, until LOCAL_ABSPATH + is reached. If RECURSIVE_SKIP is TRUE, don't open LOCAL_ABSPATH itself, + but create it marked with skip+skip_children. + */ +static svn_error_t * +ensure_state(struct diff_baton *eb, + const char *local_abspath, + svn_boolean_t recursive_skip, + apr_pool_t *scratch_pool) +{ + struct node_state_t *ns; + apr_pool_t *ns_pool; + if (!eb->cur) + { + if (!svn_dirent_is_ancestor(eb->anchor_abspath, local_abspath)) + return SVN_NO_ERROR; + + SVN_ERR(ensure_state(eb, + svn_dirent_dirname(local_abspath,scratch_pool), + FALSE, + scratch_pool)); + } + else if (svn_dirent_is_child(eb->cur->local_abspath, local_abspath, NULL)) + SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath,scratch_pool), + FALSE, + scratch_pool)); + else + return SVN_NO_ERROR; + + if (eb->cur && eb->cur->skip_children) + return SVN_NO_ERROR; + + ns_pool = svn_pool_create(eb->cur ? eb->cur->pool : eb->pool); + ns = apr_pcalloc(ns_pool, sizeof(*ns)); + + ns->pool = ns_pool; + ns->local_abspath = apr_pstrdup(ns_pool, local_abspath); + ns->relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, ns->local_abspath); + ns->parent = eb->cur; + eb->cur = ns; + + if (recursive_skip) + { + ns->skip = TRUE; + ns->skip_children = TRUE; + return SVN_NO_ERROR; + } + + { + svn_revnum_t revision; + svn_error_t *err; + + err = svn_wc__db_base_get_info(NULL, NULL, &revision, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + eb->db, local_abspath, + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + svn_error_clear(err); + + revision = 0; /* Use original revision? */ + } + ns->left_src = svn_diff__source_create(revision, ns->pool); + ns->right_src = svn_diff__source_create(SVN_INVALID_REVNUM, ns->pool); + + SVN_ERR(eb->processor->dir_opened(&ns->baton, &ns->skip, + &ns->skip_children, + ns->relpath, + ns->left_src, + ns->right_src, + NULL /* copyfrom_source */, + ns->parent ? ns->parent->baton : NULL, + eb->processor, + ns->pool, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* Implements svn_wc_status_func3_t */ +static svn_error_t * +diff_status_callback(void *baton, + const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + struct diff_baton *eb = baton; + svn_wc__db_t *db = eb->db; + + switch (status->node_status) + { + case svn_wc_status_unversioned: + case svn_wc_status_ignored: + return SVN_NO_ERROR; /* No diff */ + + case svn_wc_status_conflicted: + if (status->text_status == svn_wc_status_none + && status->prop_status == svn_wc_status_none) + { + /* Node is an actual only node describing a tree conflict */ + return SVN_NO_ERROR; + } + break; + + default: + break; /* Go check other conditions */ + } + + /* Not text/prop modified, not copied. Easy out */ + if (status->node_status == svn_wc_status_normal && !status->copied) + return SVN_NO_ERROR; + + /* Mark all directories where we are no longer inside as closed */ + while (eb->cur + && !svn_dirent_is_ancestor(eb->cur->local_abspath, local_abspath)) + { + struct node_state_t *ns = eb->cur; + + if (!ns->skip) + { + if (ns->propchanges) + SVN_ERR(eb->processor->dir_changed(ns->relpath, + ns->left_src, + ns->right_src, + ns->left_props, + ns->right_props, + ns->propchanges, + ns->baton, + eb->processor, + ns->pool)); + else + SVN_ERR(eb->processor->dir_closed(ns->relpath, + ns->left_src, + ns->right_src, + ns->baton, + eb->processor, + ns->pool)); + } + eb->cur = ns->parent; + svn_pool_clear(ns->pool); + } + SVN_ERR(ensure_state(eb, svn_dirent_dirname(local_abspath, scratch_pool), + FALSE, scratch_pool)); + + if (eb->cur && eb->cur->skip_children) + return SVN_NO_ERROR; + + if (eb->changelist_hash != NULL + && (!status->changelist + || ! svn_hash_gets(eb->changelist_hash, status->changelist))) + return SVN_NO_ERROR; /* Filtered via changelist */ + + /* This code does about the same thing as the inner body of + walk_local_nodes_diff() in diff_editor.c, except that + it is already filtered by the status walker, doesn't have to + account for remote changes (and many tiny other details) */ + + { + svn_boolean_t repos_only; + svn_boolean_t local_only; + svn_wc__db_status_t db_status; + svn_boolean_t have_base; + svn_node_kind_t base_kind; + svn_node_kind_t db_kind = status->kind; + svn_depth_t depth_below_here = svn_depth_unknown; + + const char *child_abspath = local_abspath; + const char *child_relpath = svn_dirent_skip_ancestor(eb->anchor_abspath, + local_abspath); + + + repos_only = FALSE; + local_only = FALSE; + + /* ### optimize away this call using status info. Should + be possible in almost every case (except conflict, missing, obst.)*/ + SVN_ERR(svn_wc__db_read_info(&db_status, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + &have_base, NULL, NULL, + eb->db, local_abspath, + scratch_pool, scratch_pool)); + if (!have_base) + { + local_only = TRUE; /* Only report additions */ + } + else if (db_status == svn_wc__db_status_normal) + { + /* Simple diff */ + base_kind = db_kind; + } + else if (db_status == svn_wc__db_status_deleted) + { + svn_wc__db_status_t base_status; + repos_only = TRUE; + SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + eb->db, local_abspath, + scratch_pool, scratch_pool)); + + if (base_status != svn_wc__db_status_normal) + return SVN_NO_ERROR; + } + else + { + /* working status is either added or deleted */ + svn_wc__db_status_t base_status; + + SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + eb->db, local_abspath, + scratch_pool, scratch_pool)); + + if (base_status != svn_wc__db_status_normal) + local_only = TRUE; + else if (base_kind != db_kind || !eb->ignore_ancestry) + { + repos_only = TRUE; + local_only = TRUE; + } + } + + if (repos_only) + { + /* Report repository form deleted */ + if (base_kind == svn_node_file) + SVN_ERR(svn_wc__diff_base_only_file(db, child_abspath, + child_relpath, + SVN_INVALID_REVNUM, + eb->processor, + eb->cur ? eb->cur->baton : NULL, + scratch_pool)); + else if (base_kind == svn_node_dir) + SVN_ERR(svn_wc__diff_base_only_dir(db, child_abspath, + child_relpath, + SVN_INVALID_REVNUM, + depth_below_here, + eb->processor, + eb->cur ? eb->cur->baton : NULL, + eb->cancel_func, + eb->cancel_baton, + scratch_pool)); + } + else if (!local_only) + { + /* Diff base against actual */ + if (db_kind == svn_node_file) + { + SVN_ERR(svn_wc__diff_base_working_diff(db, child_abspath, + child_relpath, + SVN_INVALID_REVNUM, + eb->changelist_hash, + eb->processor, + eb->cur + ? eb->cur->baton + : NULL, + FALSE, + eb->cancel_func, + eb->cancel_baton, + scratch_pool)); + } + else if (db_kind == svn_node_dir) + { + SVN_ERR(ensure_state(eb, local_abspath, FALSE, scratch_pool)); + + if (status->prop_status != svn_wc_status_none + && status->prop_status != svn_wc_status_normal) + { + apr_array_header_t *propchanges; + SVN_ERR(svn_wc__db_base_get_props(&eb->cur->left_props, + eb->db, local_abspath, + eb->cur->pool, + scratch_pool)); + SVN_ERR(svn_wc__db_read_props(&eb->cur->right_props, + eb->db, local_abspath, + eb->cur->pool, + scratch_pool)); + + SVN_ERR(svn_prop_diffs(&propchanges, + eb->cur->right_props, + eb->cur->left_props, + eb->cur->pool)); + + eb->cur->propchanges = propchanges; + } + } + } + + if (local_only) + { + if (db_kind == svn_node_file) + SVN_ERR(svn_wc__diff_local_only_file(db, child_abspath, + child_relpath, + eb->processor, + eb->cur ? eb->cur->baton : NULL, + eb->changelist_hash, + FALSE, + eb->cancel_func, + eb->cancel_baton, + scratch_pool)); + else if (db_kind == svn_node_dir) + SVN_ERR(svn_wc__diff_local_only_dir(db, child_abspath, + child_relpath, depth_below_here, + eb->processor, + eb->cur ? eb->cur->baton : NULL, + eb->changelist_hash, + FALSE, + eb->cancel_func, + eb->cancel_baton, + scratch_pool)); + } + + if (db_kind == svn_node_dir && (local_only || repos_only)) + SVN_ERR(ensure_state(eb, local_abspath, TRUE /* skip */, scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +/* Public Interface */ +svn_error_t * +svn_wc_diff6(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const svn_wc_diff_callbacks4_t *callbacks, + void *callback_baton, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t show_copies_as_adds, + svn_boolean_t use_git_diff_format, + const apr_array_header_t *changelist_filter, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + struct diff_baton eb = { 0 }; + svn_node_kind_t kind; + svn_boolean_t get_all; + const svn_diff_tree_processor_t *processor; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR(svn_wc__db_read_kind(&kind, wc_ctx->db, local_abspath, + FALSE /* allow_missing */, + TRUE /* show_deleted */, + FALSE /* show_hidden */, + scratch_pool)); + + if (kind == svn_node_dir) + eb.anchor_abspath = local_abspath; + else + eb.anchor_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + SVN_ERR(svn_wc__wrap_diff_callbacks(&processor, + callbacks, callback_baton, TRUE, + scratch_pool, scratch_pool)); + + if (use_git_diff_format) + show_copies_as_adds = TRUE; + if (show_copies_as_adds) + ignore_ancestry = FALSE; + + + + /* + if (reverse_order) + processor = svn_diff__tree_processor_reverse_create(processor, NULL, pool); + */ + + if (! show_copies_as_adds && !use_git_diff_format) + processor = svn_diff__tree_processor_copy_as_changed_create(processor, + scratch_pool); + + eb.db = wc_ctx->db; + eb.processor = processor; + eb.ignore_ancestry = ignore_ancestry; + eb.show_copies_as_adds = show_copies_as_adds; + eb.pool = scratch_pool; + + if (changelist_filter && changelist_filter->nelts) + SVN_ERR(svn_hash_from_cstring_keys(&eb.changelist_hash, changelist_filter, + scratch_pool)); + + if (show_copies_as_adds || use_git_diff_format || !ignore_ancestry) + get_all = TRUE; /* We need unmodified descendants of copies */ + else + get_all = FALSE; + + /* Walk status handles files and directories */ + SVN_ERR(svn_wc__internal_walk_status(wc_ctx->db, local_abspath, depth, + get_all, + TRUE /* no_ignore */, + FALSE /* ignore_text_mods */, + NULL /* ignore_patterns */, + diff_status_callback, &eb, + cancel_func, cancel_baton, + scratch_pool)); + + /* Close the remaining open directories */ + while (eb.cur) + { + struct node_state_t *ns = eb.cur; + + if (!ns->skip) + { + if (ns->propchanges) + SVN_ERR(processor->dir_changed(ns->relpath, + ns->left_src, + ns->right_src, + ns->left_props, + ns->right_props, + ns->propchanges, + ns->baton, + processor, + ns->pool)); + else + SVN_ERR(processor->dir_closed(ns->relpath, + ns->left_src, + ns->right_src, + ns->baton, + processor, + ns->pool)); + } + eb.cur = ns->parent; + svn_pool_clear(ns->pool); + } + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/entries.c b/subversion/libsvn_wc/entries.c new file mode 100644 index 000000000000..f6a73bf33134 --- /dev/null +++ b/subversion/libsvn_wc/entries.c @@ -0,0 +1,2738 @@ +/* + * entries.c : manipulating the administrative `entries' file. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <string.h> +#include <assert.h> + +#include <apr_strings.h> + +#include "svn_error.h" +#include "svn_types.h" +#include "svn_time.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_ctype.h" +#include "svn_string.h" +#include "svn_hash.h" + +#include "wc.h" +#include "adm_files.h" +#include "conflicts.h" +#include "entries.h" +#include "lock.h" +#include "tree_conflicts.h" +#include "wc_db.h" +#include "wc-queries.h" /* for STMT_* */ + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" +#include "private/svn_sqlite.h" + +#define MAYBE_ALLOC(x,p) ((x) ? (x) : apr_pcalloc((p), sizeof(*(x)))) + + +/* Temporary structures which mirror the tables in wc-metadata.sql. + For detailed descriptions of each field, see that file. */ +typedef struct db_node_t { + apr_int64_t wc_id; + const char *local_relpath; + int op_depth; + apr_int64_t repos_id; + const char *repos_relpath; + const char *parent_relpath; + svn_wc__db_status_t presence; + svn_revnum_t revision; + svn_node_kind_t kind; + svn_checksum_t *checksum; + svn_filesize_t recorded_size; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + svn_depth_t depth; + apr_time_t recorded_time; + apr_hash_t *properties; + svn_boolean_t file_external; + apr_array_header_t *inherited_props; +} db_node_t; + +typedef struct db_actual_node_t { + apr_int64_t wc_id; + const char *local_relpath; + const char *parent_relpath; + apr_hash_t *properties; + const char *conflict_old; + const char *conflict_new; + const char *conflict_working; + const char *prop_reject; + const char *changelist; + /* ### enum for text_mod */ + const char *tree_conflict_data; +} db_actual_node_t; + + + +/*** reading and writing the entries file ***/ + + +/* */ +static svn_wc_entry_t * +alloc_entry(apr_pool_t *pool) +{ + svn_wc_entry_t *entry = apr_pcalloc(pool, sizeof(*entry)); + entry->revision = SVN_INVALID_REVNUM; + entry->copyfrom_rev = SVN_INVALID_REVNUM; + entry->cmt_rev = SVN_INVALID_REVNUM; + entry->kind = svn_node_none; + entry->working_size = SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN; + entry->depth = svn_depth_infinity; + entry->file_external_peg_rev.kind = svn_opt_revision_unspecified; + entry->file_external_rev.kind = svn_opt_revision_unspecified; + return entry; +} + + +/* Is the entry in a 'hidden' state in the sense of the 'show_hidden' + * switches on svn_wc_entries_read(), svn_wc_walk_entries*(), etc.? */ +svn_error_t * +svn_wc__entry_is_hidden(svn_boolean_t *hidden, const svn_wc_entry_t *entry) +{ + /* In English, the condition is: "the entry is not present, and I haven't + scheduled something over the top of it." */ + if (entry->deleted + || entry->absent + || entry->depth == svn_depth_exclude) + { + /* These kinds of nodes cannot be marked for deletion (which also + means no "replace" either). */ + SVN_ERR_ASSERT(entry->schedule == svn_wc_schedule_add + || entry->schedule == svn_wc_schedule_normal); + + /* Hidden if something hasn't been added over it. + + ### is this even possible with absent or excluded nodes? */ + *hidden = entry->schedule != svn_wc_schedule_add; + } + else + *hidden = FALSE; + + return SVN_NO_ERROR; +} + + +/* Hit the database to check the file external information for the given + entry. The entry will be modified in place. */ +static svn_error_t * +check_file_external(svn_wc_entry_t *entry, + svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + const char *repos_relpath; + svn_revnum_t peg_revision; + svn_revnum_t revision; + svn_error_t *err; + + err = svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, + &repos_relpath, &peg_revision, &revision, + db, local_abspath, wri_abspath, + result_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + return SVN_NO_ERROR; + } + + if (status == svn_wc__db_status_normal + && kind == svn_node_file) + { + entry->file_external_path = repos_relpath; + if (SVN_IS_VALID_REVNUM(peg_revision)) + { + entry->file_external_peg_rev.kind = svn_opt_revision_number; + entry->file_external_peg_rev.value.number = peg_revision; + entry->file_external_rev = entry->file_external_peg_rev; + } + if (SVN_IS_VALID_REVNUM(revision)) + { + entry->file_external_rev.kind = svn_opt_revision_number; + entry->file_external_rev.value.number = revision; + } + } + + return SVN_NO_ERROR; +} + + +/* Fill in the following fields of ENTRY: + + REVISION + REPOS + UUID + CMT_REV + CMT_DATE + CMT_AUTHOR + DEPTH + DELETED + + Return: KIND, REPOS_RELPATH, CHECKSUM +*/ +static svn_error_t * +get_info_for_deleted(svn_wc_entry_t *entry, + svn_node_kind_t *kind, + const char **repos_relpath, + const svn_checksum_t **checksum, + svn_wc__db_lock_t **lock, + svn_wc__db_t *db, + const char *entry_abspath, + const svn_wc_entry_t *parent_entry, + svn_boolean_t have_base, + svn_boolean_t have_more_work, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (have_base && !have_more_work) + { + /* This is the delete of a BASE node */ + SVN_ERR(svn_wc__db_base_get_info(NULL, kind, + &entry->revision, + repos_relpath, + &entry->repos, + &entry->uuid, + &entry->cmt_rev, + &entry->cmt_date, + &entry->cmt_author, + &entry->depth, + checksum, + NULL, + lock, + &entry->has_props, NULL, + NULL, + db, + entry_abspath, + result_pool, + scratch_pool)); + } + else + { + const char *work_del_abspath; + const char *parent_repos_relpath; + const char *parent_abspath; + + /* This is a deleted child of a copy/move-here, + so we need to scan up the WORKING tree to find the root of + the deletion. Then examine its parent to discover its + future location in the repository. */ + SVN_ERR(svn_wc__db_read_pristine_info(NULL, kind, + &entry->cmt_rev, + &entry->cmt_date, + &entry->cmt_author, + &entry->depth, + checksum, + NULL, + &entry->has_props, NULL, + db, + entry_abspath, + result_pool, + scratch_pool)); + /* working_size and text_time unavailable */ + + SVN_ERR(svn_wc__db_scan_deletion(NULL, + NULL, + &work_del_abspath, NULL, + db, entry_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR_ASSERT(work_del_abspath != NULL); + parent_abspath = svn_dirent_dirname(work_del_abspath, scratch_pool); + + /* The parent directory of the delete root must be added, so we + can find the required information there */ + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, + &parent_repos_relpath, + &entry->repos, + &entry->uuid, + NULL, NULL, NULL, NULL, + db, parent_abspath, + result_pool, scratch_pool)); + + /* Now glue it all together */ + *repos_relpath = svn_relpath_join(parent_repos_relpath, + svn_dirent_is_child(parent_abspath, + entry_abspath, + NULL), + result_pool); + + + /* Even though this is the delete of a WORKING node, there might still + be a BASE node somewhere below with an interesting revision */ + if (have_base) + { + svn_wc__db_status_t status; + SVN_ERR(svn_wc__db_base_get_info(&status, NULL, &entry->revision, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, lock, NULL, NULL, + NULL, + db, entry_abspath, + result_pool, scratch_pool)); + + if (status == svn_wc__db_status_not_present) + entry->deleted = TRUE; + } + } + + /* Do some extra work for the child nodes. */ + if (!SVN_IS_VALID_REVNUM(entry->revision) && parent_entry != NULL) + { + /* For child nodes without a revision, pick up the parent's + revision. */ + entry->revision = parent_entry->revision; + } + + return SVN_NO_ERROR; +} + + +/* + * Encode tree conflict descriptions into a single string. + * + * Set *CONFLICT_DATA to a string, allocated in POOL, that encodes the tree + * conflicts in CONFLICTS in a form suitable for storage in a single string + * field in a WC entry. CONFLICTS is a hash of zero or more pointers to + * svn_wc_conflict_description2_t objects, index by their basenames. All of the + * conflict victim paths must be siblings. + * + * Do all allocations in POOL. + * + * @see svn_wc__read_tree_conflicts() + */ +static svn_error_t * +write_tree_conflicts(const char **conflict_data, + apr_hash_t *conflicts, + apr_pool_t *pool) +{ + svn_skel_t *skel = svn_skel__make_empty_list(pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, conflicts); hi; hi = apr_hash_next(hi)) + { + svn_skel_t *c_skel; + + SVN_ERR(svn_wc__serialize_conflict(&c_skel, svn__apr_hash_index_val(hi), + pool, pool)); + svn_skel__prepend(c_skel, skel); + } + + *conflict_data = svn_skel__unparse(skel, pool)->data; + + return SVN_NO_ERROR; +} + + +/* Read one entry from wc_db. It will be allocated in RESULT_POOL and + returned in *NEW_ENTRY. + + DIR_ABSPATH is the name of the directory to read this entry from, and + it will be named NAME (use "" for "this dir"). + + DB specifies the wc_db database, and WC_ID specifies which working copy + this information is being read from. + + If this node is "this dir", then PARENT_ENTRY should be NULL. Otherwise, + it should refer to the entry for the child's parent directory. + + Temporary allocations are made in SCRATCH_POOL. */ +static svn_error_t * +read_one_entry(const svn_wc_entry_t **new_entry, + svn_wc__db_t *db, + apr_int64_t wc_id, + const char *dir_abspath, + const char *name, + const svn_wc_entry_t *parent_entry, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t kind; + svn_wc__db_status_t status; + svn_wc__db_lock_t *lock; + const char *repos_relpath; + const svn_checksum_t *checksum; + svn_filesize_t translated_size; + svn_wc_entry_t *entry = alloc_entry(result_pool); + const char *entry_abspath; + const char *original_repos_relpath; + const char *original_root_url; + svn_boolean_t conflicted; + svn_boolean_t have_base; + svn_boolean_t have_more_work; + + entry->name = name; + + entry_abspath = svn_dirent_join(dir_abspath, entry->name, scratch_pool); + + SVN_ERR(svn_wc__db_read_info( + &status, + &kind, + &entry->revision, + &repos_relpath, + &entry->repos, + &entry->uuid, + &entry->cmt_rev, + &entry->cmt_date, + &entry->cmt_author, + &entry->depth, + &checksum, + NULL, + &original_repos_relpath, + &original_root_url, + NULL, + &entry->copyfrom_rev, + &lock, + &translated_size, + &entry->text_time, + &entry->changelist, + &conflicted, + NULL /* op_root */, + &entry->has_props /* have_props */, + &entry->has_prop_mods /* props_mod */, + &have_base, + &have_more_work, + NULL /* have_work */, + db, + entry_abspath, + result_pool, + scratch_pool)); + + if (entry->has_prop_mods) + entry->has_props = TRUE; + + if (strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) == 0) + { + /* get the tree conflict data. */ + apr_hash_t *tree_conflicts = NULL; + const apr_array_header_t *conflict_victims; + int k; + + SVN_ERR(svn_wc__db_read_conflict_victims(&conflict_victims, db, + dir_abspath, + scratch_pool, + scratch_pool)); + + for (k = 0; k < conflict_victims->nelts; k++) + { + int j; + const apr_array_header_t *child_conflicts; + const char *child_name; + const char *child_abspath; + + child_name = APR_ARRAY_IDX(conflict_victims, k, const char *); + child_abspath = svn_dirent_join(dir_abspath, child_name, + scratch_pool); + + SVN_ERR(svn_wc__read_conflicts(&child_conflicts, + db, child_abspath, + FALSE /* create tempfiles */, + scratch_pool, scratch_pool)); + + for (j = 0; j < child_conflicts->nelts; j++) + { + const svn_wc_conflict_description2_t *conflict = + APR_ARRAY_IDX(child_conflicts, j, + svn_wc_conflict_description2_t *); + + if (conflict->kind == svn_wc_conflict_kind_tree) + { + if (!tree_conflicts) + tree_conflicts = apr_hash_make(scratch_pool); + svn_hash_sets(tree_conflicts, child_name, conflict); + } + } + } + + if (tree_conflicts) + { + SVN_ERR(write_tree_conflicts(&entry->tree_conflict_data, + tree_conflicts, result_pool)); + } + } + + if (status == svn_wc__db_status_normal + || status == svn_wc__db_status_incomplete) + { + /* Plain old BASE node. */ + entry->schedule = svn_wc_schedule_normal; + + /* Grab inherited repository information, if necessary. */ + if (repos_relpath == NULL) + { + SVN_ERR(svn_wc__db_scan_base_repos(&repos_relpath, + &entry->repos, + &entry->uuid, + db, + entry_abspath, + result_pool, + scratch_pool)); + } + + entry->incomplete = (status == svn_wc__db_status_incomplete); + } + else if (status == svn_wc__db_status_deleted) + { + svn_node_kind_t path_kind; + + /* ### we don't have to worry about moves, so this is a delete. */ + entry->schedule = svn_wc_schedule_delete; + + /* If there are multiple working layers or no BASE layer, then + this is a WORKING delete or WORKING not-present. */ + if (have_more_work || !have_base) + entry->copied = TRUE; + else if (have_base && !have_more_work) + entry->copied = FALSE; + else + { + const char *work_del_abspath; + SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, + &work_del_abspath, NULL, + db, entry_abspath, + scratch_pool, scratch_pool)); + + if (work_del_abspath) + entry->copied = TRUE; + } + + /* If there is still a directory on-disk we keep it, if not it is + already deleted. Simple, isn't it? + + Before single-db we had to keep the administative area alive until + after the commit really deletes it. Setting keep alive stopped the + commit processing from deleting the directory. We don't delete it + any more, so all we have to do is provide some 'sane' value. + */ + SVN_ERR(svn_io_check_path(entry_abspath, &path_kind, scratch_pool)); + entry->keep_local = (path_kind == svn_node_dir); + } + else if (status == svn_wc__db_status_added) + { + svn_wc__db_status_t work_status; + const char *op_root_abspath; + const char *scanned_original_relpath; + svn_revnum_t original_revision; + + /* For child nodes, pick up the parent's revision. */ + if (*entry->name != '\0') + { + assert(parent_entry != NULL); + assert(entry->revision == SVN_INVALID_REVNUM); + + entry->revision = parent_entry->revision; + } + + if (have_base) + { + svn_wc__db_status_t base_status; + + /* ENTRY->REVISION is overloaded. When a node is schedule-add + or -replace, then REVISION refers to the BASE node's revision + that is being overwritten. We need to fetch it now. */ + SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, + &entry->revision, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + db, entry_abspath, + scratch_pool, + scratch_pool)); + + if (base_status == svn_wc__db_status_not_present) + { + /* The underlying node is DELETED in this revision. */ + entry->deleted = TRUE; + + /* This is an add since there isn't a node to replace. */ + entry->schedule = svn_wc_schedule_add; + } + else + entry->schedule = svn_wc_schedule_replace; + } + else + { + /* There is NO 'not-present' BASE_NODE for this node. + Therefore, we are looking at some kind of add/copy + rather than a replace. */ + + /* ### if this looks like a plain old add, then rev=0. */ + if (!SVN_IS_VALID_REVNUM(entry->copyfrom_rev) + && !SVN_IS_VALID_REVNUM(entry->cmt_rev)) + entry->revision = 0; + + entry->schedule = svn_wc_schedule_add; + } + + /* If we don't have "real" data from the entry (obstruction), + then we cannot begin a scan for data. The original node may + have important data. Set up stuff to kill that idea off, + and finish up this entry. */ + { + SVN_ERR(svn_wc__db_scan_addition(&work_status, + &op_root_abspath, + &repos_relpath, + &entry->repos, + &entry->uuid, + &scanned_original_relpath, + NULL, NULL, /* original_root|uuid */ + &original_revision, + db, + entry_abspath, + result_pool, scratch_pool)); + + /* In wc.db we want to keep the valid revision of the not-present + BASE_REV, but when we used entries we set the revision to 0 + when adding a new node over a not present base node. */ + if (work_status == svn_wc__db_status_added + && entry->deleted) + entry->revision = 0; + } + + if (!SVN_IS_VALID_REVNUM(entry->cmt_rev) + && scanned_original_relpath == NULL) + { + /* There is NOT a last-changed revision (last-changed date and + author may be unknown, but we can always check the rev). + The absence of a revision implies this node was added WITHOUT + any history. Avoid the COPIED checks in the else block. */ + /* ### scan_addition may need to be updated to avoid returning + ### status_copied in this case. */ + } + /* For backwards-compatiblity purposes we treat moves just like + * regular copies. */ + else if (work_status == svn_wc__db_status_copied || + work_status == svn_wc__db_status_moved_here) + { + entry->copied = TRUE; + + /* If this is a child of a copied subtree, then it should be + schedule_normal. */ + if (original_repos_relpath == NULL) + { + /* ### what if there is a BASE node under there? */ + entry->schedule = svn_wc_schedule_normal; + } + + /* Copied nodes need to mirror their copyfrom_rev, if they + don't have a revision of their own already. */ + if (!SVN_IS_VALID_REVNUM(entry->revision) + || entry->revision == 0 /* added */) + entry->revision = original_revision; + } + + /* Does this node have copyfrom_* information? */ + if (scanned_original_relpath != NULL) + { + svn_boolean_t is_copied_child; + svn_boolean_t is_mixed_rev = FALSE; + + SVN_ERR_ASSERT(work_status == svn_wc__db_status_copied || + work_status == svn_wc__db_status_moved_here); + + /* If this node inherits copyfrom information from an + ancestor node, then it must be a copied child. */ + is_copied_child = (original_repos_relpath == NULL); + + /* If this node has copyfrom information on it, then it may + be an actual copy-root, or it could be participating in + a mixed-revision copied tree. So if we don't already know + this is a copied child, then we need to look for this + mixed-revision situation. */ + if (!is_copied_child) + { + const char *parent_abspath; + svn_error_t *err; + const char *parent_repos_relpath; + const char *parent_root_url; + + /* When we insert entries into the database, we will + construct additional copyfrom records for mixed-revision + copies. The old entries would simply record the different + revision in the entry->revision field. That is not + available within wc-ng, so additional copies are made + (see the logic inside write_entry()). However, when + reading these back *out* of the database, the additional + copies look like new "Added" nodes rather than a simple + mixed-rev working copy. + + That would be a behavior change if we did not compensate. + If there is copyfrom information for this node, then the + code below looks at the parent to detect if it *also* has + copyfrom information, and if the copyfrom_url would align + properly. If it *does*, then we omit storing copyfrom_url + and copyfrom_rev (ie. inherit the copyfrom info like a + normal child), and update entry->revision with the + copyfrom_rev in order to (re)create the mixed-rev copied + subtree that was originally presented for storage. */ + + /* Get the copyfrom information from our parent. + + Note that the parent could be added/copied/moved-here. + There is no way for it to be deleted/moved-away and + have *this* node appear as copied. */ + parent_abspath = svn_dirent_dirname(entry_abspath, + scratch_pool); + err = svn_wc__db_scan_addition(NULL, + &op_root_abspath, + NULL, NULL, NULL, + &parent_repos_relpath, + &parent_root_url, + NULL, NULL, + db, parent_abspath, + scratch_pool, + scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + svn_error_clear(err); + } + else if (parent_root_url != NULL + && strcmp(original_root_url, parent_root_url) == 0) + { + const char *relpath_to_entry = svn_dirent_is_child( + op_root_abspath, entry_abspath, NULL); + const char *entry_repos_relpath = svn_relpath_join( + parent_repos_relpath, relpath_to_entry, scratch_pool); + + /* The copyfrom repos roots matched. + + Now we look to see if the copyfrom path of the parent + would align with our own path. If so, then it means + this copyfrom was spontaneously created and inserted + for mixed-rev purposes and can be eliminated without + changing the semantics of a mixed-rev copied subtree. + + See notes/api-errata/wc003.txt for some additional + detail, and potential issues. */ + if (strcmp(entry_repos_relpath, + original_repos_relpath) == 0) + { + is_copied_child = TRUE; + is_mixed_rev = TRUE; + } + } + } + + if (is_copied_child) + { + /* We won't be settig the copyfrom_url, yet need to + clear out the copyfrom_rev. Thus, this node becomes a + child of a copied subtree (rather than its own root). */ + entry->copyfrom_rev = SVN_INVALID_REVNUM; + + /* Children in a copied subtree are schedule normal + since we don't plan to actually *do* anything with + them. Their operation is implied by ancestors. */ + entry->schedule = svn_wc_schedule_normal; + + /* And *finally* we turn this entry into the mixed + revision node that it was intended to be. This + node's revision is taken from the copyfrom record + that we spontaneously constructed. */ + if (is_mixed_rev) + entry->revision = original_revision; + } + else if (original_repos_relpath != NULL) + { + entry->copyfrom_url = + svn_path_url_add_component2(original_root_url, + original_repos_relpath, + result_pool); + } + else + { + /* NOTE: if original_repos_relpath == NULL, then the + second call to scan_addition() will not have occurred. + Thus, this use of OP_ROOT_ABSPATH still contains the + original value where we fetched a value for + SCANNED_REPOS_RELPATH. */ + const char *relpath_to_entry = svn_dirent_is_child( + op_root_abspath, entry_abspath, NULL); + const char *entry_repos_relpath = svn_relpath_join( + scanned_original_relpath, relpath_to_entry, scratch_pool); + + entry->copyfrom_url = + svn_path_url_add_component2(original_root_url, + entry_repos_relpath, + result_pool); + } + } + } + else if (status == svn_wc__db_status_not_present) + { + /* ### buh. 'deleted' nodes are actually supposed to be + ### schedule "normal" since we aren't going to actually *do* + ### anything to this node at commit time. */ + entry->schedule = svn_wc_schedule_normal; + entry->deleted = TRUE; + } + else if (status == svn_wc__db_status_server_excluded) + { + entry->absent = TRUE; + } + else if (status == svn_wc__db_status_excluded) + { + entry->schedule = svn_wc_schedule_normal; + entry->depth = svn_depth_exclude; + } + else + { + /* ### we should have handled all possible status values. */ + SVN_ERR_MALFUNCTION(); + } + + /* ### higher levels want repos information about deleted nodes, even + ### tho they are not "part of" a repository any more. */ + if (entry->schedule == svn_wc_schedule_delete) + { + SVN_ERR(get_info_for_deleted(entry, + &kind, + &repos_relpath, + &checksum, + &lock, + db, entry_abspath, + parent_entry, + have_base, have_more_work, + result_pool, scratch_pool)); + } + + /* ### default to the infinite depth if we don't know it. */ + if (entry->depth == svn_depth_unknown) + entry->depth = svn_depth_infinity; + + if (kind == svn_node_dir) + entry->kind = svn_node_dir; + else if (kind == svn_node_file) + entry->kind = svn_node_file; + else if (kind == svn_node_symlink) + entry->kind = svn_node_file; /* ### no symlink kind */ + else + entry->kind = svn_node_unknown; + + /* We should always have a REPOS_RELPATH, except for: + - deleted nodes + - certain obstructed nodes + - not-present nodes + - absent nodes + - excluded nodes + + ### the last three should probably have an "implied" REPOS_RELPATH + */ + SVN_ERR_ASSERT(repos_relpath != NULL + || entry->schedule == svn_wc_schedule_delete + || status == svn_wc__db_status_not_present + || status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded); + if (repos_relpath) + entry->url = svn_path_url_add_component2(entry->repos, + repos_relpath, + result_pool); + + if (checksum) + { + /* We got a SHA-1, get the corresponding MD-5. */ + if (checksum->kind != svn_checksum_md5) + SVN_ERR(svn_wc__db_pristine_get_md5(&checksum, db, + entry_abspath, checksum, + scratch_pool, scratch_pool)); + + SVN_ERR_ASSERT(checksum->kind == svn_checksum_md5); + entry->checksum = svn_checksum_to_cstring(checksum, result_pool); + } + + if (conflicted) + { + svn_skel_t *conflict; + svn_boolean_t text_conflicted; + svn_boolean_t prop_conflicted; + SVN_ERR(svn_wc__db_read_conflict(&conflict, db, entry_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, &text_conflicted, + &prop_conflicted, NULL, + db, dir_abspath, conflict, + scratch_pool, scratch_pool)); + + if (text_conflicted) + { + const char *my_abspath; + const char *their_old_abspath; + const char *their_abspath; + SVN_ERR(svn_wc__conflict_read_text_conflict(&my_abspath, + &their_old_abspath, + &their_abspath, + db, dir_abspath, + conflict, scratch_pool, + scratch_pool)); + + if (my_abspath) + entry->conflict_wrk = svn_dirent_basename(my_abspath, result_pool); + + if (their_old_abspath) + entry->conflict_old = svn_dirent_basename(their_old_abspath, + result_pool); + + if (their_abspath) + entry->conflict_new = svn_dirent_basename(their_abspath, + result_pool); + } + + if (prop_conflicted) + { + const char *prej_abspath; + + SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath, NULL, + NULL, NULL, NULL, + db, dir_abspath, + conflict, scratch_pool, + scratch_pool)); + + if (prej_abspath) + entry->prejfile = svn_dirent_basename(prej_abspath, result_pool); + } + } + + if (lock) + { + entry->lock_token = lock->token; + entry->lock_owner = lock->owner; + entry->lock_comment = lock->comment; + entry->lock_creation_date = lock->date; + } + + /* Let's check for a file external. ugh. */ + if (status == svn_wc__db_status_normal + && kind == svn_node_file) + SVN_ERR(check_file_external(entry, db, entry_abspath, dir_abspath, + result_pool, scratch_pool)); + + entry->working_size = translated_size; + + *new_entry = entry; + + return SVN_NO_ERROR; +} + +/* Read entries for PATH/LOCAL_ABSPATH from DB. The entries + will be allocated in RESULT_POOL, with temporary allocations in + SCRATCH_POOL. The entries are returned in RESULT_ENTRIES. */ +static svn_error_t * +read_entries_new(apr_hash_t **result_entries, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *entries; + const apr_array_header_t *children; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + const svn_wc_entry_t *parent_entry; + apr_int64_t wc_id = 1; /* ### hacky. should remove. */ + + entries = apr_hash_make(result_pool); + + SVN_ERR(read_one_entry(&parent_entry, db, wc_id, local_abspath, + "" /* name */, + NULL /* parent_entry */, + result_pool, iterpool)); + svn_hash_sets(entries, "", parent_entry); + + /* Use result_pool so that the child names (used by reference, rather + than copied) appear in result_pool. */ + SVN_ERR(svn_wc__db_read_children(&children, db, + local_abspath, + result_pool, iterpool)); + for (i = children->nelts; i--; ) + { + const char *name = APR_ARRAY_IDX(children, i, const char *); + const svn_wc_entry_t *entry; + + svn_pool_clear(iterpool); + + SVN_ERR(read_one_entry(&entry, + db, wc_id, local_abspath, name, parent_entry, + result_pool, iterpool)); + svn_hash_sets(entries, entry->name, entry); + } + + svn_pool_destroy(iterpool); + + *result_entries = entries; + + return SVN_NO_ERROR; +} + + +/* Read a pair of entries from wc_db in the directory DIR_ABSPATH. Return + the directory's entry in *PARENT_ENTRY and NAME's entry in *ENTRY. The + two returned pointers will be the same if NAME=="" ("this dir"). + + The parent entry must exist. + + The requested entry MAY exist. If it does not, then NULL will be returned. + + The resulting entries are allocated in RESULT_POOL, and all temporary + allocations are made in SCRATCH_POOL. */ +static svn_error_t * +read_entry_pair(const svn_wc_entry_t **parent_entry, + const svn_wc_entry_t **entry, + svn_wc__db_t *db, + const char *dir_abspath, + const char *name, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_int64_t wc_id = 1; /* ### hacky. should remove. */ + + SVN_ERR(read_one_entry(parent_entry, db, wc_id, dir_abspath, + "" /* name */, + NULL /* parent_entry */, + result_pool, scratch_pool)); + + /* If we need the entry for "this dir", then return the parent_entry + in both outputs. Otherwise, read the child node. */ + if (*name == '\0') + { + /* If the retrieved node is a FILE, then we have a problem. We asked + for a directory. This implies there is an obstructing, unversioned + directory where a FILE should be. We navigated from the obstructing + subdir up to the parent dir, then returned the FILE found there. + + Let's return WC_MISSING cuz the caller thought we had a dir, but + that (versioned subdir) isn't there. */ + if ((*parent_entry)->kind == svn_node_file) + { + *parent_entry = NULL; + return svn_error_createf(SVN_ERR_WC_MISSING, NULL, + _("'%s' is not a versioned working copy"), + svn_dirent_local_style(dir_abspath, + scratch_pool)); + } + + *entry = *parent_entry; + } + else + { + const apr_array_header_t *children; + int i; + + /* Default to not finding the child. */ + *entry = NULL; + + /* Determine whether the parent KNOWS about this child. If it does + not, then we should not attempt to look for it. + + For example: the parent doesn't "know" about the child, but the + versioned directory *does* exist on disk. We don't want to look + into that subdir. */ + SVN_ERR(svn_wc__db_read_children(&children, db, dir_abspath, + scratch_pool, scratch_pool)); + for (i = children->nelts; i--; ) + { + const char *child = APR_ARRAY_IDX(children, i, const char *); + + if (strcmp(child, name) == 0) + { + svn_error_t *err; + + err = read_one_entry(entry, + db, wc_id, dir_abspath, name, *parent_entry, + result_pool, scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + /* No problem. Clear the error and leave the default value + of "missing". */ + svn_error_clear(err); + } + + /* Found it. No need to keep searching. */ + break; + } + } + /* if the loop ends without finding a child, then we have the default + ENTRY value of NULL. */ + } + + return SVN_NO_ERROR; +} + + +/* */ +static svn_error_t * +read_entries(apr_hash_t **entries, + svn_wc__db_t *db, + const char *wcroot_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int wc_format; + + SVN_ERR(svn_wc__db_temp_get_format(&wc_format, db, wcroot_abspath, + scratch_pool)); + + if (wc_format < SVN_WC__WC_NG_VERSION) + return svn_error_trace(svn_wc__read_entries_old(entries, + wcroot_abspath, + result_pool, + scratch_pool)); + + return svn_error_trace(read_entries_new(entries, db, wcroot_abspath, + result_pool, scratch_pool)); +} + + +/* For a given LOCAL_ABSPATH, using DB, set *ADM_ABSPATH to the directory in + which the entry information is located, and *ENTRY_NAME to the entry name + to access that entry. + + KIND is as in svn_wc__get_entry(). + + Return the results in RESULT_POOL and use SCRATCH_POOL for temporary + allocations. */ +static svn_error_t * +get_entry_access_info(const char **adm_abspath, + const char **entry_name, + svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_adm_access_t *adm_access; + svn_boolean_t read_from_subdir = FALSE; + + /* If the caller didn't know the node kind, then stat the path. Maybe + it is really there, and we can speed up the steps below. */ + if (kind == svn_node_unknown) + { + svn_node_kind_t on_disk; + + /* Do we already have an access baton for LOCAL_ABSPATH? */ + adm_access = svn_wc__adm_retrieve_internal2(db, local_abspath, + scratch_pool); + if (adm_access) + { + /* Sweet. The node is a directory. */ + on_disk = svn_node_dir; + } + else + { + svn_boolean_t special; + + /* What's on disk? */ + SVN_ERR(svn_io_check_special_path(local_abspath, &on_disk, &special, + scratch_pool)); + } + + if (on_disk != svn_node_dir) + { + /* If this is *anything* besides a directory (FILE, NONE, or + UNKNOWN), then we cannot treat it as a versioned directory + containing entries to read. Leave READ_FROM_SUBDIR as FALSE, + so that the parent will be examined. + + For NONE and UNKNOWN, it may be that metadata exists for the + node, even though on-disk is unhelpful. + + If NEED_PARENT_STUB is TRUE, and the entry is not a DIRECTORY, + then we'll error. + + If NEED_PARENT_STUB if FALSE, and we successfully read a stub, + then this on-disk node is obstructing the read. */ + } + else + { + /* We found a directory for this UNKNOWN node. Determine whether + we need to read inside it. */ + read_from_subdir = TRUE; + } + } + else if (kind == svn_node_dir) + { + read_from_subdir = TRUE; + } + + if (read_from_subdir) + { + /* KIND must be a DIR or UNKNOWN (and we found a subdir). We want + the "real" data, so treat LOCAL_ABSPATH as a versioned directory. */ + *adm_abspath = apr_pstrdup(result_pool, local_abspath); + *entry_name = ""; + } + else + { + /* FILE node needs to read the parent directory. Or a DIR node + needs to read from the parent to get at the stub entry. Or this + is an UNKNOWN node, and we need to examine the parent. */ + svn_dirent_split(adm_abspath, entry_name, local_abspath, result_pool); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__get_entry(const svn_wc_entry_t **entry, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t allow_unversioned, + svn_node_kind_t kind, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *dir_abspath; + const char *entry_name; + + SVN_ERR(get_entry_access_info(&dir_abspath, &entry_name, db, local_abspath, + kind, scratch_pool, scratch_pool)); + + { + const svn_wc_entry_t *parent_entry; + svn_error_t *err; + + /* NOTE: if KIND is UNKNOWN and we decided to examine the *parent* + directory, then it is possible we moved out of the working copy. + If the on-disk node is a DIR, and we asked for a stub, then we + obviously can't provide that (parent has no info). If the on-disk + node is a FILE/NONE/UNKNOWN, then it is obstructing the real + LOCAL_ABSPATH (or it was never a versioned item). In all these + cases, the read_entries() will (properly) throw an error. + + NOTE: if KIND is a DIR and we asked for the real data, but it is + obstructed on-disk by some other node kind (NONE, FILE, UNKNOWN), + then this will throw an error. */ + + err = read_entry_pair(&parent_entry, entry, + db, dir_abspath, entry_name, + result_pool, scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_MISSING || kind != svn_node_unknown + || *entry_name != '\0') + return svn_error_trace(err); + svn_error_clear(err); + + /* The caller didn't know the node type, we saw a directory there, + we attempted to read IN that directory, and then wc_db reports + that it is NOT a working copy directory. It is possible that + one of two things has happened: + + 1) a directory is obstructing a file in the parent + 2) the (versioned) directory's contents have been removed + + Let's assume situation (1); if that is true, then we can just + return the newly-found data. + + If we assumed (2), then a valid result still won't help us + since the caller asked for the actual contents, not the stub + (which is why we read *into* the directory). However, if we + assume (1) and get back a stub, then we have verified a + missing, versioned directory, and can return an error + describing that. + + Redo the fetch, but "insist" we are trying to find a file. + This will read from the parent directory of the "file". */ + err = svn_wc__get_entry(entry, db, local_abspath, allow_unversioned, + svn_node_file, result_pool, scratch_pool); + if (err == SVN_NO_ERROR) + return SVN_NO_ERROR; + if (err->apr_err != SVN_ERR_NODE_UNEXPECTED_KIND) + return svn_error_trace(err); + svn_error_clear(err); + + /* We asked for a FILE, but the node found is a DIR. Thus, we + are looking at a stub. Originally, we tried to read into the + subdir because NEED_PARENT_STUB is FALSE. The stub we just + read is not going to work for the caller, so inform them of + the missing subdirectory. */ + SVN_ERR_ASSERT(*entry != NULL && (*entry)->kind == svn_node_dir); + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("Admin area of '%s' is missing"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + } + + if (*entry == NULL) + { + if (allow_unversioned) + return SVN_NO_ERROR; + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("'%s' is not under version control"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + /* The caller had the wrong information. */ + if ((kind == svn_node_file && (*entry)->kind != svn_node_file) + || (kind == svn_node_dir && (*entry)->kind != svn_node_dir)) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("'%s' is not of the right kind"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* TODO ### Rewrite doc string to mention ENTRIES_ALL; not ADM_ACCESS. + + Prune the deleted entries from the cached entries in ADM_ACCESS, and + return that collection in *ENTRIES_PRUNED. SCRATCH_POOL is used for local, + short term, memory allocation, RESULT_POOL for permanent stuff. */ +static svn_error_t * +prune_deleted(apr_hash_t **entries_pruned, + apr_hash_t *entries_all, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + if (!entries_all) + { + *entries_pruned = NULL; + return SVN_NO_ERROR; + } + + /* I think it will be common for there to be no deleted entries, so + it is worth checking for that case as we can optimise it. */ + for (hi = apr_hash_first(scratch_pool, entries_all); + hi; + hi = apr_hash_next(hi)) + { + svn_boolean_t hidden; + + SVN_ERR(svn_wc__entry_is_hidden(&hidden, + svn__apr_hash_index_val(hi))); + if (hidden) + break; + } + + if (! hi) + { + /* There are no deleted entries, so we can use the full hash */ + *entries_pruned = entries_all; + return SVN_NO_ERROR; + } + + /* Construct pruned hash without deleted entries */ + *entries_pruned = apr_hash_make(result_pool); + for (hi = apr_hash_first(scratch_pool, entries_all); + hi; + hi = apr_hash_next(hi)) + { + const void *key = svn__apr_hash_index_key(hi); + const svn_wc_entry_t *entry = svn__apr_hash_index_val(hi); + svn_boolean_t hidden; + + SVN_ERR(svn_wc__entry_is_hidden(&hidden, entry)); + if (!hidden) + svn_hash_sets(*entries_pruned, key, entry); + } + + return SVN_NO_ERROR; +} + +struct entries_read_baton_t +{ + apr_hash_t **new_entries; + svn_wc__db_t *db; + const char *local_abspath; + apr_pool_t *result_pool; +}; + +static svn_error_t * +entries_read_txn(void *baton, svn_sqlite__db_t *db, apr_pool_t *scratch_pool) +{ + struct entries_read_baton_t *erb = baton; + + SVN_ERR(read_entries(erb->new_entries, erb->db, erb->local_abspath, + erb->result_pool, scratch_pool)); + + return NULL; +} + +svn_error_t * +svn_wc__entries_read_internal(apr_hash_t **entries, + svn_wc_adm_access_t *adm_access, + svn_boolean_t show_hidden, + apr_pool_t *pool) +{ + apr_hash_t *new_entries; + + new_entries = svn_wc__adm_access_entries(adm_access); + if (! new_entries) + { + svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); + const char *local_abspath = svn_wc__adm_access_abspath(adm_access); + apr_pool_t *result_pool = svn_wc__adm_access_pool_internal(adm_access); + svn_sqlite__db_t *sdb; + struct entries_read_baton_t erb; + + /* ### Use the borrow DB api to handle all calls in a single read + ### transaction. This api is used extensively in our test suite + ### via the entries-read application. */ + + SVN_ERR(svn_wc__db_temp_borrow_sdb(&sdb, db, local_abspath, pool)); + + erb.db = db; + erb.local_abspath = local_abspath; + erb.new_entries = &new_entries; + erb.result_pool = result_pool; + + SVN_ERR(svn_sqlite__with_lock(sdb, entries_read_txn, &erb, pool)); + + svn_wc__adm_access_set_entries(adm_access, new_entries); + } + + if (show_hidden) + *entries = new_entries; + else + SVN_ERR(prune_deleted(entries, new_entries, + svn_wc__adm_access_pool_internal(adm_access), + pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_entries_read(apr_hash_t **entries, + svn_wc_adm_access_t *adm_access, + svn_boolean_t show_hidden, + apr_pool_t *pool) +{ + return svn_error_trace(svn_wc__entries_read_internal(entries, adm_access, + show_hidden, pool)); +} + +/* No transaction required: called from write_entry which is itself + transaction-wrapped. */ +static svn_error_t * +insert_node(svn_sqlite__db_t *sdb, + const db_node_t *node, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR_ASSERT(node->op_depth > 0 || node->repos_relpath); + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnnnsnrisnnni", + node->wc_id, + node->local_relpath, + node->op_depth, + node->parent_relpath, + /* Setting depth for files? */ + svn_depth_to_word(node->depth), + node->changed_rev, + node->changed_date, + node->changed_author, + node->recorded_time)); + + if (node->repos_relpath) + { + SVN_ERR(svn_sqlite__bind_int64(stmt, 5, + node->repos_id)); + SVN_ERR(svn_sqlite__bind_text(stmt, 6, + node->repos_relpath)); + SVN_ERR(svn_sqlite__bind_revnum(stmt, 7, node->revision)); + } + + if (node->presence == svn_wc__db_status_normal) + SVN_ERR(svn_sqlite__bind_text(stmt, 8, "normal")); + else if (node->presence == svn_wc__db_status_not_present) + SVN_ERR(svn_sqlite__bind_text(stmt, 8, "not-present")); + else if (node->presence == svn_wc__db_status_base_deleted) + SVN_ERR(svn_sqlite__bind_text(stmt, 8, "base-deleted")); + else if (node->presence == svn_wc__db_status_incomplete) + SVN_ERR(svn_sqlite__bind_text(stmt, 8, "incomplete")); + else if (node->presence == svn_wc__db_status_excluded) + SVN_ERR(svn_sqlite__bind_text(stmt, 8, "excluded")); + else if (node->presence == svn_wc__db_status_server_excluded) + SVN_ERR(svn_sqlite__bind_text(stmt, 8, "server-excluded")); + + if (node->kind == svn_node_none) + SVN_ERR(svn_sqlite__bind_text(stmt, 10, "unknown")); + else + SVN_ERR(svn_sqlite__bind_text(stmt, 10, + svn_node_kind_to_word(node->kind))); + + if (node->kind == svn_node_file) + { + if (!node->checksum + && node->op_depth == 0 + && node->presence != svn_wc__db_status_not_present + && node->presence != svn_wc__db_status_excluded + && node->presence != svn_wc__db_status_server_excluded) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("The file '%s' has no checksum"), + svn_dirent_local_style(node->local_relpath, + scratch_pool)); + + SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, node->checksum, + scratch_pool)); + } + + if (node->properties) /* ### Never set, props done later */ + SVN_ERR(svn_sqlite__bind_properties(stmt, 15, node->properties, + scratch_pool)); + + if (node->recorded_size != SVN_INVALID_FILESIZE) + SVN_ERR(svn_sqlite__bind_int64(stmt, 16, node->recorded_size)); + + if (node->file_external) + SVN_ERR(svn_sqlite__bind_int(stmt, 20, 1)); + + if (node->inherited_props) + SVN_ERR(svn_sqlite__bind_iprops(stmt, 23, node->inherited_props, + scratch_pool)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + + return SVN_NO_ERROR; +} + + +/* */ +static svn_error_t * +insert_actual_node(svn_sqlite__db_t *sdb, + svn_wc__db_t *db, + const char *wri_abspath, + const db_actual_node_t *actual_node, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_skel_t *conflict_data = NULL; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_ACTUAL_NODE)); + + SVN_ERR(svn_sqlite__bind_int64(stmt, 1, actual_node->wc_id)); + SVN_ERR(svn_sqlite__bind_text(stmt, 2, actual_node->local_relpath)); + SVN_ERR(svn_sqlite__bind_text(stmt, 3, actual_node->parent_relpath)); + + if (actual_node->properties) + SVN_ERR(svn_sqlite__bind_properties(stmt, 4, actual_node->properties, + scratch_pool)); + + if (actual_node->changelist) + SVN_ERR(svn_sqlite__bind_text(stmt, 5, actual_node->changelist)); + + SVN_ERR(svn_wc__upgrade_conflict_skel_from_raw( + &conflict_data, + db, wri_abspath, + actual_node->local_relpath, + actual_node->conflict_old, + actual_node->conflict_working, + actual_node->conflict_new, + actual_node->prop_reject, + actual_node->tree_conflict_data, + actual_node->tree_conflict_data + ? strlen(actual_node->tree_conflict_data) + : 0, + scratch_pool, scratch_pool)); + + if (conflict_data) + { + svn_stringbuf_t *data = svn_skel__unparse(conflict_data, scratch_pool); + + SVN_ERR(svn_sqlite__bind_blob(stmt, 6, data->data, data->len)); + } + + /* Execute and reset the insert clause. */ + return svn_error_trace(svn_sqlite__insert(NULL, stmt)); +} + +static svn_boolean_t +is_switched(db_node_t *parent, + db_node_t *child, + apr_pool_t *scratch_pool) +{ + if (parent && child) + { + if (parent->repos_id != child->repos_id) + return TRUE; + + if (parent->repos_relpath && child->repos_relpath) + { + const char *unswitched + = svn_relpath_join(parent->repos_relpath, + svn_relpath_basename(child->local_relpath, + scratch_pool), + scratch_pool); + if (strcmp(unswitched, child->repos_relpath)) + return TRUE; + } + } + + return FALSE; +} + +struct write_baton { + db_node_t *base; + db_node_t *work; + db_node_t *below_work; + apr_hash_t *tree_conflicts; +}; + +#define WRITE_ENTRY_ASSERT(expr) \ + if (!(expr)) \ + return svn_error_createf(SVN_ERR_ASSERTION_FAIL, NULL, \ + _("Unable to upgrade '%s' at line %d"), \ + svn_dirent_local_style( \ + svn_dirent_join(root_abspath, \ + local_relpath, \ + scratch_pool), \ + scratch_pool), __LINE__) + +/* Write the information for ENTRY to WC_DB. The WC_ID, REPOS_ID and + REPOS_ROOT will all be used for writing ENTRY. + ### transitioning from straight sql to using the wc_db APIs. For the + ### time being, we'll need both parameters. */ +static svn_error_t * +write_entry(struct write_baton **entry_node, + const struct write_baton *parent_node, + svn_wc__db_t *db, + svn_sqlite__db_t *sdb, + apr_int64_t wc_id, + apr_int64_t repos_id, + const svn_wc_entry_t *entry, + const svn_wc__text_base_info_t *text_base_info, + const char *local_relpath, + const char *tmp_entry_abspath, + const char *root_abspath, + const svn_wc_entry_t *this_dir, + svn_boolean_t create_locks, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + db_node_t *base_node = NULL; + db_node_t *working_node = NULL, *below_working_node = NULL; + db_actual_node_t *actual_node = NULL; + const char *parent_relpath; + apr_hash_t *tree_conflicts; + + if (*local_relpath == '\0') + parent_relpath = NULL; + else + parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool); + + /* This is how it should work, it doesn't work like this yet because + we need proper op_depth to layer the working nodes. + + Using "svn add", "svn rm", "svn cp" only files can be replaced + pre-wcng; directories can only be normal, deleted or added. + Files cannot be replaced within a deleted directory, so replaced + files can only exist in a normal directory, or a directory that + is added+copied. In a normal directory a replaced file needs a + base node and a working node, in an added+copied directory a + replaced file needs two working nodes at different op-depths. + + With just the above operations the conversion for files and + directories is straightforward: + + pre-wcng wcng + parent child parent child + + normal normal base base + add+copied normal+copied work work + normal+copied normal+copied work work + normal delete base base+work + delete delete base+work base+work + add+copied delete work work + normal add base work + add add work work + add+copied add work work + normal add+copied base work + add add+copied work work + add+copied add+copied work work + normal replace base base+work + add+copied replace work work+work + normal replace+copied base base+work + add+copied replace+copied work work+work + + However "svn merge" make this more complicated. The pre-wcng + "svn merge" is capable of replacing a directory, that is it can + mark the whole tree deleted, and then copy another tree on top. + The entries then represent the replacing tree overlayed on the + deleted tree. + + original replace schedule in + tree tree combined tree + + A A replace+copied + A/f delete+copied + A/g A/g replace+copied + A/h add+copied + A/B A/B replace+copied + A/B/f delete+copied + A/B/g A/B/g replace+copied + A/B/h add+copied + A/C delete+copied + A/C/f delete+copied + A/D add+copied + A/D/f add+copied + + The original tree could be normal tree, or an add+copied tree. + Committing such a merge generally worked, but making further tree + modifications before commit sometimes failed. + + The root of the replace is handled like the file replace: + + pre-wcng wcng + parent child parent child + + normal replace+copied base base+work + add+copied replace+copied work work+work + + although obviously the node is a directory rather then a file. + There are then more conversion states where the parent is + replaced. + + pre-wcng wcng + parent child parent child + + replace+copied add [base|work]+work work + replace+copied add+copied [base|work]+work work + replace+copied delete+copied [base|work]+work [base|work]+work + delete+copied delete+copied [base|work]+work [base|work]+work + replace+copied replace+copied [base|work]+work [base|work]+work + */ + + WRITE_ENTRY_ASSERT(parent_node || entry->schedule == svn_wc_schedule_normal); + + WRITE_ENTRY_ASSERT(!parent_node || parent_node->base + || parent_node->below_work || parent_node->work); + + switch (entry->schedule) + { + case svn_wc_schedule_normal: + if (entry->copied || + (entry->depth == svn_depth_exclude + && parent_node && !parent_node->base && parent_node->work)) + working_node = MAYBE_ALLOC(working_node, result_pool); + else + base_node = MAYBE_ALLOC(base_node, result_pool); + break; + + case svn_wc_schedule_add: + working_node = MAYBE_ALLOC(working_node, result_pool); + if (entry->deleted) + { + if (parent_node->base) + base_node = MAYBE_ALLOC(base_node, result_pool); + else + below_working_node = MAYBE_ALLOC(below_working_node, result_pool); + } + break; + + case svn_wc_schedule_delete: + working_node = MAYBE_ALLOC(working_node, result_pool); + if (parent_node->base) + base_node = MAYBE_ALLOC(base_node, result_pool); + if (parent_node->work) + below_working_node = MAYBE_ALLOC(below_working_node, result_pool); + break; + + case svn_wc_schedule_replace: + working_node = MAYBE_ALLOC(working_node, result_pool); + if (parent_node->base) + base_node = MAYBE_ALLOC(base_node, result_pool); + else + below_working_node = MAYBE_ALLOC(below_working_node, result_pool); + break; + } + + /* Something deleted in this revision means there should always be a + BASE node to indicate the not-present node. */ + if (entry->deleted) + { + WRITE_ENTRY_ASSERT(base_node || below_working_node); + WRITE_ENTRY_ASSERT(!entry->incomplete); + if (base_node) + base_node->presence = svn_wc__db_status_not_present; + else + below_working_node->presence = svn_wc__db_status_not_present; + } + else if (entry->absent) + { + WRITE_ENTRY_ASSERT(base_node && !working_node && !below_working_node); + WRITE_ENTRY_ASSERT(!entry->incomplete); + base_node->presence = svn_wc__db_status_server_excluded; + } + + if (entry->copied) + { + if (entry->copyfrom_url) + { + working_node->repos_id = repos_id; + working_node->repos_relpath = svn_uri_skip_ancestor( + this_dir->repos, entry->copyfrom_url, + result_pool); + working_node->revision = entry->copyfrom_rev; + working_node->op_depth + = svn_wc__db_op_depth_for_upgrade(local_relpath); + } + else if (parent_node->work && parent_node->work->repos_relpath) + { + working_node->repos_id = repos_id; + working_node->repos_relpath + = svn_relpath_join(parent_node->work->repos_relpath, + svn_relpath_basename(local_relpath, NULL), + result_pool); + working_node->revision = parent_node->work->revision; + working_node->op_depth = parent_node->work->op_depth; + } + else if (parent_node->below_work + && parent_node->below_work->repos_relpath) + { + working_node->repos_id = repos_id; + working_node->repos_relpath + = svn_relpath_join(parent_node->below_work->repos_relpath, + svn_relpath_basename(local_relpath, NULL), + result_pool); + working_node->revision = parent_node->below_work->revision; + working_node->op_depth = parent_node->below_work->op_depth; + } + else + return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, + _("No copyfrom URL for '%s'"), + svn_dirent_local_style(local_relpath, + scratch_pool)); + } + + if (entry->conflict_old) + { + actual_node = MAYBE_ALLOC(actual_node, scratch_pool); + if (parent_relpath && entry->conflict_old) + actual_node->conflict_old = svn_relpath_join(parent_relpath, + entry->conflict_old, + scratch_pool); + else + actual_node->conflict_old = entry->conflict_old; + if (parent_relpath && entry->conflict_new) + actual_node->conflict_new = svn_relpath_join(parent_relpath, + entry->conflict_new, + scratch_pool); + else + actual_node->conflict_new = entry->conflict_new; + if (parent_relpath && entry->conflict_wrk) + actual_node->conflict_working = svn_relpath_join(parent_relpath, + entry->conflict_wrk, + scratch_pool); + else + actual_node->conflict_working = entry->conflict_wrk; + } + + if (entry->prejfile) + { + actual_node = MAYBE_ALLOC(actual_node, scratch_pool); + actual_node->prop_reject = svn_relpath_join((entry->kind == svn_node_dir + ? local_relpath + : parent_relpath), + entry->prejfile, + scratch_pool); + } + + if (entry->changelist) + { + actual_node = MAYBE_ALLOC(actual_node, scratch_pool); + actual_node->changelist = entry->changelist; + } + + /* ### set the text_mod value? */ + + if (entry_node && entry->tree_conflict_data) + { + /* Issues #3840/#3916: 1.6 stores multiple tree conflicts on the + parent node, 1.7 stores them directly on the conflited nodes. + So "((skel1) (skel2))" becomes "(skel1)" and "(skel2)" */ + svn_skel_t *skel; + + skel = svn_skel__parse(entry->tree_conflict_data, + strlen(entry->tree_conflict_data), + scratch_pool); + tree_conflicts = apr_hash_make(result_pool); + skel = skel->children; + while(skel) + { + svn_wc_conflict_description2_t *conflict; + svn_skel_t *new_skel; + const char *key; + + /* *CONFLICT is allocated so it is safe to use a non-const pointer */ + SVN_ERR(svn_wc__deserialize_conflict( + (const svn_wc_conflict_description2_t**)&conflict, + skel, + svn_dirent_join(root_abspath, + local_relpath, + scratch_pool), + scratch_pool, scratch_pool)); + + WRITE_ENTRY_ASSERT(conflict->kind == svn_wc_conflict_kind_tree); + + /* Fix dubious data stored by old clients, local adds don't have + a repository URL. */ + if (conflict->reason == svn_wc_conflict_reason_added) + conflict->src_left_version = NULL; + + SVN_ERR(svn_wc__serialize_conflict(&new_skel, conflict, + scratch_pool, scratch_pool)); + + /* Store in hash to be retrieved when writing the child + row. */ + key = svn_dirent_skip_ancestor(root_abspath, conflict->local_abspath); + svn_hash_sets(tree_conflicts, apr_pstrdup(result_pool, key), + svn_skel__unparse(new_skel, result_pool)->data); + skel = skel->next; + } + } + else + tree_conflicts = NULL; + + if (parent_node && parent_node->tree_conflicts) + { + const char *tree_conflict_data = + svn_hash_gets(parent_node->tree_conflicts, local_relpath); + if (tree_conflict_data) + { + actual_node = MAYBE_ALLOC(actual_node, scratch_pool); + actual_node->tree_conflict_data = tree_conflict_data; + } + + /* Reset hash so that we don't write the row again when writing + actual-only nodes */ + svn_hash_sets(parent_node->tree_conflicts, local_relpath, NULL); + } + + if (entry->file_external_path != NULL) + { + base_node = MAYBE_ALLOC(base_node, result_pool); + } + + + /* Insert the base node. */ + if (base_node) + { + base_node->wc_id = wc_id; + base_node->local_relpath = local_relpath; + base_node->op_depth = 0; + base_node->parent_relpath = parent_relpath; + base_node->revision = entry->revision; + base_node->recorded_time = entry->text_time; + base_node->recorded_size = entry->working_size; + + if (entry->depth != svn_depth_exclude) + base_node->depth = entry->depth; + else + { + base_node->presence = svn_wc__db_status_excluded; + base_node->depth = svn_depth_infinity; + } + + if (entry->deleted) + { + WRITE_ENTRY_ASSERT(base_node->presence + == svn_wc__db_status_not_present); + /* ### should be svn_node_unknown, but let's store what we have. */ + base_node->kind = entry->kind; + } + else if (entry->absent) + { + WRITE_ENTRY_ASSERT(base_node->presence + == svn_wc__db_status_server_excluded); + /* ### should be svn_node_unknown, but let's store what we have. */ + base_node->kind = entry->kind; + + /* Store the most likely revision in the node to avoid + base nodes without a valid revision. Of course + we remember that the data is still incomplete. */ + if (!SVN_IS_VALID_REVNUM(base_node->revision) && parent_node->base) + base_node->revision = parent_node->base->revision; + } + else + { + base_node->kind = entry->kind; + + if (base_node->presence != svn_wc__db_status_excluded) + { + /* All subdirs are initially incomplete, they stop being + incomplete when the entries file in the subdir is + upgraded and remain incomplete if that doesn't happen. */ + if (entry->kind == svn_node_dir + && strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR)) + { + base_node->presence = svn_wc__db_status_incomplete; + + /* Store the most likely revision in the node to avoid + base nodes without a valid revision. Of course + we remember that the data is still incomplete. */ + if (parent_node->base) + base_node->revision = parent_node->base->revision; + } + else if (entry->incomplete) + { + /* ### nobody should have set the presence. */ + WRITE_ENTRY_ASSERT(base_node->presence + == svn_wc__db_status_normal); + base_node->presence = svn_wc__db_status_incomplete; + } + } + } + + if (entry->kind == svn_node_dir) + base_node->checksum = NULL; + else + { + if (text_base_info && text_base_info->revert_base.sha1_checksum) + base_node->checksum = text_base_info->revert_base.sha1_checksum; + else if (text_base_info && text_base_info->normal_base.sha1_checksum) + base_node->checksum = text_base_info->normal_base.sha1_checksum; + else + base_node->checksum = NULL; + + /* The base MD5 checksum is available in the entry, unless there + * is a copied WORKING node. If possible, verify that the entry + * checksum matches the base file that we found. */ + if (! (working_node && entry->copied)) + { + svn_checksum_t *entry_md5_checksum, *found_md5_checksum; + SVN_ERR(svn_checksum_parse_hex(&entry_md5_checksum, + svn_checksum_md5, + entry->checksum, scratch_pool)); + if (text_base_info && text_base_info->revert_base.md5_checksum) + found_md5_checksum = text_base_info->revert_base.md5_checksum; + else if (text_base_info + && text_base_info->normal_base.md5_checksum) + found_md5_checksum = text_base_info->normal_base.md5_checksum; + else + found_md5_checksum = NULL; + if (entry_md5_checksum && found_md5_checksum && + !svn_checksum_match(entry_md5_checksum, found_md5_checksum)) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Bad base MD5 checksum for '%s'; " + "expected: '%s'; found '%s'; "), + svn_dirent_local_style( + svn_dirent_join(root_abspath, + local_relpath, + scratch_pool), + scratch_pool), + svn_checksum_to_cstring_display( + entry_md5_checksum, scratch_pool), + svn_checksum_to_cstring_display( + found_md5_checksum, scratch_pool)); + else + { + /* ### Not sure what conditions this should cover. */ + /* SVN_ERR_ASSERT(entry->deleted || ...); */ + } + } + } + + if (this_dir->repos) + { + base_node->repos_id = repos_id; + + if (entry->url != NULL) + { + base_node->repos_relpath = svn_uri_skip_ancestor( + this_dir->repos, entry->url, + result_pool); + } + else + { + const char *relpath = svn_uri_skip_ancestor(this_dir->repos, + this_dir->url, + scratch_pool); + if (relpath == NULL || *relpath == '\0') + base_node->repos_relpath = entry->name; + else + base_node->repos_relpath = + svn_dirent_join(relpath, entry->name, result_pool); + } + } + + /* TODO: These values should always be present, if they are missing + during an upgrade, set a flag, and then ask the user to talk to the + server. + + Note: cmt_rev is the distinguishing value. The others may be 0 or + NULL if the corresponding revprop has been deleted. */ + base_node->changed_rev = entry->cmt_rev; + base_node->changed_date = entry->cmt_date; + base_node->changed_author = entry->cmt_author; + + if (entry->file_external_path) + base_node->file_external = TRUE; + + /* Switched nodes get an empty iprops cache. */ + if (parent_node + && is_switched(parent_node->base, base_node, scratch_pool)) + base_node->inherited_props + = apr_array_make(scratch_pool, 0, sizeof(svn_prop_inherited_item_t*)); + + SVN_ERR(insert_node(sdb, base_node, scratch_pool)); + + /* We have to insert the lock after the base node, because the node + must exist to lookup various bits of repos related information for + the abs path. */ + if (entry->lock_token && create_locks) + { + svn_wc__db_lock_t lock; + + lock.token = entry->lock_token; + lock.owner = entry->lock_owner; + lock.comment = entry->lock_comment; + lock.date = entry->lock_creation_date; + + SVN_ERR(svn_wc__db_lock_add(db, tmp_entry_abspath, &lock, + scratch_pool)); + } + } + + if (below_working_node) + { + db_node_t *work + = parent_node->below_work ? parent_node->below_work : parent_node->work; + + below_working_node->wc_id = wc_id; + below_working_node->local_relpath = local_relpath; + below_working_node->op_depth = work->op_depth; + below_working_node->parent_relpath = parent_relpath; + below_working_node->presence = svn_wc__db_status_normal; + below_working_node->kind = entry->kind; + below_working_node->repos_id = work->repos_id; + + /* This is just guessing. If the node below would have been switched + or if it was updated to a different version, the guess would + fail. But we don't have better information pre wc-ng :( */ + if (work->repos_relpath) + below_working_node->repos_relpath + = svn_relpath_join(work->repos_relpath, entry->name, + result_pool); + else + below_working_node->repos_relpath = NULL; + below_working_node->revision = parent_node->work->revision; + + /* The revert_base checksum isn't available in the entry structure, + so the caller provides it. */ + + /* text_base_info is NULL for files scheduled to be added. */ + below_working_node->checksum = NULL; + if (text_base_info) + { + if (entry->schedule == svn_wc_schedule_delete) + below_working_node->checksum = + text_base_info->normal_base.sha1_checksum; + else + below_working_node->checksum = + text_base_info->revert_base.sha1_checksum; + } + below_working_node->recorded_size = 0; + below_working_node->changed_rev = SVN_INVALID_REVNUM; + below_working_node->changed_date = 0; + below_working_node->changed_author = NULL; + below_working_node->depth = svn_depth_infinity; + below_working_node->recorded_time = 0; + below_working_node->properties = NULL; + + if (working_node + && entry->schedule == svn_wc_schedule_delete + && working_node->repos_relpath) + { + /* We are lucky, our guesses above are not necessary. The known + correct information is in working. But our op_depth design + expects more information here */ + below_working_node->repos_relpath = working_node->repos_relpath; + below_working_node->repos_id = working_node->repos_id; + below_working_node->revision = working_node->revision; + + /* Nice for 'svn status' */ + below_working_node->changed_rev = entry->cmt_rev; + below_working_node->changed_date = entry->cmt_date; + below_working_node->changed_author = entry->cmt_author; + + /* And now remove it from WORKING, because in wc-ng code + should read it from the lower layer */ + working_node->repos_relpath = NULL; + working_node->repos_id = 0; + working_node->revision = SVN_INVALID_REVNUM; + } + + SVN_ERR(insert_node(sdb, below_working_node, scratch_pool)); + } + + /* Insert the working node. */ + if (working_node) + { + working_node->wc_id = wc_id; + working_node->local_relpath = local_relpath; + working_node->parent_relpath = parent_relpath; + working_node->changed_rev = SVN_INVALID_REVNUM; + working_node->recorded_time = entry->text_time; + working_node->recorded_size = entry->working_size; + + if (entry->depth != svn_depth_exclude) + working_node->depth = entry->depth; + else + { + working_node->presence = svn_wc__db_status_excluded; + working_node->depth = svn_depth_infinity; + } + + if (entry->kind == svn_node_dir) + working_node->checksum = NULL; + else + { + working_node->checksum = NULL; + /* text_base_info is NULL for files scheduled to be added. */ + if (text_base_info) + working_node->checksum = text_base_info->normal_base.sha1_checksum; + + + /* If an MD5 checksum is present in the entry, we can verify that + * it matches the MD5 of the base file we found earlier. */ +#ifdef SVN_DEBUG + if (entry->checksum && text_base_info) + { + svn_checksum_t *md5_checksum; + SVN_ERR(svn_checksum_parse_hex(&md5_checksum, svn_checksum_md5, + entry->checksum, result_pool)); + SVN_ERR_ASSERT( + md5_checksum && text_base_info->normal_base.md5_checksum); + SVN_ERR_ASSERT(svn_checksum_match( + md5_checksum, text_base_info->normal_base.md5_checksum)); + } +#endif + } + + working_node->kind = entry->kind; + if (working_node->presence != svn_wc__db_status_excluded) + { + /* All subdirs start of incomplete, and stop being incomplete + when the entries file in the subdir is upgraded. */ + if (entry->kind == svn_node_dir + && strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR)) + { + working_node->presence = svn_wc__db_status_incomplete; + working_node->kind = svn_node_dir; + } + else if (entry->schedule == svn_wc_schedule_delete) + { + working_node->presence = svn_wc__db_status_base_deleted; + working_node->kind = entry->kind; + } + else + { + /* presence == normal */ + working_node->kind = entry->kind; + + if (entry->incomplete) + { + /* We shouldn't be overwriting another status. */ + WRITE_ENTRY_ASSERT(working_node->presence + == svn_wc__db_status_normal); + working_node->presence = svn_wc__db_status_incomplete; + } + } + } + + /* These should generally be unset for added and deleted files, + and contain whatever information we have for copied files. Let's + just store whatever we have. + + Note: cmt_rev is the distinguishing value. The others may be 0 or + NULL if the corresponding revprop has been deleted. */ + if (working_node->presence != svn_wc__db_status_base_deleted) + { + working_node->changed_rev = entry->cmt_rev; + working_node->changed_date = entry->cmt_date; + working_node->changed_author = entry->cmt_author; + } + + if (entry->schedule == svn_wc_schedule_delete + && parent_node->work + && parent_node->work->presence == svn_wc__db_status_base_deleted) + { + working_node->op_depth = parent_node->work->op_depth; + } + else if (!entry->copied) + { + working_node->op_depth + = svn_wc__db_op_depth_for_upgrade(local_relpath); + } + + SVN_ERR(insert_node(sdb, working_node, scratch_pool)); + } + + /* Insert the actual node. */ + if (actual_node) + { + actual_node = MAYBE_ALLOC(actual_node, scratch_pool); + + actual_node->wc_id = wc_id; + actual_node->local_relpath = local_relpath; + actual_node->parent_relpath = parent_relpath; + + SVN_ERR(insert_actual_node(sdb, db, tmp_entry_abspath, + actual_node, scratch_pool)); + } + + if (entry_node) + { + *entry_node = apr_palloc(result_pool, sizeof(**entry_node)); + (*entry_node)->base = base_node; + (*entry_node)->work = working_node; + (*entry_node)->below_work = below_working_node; + (*entry_node)->tree_conflicts = tree_conflicts; + } + + if (entry->file_external_path) + { + /* TODO: Maybe add a file external registration inside EXTERNALS here, + to allow removing file externals that aren't referenced from + svn:externals. + + The svn:externals values are processed anyway after everything is + upgraded */ + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +write_actual_only_entries(apr_hash_t *tree_conflicts, + svn_sqlite__db_t *sdb, + svn_wc__db_t *db, + const char *wri_abspath, + apr_int64_t wc_id, + const char *parent_relpath, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, tree_conflicts); + hi; + hi = apr_hash_next(hi)) + { + db_actual_node_t *actual_node = NULL; + + actual_node = MAYBE_ALLOC(actual_node, scratch_pool); + actual_node->wc_id = wc_id; + actual_node->local_relpath = svn__apr_hash_index_key(hi); + actual_node->parent_relpath = parent_relpath; + actual_node->tree_conflict_data = svn__apr_hash_index_val(hi); + + SVN_ERR(insert_actual_node(sdb, db, wri_abspath, actual_node, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__write_upgraded_entries(void **dir_baton, + void *parent_baton, + svn_wc__db_t *db, + svn_sqlite__db_t *sdb, + apr_int64_t repos_id, + apr_int64_t wc_id, + const char *dir_abspath, + const char *new_root_abspath, + apr_hash_t *entries, + apr_hash_t *text_bases_info, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const svn_wc_entry_t *this_dir; + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + const char *old_root_abspath, *dir_relpath; + struct write_baton *parent_node = parent_baton; + struct write_baton *dir_node; + + /* Get a copy of the "this dir" entry for comparison purposes. */ + this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR); + + /* If there is no "this dir" entry, something is wrong. */ + if (! this_dir) + return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, + _("No default entry in directory '%s'"), + svn_dirent_local_style(dir_abspath, + iterpool)); + old_root_abspath = svn_dirent_get_longest_ancestor(dir_abspath, + new_root_abspath, + scratch_pool); + + SVN_ERR_ASSERT(old_root_abspath[0]); + + dir_relpath = svn_dirent_skip_ancestor(old_root_abspath, dir_abspath); + + /* Write out "this dir" */ + SVN_ERR(write_entry(&dir_node, parent_node, db, sdb, + wc_id, repos_id, this_dir, NULL, dir_relpath, + svn_dirent_join(new_root_abspath, dir_relpath, + iterpool), + old_root_abspath, + this_dir, FALSE, result_pool, iterpool)); + + for (hi = apr_hash_first(scratch_pool, entries); hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + const svn_wc_entry_t *this_entry = svn__apr_hash_index_val(hi); + const char *child_abspath, *child_relpath; + svn_wc__text_base_info_t *text_base_info + = svn_hash_gets(text_bases_info, name); + + svn_pool_clear(iterpool); + + /* Don't rewrite the "this dir" entry! */ + if (strcmp(name, SVN_WC_ENTRY_THIS_DIR) == 0) + continue; + + /* Write the entry. Pass TRUE for create locks, because we still + use this function for upgrading old working copies. */ + child_abspath = svn_dirent_join(dir_abspath, name, iterpool); + child_relpath = svn_dirent_skip_ancestor(old_root_abspath, child_abspath); + SVN_ERR(write_entry(NULL, dir_node, db, sdb, + wc_id, repos_id, + this_entry, text_base_info, child_relpath, + svn_dirent_join(new_root_abspath, child_relpath, + iterpool), + old_root_abspath, + this_dir, TRUE, iterpool, iterpool)); + } + + if (dir_node->tree_conflicts) + SVN_ERR(write_actual_only_entries(dir_node->tree_conflicts, sdb, db, + new_root_abspath, wc_id, dir_relpath, + iterpool)); + + *dir_baton = dir_node; + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + + +svn_wc_entry_t * +svn_wc_entry_dup(const svn_wc_entry_t *entry, apr_pool_t *pool) +{ + svn_wc_entry_t *dupentry = apr_palloc(pool, sizeof(*dupentry)); + + /* Perform a trivial copy ... */ + *dupentry = *entry; + + /* ...and then re-copy stuff that needs to be duped into our pool. */ + if (entry->name) + dupentry->name = apr_pstrdup(pool, entry->name); + if (entry->url) + dupentry->url = apr_pstrdup(pool, entry->url); + if (entry->repos) + dupentry->repos = apr_pstrdup(pool, entry->repos); + if (entry->uuid) + dupentry->uuid = apr_pstrdup(pool, entry->uuid); + if (entry->copyfrom_url) + dupentry->copyfrom_url = apr_pstrdup(pool, entry->copyfrom_url); + if (entry->conflict_old) + dupentry->conflict_old = apr_pstrdup(pool, entry->conflict_old); + if (entry->conflict_new) + dupentry->conflict_new = apr_pstrdup(pool, entry->conflict_new); + if (entry->conflict_wrk) + dupentry->conflict_wrk = apr_pstrdup(pool, entry->conflict_wrk); + if (entry->prejfile) + dupentry->prejfile = apr_pstrdup(pool, entry->prejfile); + if (entry->checksum) + dupentry->checksum = apr_pstrdup(pool, entry->checksum); + if (entry->cmt_author) + dupentry->cmt_author = apr_pstrdup(pool, entry->cmt_author); + if (entry->lock_token) + dupentry->lock_token = apr_pstrdup(pool, entry->lock_token); + if (entry->lock_owner) + dupentry->lock_owner = apr_pstrdup(pool, entry->lock_owner); + if (entry->lock_comment) + dupentry->lock_comment = apr_pstrdup(pool, entry->lock_comment); + if (entry->changelist) + dupentry->changelist = apr_pstrdup(pool, entry->changelist); + + /* NOTE: we do not dup cachable_props or present_props since they + are deprecated. Use "" to indicate "nothing cachable or cached". */ + dupentry->cachable_props = ""; + dupentry->present_props = ""; + + if (entry->tree_conflict_data) + dupentry->tree_conflict_data = apr_pstrdup(pool, + entry->tree_conflict_data); + if (entry->file_external_path) + dupentry->file_external_path = apr_pstrdup(pool, + entry->file_external_path); + return dupentry; +} + + +/*** Generic Entry Walker */ + +/* A recursive entry-walker, helper for svn_wc_walk_entries3(). + * + * For this directory (DIRPATH, ADM_ACCESS), call the "found_entry" callback + * in WALK_CALLBACKS, passing WALK_BATON to it. Then, for each versioned + * entry in this directory, call the "found entry" callback and then recurse + * (if it is a directory and if DEPTH allows). + * + * If SHOW_HIDDEN is true, include entries that are in a 'deleted' or + * 'absent' state (and not scheduled for re-addition), else skip them. + * + * Call CANCEL_FUNC with CANCEL_BATON to allow cancellation. + */ +static svn_error_t * +walker_helper(const char *dirpath, + svn_wc_adm_access_t *adm_access, + const svn_wc_entry_callbacks2_t *walk_callbacks, + void *walk_baton, + svn_depth_t depth, + svn_boolean_t show_hidden, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + apr_pool_t *subpool = svn_pool_create(pool); + apr_hash_t *entries; + apr_hash_index_t *hi; + svn_wc_entry_t *dot_entry; + svn_error_t *err; + svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); + + err = svn_wc__entries_read_internal(&entries, adm_access, show_hidden, + pool); + + if (err) + SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool)); + + /* As promised, always return the '.' entry first. */ + dot_entry = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR); + if (! dot_entry) + return walk_callbacks->handle_error + (dirpath, svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, + _("Directory '%s' has no THIS_DIR entry"), + svn_dirent_local_style(dirpath, pool)), + walk_baton, pool); + + /* Call the "found entry" callback for this directory as a "this dir" + * entry. Note that if this directory has been reached by recursion, this + * is the second visit as it will already have been visited once as a + * child entry of its parent. */ + + err = walk_callbacks->found_entry(dirpath, dot_entry, walk_baton, subpool); + + + if(err) + SVN_ERR(walk_callbacks->handle_error(dirpath, err, walk_baton, pool)); + + if (depth == svn_depth_empty) + return SVN_NO_ERROR; + + /* Loop over each of the other entries. */ + for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + const svn_wc_entry_t *current_entry = svn__apr_hash_index_val(hi); + const char *entrypath; + const char *entry_abspath; + svn_boolean_t hidden; + + svn_pool_clear(subpool); + + /* See if someone wants to cancel this operation. */ + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + /* Skip the "this dir" entry. */ + if (strcmp(current_entry->name, SVN_WC_ENTRY_THIS_DIR) == 0) + continue; + + entrypath = svn_dirent_join(dirpath, name, subpool); + SVN_ERR(svn_wc__entry_is_hidden(&hidden, current_entry)); + SVN_ERR(svn_dirent_get_absolute(&entry_abspath, entrypath, subpool)); + + /* Call the "found entry" callback for this entry. (For a directory, + * this is the first visit: as a child.) */ + if (current_entry->kind == svn_node_file + || depth >= svn_depth_immediates) + { + err = walk_callbacks->found_entry(entrypath, current_entry, + walk_baton, subpool); + + if (err) + SVN_ERR(walk_callbacks->handle_error(entrypath, err, + walk_baton, pool)); + } + + /* Recurse into this entry if appropriate. */ + if (current_entry->kind == svn_node_dir + && !hidden + && depth >= svn_depth_immediates) + { + svn_wc_adm_access_t *entry_access; + svn_depth_t depth_below_here = depth; + + if (depth == svn_depth_immediates) + depth_below_here = svn_depth_empty; + + entry_access = svn_wc__adm_retrieve_internal2(db, entry_abspath, + subpool); + + if (entry_access) + SVN_ERR(walker_helper(entrypath, entry_access, + walk_callbacks, walk_baton, + depth_below_here, show_hidden, + cancel_func, cancel_baton, + subpool)); + } + } + + svn_pool_destroy(subpool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__walker_default_error_handler(const char *path, + svn_error_t *err, + void *walk_baton, + apr_pool_t *pool) +{ + /* Note: don't trace this. We don't want to insert a false "stack frame" + onto an error generated elsewhere. */ + return svn_error_trace(err); +} + + +/* The public API. */ +svn_error_t * +svn_wc_walk_entries3(const char *path, + svn_wc_adm_access_t *adm_access, + const svn_wc_entry_callbacks2_t *walk_callbacks, + void *walk_baton, + svn_depth_t walk_depth, + svn_boolean_t show_hidden, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + const char *local_abspath; + svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); + svn_error_t *err; + svn_node_kind_t kind; + svn_depth_t depth; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + err = svn_wc__db_read_info(NULL, &kind, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &depth, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, + pool, pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + /* Remap into SVN_ERR_UNVERSIONED_RESOURCE. */ + svn_error_clear(err); + return walk_callbacks->handle_error( + path, svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL, + _("'%s' is not under version control"), + svn_dirent_local_style(local_abspath, pool)), + walk_baton, pool); + } + + if (kind == svn_node_file || depth == svn_depth_exclude) + { + const svn_wc_entry_t *entry; + + /* ### we should stop passing out entry structures. + ### + ### we should not call handle_error for an error the *callback* + ### gave us. let it deal with the problem before returning. */ + + if (!show_hidden) + { + svn_boolean_t hidden; + SVN_ERR(svn_wc__db_node_hidden(&hidden, db, local_abspath, pool)); + + if (hidden) + { + /* The fool asked to walk a "hidden" node. Report the node as + unversioned. + + ### this is incorrect behavior. see depth_test 36. the walk + ### API will be revamped to avoid entry structures. we should + ### be able to solve the problem with the new API. (since we + ### shouldn't return a hidden entry here) */ + return walk_callbacks->handle_error( + path, svn_error_createf( + SVN_ERR_UNVERSIONED_RESOURCE, NULL, + _("'%s' is not under version control"), + svn_dirent_local_style(local_abspath, pool)), + walk_baton, pool); + } + } + + SVN_ERR(svn_wc__get_entry(&entry, db, local_abspath, FALSE, + svn_node_file, pool, pool)); + + err = walk_callbacks->found_entry(path, entry, walk_baton, pool); + if (err) + return walk_callbacks->handle_error(path, err, walk_baton, pool); + + return SVN_NO_ERROR; + } + + if (kind == svn_node_dir) + return walker_helper(path, adm_access, walk_callbacks, walk_baton, + walk_depth, show_hidden, cancel_func, cancel_baton, + pool); + + return walk_callbacks->handle_error( + path, svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL, + _("'%s' has an unrecognized node kind"), + svn_dirent_local_style(local_abspath, pool)), + walk_baton, pool); +} diff --git a/subversion/libsvn_wc/entries.h b/subversion/libsvn_wc/entries.h new file mode 100644 index 000000000000..87caa462c618 --- /dev/null +++ b/subversion/libsvn_wc/entries.h @@ -0,0 +1,164 @@ +/* + * entries.h : manipulating entries + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#ifndef SVN_LIBSVN_WC_ENTRIES_H +#define SVN_LIBSVN_WC_ENTRIES_H + +#include <apr_pools.h> + +#include "svn_types.h" + +#include "wc_db.h" +#include "private/svn_sqlite.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** Get an ENTRY for the given LOCAL_ABSPATH. + * + * This API does not require an access baton, just a wc_db handle (DB). + * The requested entry MUST be present and version-controlled when + * ALLOW_UNVERSIONED is FALSE; otherwise, SVN_ERR_WC_PATH_NOT_FOUND is + * returned. When ALLOW_UNVERSIONED is TRUE, and the node is not under + * version control, *ENTRY will be set to NULL (this is easier for callers + * to handle, than detecting the error and clearing it). + * + * If you know the entry is a FILE or DIR, then specify that in KIND. If you + * are unsure, then specify 'svn_node_unknown' for KIND. This value will be + * used to optimize the access to the entry, so it is best to know the kind. + * If you specify FILE/DIR, and the entry is *something else*, then + * SVN_ERR_NODE_UNEXPECTED_KIND will be returned. + * + * If KIND == UNKNOWN, and you request the parent stub, and the node turns + * out to NOT be a directory, then SVN_ERR_NODE_UNEXPECTED_KIND is returned. + * + * NOTE: if SVN_ERR_NODE_UNEXPECTED_KIND is returned, then the ENTRY *IS* + * valid and may be examined. For any other error, ENTRY *IS NOT* valid. + * + * NOTE: if an access baton is available, then it will be examined for + * cached entries (and this routine may even cache them for you). It is + * not required, however, to do any access baton management for this API. + * + * ENTRY will be allocated in RESULT_POOL, and all temporary allocations + * will be performed in SCRATCH_POOL. + */ +svn_error_t * +svn_wc__get_entry(const svn_wc_entry_t **entry, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t allow_unversioned, + svn_node_kind_t kind, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Is 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); + + +/* The checksums of one pre-1.7 text-base file. If the text-base file + * exists, both checksums are filled in, otherwise both fields are NULL. */ +typedef struct svn_wc__text_base_file_info_t +{ + svn_checksum_t *sha1_checksum; + svn_checksum_t *md5_checksum; +} svn_wc__text_base_file_info_t; + +/* The text-base checksums of the normal base and/or the revert-base of one + * pre-1.7 versioned text file. */ +typedef struct svn_wc__text_base_info_t +{ + svn_wc__text_base_file_info_t normal_base; + svn_wc__text_base_file_info_t revert_base; +} svn_wc__text_base_info_t; + +/* For internal use by upgrade.c to write entries in the wc-ng format. + Return in DIR_BATON the baton to be passed as PARENT_BATON when + upgrading child directories. Pass a NULL PARENT_BATON when upgrading + the root directory. + + TEXT_BASES_INFO is a hash of information about all the text bases found + in this directory's admin area, keyed on (const char *) name of the + versioned file, with (svn_wc__text_base_info_t *) values. */ +svn_error_t * +svn_wc__write_upgraded_entries(void **dir_baton, + void *parent_baton, + svn_wc__db_t *db, + svn_sqlite__db_t *sdb, + apr_int64_t repos_id, + apr_int64_t wc_id, + const char *dir_abspath, + const char *new_root_abspath, + apr_hash_t *entries, + apr_hash_t *text_bases_info, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Parse a file external specification in the NULL terminated STR and + place the path in PATH_RESULT, the peg revision in PEG_REV_RESULT + and revision number in REV_RESULT. STR may be NULL, in which case + PATH_RESULT will be set to NULL and both PEG_REV_RESULT and + REV_RESULT set to svn_opt_revision_unspecified. + + The format that is read is the same as a working-copy path with a + peg revision; see svn_opt_parse_path(). */ +svn_error_t * +svn_wc__unserialize_file_external(const char **path_result, + svn_opt_revision_t *peg_rev_result, + svn_opt_revision_t *rev_result, + const char *str, + apr_pool_t *pool); + +/* Serialize into STR the file external path, peg revision number and + the operative revision number into a format that + unserialize_file_external() can parse. The format is + %{peg_rev}:%{rev}:%{path} + where a rev will either be HEAD or the string revision number. If + PATH is NULL then STR will be set to NULL. This method writes to a + string instead of a svn_stringbuf_t so that the string can be + protected by write_str(). */ +svn_error_t * +svn_wc__serialize_file_external(const char **str, + const char *path, + const svn_opt_revision_t *peg_rev, + const svn_opt_revision_t *rev, + apr_pool_t *pool); + +/* Non-deprecated wrapper variant of svn_wc_entries_read used implement + legacy API functions. See svn_wc_entries_read for a detailed description. + */ +svn_error_t * +svn_wc__entries_read_internal(apr_hash_t **entries, + svn_wc_adm_access_t *adm_access, + svn_boolean_t show_hidden, + apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_WC_ENTRIES_H */ diff --git a/subversion/libsvn_wc/externals.c b/subversion/libsvn_wc/externals.c new file mode 100644 index 000000000000..514148fe3e3f --- /dev/null +++ b/subversion/libsvn_wc/externals.c @@ -0,0 +1,1686 @@ +/* + * externals.c : routines dealing with (file) externals in the working copy + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <stdlib.h> +#include <string.h> + +#include <apr_pools.h> +#include <apr_hash.h> +#include <apr_tables.h> +#include <apr_general.h> +#include <apr_uri.h> + +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_error.h" +#include "svn_hash.h" +#include "svn_io.h" +#include "svn_pools.h" +#include "svn_props.h" +#include "svn_string.h" +#include "svn_time.h" +#include "svn_types.h" +#include "svn_wc.h" + +#include "private/svn_skel.h" +#include "private/svn_subr_private.h" + +#include "wc.h" +#include "adm_files.h" +#include "props.h" +#include "translate.h" +#include "workqueue.h" +#include "conflicts.h" + +#include "svn_private_config.h" + +/** Externals **/ + +/* + * Look for either + * + * -r N + * -rN + * + * in the LINE_PARTS array and update the revision field in ITEM with + * the revision if the revision is found. Set REV_IDX to the index in + * LINE_PARTS where the revision specification starts. Remove from + * LINE_PARTS the element(s) that specify the revision. + * PARENT_DIRECTORY_DISPLAY and LINE are given to return a nice error + * string. + * + * If this function returns successfully, then LINE_PARTS will have + * only two elements in it. + */ +static svn_error_t * +find_and_remove_externals_revision(int *rev_idx, + const char **line_parts, + int num_line_parts, + svn_wc_external_item2_t *item, + const char *parent_directory_display, + const char *line, + apr_pool_t *pool) +{ + int i; + + for (i = 0; i < 2; ++i) + { + const char *token = line_parts[i]; + + if (token[0] == '-' && token[1] == 'r') + { + svn_opt_revision_t end_revision = { svn_opt_revision_unspecified }; + const char *digits_ptr; + int shift_count; + int j; + + *rev_idx = i; + + if (token[2] == '\0') + { + /* There must be a total of four elements in the line if + -r N is used. */ + if (num_line_parts != 4) + goto parse_error; + + shift_count = 2; + digits_ptr = line_parts[i+1]; + } + else + { + /* There must be a total of three elements in the line + if -rN is used. */ + if (num_line_parts != 3) + goto parse_error; + + shift_count = 1; + digits_ptr = token+2; + } + + if (svn_opt_parse_revision(&item->revision, + &end_revision, + digits_ptr, pool) != 0) + goto parse_error; + /* We want a single revision, not a range. */ + if (end_revision.kind != svn_opt_revision_unspecified) + goto parse_error; + /* Allow only numbers and dates, not keywords. */ + if (item->revision.kind != svn_opt_revision_number + && item->revision.kind != svn_opt_revision_date) + goto parse_error; + + /* Shift any line elements past the revision specification + down over the revision specification. */ + for (j = i; j < num_line_parts-shift_count; ++j) + line_parts[j] = line_parts[j+shift_count]; + line_parts[num_line_parts-shift_count] = NULL; + + /* Found the revision, so leave the function immediately, do + * not continue looking for additional revisions. */ + return SVN_NO_ERROR; + } + } + + /* No revision was found, so there must be exactly two items in the + line array. */ + if (num_line_parts == 2) + return SVN_NO_ERROR; + + parse_error: + return svn_error_createf + (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, + _("Error parsing %s property on '%s': '%s'"), + SVN_PROP_EXTERNALS, + parent_directory_display, + line); +} + +svn_error_t * +svn_wc_parse_externals_description3(apr_array_header_t **externals_p, + const char *parent_directory, + const char *desc, + svn_boolean_t canonicalize_url, + apr_pool_t *pool) +{ + int i; + apr_array_header_t *externals = NULL; + apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool); + const char *parent_directory_display = svn_path_is_url(parent_directory) ? + parent_directory : svn_dirent_local_style(parent_directory, pool); + + /* If an error occurs halfway through parsing, *externals_p should stay + * untouched. So, store the list in a local var first. */ + if (externals_p) + externals = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *)); + + for (i = 0; i < lines->nelts; i++) + { + const char *line = APR_ARRAY_IDX(lines, i, const char *); + apr_status_t status; + char **line_parts; + int num_line_parts; + svn_wc_external_item2_t *item; + const char *token0; + const char *token1; + svn_boolean_t token0_is_url; + svn_boolean_t token1_is_url; + + /* Index into line_parts where the revision specification + started. */ + int rev_idx = -1; + + if ((! line) || (line[0] == '#')) + continue; + + /* else proceed */ + + status = apr_tokenize_to_argv(line, &line_parts, pool); + if (status) + return svn_error_wrap_apr(status, + _("Can't split line into components: '%s'"), + line); + /* Count the number of tokens. */ + for (num_line_parts = 0; line_parts[num_line_parts]; num_line_parts++) + ; + + SVN_ERR(svn_wc_external_item2_create(&item, pool)); + item->revision.kind = svn_opt_revision_unspecified; + item->peg_revision.kind = svn_opt_revision_unspecified; + + /* + * There are six different formats of externals: + * + * 1) DIR URL + * 2) DIR -r N URL + * 3) DIR -rN URL + * 4) URL DIR + * 5) -r N URL DIR + * 6) -rN URL DIR + * + * The last three allow peg revisions in the URL. + * + * With relative URLs and no '-rN' or '-r N', there is no way to + * distinguish between 'DIR URL' and 'URL DIR' when URL is a + * relative URL like /svn/repos/trunk, so this case is taken as + * case 4). + */ + if (num_line_parts < 2 || num_line_parts > 4) + return svn_error_createf + (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, + _("Error parsing %s property on '%s': '%s'"), + SVN_PROP_EXTERNALS, + parent_directory_display, + line); + + /* To make it easy to check for the forms, find and remove -r N + or -rN from the line item array. If it is found, rev_idx + contains the index into line_parts where '-r' was found and + set item->revision to the parsed revision. */ + /* ### ugh. stupid cast. */ + SVN_ERR(find_and_remove_externals_revision(&rev_idx, + (const char **)line_parts, + num_line_parts, item, + parent_directory_display, + line, pool)); + + token0 = line_parts[0]; + token1 = line_parts[1]; + + token0_is_url = svn_path_is_url(token0); + token1_is_url = svn_path_is_url(token1); + + if (token0_is_url && token1_is_url) + return svn_error_createf + (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, + _("Invalid %s property on '%s': " + "cannot use two absolute URLs ('%s' and '%s') in an external; " + "one must be a path where an absolute or relative URL is " + "checked out to"), + SVN_PROP_EXTERNALS, parent_directory_display, token0, token1); + + if (0 == rev_idx && token1_is_url) + return svn_error_createf + (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, + _("Invalid %s property on '%s': " + "cannot use a URL '%s' as the target directory for an external " + "definition"), + SVN_PROP_EXTERNALS, parent_directory_display, token1); + + if (1 == rev_idx && token0_is_url) + return svn_error_createf + (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, + _("Invalid %s property on '%s': " + "cannot use a URL '%s' as the target directory for an external " + "definition"), + SVN_PROP_EXTERNALS, parent_directory_display, token0); + + /* The appearence of -r N or -rN forces the type of external. + If -r is at the beginning of the line or the first token is + an absolute URL or if the second token is not an absolute + URL, then the URL supports peg revisions. */ + if (0 == rev_idx || + (-1 == rev_idx && (token0_is_url || ! token1_is_url))) + { + /* The URL is passed to svn_opt_parse_path in + uncanonicalized form so that the scheme relative URL + //hostname/foo is not collapsed to a server root relative + URL /hostname/foo. */ + SVN_ERR(svn_opt_parse_path(&item->peg_revision, &item->url, + token0, pool)); + item->target_dir = token1; + } + else + { + item->target_dir = token0; + item->url = token1; + item->peg_revision = item->revision; + } + + SVN_ERR(svn_opt_resolve_revisions(&item->peg_revision, + &item->revision, TRUE, FALSE, + pool)); + + item->target_dir = svn_dirent_internal_style(item->target_dir, pool); + + if (item->target_dir[0] == '\0' + || svn_dirent_is_absolute(item->target_dir) + || svn_path_is_backpath_present(item->target_dir) + || !svn_dirent_skip_ancestor("dummy", + svn_dirent_join("dummy", + item->target_dir, + pool))) + return svn_error_createf + (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, + _("Invalid %s property on '%s': " + "target '%s' is an absolute path or involves '..'"), + SVN_PROP_EXTERNALS, + parent_directory_display, + item->target_dir); + + if (canonicalize_url) + { + /* Uh... this is stupid. But it's consistent with what our + code did before we split up the relpath/dirent/uri APIs. + Still, given this, it's no wonder that our own libraries + don't ask this function to canonicalize the results. */ + if (svn_path_is_url(item->url)) + item->url = svn_uri_canonicalize(item->url, pool); + else + item->url = svn_dirent_canonicalize(item->url, pool); + } + + if (externals) + APR_ARRAY_PUSH(externals, svn_wc_external_item2_t *) = item; + } + + if (externals_p) + *externals_p = externals; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets, + apr_array_header_t *externals, + apr_pool_t *pool, + apr_pool_t *scratch_pool) +{ + int i; + unsigned int len; + unsigned int len2; + const char *target; + apr_hash_t *targets = apr_hash_make(scratch_pool); + apr_hash_t *targets2 = NULL; + *duplicate_targets = NULL; + + for (i = 0; i < externals->nelts; i++) + { + target = APR_ARRAY_IDX(externals, i, + svn_wc_external_item2_t*)->target_dir; + len = apr_hash_count(targets); + svn_hash_sets(targets, target, ""); + if (len == apr_hash_count(targets)) + { + /* Hashtable length is unchanged. This must be a duplicate. */ + + /* Collapse multiple duplicates of the same target by using a second + * hash layer. */ + if (! targets2) + targets2 = apr_hash_make(scratch_pool); + len2 = apr_hash_count(targets2); + svn_hash_sets(targets2, target, ""); + if (len2 < apr_hash_count(targets2)) + { + /* The second hash list just got bigger, i.e. this target has + * not been counted as duplicate before. */ + if (! *duplicate_targets) + { + *duplicate_targets = apr_array_make( + pool, 1, sizeof(svn_wc_external_item2_t*)); + } + APR_ARRAY_PUSH((*duplicate_targets), const char *) = target; + } + /* Else, this same target has already been recorded as a duplicate, + * don't count it again. */ + } + } + return SVN_NO_ERROR; +} + +struct edit_baton +{ + apr_pool_t *pool; + svn_wc__db_t *db; + + /* We explicitly use wri_abspath and local_abspath here, because we + might want to install file externals in an obstructing working copy */ + const char *wri_abspath; /* The working defining the file external */ + const char *local_abspath; /* The file external itself */ + const char *name; /* The basename of the file external itself */ + + /* Information from the caller */ + svn_boolean_t use_commit_times; + const apr_array_header_t *ext_patterns; + const char *diff3cmd; + + const char *url; + const char *repos_root_url; + const char *repos_uuid; + + const char *record_ancestor_abspath; + const char *recorded_repos_relpath; + svn_revnum_t recorded_peg_revision; + svn_revnum_t recorded_revision; + + /* Introducing a new file external */ + svn_boolean_t added; + + svn_wc_conflict_resolver_func2_t conflict_func; + void *conflict_baton; + svn_cancel_func_t cancel_func; + void *cancel_baton; + svn_wc_notify_func2_t notify_func; + void *notify_baton; + + svn_revnum_t *target_revision; + + /* What was there before the update */ + svn_revnum_t original_revision; + const svn_checksum_t *original_checksum; + + /* What we are installing now */ + const char *new_pristine_abspath; + svn_checksum_t *new_sha1_checksum; + svn_checksum_t *new_md5_checksum; + + /* List of incoming propchanges */ + apr_array_header_t *propchanges; + + /* Array of svn_prop_inherited_item_t * structures representing the + properties inherited by the base node at LOCAL_ABSPATH. */ + apr_array_header_t *iprops; + + /* The last change information */ + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + + svn_boolean_t had_props; + + svn_boolean_t file_closed; +}; + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +set_target_revision(void *edit_baton, + svn_revnum_t target_revision, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + + *eb->target_revision = target_revision; + + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +open_root(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *dir_pool, + void **root_baton) +{ + *root_baton = edit_baton; + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +add_file(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *file_pool, + void **file_baton) +{ + struct edit_baton *eb = parent_baton; + if (strcmp(path, eb->name)) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("This editor can only update '%s'"), + svn_dirent_local_style(eb->local_abspath, + file_pool)); + + *file_baton = eb; + eb->original_revision = SVN_INVALID_REVNUM; + eb->added = TRUE; + + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +open_file(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *file_pool, + void **file_baton) +{ + struct edit_baton *eb = parent_baton; + svn_node_kind_t kind; + if (strcmp(path, eb->name)) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("This editor can only update '%s'"), + svn_dirent_local_style(eb->local_abspath, + file_pool)); + + *file_baton = eb; + SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision, + NULL, NULL, NULL, &eb->changed_rev, + &eb->changed_date, &eb->changed_author, + NULL, &eb->original_checksum, NULL, NULL, + &eb->had_props, NULL, NULL, + eb->db, eb->local_abspath, + eb->pool, file_pool)); + + if (kind != svn_node_file) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Node '%s' is no existing file external"), + svn_dirent_local_style(eb->local_abspath, + file_pool)); + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +apply_textdelta(void *file_baton, + const char *base_checksum_digest, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct edit_baton *eb = file_baton; + svn_stream_t *src_stream; + svn_stream_t *dest_stream; + + if (eb->original_checksum) + { + if (base_checksum_digest) + { + svn_checksum_t *expected_checksum; + const svn_checksum_t *original_md5; + + SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, + base_checksum_digest, pool)); + + if (eb->original_checksum->kind != svn_checksum_md5) + SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5, + eb->db, eb->wri_abspath, + eb->original_checksum, + pool, pool)); + else + original_md5 = eb->original_checksum; + + if (!svn_checksum_match(expected_checksum, original_md5)) + return svn_error_trace(svn_checksum_mismatch_err( + expected_checksum, + original_md5, + pool, + _("Base checksum mismatch for '%s'"), + svn_dirent_local_style(eb->local_abspath, + pool))); + } + + SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db, + eb->wri_abspath, eb->original_checksum, + pool, pool)); + } + else + src_stream = svn_stream_empty(pool); + + SVN_ERR(svn_wc__open_writable_base(&dest_stream, &eb->new_pristine_abspath, + &eb->new_md5_checksum, + &eb->new_sha1_checksum, + eb->db, eb->wri_abspath, + eb->pool, pool)); + + svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool, + handler, handler_baton); + + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +change_file_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct edit_baton *eb = file_baton; + svn_prop_t *propchange; + + propchange = apr_array_push(eb->propchanges); + propchange->name = apr_pstrdup(eb->pool, name); + propchange->value = value ? svn_string_dup(value, eb->pool) : NULL; + + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +close_file(void *file_baton, + const char *expected_md5_digest, + apr_pool_t *pool) +{ + struct edit_baton *eb = file_baton; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown; + svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown; + svn_boolean_t obstructed = FALSE; + + eb->file_closed = TRUE; /* We bump the revision here */ + + /* Check the checksum, if provided */ + if (expected_md5_digest) + { + svn_checksum_t *expected_md5_checksum; + const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum; + + SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5, + expected_md5_digest, pool)); + + if (actual_md5_checksum == NULL) + { + actual_md5_checksum = eb->original_checksum; + + if (actual_md5_checksum != NULL + && actual_md5_checksum->kind != svn_checksum_md5) + { + SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum, + eb->db, eb->wri_abspath, + actual_md5_checksum, + pool, pool)); + } + } + + if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum)) + return svn_checksum_mismatch_err( + expected_md5_checksum, + actual_md5_checksum, pool, + _("Checksum mismatch for '%s'"), + svn_dirent_local_style(eb->local_abspath, pool)); + } + + /* First move the file in the pristine store; this hands over the cleanup + behavior to the pristine store. */ + if (eb->new_sha1_checksum) + { + SVN_ERR(svn_wc__db_pristine_install(eb->db, eb->new_pristine_abspath, + eb->new_sha1_checksum, + eb->new_md5_checksum, pool)); + + eb->new_pristine_abspath = NULL; + } + + /* Merge the changes */ + { + svn_skel_t *all_work_items = NULL; + svn_skel_t *conflict_skel = NULL; + svn_skel_t *work_item; + apr_hash_t *base_props = NULL; + apr_hash_t *actual_props = NULL; + apr_hash_t *new_pristine_props = NULL; + apr_hash_t *new_actual_props = NULL; + apr_hash_t *new_dav_props = NULL; + const svn_checksum_t *new_checksum = NULL; + const svn_checksum_t *original_checksum = NULL; + + svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision); + const char *repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url, + eb->url, pool); + + if (! added) + { + new_checksum = eb->original_checksum; + + if (eb->had_props) + SVN_ERR(svn_wc__db_base_get_props( + &base_props, eb->db, eb->local_abspath, pool, pool)); + + SVN_ERR(svn_wc__db_read_props( + &actual_props, eb->db, eb->local_abspath, pool, pool)); + } + + if (!base_props) + base_props = apr_hash_make(pool); + + if (!actual_props) + actual_props = apr_hash_make(pool); + + if (eb->new_sha1_checksum) + new_checksum = eb->new_sha1_checksum; + + /* Merge the properties */ + { + apr_array_header_t *entry_prop_changes; + apr_array_header_t *dav_prop_changes; + apr_array_header_t *regular_prop_changes; + int i; + + SVN_ERR(svn_categorize_props(eb->propchanges, &entry_prop_changes, + &dav_prop_changes, ®ular_prop_changes, + pool)); + + /* Read the entry-prop changes to update the last-changed info. */ + for (i = 0; i < entry_prop_changes->nelts; i++) + { + const svn_prop_t *prop = &APR_ARRAY_IDX(entry_prop_changes, i, + svn_prop_t); + + if (! prop->value) + continue; /* authz or something */ + + if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR)) + eb->changed_author = apr_pstrdup(pool, prop->value->data); + else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV)) + { + apr_int64_t rev; + SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data)); + eb->changed_rev = (svn_revnum_t)rev; + } + else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE)) + SVN_ERR(svn_time_from_cstring(&eb->changed_date, prop->value->data, + pool)); + } + + /* Store the DAV-prop (aka WC-prop) changes. (This treats a list + * of changes as a list of new props, but we only use this when + * adding a new file and it's equivalent in that case.) */ + if (dav_prop_changes->nelts > 0) + new_dav_props = svn_prop_array_to_hash(dav_prop_changes, pool); + + /* Merge the regular prop changes. */ + if (regular_prop_changes->nelts > 0) + { + new_pristine_props = svn_prop__patch(base_props, regular_prop_changes, + pool); + SVN_ERR(svn_wc__merge_props(&conflict_skel, + &prop_state, + &new_actual_props, + eb->db, eb->local_abspath, + NULL /* server_baseprops*/, + base_props, + actual_props, + regular_prop_changes, + pool, pool)); + } + else + { + new_pristine_props = base_props; + new_actual_props = actual_props; + } + } + + /* Merge the text */ + if (eb->new_sha1_checksum) + { + svn_node_kind_t disk_kind; + svn_boolean_t install_pristine = FALSE; + const char *install_from = NULL; + + SVN_ERR(svn_io_check_path(eb->local_abspath, &disk_kind, pool)); + + if (disk_kind == svn_node_none) + { + /* Just install the file */ + install_pristine = TRUE; + content_state = svn_wc_notify_state_changed; + } + else if (disk_kind != svn_node_file + || (eb->added && disk_kind == svn_node_file)) + { + /* The node is obstructed; we just change the DB */ + obstructed = TRUE; + content_state = svn_wc_notify_state_unchanged; + } + else + { + svn_boolean_t is_mod; + SVN_ERR(svn_wc__internal_file_modified_p(&is_mod, + eb->db, eb->local_abspath, + FALSE, pool)); + + if (!is_mod) + { + install_pristine = TRUE; + content_state = svn_wc_notify_state_changed; + } + else + { + svn_boolean_t found_text_conflict; + + /* Ok, we have to do some work to merge a local change */ + SVN_ERR(svn_wc__perform_file_merge(&work_item, + &conflict_skel, + &found_text_conflict, + eb->db, + eb->local_abspath, + eb->wri_abspath, + new_checksum, + original_checksum, + actual_props, + eb->ext_patterns, + eb->original_revision, + *eb->target_revision, + eb->propchanges, + eb->diff3cmd, + eb->cancel_func, + eb->cancel_baton, + pool, pool)); + + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + pool); + + if (found_text_conflict) + content_state = svn_wc_notify_state_conflicted; + else + content_state = svn_wc_notify_state_merged; + } + } + if (install_pristine) + { + SVN_ERR(svn_wc__wq_build_file_install(&work_item, eb->db, + eb->local_abspath, + install_from, + eb->use_commit_times, TRUE, + pool, pool)); + + all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool); + } + } + else + { + content_state = svn_wc_notify_state_unchanged; + /* ### Retranslate on magic property changes, etc. */ + } + + /* Generate a conflict description, if needed */ + if (conflict_skel) + { + SVN_ERR(svn_wc__conflict_skel_set_op_switch( + conflict_skel, + svn_wc_conflict_version_create2( + eb->repos_root_url, + eb->repos_uuid, + repos_relpath, + eb->original_revision, + svn_node_file, + pool), + svn_wc_conflict_version_create2( + eb->repos_root_url, + eb->repos_uuid, + repos_relpath, + *eb->target_revision, + svn_node_file, + pool), + pool, pool)); + SVN_ERR(svn_wc__conflict_create_markers(&work_item, + eb->db, eb->local_abspath, + conflict_skel, + pool, pool)); + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + pool); + } + + /* Install the file in the DB */ + SVN_ERR(svn_wc__db_external_add_file( + eb->db, + eb->local_abspath, + eb->wri_abspath, + repos_relpath, + eb->repos_root_url, + eb->repos_uuid, + *eb->target_revision, + new_pristine_props, + eb->iprops, + eb->changed_rev, + eb->changed_date, + eb->changed_author, + new_checksum, + new_dav_props, + eb->record_ancestor_abspath, + eb->recorded_repos_relpath, + eb->recorded_peg_revision, + eb->recorded_revision, + TRUE, new_actual_props, + FALSE /* keep_recorded_info */, + conflict_skel, + all_work_items, + pool)); + + /* close_edit may also update iprops for switched files, catching + those for which close_file is never called (e.g. an update of a + file external with no changes). So as a minor optimization we + clear the iprops so as not to set them again in close_edit. */ + eb->iprops = NULL; + + /* Run the work queue to complete the installation */ + SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath, + eb->cancel_func, eb->cancel_baton, pool)); + } + + /* Notify */ + if (eb->notify_func) + { + svn_wc_notify_action_t action; + svn_wc_notify_t *notify; + + if (!eb->added) + action = obstructed ? svn_wc_notify_update_shadowed_update + : svn_wc_notify_update_update; + else + action = obstructed ? svn_wc_notify_update_shadowed_add + : svn_wc_notify_update_add; + + notify = svn_wc_create_notify(eb->local_abspath, action, pool); + notify->kind = svn_node_file; + + notify->revision = *eb->target_revision; + notify->prop_state = prop_state; + notify->content_state = content_state; + + notify->old_revision = eb->original_revision; + + eb->notify_func(eb->notify_baton, notify, pool); + } + + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function for svn_wc__get_file_external_editor */ +static svn_error_t * +close_edit(void *edit_baton, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + + if (!eb->file_closed + || eb->iprops) + { + apr_hash_t *wcroot_iprops = NULL; + + if (eb->iprops) + { + wcroot_iprops = apr_hash_make(pool); + svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops); + } + + /* The node wasn't updated, so we just have to bump its revision */ + SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db, + eb->local_abspath, + svn_depth_infinity, + NULL, NULL, NULL, + *eb->target_revision, + apr_hash_make(pool), + wcroot_iprops, + eb->notify_func, + eb->notify_baton, + pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__get_file_external_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_revnum_t *target_revision, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *wri_abspath, + const char *url, + const char *repos_root_url, + const char *repos_uuid, + apr_array_header_t *iprops, + svn_boolean_t use_commit_times, + const char *diff3_cmd, + const apr_array_header_t *preserved_exts, + const char *record_ancestor_abspath, + const char *recorded_url, + const svn_opt_revision_t *recorded_peg_rev, + const svn_opt_revision_t *recorded_rev, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + apr_pool_t *edit_pool = result_pool; + struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb)); + svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool); + + eb->pool = edit_pool; + eb->db = db; + eb->local_abspath = apr_pstrdup(edit_pool, local_abspath); + if (wri_abspath) + eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath); + else + eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool); + eb->name = svn_dirent_basename(eb->local_abspath, NULL); + eb->target_revision = target_revision; + + eb->url = apr_pstrdup(edit_pool, url); + eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url); + eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid); + + eb->iprops = iprops; + + eb->use_commit_times = use_commit_times; + eb->ext_patterns = preserved_exts; + eb->diff3cmd = diff3_cmd; + + eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath); + eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url, + edit_pool); + + eb->changed_rev = SVN_INVALID_REVNUM; + + if (recorded_peg_rev->kind == svn_opt_revision_number) + eb->recorded_peg_revision = recorded_peg_rev->value.number; + else + eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */ + + if (recorded_rev->kind == svn_opt_revision_number) + eb->recorded_revision = recorded_rev->value.number; + else + eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */ + + eb->conflict_func = conflict_func; + eb->conflict_baton = conflict_baton; + eb->cancel_func = cancel_func; + eb->cancel_baton = cancel_baton; + eb->notify_func = notify_func; + eb->notify_baton = notify_baton; + + eb->propchanges = apr_array_make(edit_pool, 1, sizeof(svn_prop_t)); + + tree_editor->open_root = open_root; + tree_editor->set_target_revision = set_target_revision; + tree_editor->add_file = add_file; + tree_editor->open_file = open_file; + tree_editor->apply_textdelta = apply_textdelta; + tree_editor->change_file_prop = change_file_prop; + tree_editor->close_file = close_file; + tree_editor->close_edit = close_edit; + + return svn_delta_get_cancellation_editor(cancel_func, cancel_baton, + tree_editor, eb, + editor, edit_baton, + result_pool); +} + +svn_error_t * +svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const svn_ra_reporter3_t *reporter, + void *report_baton, + svn_boolean_t restore_files, + svn_boolean_t use_commit_times, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + svn_error_t *err; + svn_node_kind_t kind; + svn_wc__db_lock_t *lock; + svn_revnum_t revision; + const char *repos_root_url; + const char *repos_relpath; + svn_boolean_t update_root; + + err = svn_wc__db_base_get_info(NULL, &kind, &revision, + &repos_relpath, &repos_root_url, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, &lock, + NULL, NULL, &update_root, + db, local_abspath, + scratch_pool, scratch_pool); + + if (err + || kind == svn_node_dir + || !update_root) + { + if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + + /* We don't know about this node, so all we have to do is tell + the reporter that we don't know this node. + + But first we have to start the report by sending some basic + information for the root. */ + + SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity, + FALSE, NULL, scratch_pool)); + SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool)); + + /* Finish the report, which causes the update editor to be + driven. */ + SVN_ERR(reporter->finish_report(report_baton, scratch_pool)); + + return SVN_NO_ERROR; + } + else + { + if (restore_files) + { + svn_node_kind_t disk_kind; + SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); + + if (disk_kind == svn_node_none) + { + err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times, + scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + + svn_error_clear(err); + } + } + } + + /* Report that we know the path */ + SVN_ERR(reporter->set_path(report_baton, "", revision, + svn_depth_infinity, FALSE, NULL, + scratch_pool)); + + /* For compatibility with the normal update editor report we report + the target as switched. + + ### We can probably report a parent url and unswitched later */ + SVN_ERR(reporter->link_path(report_baton, "", + svn_path_url_add_component2(repos_root_url, + repos_relpath, + scratch_pool), + revision, + svn_depth_infinity, + FALSE /* start_empty*/, + lock ? lock->token : NULL, + scratch_pool)); + } + + return svn_error_trace(reporter->finish_report(report_baton, scratch_pool)); +} + +svn_error_t * +svn_wc__read_external_info(svn_node_kind_t *external_kind, + const char **defining_abspath, + const char **defining_url, + svn_revnum_t *defining_operational_revision, + svn_revnum_t *defining_revision, + svn_wc_context_t *wc_ctx, + const char *wri_abspath, + const char *local_abspath, + svn_boolean_t ignore_enoent, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *repos_root_url; + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_error_t *err; + + err = svn_wc__db_external_read(&status, &kind, defining_abspath, + defining_url ? &repos_root_url : NULL, NULL, + defining_url, defining_operational_revision, + defining_revision, + wc_ctx->db, local_abspath, wri_abspath, + result_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent) + return svn_error_trace(err); + + svn_error_clear(err); + + if (external_kind) + *external_kind = svn_node_none; + + if (defining_abspath) + *defining_abspath = NULL; + + if (defining_url) + *defining_url = NULL; + + if (defining_operational_revision) + *defining_operational_revision = SVN_INVALID_REVNUM; + + if (defining_revision) + *defining_revision = SVN_INVALID_REVNUM; + + return SVN_NO_ERROR; + } + + if (external_kind) + { + if (status != svn_wc__db_status_normal) + *external_kind = svn_node_unknown; + else + switch(kind) + { + case svn_node_file: + case svn_node_symlink: + *external_kind = svn_node_file; + break; + case svn_node_dir: + *external_kind = svn_node_dir; + break; + default: + *external_kind = svn_node_none; + } + } + + if (defining_url && *defining_url) + *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url, + result_pool); + + return SVN_NO_ERROR; +} + +/* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and + * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and + * XINFO->REPOS_RELPATH. All allocations are made in SCRATCH_POOL. */ +static svn_error_t * +is_external_rolled_out(svn_boolean_t *is_rolled_out, + svn_wc_context_t *wc_ctx, + svn_wc__committable_external_info_t *xinfo, + apr_pool_t *scratch_pool) +{ + const char *repos_relpath; + const char *repos_root_url; + svn_error_t *err; + + *is_rolled_out = FALSE; + + err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath, + &repos_root_url, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + wc_ctx->db, xinfo->local_abspath, + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + SVN_ERR(err); + } + + *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 && + strcmp(xinfo->repos_relpath, repos_relpath) == 0); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__committable_externals_below(apr_array_header_t **externals, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *orig_externals; + int i; + apr_pool_t *iterpool; + + /* For svn_depth_files, this also fetches dirs. They are filtered later. */ + SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals, + wc_ctx->db, + local_abspath, + depth != svn_depth_infinity, + result_pool, scratch_pool)); + + if (orig_externals == NULL) + return SVN_NO_ERROR; + + iterpool = svn_pool_create(scratch_pool); + + for (i = 0; i < orig_externals->nelts; i++) + { + svn_boolean_t is_rolled_out; + + svn_wc__committable_external_info_t *xinfo = + APR_ARRAY_IDX(orig_externals, i, + svn_wc__committable_external_info_t *); + + /* Discard dirs for svn_depth_files (s.a.). */ + if (depth == svn_depth_files + && xinfo->kind == svn_node_dir) + continue; + + svn_pool_clear(iterpool); + + /* Discard those externals that are not currently checked out. */ + SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo, + iterpool)); + if (! is_rolled_out) + continue; + + if (*externals == NULL) + *externals = apr_array_make( + result_pool, 0, + sizeof(svn_wc__committable_external_info_t *)); + + APR_ARRAY_PUSH(*externals, + svn_wc__committable_external_info_t *) = xinfo; + + if (depth != svn_depth_infinity) + continue; + + /* Are there any nested externals? */ + SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx, + xinfo->local_abspath, + svn_depth_infinity, + result_pool, iterpool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__externals_defined_below(apr_hash_t **externals, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__db_externals_defined_below(externals, + wc_ctx->db, local_abspath, + result_pool, scratch_pool)); +} + +svn_error_t * +svn_wc__external_register(svn_wc_context_t *wc_ctx, + const char *defining_abspath, + const char *local_abspath, + svn_node_kind_t kind, + const char *repos_root_url, + const char *repos_uuid, + const char *repos_relpath, + svn_revnum_t operational_revision, + svn_revnum_t revision, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(kind == svn_node_dir); + return svn_error_trace( + svn_wc__db_external_add_dir(wc_ctx->db, local_abspath, + defining_abspath, + repos_root_url, + repos_uuid, + defining_abspath, + repos_relpath, + operational_revision, + revision, + NULL, + scratch_pool)); +} + +svn_error_t * +svn_wc__external_remove(svn_wc_context_t *wc_ctx, + const char *wri_abspath, + const char *local_abspath, + svn_boolean_t declaration_only, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + + SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL, + NULL, NULL, + wc_ctx->db, local_abspath, wri_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath, + NULL, scratch_pool)); + + if (declaration_only) + return SVN_NO_ERROR; + + if (kind == svn_node_dir) + SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath, + TRUE, TRUE, + cancel_func, cancel_baton, + scratch_pool)); + else + { + SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath, + FALSE /* keep_as_working */, + TRUE /* queue_deletes */, + SVN_INVALID_REVNUM, + NULL, NULL, scratch_pool)); + SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath, + cancel_func, cancel_baton, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__externals_gather_definitions(apr_hash_t **externals, + apr_hash_t **depths, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (depth == svn_depth_infinity + || depth == svn_depth_unknown) + { + return svn_error_trace( + svn_wc__db_externals_gather_definitions(externals, depths, + wc_ctx->db, local_abspath, + result_pool, scratch_pool)); + } + else + { + const svn_string_t *value; + svn_error_t *err; + *externals = apr_hash_make(result_pool); + + local_abspath = apr_pstrdup(result_pool, local_abspath); + + err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, + SVN_PROP_EXTERNALS, result_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + value = NULL; + } + + if (value) + svn_hash_sets(*externals, local_abspath, value->data); + + if (value && depths) + { + svn_depth_t node_depth; + *depths = apr_hash_make(result_pool); + + SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &node_depth, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth)); + } + + return SVN_NO_ERROR; + } +} + +svn_error_t * +svn_wc__close_db(const char *external_abspath, + svn_wc_context_t *wc_ctx, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* Return the scheme of @a uri in @a scheme allocated from @a pool. + If @a uri does not appear to be a valid URI, then @a scheme will + not be updated. */ +static svn_error_t * +uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool) +{ + apr_size_t i; + + for (i = 0; uri[i] && uri[i] != ':'; ++i) + if (uri[i] == '/') + goto error; + + if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/') + { + *scheme = apr_pstrmemdup(pool, uri, i); + return SVN_NO_ERROR; + } + +error: + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("URL '%s' does not begin with a scheme"), + uri); +} + +svn_error_t * +svn_wc__resolve_relative_external_url(const char **resolved_url, + const svn_wc_external_item2_t *item, + const char *repos_root_url, + const char *parent_dir_url, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *url = item->url; + apr_uri_t parent_dir_uri; + apr_status_t status; + + *resolved_url = item->url; + + /* If the URL is already absolute, there is nothing to do. */ + if (svn_path_is_url(url)) + { + /* "http://server/path" */ + *resolved_url = svn_uri_canonicalize(url, result_pool); + return SVN_NO_ERROR; + } + + if (url[0] == '/') + { + /* "/path", "//path", and "///path" */ + int num_leading_slashes = 1; + if (url[1] == '/') + { + num_leading_slashes++; + if (url[2] == '/') + num_leading_slashes++; + } + + /* "//schema-relative" and in some cases "///schema-relative". + This last format is supported on file:// schema relative. */ + url = apr_pstrcat(scratch_pool, + apr_pstrndup(scratch_pool, url, num_leading_slashes), + svn_relpath_canonicalize(url + num_leading_slashes, + scratch_pool), + (char*)NULL); + } + else + { + /* "^/path" and "../path" */ + url = svn_relpath_canonicalize(url, scratch_pool); + } + + /* Parse the parent directory URL into its parts. */ + status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri); + if (status) + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("Illegal parent directory URL '%s'"), + parent_dir_url); + + /* If the parent directory URL is at the server root, then the URL + may have no / after the hostname so apr_uri_parse() will leave + the URL's path as NULL. */ + if (! parent_dir_uri.path) + parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1); + parent_dir_uri.query = NULL; + parent_dir_uri.fragment = NULL; + + /* Handle URLs relative to the current directory or to the + repository root. The backpaths may only remove path elements, + not the hostname. This allows an external to refer to another + repository in the same server relative to the location of this + repository, say using SVNParentPath. */ + if ((0 == strncmp("../", url, 3)) || + (0 == strncmp("^/", url, 2))) + { + apr_array_header_t *base_components; + apr_array_header_t *relative_components; + int i; + + /* Decompose either the parent directory's URL path or the + repository root's URL path into components. */ + if (0 == strncmp("../", url, 3)) + { + base_components = svn_path_decompose(parent_dir_uri.path, + scratch_pool); + relative_components = svn_path_decompose(url, scratch_pool); + } + else + { + apr_uri_t repos_root_uri; + + status = apr_uri_parse(scratch_pool, repos_root_url, + &repos_root_uri); + if (status) + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("Illegal repository root URL '%s'"), + repos_root_url); + + /* If the repository root URL is at the server root, then + the URL may have no / after the hostname so + apr_uri_parse() will leave the URL's path as NULL. */ + if (! repos_root_uri.path) + repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1); + + base_components = svn_path_decompose(repos_root_uri.path, + scratch_pool); + relative_components = svn_path_decompose(url + 2, scratch_pool); + } + + for (i = 0; i < relative_components->nelts; ++i) + { + const char *component = APR_ARRAY_IDX(relative_components, + i, + const char *); + if (0 == strcmp("..", component)) + { + /* Constructing the final absolute URL together with + apr_uri_unparse() requires that the path be absolute, + so only pop a component if the component being popped + is not the component for the root directory. */ + if (base_components->nelts > 1) + apr_array_pop(base_components); + } + else + APR_ARRAY_PUSH(base_components, const char *) = component; + } + + parent_dir_uri.path = (char *)svn_path_compose(base_components, + scratch_pool); + *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool, + &parent_dir_uri, 0), + result_pool); + return SVN_NO_ERROR; + } + + /* The remaining URLs are relative to either the scheme or server root + and can only refer to locations inside that scope, so backpaths are + not allowed. */ + if (svn_path_is_backpath_present(url)) + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("The external relative URL '%s' cannot have " + "backpaths, i.e. '..'"), + item->url); + + /* Relative to the scheme: Build a new URL from the parts we know. */ + if (0 == strncmp("//", url, 2)) + { + const char *scheme; + + SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool)); + *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme, + ":", url, (char *)NULL), + result_pool); + return SVN_NO_ERROR; + } + + /* Relative to the server root: Just replace the path portion of the + parent's URL. */ + if (url[0] == '/') + { + parent_dir_uri.path = (char *)url; + *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool, + &parent_dir_uri, 0), + result_pool); + return SVN_NO_ERROR; + } + + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("Unrecognized format for the relative external " + "URL '%s'"), + item->url); +} diff --git a/subversion/libsvn_wc/info.c b/subversion/libsvn_wc/info.c new file mode 100644 index 000000000000..4a37e00ab571 --- /dev/null +++ b/subversion/libsvn_wc/info.c @@ -0,0 +1,580 @@ +/** + * @copyright + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * @endcopyright + */ + +#include "svn_dirent_uri.h" +#include "svn_hash.h" +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_wc.h" + +#include "wc.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" + + + +svn_wc_info_t * +svn_wc_info_dup(const svn_wc_info_t *info, + apr_pool_t *pool) +{ + svn_wc_info_t *new_info = apr_pmemdup(pool, info, sizeof(*new_info)); + + if (info->changelist) + new_info->changelist = apr_pstrdup(pool, info->changelist); + new_info->checksum = svn_checksum_dup(info->checksum, pool); + if (info->conflicts) + { + int i; + + apr_array_header_t *new_conflicts + = apr_array_make(pool, info->conflicts->nelts, info->conflicts->elt_size); + for (i = 0; i < info->conflicts->nelts; i++) + { + APR_ARRAY_PUSH(new_conflicts, svn_wc_conflict_description2_t *) + = svn_wc__conflict_description2_dup( + APR_ARRAY_IDX(info->conflicts, i, + const svn_wc_conflict_description2_t *), + pool); + } + new_info->conflicts = new_conflicts; + } + if (info->copyfrom_url) + new_info->copyfrom_url = apr_pstrdup(pool, info->copyfrom_url); + if (info->wcroot_abspath) + new_info->wcroot_abspath = apr_pstrdup(pool, info->wcroot_abspath); + if (info->moved_from_abspath) + new_info->moved_from_abspath = apr_pstrdup(pool, info->moved_from_abspath); + if (info->moved_to_abspath) + new_info->moved_to_abspath = apr_pstrdup(pool, info->moved_to_abspath); + + return new_info; +} + + +/* Set *INFO to a new struct, allocated in RESULT_POOL, built from the WC + metadata of LOCAL_ABSPATH. Pointer fields are copied by reference, not + dup'd. */ +static svn_error_t * +build_info_for_node(svn_wc__info2_t **info, + svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__info2_t *tmpinfo; + const char *repos_relpath; + svn_wc__db_status_t status; + svn_node_kind_t db_kind; + const char *original_repos_relpath; + const char *original_repos_root_url; + const char *original_uuid; + svn_revnum_t original_revision; + svn_wc__db_lock_t *lock; + svn_boolean_t conflicted; + svn_boolean_t op_root; + svn_boolean_t have_base; + svn_boolean_t have_more_work; + svn_wc_info_t *wc_info; + + tmpinfo = apr_pcalloc(result_pool, sizeof(*tmpinfo)); + tmpinfo->kind = kind; + + wc_info = apr_pcalloc(result_pool, sizeof(*wc_info)); + tmpinfo->wc_info = wc_info; + + wc_info->copyfrom_rev = SVN_INVALID_REVNUM; + + SVN_ERR(svn_wc__db_read_info(&status, &db_kind, &tmpinfo->rev, + &repos_relpath, + &tmpinfo->repos_root_URL, &tmpinfo->repos_UUID, + &tmpinfo->last_changed_rev, + &tmpinfo->last_changed_date, + &tmpinfo->last_changed_author, + &wc_info->depth, &wc_info->checksum, NULL, + &original_repos_relpath, + &original_repos_root_url, &original_uuid, + &original_revision, &lock, + &wc_info->recorded_size, + &wc_info->recorded_time, + &wc_info->changelist, + &conflicted, &op_root, NULL, NULL, + &have_base, &have_more_work, NULL, + db, local_abspath, + result_pool, scratch_pool)); + + if (original_repos_root_url != NULL) + { + tmpinfo->repos_root_URL = original_repos_root_url; + tmpinfo->repos_UUID = original_uuid; + } + + if (status == svn_wc__db_status_added) + { + /* ### We should also just be fetching the true BASE revision + ### here, which means copied items would also not have a + ### revision to display. But WC-1 wants to show the revision of + ### copy targets as the copyfrom-rev. *sigh* */ + + if (original_repos_relpath) + { + /* Root or child of copy */ + tmpinfo->rev = original_revision; + repos_relpath = original_repos_relpath; + + if (op_root) + { + svn_error_t *err; + wc_info->copyfrom_url = + svn_path_url_add_component2(tmpinfo->repos_root_URL, + original_repos_relpath, + result_pool); + + wc_info->copyfrom_rev = original_revision; + + err = svn_wc__db_scan_moved(&wc_info->moved_from_abspath, + NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + svn_error_clear(err); + wc_info->moved_from_abspath = NULL; + } + } + } + else if (op_root) + { + /* Local addition */ + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, &repos_relpath, + &tmpinfo->repos_root_URL, + &tmpinfo->repos_UUID, + NULL, NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool)); + + if (have_base) + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &tmpinfo->rev, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + } + else + { + /* Child of copy. ### Not WC-NG like */ + SVN_ERR(svn_wc__internal_get_origin(NULL, &tmpinfo->rev, + &repos_relpath, + &tmpinfo->repos_root_URL, + &tmpinfo->repos_UUID, NULL, + db, local_abspath, TRUE, + result_pool, scratch_pool)); + } + + /* ### We should be able to avoid both these calls with the information + from read_info() in most cases */ + if (! op_root) + wc_info->schedule = svn_wc_schedule_normal; + else if (! have_more_work && ! have_base) + wc_info->schedule = svn_wc_schedule_add; + else + { + svn_wc__db_status_t below_working; + svn_boolean_t have_work; + + SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work, + &below_working, + db, local_abspath, + scratch_pool)); + + /* If the node is not present or deleted (read: not present + in working), then the node is not a replacement */ + if (below_working != svn_wc__db_status_not_present + && below_working != svn_wc__db_status_deleted) + { + wc_info->schedule = svn_wc_schedule_replace; + } + else + wc_info->schedule = svn_wc_schedule_add; + } + SVN_ERR(svn_wc__db_read_url(&tmpinfo->URL, db, local_abspath, + result_pool, scratch_pool)); + } + else if (status == svn_wc__db_status_deleted) + { + const char *work_del_abspath; + + SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, + &tmpinfo->last_changed_rev, + &tmpinfo->last_changed_date, + &tmpinfo->last_changed_author, + &wc_info->depth, + &wc_info->checksum, + NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool)); + + /* And now fetch the url and revision of what will be deleted */ + SVN_ERR(svn_wc__db_scan_deletion(NULL, &wc_info->moved_to_abspath, + &work_del_abspath, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + if (work_del_abspath != NULL) + { + /* This is a deletion within a copied subtree. Get the copied-from + * revision. */ + const char *added_abspath = svn_dirent_dirname(work_del_abspath, + scratch_pool); + + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, &repos_relpath, + &tmpinfo->repos_root_URL, + &tmpinfo->repos_UUID, + NULL, NULL, NULL, + &tmpinfo->rev, + db, added_abspath, + result_pool, scratch_pool)); + + tmpinfo->URL = svn_path_url_add_component2( + tmpinfo->repos_root_URL, + svn_relpath_join(repos_relpath, + svn_dirent_skip_ancestor(added_abspath, + local_abspath), + scratch_pool), + result_pool); + } + else + { + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &tmpinfo->rev, + &repos_relpath, + &tmpinfo->repos_root_URL, + &tmpinfo->repos_UUID, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool)); + + tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL, + repos_relpath, + result_pool); + } + + wc_info->schedule = svn_wc_schedule_delete; + } + else if (status == svn_wc__db_status_not_present + || status == svn_wc__db_status_server_excluded) + { + *info = NULL; + return SVN_NO_ERROR; + } + else + { + /* Just a BASE node. We have all the info we need */ + tmpinfo->URL = svn_path_url_add_component2(tmpinfo->repos_root_URL, + repos_relpath, + result_pool); + wc_info->schedule = svn_wc_schedule_normal; + } + + if (status == svn_wc__db_status_excluded) + tmpinfo->wc_info->depth = svn_depth_exclude; + + /* A default */ + tmpinfo->size = SVN_INVALID_FILESIZE; + + SVN_ERR(svn_wc__db_get_wcroot(&tmpinfo->wc_info->wcroot_abspath, db, + local_abspath, result_pool, scratch_pool)); + + if (conflicted) + SVN_ERR(svn_wc__read_conflicts(&wc_info->conflicts, db, + local_abspath, + TRUE /* ### create tempfiles */, + result_pool, scratch_pool)); + else + wc_info->conflicts = NULL; + + /* lock stuff */ + if (lock != NULL) + { + tmpinfo->lock = apr_pcalloc(result_pool, sizeof(*(tmpinfo->lock))); + tmpinfo->lock->token = lock->token; + tmpinfo->lock->owner = lock->owner; + tmpinfo->lock->comment = lock->comment; + tmpinfo->lock->creation_date = lock->date; + } + + *info = tmpinfo; + return SVN_NO_ERROR; +} + + +/* Set *INFO to a new struct with minimal content, to be + used in reporting info for unversioned tree conflict victims. */ +/* ### Some fields we could fill out based on the parent dir's entry + or by looking at an obstructing item. */ +static svn_error_t * +build_info_for_unversioned(svn_wc__info2_t **info, + apr_pool_t *pool) +{ + svn_wc__info2_t *tmpinfo = apr_pcalloc(pool, sizeof(*tmpinfo)); + svn_wc_info_t *wc_info = apr_pcalloc(pool, sizeof (*wc_info)); + + tmpinfo->URL = NULL; + tmpinfo->repos_UUID = NULL; + tmpinfo->repos_root_URL = NULL; + tmpinfo->rev = SVN_INVALID_REVNUM; + tmpinfo->kind = svn_node_none; + tmpinfo->size = SVN_INVALID_FILESIZE; + tmpinfo->last_changed_rev = SVN_INVALID_REVNUM; + tmpinfo->last_changed_date = 0; + tmpinfo->last_changed_author = NULL; + tmpinfo->lock = NULL; + + tmpinfo->wc_info = wc_info; + + wc_info->copyfrom_rev = SVN_INVALID_REVNUM; + wc_info->depth = svn_depth_unknown; + wc_info->recorded_size = SVN_INVALID_FILESIZE; + + *info = tmpinfo; + return SVN_NO_ERROR; +} + +/* Callback and baton for crawl_entries() walk over entries files. */ +struct found_entry_baton +{ + svn_wc__info_receiver2_t receiver; + void *receiver_baton; + svn_wc__db_t *db; + svn_boolean_t actual_only; + svn_boolean_t first; + /* The set of tree conflicts that have been found but not (yet) visited by + * the tree walker. Map of abspath -> svn_wc_conflict_description2_t. */ + apr_hash_t *tree_conflicts; + apr_pool_t *pool; +}; + +/* Call WALK_BATON->receiver with WALK_BATON->receiver_baton, passing to it + * info about the path LOCAL_ABSPATH. + * An svn_wc__node_found_func_t callback function. */ +static svn_error_t * +info_found_node_callback(const char *local_abspath, + svn_node_kind_t kind, + void *walk_baton, + apr_pool_t *scratch_pool) +{ + struct found_entry_baton *fe_baton = walk_baton; + svn_wc__info2_t *info; + + SVN_ERR(build_info_for_node(&info, fe_baton->db, local_abspath, + kind, scratch_pool, scratch_pool)); + + if (info == NULL) + { + if (!fe_baton->first) + return SVN_NO_ERROR; /* not present or server excluded descendant */ + + /* If the info root is not found, that is an error */ + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + fe_baton->first = FALSE; + + SVN_ERR_ASSERT(info->wc_info != NULL); + SVN_ERR(fe_baton->receiver(fe_baton->receiver_baton, local_abspath, + info, scratch_pool)); + + /* If this node is a versioned directory, make a note of any tree conflicts + * on all immediate children. Some of these may be visited later in this + * walk, at which point they will be removed from the list, while any that + * are not visited will remain in the list. */ + if (fe_baton->actual_only && kind == svn_node_dir) + { + const apr_array_header_t *victims; + int i; + + SVN_ERR(svn_wc__db_read_conflict_victims(&victims, + fe_baton->db, local_abspath, + scratch_pool, scratch_pool)); + + for (i = 0; i < victims->nelts; i++) + { + const char *this_basename = APR_ARRAY_IDX(victims, i, const char *); + + svn_hash_sets(fe_baton->tree_conflicts, + svn_dirent_join(local_abspath, this_basename, + fe_baton->pool), + ""); + } + } + + /* Delete this path which we are currently visiting from the list of tree + * conflicts. This relies on the walker visiting a directory before visiting + * its children. */ + svn_hash_sets(fe_baton->tree_conflicts, local_abspath, NULL); + + return SVN_NO_ERROR; +} + + +/* Return TRUE iff the subtree at ROOT_ABSPATH, restricted to depth DEPTH, + * would include the path CHILD_ABSPATH of kind CHILD_KIND. */ +static svn_boolean_t +depth_includes(const char *root_abspath, + svn_depth_t depth, + const char *child_abspath, + svn_node_kind_t child_kind, + apr_pool_t *scratch_pool) +{ + const char *parent_abspath = svn_dirent_dirname(child_abspath, scratch_pool); + + return (depth == svn_depth_infinity + || ((depth == svn_depth_immediates + || (depth == svn_depth_files && child_kind == svn_node_file)) + && strcmp(root_abspath, parent_abspath) == 0) + || strcmp(root_abspath, child_abspath) == 0); +} + + +svn_error_t * +svn_wc__get_info(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t fetch_excluded, + svn_boolean_t fetch_actual_only, + const apr_array_header_t *changelist_filter, + svn_wc__info_receiver2_t receiver, + void *receiver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + struct found_entry_baton fe_baton; + svn_error_t *err; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_index_t *hi; + const char *repos_root_url = NULL; + const char *repos_uuid = NULL; + + fe_baton.receiver = receiver; + fe_baton.receiver_baton = receiver_baton; + fe_baton.db = wc_ctx->db; + fe_baton.actual_only = fetch_actual_only; + fe_baton.first = TRUE; + fe_baton.tree_conflicts = apr_hash_make(scratch_pool); + fe_baton.pool = scratch_pool; + + err = svn_wc__internal_walk_children(wc_ctx->db, local_abspath, + fetch_excluded, + changelist_filter, + info_found_node_callback, + &fe_baton, depth, + cancel_func, cancel_baton, + iterpool); + + /* If the target root node is not present, svn_wc__internal_walk_children() + returns a PATH_NOT_FOUND error and doesn't call the callback. If there + is a tree conflict on this node, that is not an error. */ + if (fe_baton.first /* not visited by walk_children */ + && fetch_actual_only + && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_boolean_t tree_conflicted; + svn_error_t *err2; + + err2 = svn_wc__internal_conflicted_p(NULL, NULL, &tree_conflicted, + wc_ctx->db, local_abspath, + iterpool); + + if ((err2 && err2->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)) + { + svn_error_clear(err2); + return svn_error_trace(err); + } + else if (err2 || !tree_conflicted) + return svn_error_compose_create(err, err2); + + svn_error_clear(err); + + svn_hash_sets(fe_baton.tree_conflicts, local_abspath, ""); + } + else + SVN_ERR(err); + + /* If there are any tree conflicts that we have found but have not reported, + * send a minimal info struct for each one now. */ + for (hi = apr_hash_first(scratch_pool, fe_baton.tree_conflicts); hi; + hi = apr_hash_next(hi)) + { + const char *this_abspath = svn__apr_hash_index_key(hi); + const svn_wc_conflict_description2_t *tree_conflict; + svn_wc__info2_t *info; + + svn_pool_clear(iterpool); + + SVN_ERR(build_info_for_unversioned(&info, iterpool)); + + if (!repos_root_url) + { + SVN_ERR(svn_wc__internal_get_repos_info(NULL, NULL, + &repos_root_url, + &repos_uuid, + wc_ctx->db, + svn_dirent_dirname( + local_abspath, + iterpool), + scratch_pool, + iterpool)); + } + + info->repos_root_URL = repos_root_url; + info->repos_UUID = repos_uuid; + + SVN_ERR(svn_wc__read_conflicts(&info->wc_info->conflicts, + wc_ctx->db, this_abspath, + TRUE /* ### create tempfiles */, + iterpool, iterpool)); + + if (! info->wc_info->conflicts || ! info->wc_info->conflicts->nelts) + continue; + + tree_conflict = APR_ARRAY_IDX(info->wc_info->conflicts, 0, + svn_wc_conflict_description2_t *); + + if (!depth_includes(local_abspath, depth, tree_conflict->local_abspath, + tree_conflict->node_kind, iterpool)) + continue; + + SVN_ERR(receiver(receiver_baton, this_abspath, info, iterpool)); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/lock.c b/subversion/libsvn_wc/lock.c new file mode 100644 index 000000000000..36fbb0eeb199 --- /dev/null +++ b/subversion/libsvn_wc/lock.c @@ -0,0 +1,1656 @@ +/* + * lock.c: routines for locking working copy subdirectories. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#define SVN_DEPRECATED + +#include <apr_pools.h> +#include <apr_time.h> + +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_sorts.h" +#include "svn_hash.h" +#include "svn_types.h" + +#include "wc.h" +#include "adm_files.h" +#include "lock.h" +#include "props.h" +#include "wc_db.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" + + + + +struct svn_wc_adm_access_t +{ + /* PATH to directory which contains the administrative area */ + const char *path; + + /* And the absolute form of the path. */ + const char *abspath; + + /* Indicates that the baton has been closed. */ + svn_boolean_t closed; + + /* Handle to the administrative database. */ + svn_wc__db_t *db; + + /* Was the DB provided to us? If so, then we'll never close it. */ + svn_boolean_t db_provided; + + /* ENTRIES_HIDDEN is all cached entries including those in + state deleted or state absent. It may be NULL. */ + apr_hash_t *entries_all; + + /* POOL is used to allocate cached items, they need to persist for the + lifetime of this access baton */ + apr_pool_t *pool; + +}; + + +/* This is a placeholder used in the set hash to represent missing + directories. Only its address is important, it contains no useful + data. */ +static const svn_wc_adm_access_t missing = { 0 }; +#define IS_MISSING(lock) ((lock) == &missing) + +/* ### hack for now. future functionality coming in a future revision. */ +#define svn_wc__db_is_closed(db) FALSE + + +svn_error_t * +svn_wc__internal_check_wc(int *wc_format, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t check_path, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + + err = svn_wc__db_temp_get_format(wc_format, db, local_abspath, scratch_pool); + if (err) + { + svn_node_kind_t kind; + + if (err->apr_err != SVN_ERR_WC_MISSING && + err->apr_err != SVN_ERR_WC_UNSUPPORTED_FORMAT && + err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED) + return svn_error_trace(err); + svn_error_clear(err); + + /* ### the stuff below seems to be redundant. get_format() probably + ### does all this. + ### + ### investigate all callers. DEFINITELY keep in mind the + ### svn_wc_check_wc() entrypoint. + */ + + /* If the format file does not exist or path not directory, then for + our purposes this is not a working copy, so return 0. */ + *wc_format = 0; + + /* Check path itself exists. */ + SVN_ERR(svn_io_check_path(local_abspath, &kind, scratch_pool)); + if (kind == svn_node_none) + { + return svn_error_createf(APR_ENOENT, NULL, _("'%s' does not exist"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + } + + if (*wc_format >= SVN_WC__WC_NG_VERSION) + { + svn_wc__db_status_t db_status; + svn_node_kind_t db_kind; + + if (check_path) + { + /* If a node is not a directory, it is not a working copy + directory. This allows creating new working copies as + a path below an existing working copy. */ + svn_node_kind_t wc_kind; + + SVN_ERR(svn_io_check_path(local_abspath, &wc_kind, scratch_pool)); + if (wc_kind != svn_node_dir) + { + *wc_format = 0; /* Not a directory, so not a wc-directory */ + return SVN_NO_ERROR; + } + } + + err = svn_wc__db_read_info(&db_status, &db_kind, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + *wc_format = 0; + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + if (db_kind != svn_node_dir) + { + /* The WC thinks there must be a file, so this is not + a wc-directory */ + *wc_format = 0; + return SVN_NO_ERROR; + } + + switch (db_status) + { + case svn_wc__db_status_not_present: + case svn_wc__db_status_server_excluded: + case svn_wc__db_status_excluded: + /* If there is a directory here, it is not related to the parent + working copy: Obstruction */ + *wc_format = 0; + return SVN_NO_ERROR; + default: + break; + } + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_check_wc2(int *wc_format, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + /* ### Should we pass TRUE for check_path to find obstructions and + missing directories? */ + return svn_error_trace( + svn_wc__internal_check_wc(wc_format, wc_ctx->db, local_abspath, FALSE, + scratch_pool)); +} + + +/* */ +static svn_error_t * +add_to_shared(svn_wc_adm_access_t *lock, apr_pool_t *scratch_pool) +{ + /* ### sometimes we replace &missing with a now-valid lock. */ + { + svn_wc_adm_access_t *prior = svn_wc__db_temp_get_access(lock->db, + lock->abspath, + scratch_pool); + if (IS_MISSING(prior)) + SVN_ERR(svn_wc__db_temp_close_access(lock->db, lock->abspath, + prior, scratch_pool)); + } + + svn_wc__db_temp_set_access(lock->db, lock->abspath, lock, + scratch_pool); + + return SVN_NO_ERROR; +} + + +/* */ +static svn_wc_adm_access_t * +get_from_shared(const char *abspath, + svn_wc__db_t *db, + apr_pool_t *scratch_pool) +{ + /* We closed the DB when it became empty. ABSPATH is not present. */ + if (db == NULL) + return NULL; + return svn_wc__db_temp_get_access(db, abspath, scratch_pool); +} + + +/* */ +static svn_error_t * +close_single(svn_wc_adm_access_t *adm_access, + svn_boolean_t preserve_lock, + apr_pool_t *scratch_pool) +{ + svn_boolean_t locked; + + if (adm_access->closed) + return SVN_NO_ERROR; + + /* Physically unlock if required */ + SVN_ERR(svn_wc__db_wclock_owns_lock(&locked, adm_access->db, + adm_access->abspath, TRUE, + scratch_pool)); + if (locked) + { + if (!preserve_lock) + { + /* Remove the physical lock in the admin directory for + PATH. It is acceptable for the administrative area to + have disappeared, such as when the directory is removed + from the working copy. It is an error for the lock to + have disappeared if the administrative area still exists. */ + + svn_error_t *err = svn_wc__db_wclock_release(adm_access->db, + adm_access->abspath, + scratch_pool); + if (err) + { + if (svn_wc__adm_area_exists(adm_access->abspath, scratch_pool)) + return err; + svn_error_clear(err); + } + } + } + + /* Reset to prevent further use of the lock. */ + adm_access->closed = TRUE; + + /* Detach from set */ + SVN_ERR(svn_wc__db_temp_close_access(adm_access->db, adm_access->abspath, + adm_access, scratch_pool)); + + /* Possibly close the underlying wc_db. */ + if (!adm_access->db_provided) + { + apr_hash_t *opened = svn_wc__db_temp_get_all_access(adm_access->db, + scratch_pool); + if (apr_hash_count(opened) == 0) + { + SVN_ERR(svn_wc__db_close(adm_access->db)); + adm_access->db = NULL; + } + } + + return SVN_NO_ERROR; +} + + +/* Cleanup for a locked access baton. + + This handles closing access batons when their pool gets destroyed. + The physical locks associated with such batons remain in the working + copy if they are protecting work items in the workqueue. */ +static apr_status_t +pool_cleanup_locked(void *p) +{ + svn_wc_adm_access_t *lock = p; + apr_uint64_t id; + svn_skel_t *work_item; + svn_error_t *err; + + if (lock->closed) + return APR_SUCCESS; + + /* If the DB is closed, then we have a bunch of extra work to do. */ + if (svn_wc__db_is_closed(lock->db)) + { + apr_pool_t *scratch_pool; + svn_wc__db_t *db; + + lock->closed = TRUE; + + /* If there is no ADM area, then we definitely have no work items + or physical locks to worry about. Bail out. */ + if (!svn_wc__adm_area_exists(lock->abspath, lock->pool)) + return APR_SUCCESS; + + /* Creating a subpool is safe within a pool cleanup, as long as + we're absolutely sure to destroy it before we exit this function. + + We avoid using LOCK->POOL to keep the following functions from + hanging cleanups or subpools from it. (the cleanups *might* get + run, but the subpools will NOT be destroyed) */ + scratch_pool = svn_pool_create(lock->pool); + + err = svn_wc__db_open(&db, NULL /* ### config. need! */, FALSE, TRUE, + scratch_pool, scratch_pool); + if (!err) + { + err = svn_wc__db_wq_fetch_next(&id, &work_item, db, lock->abspath, 0, + scratch_pool, scratch_pool); + if (!err && work_item == NULL) + { + /* There is no remaining work, so we're good to remove any + potential "physical" lock. */ + err = svn_wc__db_wclock_release(db, lock->abspath, scratch_pool); + } + } + svn_error_clear(err); + + /* Closes the DB, too. */ + svn_pool_destroy(scratch_pool); + + return APR_SUCCESS; + } + + /* ### should we create an API that just looks, but doesn't return? */ + err = svn_wc__db_wq_fetch_next(&id, &work_item, lock->db, lock->abspath, 0, + lock->pool, lock->pool); + + /* Close just this access baton. The pool cleanup will close the rest. */ + if (!err) + err = close_single(lock, + work_item != NULL /* preserve_lock */, + lock->pool); + + if (err) + { + apr_status_t apr_err = err->apr_err; + svn_error_clear(err); + return apr_err; + } + + return APR_SUCCESS; +} + + +/* Cleanup for a readonly access baton. */ +static apr_status_t +pool_cleanup_readonly(void *data) +{ + svn_wc_adm_access_t *lock = data; + svn_error_t *err; + + if (lock->closed) + return APR_SUCCESS; + + /* If the DB is closed, then we have nothing to do. There are no + "physical" locks to remove, and we don't care whether this baton + is registered with the DB. */ + if (svn_wc__db_is_closed(lock->db)) + return APR_SUCCESS; + + /* Close this baton. No lock to preserve. Since this is part of the + pool cleanup, we don't need to close children -- the cleanup process + will close all children. */ + err = close_single(lock, FALSE /* preserve_lock */, lock->pool); + if (err) + { + apr_status_t result = err->apr_err; + svn_error_clear(err); + return result; + } + + return APR_SUCCESS; +} + + +/* An APR pool cleanup handler. This is a child handler, it removes the + main pool handler. */ +static apr_status_t +pool_cleanup_child(void *p) +{ + svn_wc_adm_access_t *lock = p; + + apr_pool_cleanup_kill(lock->pool, lock, pool_cleanup_locked); + apr_pool_cleanup_kill(lock->pool, lock, pool_cleanup_readonly); + + return APR_SUCCESS; +} + + +/* Allocate from POOL, initialise and return an access baton. TYPE and PATH + are used to initialise the baton. If STEAL_LOCK, steal the lock if path + is already locked */ +static svn_error_t * +adm_access_alloc(svn_wc_adm_access_t **adm_access, + const char *path, + svn_wc__db_t *db, + svn_boolean_t db_provided, + svn_boolean_t write_lock, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_wc_adm_access_t *lock = apr_palloc(result_pool, sizeof(*lock)); + + lock->closed = FALSE; + lock->entries_all = NULL; + lock->db = db; + lock->db_provided = db_provided; + lock->path = apr_pstrdup(result_pool, path); + lock->pool = result_pool; + + SVN_ERR(svn_dirent_get_absolute(&lock->abspath, path, result_pool)); + + *adm_access = lock; + + if (write_lock) + { + svn_boolean_t owns_lock; + + /* If the db already owns a lock, we can't add an extra lock record */ + SVN_ERR(svn_wc__db_wclock_owns_lock(&owns_lock, db, path, FALSE, + scratch_pool)); + + /* If DB owns the lock, but when there is no access baton open for this + directory, old access baton based code is trying to access data that + was previously locked by new code. Just hand them the lock, or + important code paths like svn_wc_add3() will start failing */ + if (!owns_lock + || svn_wc__adm_retrieve_internal2(db, lock->abspath, scratch_pool)) + { + SVN_ERR(svn_wc__db_wclock_obtain(db, lock->abspath, 0, FALSE, + scratch_pool)); + } + } + + err = add_to_shared(lock, scratch_pool); + + if (err) + return svn_error_compose_create( + err, + svn_wc__db_wclock_release(db, lock->abspath, scratch_pool)); + + /* ### does this utf8 thing really/still apply?? */ + /* It's important that the cleanup handler is registered *after* at least + one UTF8 conversion has been done, since such a conversion may create + the apr_xlate_t object in the pool, and that object must be around + when the cleanup handler runs. If the apr_xlate_t cleanup handler + were to run *before* the access baton cleanup handler, then the access + baton's handler won't work. */ + + /* Register an appropriate cleanup handler, based on the whether this + access baton is locked or not. */ + apr_pool_cleanup_register(lock->pool, lock, + write_lock + ? pool_cleanup_locked + : pool_cleanup_readonly, + pool_cleanup_child); + + return SVN_NO_ERROR; +} + + +/* */ +static svn_error_t * +probe(svn_wc__db_t *db, + const char **dir, + const char *path, + apr_pool_t *pool) +{ + svn_node_kind_t kind; + int wc_format = 0; + + SVN_ERR(svn_io_check_path(path, &kind, pool)); + if (kind == svn_node_dir) + { + const char *local_abspath; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__internal_check_wc(&wc_format, db, local_abspath, + FALSE, pool)); + } + + /* a "version" of 0 means a non-wc directory */ + if (kind != svn_node_dir || wc_format == 0) + { + /* Passing a path ending in "." or ".." to svn_dirent_dirname() is + probably always a bad idea; certainly it is in this case. + Unfortunately, svn_dirent_dirname()'s current signature can't + return an error, so we have to insert the protection in this + caller, ideally the API needs a change. See issue #1617. */ + const char *base_name = svn_dirent_basename(path, pool); + if ((strcmp(base_name, "..") == 0) + || (strcmp(base_name, ".") == 0)) + { + return svn_error_createf + (SVN_ERR_WC_BAD_PATH, NULL, + _("Path '%s' ends in '%s', " + "which is unsupported for this operation"), + svn_dirent_local_style(path, pool), base_name); + } + + *dir = svn_dirent_dirname(path, pool); + } + else + *dir = path; + + return SVN_NO_ERROR; +} + + +/* */ +static svn_error_t * +open_single(svn_wc_adm_access_t **adm_access, + const char *path, + svn_boolean_t write_lock, + svn_wc__db_t *db, + svn_boolean_t db_provided, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *local_abspath; + int wc_format = 0; + svn_error_t *err; + svn_wc_adm_access_t *lock; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool)); + err = svn_wc__internal_check_wc(&wc_format, db, local_abspath, FALSE, + scratch_pool); + if (wc_format == 0 || (err && APR_STATUS_IS_ENOENT(err->apr_err))) + { + return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, err, + _("'%s' is not a working copy"), + svn_dirent_local_style(path, scratch_pool)); + } + SVN_ERR(err); + + /* The format version must match exactly. Note that wc_db will perform + an auto-upgrade if allowed. If it does *not*, then it has decided a + manual upgrade is required and it should have raised an error. */ + SVN_ERR_ASSERT(wc_format == SVN_WC__VERSION); + + /* Need to create a new lock */ + SVN_ERR(adm_access_alloc(&lock, path, db, db_provided, write_lock, + result_pool, scratch_pool)); + + /* ### recurse was here */ + *adm_access = lock; + + return SVN_NO_ERROR; +} + + +/* Retrieves the KIND of LOCAL_ABSPATH and whether its administrative data is + available in the working copy. + + *AVAILABLE is set to TRUE when the node and its metadata are available, + otherwise to FALSE (due to obstruction, missing, absence, exclusion, + or a "not-present" child). + + KIND can be NULL. + + ### note: this function should go away when we move to a single + ### adminstrative area. */ +static svn_error_t * +adm_available(svn_boolean_t *available, + svn_node_kind_t *kind, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + + if (kind) + *kind = svn_node_unknown; + + SVN_ERR(svn_wc__db_read_info(&status, kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool)); + + *available = !(status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_not_present); + + return SVN_NO_ERROR; +} +/* This is essentially the guts of svn_wc_adm_open3. + * + * If the working copy is already locked, return SVN_ERR_WC_LOCKED; if + * it is not a versioned directory, return SVN_ERR_WC_NOT_WORKING_COPY. + */ +static svn_error_t * +do_open(svn_wc_adm_access_t **adm_access, + const char *path, + svn_wc__db_t *db, + svn_boolean_t db_provided, + apr_array_header_t *rollback, + svn_boolean_t write_lock, + int levels_to_lock, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_adm_access_t *lock; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(open_single(&lock, path, write_lock, db, db_provided, + result_pool, iterpool)); + + /* Add self to the rollback list in case of error. */ + APR_ARRAY_PUSH(rollback, svn_wc_adm_access_t *) = lock; + + if (levels_to_lock != 0) + { + const apr_array_header_t *children; + const char *local_abspath = svn_wc__adm_access_abspath(lock); + int i; + + /* Reduce levels_to_lock since we are about to recurse */ + if (levels_to_lock > 0) + levels_to_lock--; + + SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath, + scratch_pool, iterpool)); + + /* Open the tree */ + for (i = 0; i < children->nelts; i++) + { + const char *node_abspath; + svn_node_kind_t kind; + svn_boolean_t available; + const char *name = APR_ARRAY_IDX(children, i, const char *); + + svn_pool_clear(iterpool); + + /* See if someone wants to cancel this operation. */ + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + node_abspath = svn_dirent_join(local_abspath, name, iterpool); + + SVN_ERR(adm_available(&available, + &kind, + db, + node_abspath, + scratch_pool)); + + if (kind != svn_node_dir) + continue; + + if (available) + { + const char *node_path = svn_dirent_join(path, name, iterpool); + svn_wc_adm_access_t *node_access; + + SVN_ERR(do_open(&node_access, node_path, db, db_provided, + rollback, write_lock, levels_to_lock, + cancel_func, cancel_baton, + lock->pool, iterpool)); + /* node_access has been registered in DB, so we don't need + to do anything with it. */ + } + } + } + svn_pool_destroy(iterpool); + + *adm_access = lock; + + return SVN_NO_ERROR; +} + + +/* */ +static svn_error_t * +open_all(svn_wc_adm_access_t **adm_access, + const char *path, + svn_wc__db_t *db, + svn_boolean_t db_provided, + svn_boolean_t write_lock, + int levels_to_lock, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + apr_array_header_t *rollback; + svn_error_t *err; + + rollback = apr_array_make(pool, 10, sizeof(svn_wc_adm_access_t *)); + + err = do_open(adm_access, path, db, db_provided, rollback, + write_lock, levels_to_lock, + cancel_func, cancel_baton, pool, pool); + if (err) + { + int i; + + for (i = rollback->nelts; i--; ) + { + svn_wc_adm_access_t *lock = APR_ARRAY_IDX(rollback, i, + svn_wc_adm_access_t *); + SVN_ERR_ASSERT(!IS_MISSING(lock)); + + svn_error_clear(close_single(lock, FALSE /* preserve_lock */, pool)); + } + } + + return svn_error_trace(err); +} + + +svn_error_t * +svn_wc_adm_open3(svn_wc_adm_access_t **adm_access, + svn_wc_adm_access_t *associated, + const char *path, + svn_boolean_t write_lock, + int levels_to_lock, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_wc__db_t *db; + svn_boolean_t db_provided; + + /* Make sure that ASSOCIATED has a set of access batons, so that we can + glom a reference to self into it. */ + if (associated) + { + const char *abspath; + svn_wc_adm_access_t *lock; + + SVN_ERR(svn_dirent_get_absolute(&abspath, path, pool)); + lock = get_from_shared(abspath, associated->db, pool); + if (lock && !IS_MISSING(lock)) + /* Already locked. The reason we don't return the existing baton + here is that the user is supposed to know whether a directory is + locked: if it's not locked call svn_wc_adm_open, if it is locked + call svn_wc_adm_retrieve. */ + return svn_error_createf(SVN_ERR_WC_LOCKED, NULL, + _("Working copy '%s' locked"), + svn_dirent_local_style(path, pool)); + db = associated->db; + db_provided = associated->db_provided; + } + else + { + /* Any baton creation is going to need a shared structure for holding + data across the entire set. The caller isn't providing one, so we + do it here. */ + /* ### we could optimize around levels_to_lock==0, but much of this + ### is going to be simplified soon anyways. */ + SVN_ERR(svn_wc__db_open(&db, NULL /* ### config. need! */, FALSE, TRUE, + pool, pool)); + db_provided = FALSE; + } + + return svn_error_trace(open_all(adm_access, path, db, db_provided, + write_lock, levels_to_lock, + cancel_func, cancel_baton, pool)); +} + + +svn_error_t * +svn_wc_adm_probe_open3(svn_wc_adm_access_t **adm_access, + svn_wc_adm_access_t *associated, + const char *path, + svn_boolean_t write_lock, + int levels_to_lock, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_error_t *err; + const char *dir; + + if (associated == NULL) + { + svn_wc__db_t *db; + + /* Ugh. Too bad about having to open a DB. */ + SVN_ERR(svn_wc__db_open(&db, + NULL /* ### config */, FALSE, TRUE, pool, pool)); + err = probe(db, &dir, path, pool); + svn_error_clear(svn_wc__db_close(db)); + SVN_ERR(err); + } + else + { + SVN_ERR(probe(associated->db, &dir, path, pool)); + } + + /* If we moved up a directory, then the path is not a directory, or it + is not under version control. In either case, the notion of + levels_to_lock does not apply to the provided path. Disable it so + that we don't end up trying to lock more than we need. */ + if (dir != path) + levels_to_lock = 0; + + err = svn_wc_adm_open3(adm_access, associated, dir, write_lock, + levels_to_lock, cancel_func, cancel_baton, pool); + if (err) + { + svn_error_t *err2; + + /* If we got an error on the parent dir, that means we failed to + get an access baton for the child in the first place. And if + the reason we couldn't get the child access baton is that the + child is not a versioned directory, then return an error + about the child, not the parent. */ + svn_node_kind_t child_kind; + if ((err2 = svn_io_check_path(path, &child_kind, pool))) + { + svn_error_compose(err, err2); + return err; + } + + if ((dir != path) + && (child_kind == svn_node_dir) + && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)) + { + svn_error_clear(err); + return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, + _("'%s' is not a working copy"), + svn_dirent_local_style(path, pool)); + } + + return err; + } + + return SVN_NO_ERROR; +} + + +svn_wc_adm_access_t * +svn_wc__adm_retrieve_internal2(svn_wc__db_t *db, + const char *abspath, + apr_pool_t *scratch_pool) +{ + svn_wc_adm_access_t *adm_access = get_from_shared(abspath, db, scratch_pool); + + /* If the entry is marked as "missing", then return nothing. */ + if (IS_MISSING(adm_access)) + adm_access = NULL; + + return adm_access; +} + + +/* SVN_DEPRECATED */ +svn_error_t * +svn_wc_adm_retrieve(svn_wc_adm_access_t **adm_access, + svn_wc_adm_access_t *associated, + const char *path, + apr_pool_t *pool) +{ + const char *local_abspath; + svn_node_kind_t kind = svn_node_unknown; + svn_node_kind_t wckind; + svn_error_t *err; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + + if (strcmp(associated->path, path) == 0) + *adm_access = associated; + else + *adm_access = svn_wc__adm_retrieve_internal2(associated->db, local_abspath, + pool); + + /* We found what we're looking for, so bail. */ + if (*adm_access) + return SVN_NO_ERROR; + + /* Most of the code expects access batons to exist, so returning an error + generally makes the calling code simpler as it doesn't need to check + for NULL batons. */ + /* We are going to send a SVN_ERR_WC_NOT_LOCKED, but let's provide + a bit more information to our caller */ + + err = svn_io_check_path(path, &wckind, pool); + + /* If we can't check the path, we can't make a good error message. */ + if (err) + { + return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, err, + _("Unable to check path existence for '%s'"), + svn_dirent_local_style(path, pool)); + } + + if (associated) + { + err = svn_wc__db_read_kind(&kind, svn_wc__adm_get_db(associated), + local_abspath, + TRUE /* allow_missing */, + TRUE /* show_deleted */, + FALSE /* show_hidden */, pool); + + if (err) + { + kind = svn_node_unknown; + svn_error_clear(err); + } + } + + if (kind == svn_node_dir && wckind == svn_node_file) + { + err = svn_error_createf( + SVN_ERR_WC_NOT_WORKING_COPY, NULL, + _("Expected '%s' to be a directory but found a file"), + svn_dirent_local_style(path, pool)); + + return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message); + } + + if (kind != svn_node_dir && kind != svn_node_unknown) + { + err = svn_error_createf( + SVN_ERR_WC_NOT_WORKING_COPY, NULL, + _("Can't retrieve an access baton for non-directory '%s'"), + svn_dirent_local_style(path, pool)); + + return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message); + } + + if (kind == svn_node_unknown || wckind == svn_node_none) + { + err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("Directory '%s' is missing"), + svn_dirent_local_style(path, pool)); + + return svn_error_create(SVN_ERR_WC_NOT_LOCKED, err, err->message); + } + + /* If all else fails, return our useless generic error. */ + return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL, + _("Working copy '%s' is not locked"), + svn_dirent_local_style(path, pool)); +} + + +/* SVN_DEPRECATED */ +svn_error_t * +svn_wc_adm_probe_retrieve(svn_wc_adm_access_t **adm_access, + svn_wc_adm_access_t *associated, + const char *path, + apr_pool_t *pool) +{ + const char *dir; + const char *local_abspath; + svn_node_kind_t kind; + svn_error_t *err; + + SVN_ERR_ASSERT(associated != NULL); + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + SVN_ERR(svn_wc__db_read_kind(&kind, associated->db, local_abspath, + TRUE /* allow_missing */, + TRUE /* show_deleted */, + FALSE /* show_hidden*/, + pool)); + + if (kind == svn_node_dir) + dir = path; + else if (kind != svn_node_unknown) + dir = svn_dirent_dirname(path, pool); + else + /* Not a versioned item, probe it */ + SVN_ERR(probe(associated->db, &dir, path, pool)); + + err = svn_wc_adm_retrieve(adm_access, associated, dir, pool); + if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED) + { + /* We'll receive a NOT LOCKED error for various reasons, + including the reason we'll actually want to test for: + The path is a versioned directory, but missing, in which case + we want its parent's adm_access (which holds minimal data + on the child) */ + svn_error_clear(err); + SVN_ERR(probe(associated->db, &dir, path, pool)); + SVN_ERR(svn_wc_adm_retrieve(adm_access, associated, dir, pool)); + } + else + return svn_error_trace(err); + + return SVN_NO_ERROR; +} + + +/* SVN_DEPRECATED */ +svn_error_t * +svn_wc_adm_probe_try3(svn_wc_adm_access_t **adm_access, + svn_wc_adm_access_t *associated, + const char *path, + svn_boolean_t write_lock, + int levels_to_lock, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + svn_error_t *err; + + err = svn_wc_adm_probe_retrieve(adm_access, associated, path, pool); + + /* SVN_ERR_WC_NOT_LOCKED would mean there was no access baton for + path in associated, in which case we want to open an access + baton and add it to associated. */ + if (err && (err->apr_err == SVN_ERR_WC_NOT_LOCKED)) + { + svn_error_clear(err); + err = svn_wc_adm_probe_open3(adm_access, associated, + path, write_lock, levels_to_lock, + cancel_func, cancel_baton, + svn_wc_adm_access_pool(associated)); + + /* If the path is not a versioned directory, we just return a + null access baton with no error. Note that of the errors we + do report, the most important (and probably most likely) is + SVN_ERR_WC_LOCKED. That error would mean that someone else + has this area locked, and we definitely want to bail in that + case. */ + if (err && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)) + { + svn_error_clear(err); + *adm_access = NULL; + err = NULL; + } + } + + return err; +} + + +/* */ +static svn_error_t * +child_is_disjoint(svn_boolean_t *disjoint, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_boolean_t is_switched; + + /* Check if the parent directory knows about this node */ + SVN_ERR(svn_wc__db_is_switched(disjoint, &is_switched, NULL, + db, local_abspath, scratch_pool)); + + if (*disjoint) + return SVN_NO_ERROR; + + if (is_switched) + *disjoint = TRUE; + + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +open_anchor(svn_wc_adm_access_t **anchor_access, + svn_wc_adm_access_t **target_access, + const char **target, + svn_wc__db_t *db, + svn_boolean_t db_provided, + const char *path, + svn_boolean_t write_lock, + int levels_to_lock, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + const char *base_name = svn_dirent_basename(path, pool); + + /* Any baton creation is going to need a shared structure for holding + data across the entire set. The caller isn't providing one, so we + do it here. */ + /* ### we could maybe skip the shared struct for levels_to_lock==0, but + ### given that we need DB for format detection, may as well keep this. + ### in any case, much of this is going to be simplified soon anyways. */ + if (!db_provided) + SVN_ERR(svn_wc__db_open(&db, NULL, /* ### config. need! */ FALSE, TRUE, + pool, pool)); + + if (svn_path_is_empty(path) + || svn_dirent_is_root(path, strlen(path)) + || ! strcmp(base_name, "..")) + { + SVN_ERR(open_all(anchor_access, path, db, db_provided, + write_lock, levels_to_lock, + cancel_func, cancel_baton, pool)); + *target_access = *anchor_access; + *target = ""; + } + else + { + svn_error_t *err; + svn_wc_adm_access_t *p_access = NULL; + svn_wc_adm_access_t *t_access = NULL; + const char *parent = svn_dirent_dirname(path, pool); + const char *local_abspath; + svn_error_t *p_access_err = SVN_NO_ERROR; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + + /* Try to open parent of PATH to setup P_ACCESS */ + err = open_single(&p_access, parent, write_lock, db, db_provided, + pool, pool); + if (err) + { + const char *abspath = svn_dirent_dirname(local_abspath, pool); + svn_wc_adm_access_t *existing_adm = svn_wc__db_temp_get_access(db, abspath, pool); + + if (IS_MISSING(existing_adm)) + svn_wc__db_temp_clear_access(db, abspath, pool); + else + SVN_ERR_ASSERT(existing_adm == NULL); + + if (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY) + { + svn_error_clear(err); + p_access = NULL; + } + else if (write_lock && (err->apr_err == SVN_ERR_WC_LOCKED + || APR_STATUS_IS_EACCES(err->apr_err))) + { + /* If P_ACCESS isn't to be returned then a read-only baton + will do for now, but keep the error in case we need it. */ + svn_error_t *err2 = open_single(&p_access, parent, FALSE, + db, db_provided, pool, pool); + if (err2) + { + svn_error_clear(err2); + return err; + } + p_access_err = err; + } + else + return err; + } + + /* Try to open PATH to setup T_ACCESS */ + err = open_all(&t_access, path, db, db_provided, write_lock, + levels_to_lock, cancel_func, cancel_baton, pool); + if (err) + { + if (p_access == NULL) + { + /* Couldn't open the parent or the target. Bail out. */ + svn_error_clear(p_access_err); + return svn_error_trace(err); + } + + if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) + { + if (p_access) + svn_error_clear(svn_wc_adm_close2(p_access, pool)); + svn_error_clear(p_access_err); + return svn_error_trace(err); + } + + /* This directory is not under version control. Ignore it. */ + svn_error_clear(err); + t_access = NULL; + } + + /* At this stage might have P_ACCESS, T_ACCESS or both */ + + /* Check for switched or disjoint P_ACCESS and T_ACCESS */ + if (p_access && t_access) + { + svn_boolean_t disjoint; + + err = child_is_disjoint(&disjoint, db, local_abspath, pool); + if (err) + { + svn_error_clear(p_access_err); + svn_error_clear(svn_wc_adm_close2(p_access, pool)); + svn_error_clear(svn_wc_adm_close2(t_access, pool)); + return svn_error_trace(err); + } + + if (disjoint) + { + /* Switched or disjoint, so drop P_ACCESS. Don't close any + descendents, or we might blast the child. */ + err = close_single(p_access, FALSE /* preserve_lock */, pool); + if (err) + { + svn_error_clear(p_access_err); + svn_error_clear(svn_wc_adm_close2(t_access, pool)); + return svn_error_trace(err); + } + p_access = NULL; + } + } + + /* We have a parent baton *and* we have an error related to opening + the baton. That means we have a readonly baton, but that isn't + going to work for us. (p_access would have been set to NULL if + a writable parent baton is not required) */ + if (p_access && p_access_err) + { + if (t_access) + svn_error_clear(svn_wc_adm_close2(t_access, pool)); + svn_error_clear(svn_wc_adm_close2(p_access, pool)); + return svn_error_trace(p_access_err); + } + svn_error_clear(p_access_err); + + if (! t_access) + { + svn_boolean_t available; + svn_node_kind_t kind; + + err = adm_available(&available, &kind, db, local_abspath, pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + svn_error_clear(err); + else if (err) + { + svn_error_clear(svn_wc_adm_close2(p_access, pool)); + return svn_error_trace(err); + } + } + + *anchor_access = p_access ? p_access : t_access; + *target_access = t_access ? t_access : p_access; + + if (! p_access) + *target = ""; + else + *target = base_name; + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_adm_open_anchor(svn_wc_adm_access_t **anchor_access, + svn_wc_adm_access_t **target_access, + const char **target, + const char *path, + svn_boolean_t write_lock, + int levels_to_lock, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + return svn_error_trace(open_anchor(anchor_access, target_access, target, + NULL, FALSE, path, write_lock, + levels_to_lock, cancel_func, + cancel_baton, pool)); +} + + +/* Does the work of closing the access baton ADM_ACCESS. Any physical + locks are removed from the working copy if PRESERVE_LOCK is FALSE, or + are left if PRESERVE_LOCK is TRUE. Any associated access batons that + are direct descendants will also be closed. + */ +static svn_error_t * +do_close(svn_wc_adm_access_t *adm_access, + svn_boolean_t preserve_lock, + apr_pool_t *scratch_pool) +{ + svn_wc_adm_access_t *look; + + if (adm_access->closed) + return SVN_NO_ERROR; + + /* If we are part of the shared set, then close descendant batons. */ + look = get_from_shared(adm_access->abspath, adm_access->db, scratch_pool); + if (look != NULL) + { + apr_hash_t *opened; + apr_hash_index_t *hi; + + /* Gather all the opened access batons from the DB. */ + opened = svn_wc__db_temp_get_all_access(adm_access->db, scratch_pool); + + /* Close any that are descendents of this baton. */ + for (hi = apr_hash_first(scratch_pool, opened); + hi; + hi = apr_hash_next(hi)) + { + const char *abspath = svn__apr_hash_index_key(hi); + svn_wc_adm_access_t *child = svn__apr_hash_index_val(hi); + const char *path = child->path; + + if (IS_MISSING(child)) + { + /* We don't close the missing entry, but get rid of it from + the set. */ + svn_wc__db_temp_clear_access(adm_access->db, abspath, + scratch_pool); + continue; + } + + if (! svn_dirent_is_ancestor(adm_access->path, path) + || strcmp(adm_access->path, path) == 0) + continue; + + SVN_ERR(close_single(child, preserve_lock, scratch_pool)); + } + } + + return svn_error_trace(close_single(adm_access, preserve_lock, + scratch_pool)); +} + + +/* SVN_DEPRECATED */ +svn_error_t * +svn_wc_adm_close2(svn_wc_adm_access_t *adm_access, apr_pool_t *scratch_pool) +{ + return svn_error_trace(do_close(adm_access, FALSE, scratch_pool)); +} + + +/* SVN_DEPRECATED */ +svn_boolean_t +svn_wc_adm_locked(const svn_wc_adm_access_t *adm_access) +{ + svn_boolean_t locked; + apr_pool_t *subpool = svn_pool_create(adm_access->pool); + svn_error_t *err = svn_wc__db_wclock_owns_lock(&locked, adm_access->db, + adm_access->abspath, TRUE, + subpool); + svn_pool_destroy(subpool); + + if (err) + { + svn_error_clear(err); + /* ### is this right? */ + return FALSE; + } + + return locked; +} + +svn_error_t * +svn_wc__write_check(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_boolean_t locked; + + SVN_ERR(svn_wc__db_wclock_owns_lock(&locked, db, local_abspath, FALSE, + scratch_pool)); + if (!locked) + return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL, + _("No write-lock in '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_locked2(svn_boolean_t *locked_here, + svn_boolean_t *locked, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (locked_here != NULL) + SVN_ERR(svn_wc__db_wclock_owns_lock(locked_here, wc_ctx->db, local_abspath, + FALSE, scratch_pool)); + if (locked != NULL) + SVN_ERR(svn_wc__db_wclocked(locked, wc_ctx->db, local_abspath, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* SVN_DEPRECATED */ +const char * +svn_wc_adm_access_path(const svn_wc_adm_access_t *adm_access) +{ + return adm_access->path; +} + + +const char * +svn_wc__adm_access_abspath(const svn_wc_adm_access_t *adm_access) +{ + return adm_access->abspath; +} + + +/* SVN_DEPRECATED */ +apr_pool_t * +svn_wc_adm_access_pool(const svn_wc_adm_access_t *adm_access) +{ + return adm_access->pool; +} + +apr_pool_t * +svn_wc__adm_access_pool_internal(const svn_wc_adm_access_t *adm_access) +{ + return adm_access->pool; +} + +void +svn_wc__adm_access_set_entries(svn_wc_adm_access_t *adm_access, + apr_hash_t *entries) +{ + adm_access->entries_all = entries; +} + + +apr_hash_t * +svn_wc__adm_access_entries(svn_wc_adm_access_t *adm_access) +{ + /* Compile with -DSVN_DISABLE_ENTRY_CACHE to disable the in-memory + entry caching. As of 2010-03-18 (r924708) merge_tests 34 and 134 + fail during "make check". */ +#ifdef SVN_DISABLE_ENTRY_CACHE + return NULL; +#else + return adm_access->entries_all; +#endif +} + + +svn_wc__db_t * +svn_wc__adm_get_db(const svn_wc_adm_access_t *adm_access) +{ + return adm_access->db; +} + +svn_error_t * +svn_wc__acquire_write_lock(const char **lock_root_abspath, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t lock_anchor, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + svn_boolean_t is_wcroot; + svn_boolean_t is_switched; + svn_node_kind_t kind; + svn_error_t *err; + + err = svn_wc__db_is_switched(&is_wcroot, &is_switched, &kind, + db, local_abspath, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + + kind = svn_node_none; + is_wcroot = FALSE; + is_switched = FALSE; + } + + if (!lock_root_abspath && kind != svn_node_dir) + return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL, + _("Can't obtain lock on non-directory '%s'."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + if (lock_anchor && kind == svn_node_dir) + { + if (is_wcroot) + lock_anchor = FALSE; + } + + if (lock_anchor) + { + const char *parent_abspath; + SVN_ERR_ASSERT(lock_root_abspath != NULL); + + parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + if (kind == svn_node_dir) + { + if (! is_switched) + local_abspath = parent_abspath; + } + else if (kind != svn_node_none && kind != svn_node_unknown) + { + /* In the single-DB world we know parent exists */ + local_abspath = parent_abspath; + } + else + { + /* Can't lock parents that don't exist */ + svn_node_kind_t parent_kind; + err = svn_wc__db_read_kind(&parent_kind, db, parent_abspath, + TRUE /* allow_missing */, + TRUE /* show_deleted */, + FALSE /* show_hidden */, + scratch_pool); + if (err && SVN_WC__ERR_IS_NOT_CURRENT_WC(err)) + { + svn_error_clear(err); + parent_kind = svn_node_unknown; + } + else + SVN_ERR(err); + + if (parent_kind != svn_node_dir) + return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, + _("'%s' is not a working copy"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + local_abspath = parent_abspath; + } + } + else if (kind != svn_node_dir) + { + local_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + } + + if (lock_root_abspath) + *lock_root_abspath = apr_pstrdup(result_pool, local_abspath); + + SVN_ERR(svn_wc__db_wclock_obtain(wc_ctx->db, local_abspath, + -1 /* levels_to_lock (infinite) */, + FALSE /* steal_lock */, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__release_write_lock(svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + apr_uint64_t id; + svn_skel_t *work_item; + + SVN_ERR(svn_wc__db_wq_fetch_next(&id, &work_item, wc_ctx->db, local_abspath, + 0, scratch_pool, scratch_pool)); + if (work_item) + { + /* Do not release locks (here or below) if there is work to do. */ + return SVN_NO_ERROR; + } + + SVN_ERR(svn_wc__db_wclock_release(wc_ctx->db, local_abspath, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__call_with_write_lock(svn_wc__with_write_lock_func_t func, + void *baton, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t lock_anchor, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_error_t *err1, *err2; + const char *lock_root_abspath; + + SVN_ERR(svn_wc__acquire_write_lock(&lock_root_abspath, wc_ctx, local_abspath, + lock_anchor, scratch_pool, scratch_pool)); + err1 = svn_error_trace(func(baton, result_pool, scratch_pool)); + err2 = svn_wc__release_write_lock(wc_ctx, lock_root_abspath, scratch_pool); + return svn_error_compose_create(err1, err2); +} + + +svn_error_t * +svn_wc__acquire_write_lock_for_resolve(const char **lock_root_abspath, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t locked = FALSE; + const char *obtained_abspath; + const char *requested_abspath = local_abspath; + + while (!locked) + { + const char *required_abspath; + const char *child; + + SVN_ERR(svn_wc__acquire_write_lock(&obtained_abspath, wc_ctx, + requested_abspath, FALSE, + scratch_pool, scratch_pool)); + locked = TRUE; + + SVN_ERR(svn_wc__required_lock_for_resolve(&required_abspath, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + /* It's possible for the required lock path to be an ancestor + of, a descendent of, or equal to, the obtained lock path. If + it's an ancestor we have to try again, otherwise the obtained + lock will do. */ + child = svn_dirent_skip_ancestor(required_abspath, obtained_abspath); + if (child && child[0]) + { + SVN_ERR(svn_wc__release_write_lock(wc_ctx, obtained_abspath, + scratch_pool)); + locked = FALSE; + requested_abspath = required_abspath; + } + else + { + /* required should be a descendent of, or equal to, obtained */ + SVN_ERR_ASSERT(!strcmp(required_abspath, obtained_abspath) + || svn_dirent_skip_ancestor(obtained_abspath, + required_abspath)); + } + } + + *lock_root_abspath = apr_pstrdup(result_pool, obtained_abspath); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/lock.h b/subversion/libsvn_wc/lock.h new file mode 100644 index 000000000000..e015c7e076fe --- /dev/null +++ b/subversion/libsvn_wc/lock.h @@ -0,0 +1,91 @@ +/* + * lock.h: routines for locking working copy subdirectories. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#ifndef SVN_LIBSVN_WC_LOCK_H +#define SVN_LIBSVN_WC_LOCK_H + +#include <apr_pools.h> +#include <apr_hash.h> + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_wc.h" + +#include "wc_db.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/*** General utilities that may get moved upstairs at some point. */ + +/* Store ENTRIES in the cache in ADM_ACCESS. ENTRIES may be NULL. */ +void svn_wc__adm_access_set_entries(svn_wc_adm_access_t *adm_access, + apr_hash_t *entries); + +/* Return the entries hash cached in ADM_ACCESS. The returned hash may + be NULL. */ +apr_hash_t *svn_wc__adm_access_entries(svn_wc_adm_access_t *adm_access); + +/* Same as svn_wc__adm_retrieve_internal, but takes a DB and an absolute + directory path. */ +svn_wc_adm_access_t * +svn_wc__adm_retrieve_internal2(svn_wc__db_t *db, + const char *abspath, + apr_pool_t *scratch_pool); + +/* ### this is probably bunk. but I dunna want to trace backwards-compat + ### users of svn_wc_check_wc(). probably gonna be rewritten for wc-ng + ### in any case. + + If CHECK_PATH is TRUE, a not-existing directory is not a working copy */ +svn_error_t * +svn_wc__internal_check_wc(int *wc_format, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t check_path, + apr_pool_t *scratch_pool); + +/* Return the working copy database associated with this access baton. */ +svn_wc__db_t * +svn_wc__adm_get_db(const svn_wc_adm_access_t *adm_access); + + +/* Get a reference to the baton's internal ABSPATH. */ +const char * +svn_wc__adm_access_abspath(const svn_wc_adm_access_t *adm_access); + +/* Return the pool used by access baton ADM_ACCESS. + * Note: This is a non-deprecated variant of svn_wc_adm_access_pool for + * libsvn_wc internal usage only. + */ +apr_pool_t * +svn_wc__adm_access_pool_internal(const svn_wc_adm_access_t *adm_access); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_WC_LOCK_H */ diff --git a/subversion/libsvn_wc/merge.c b/subversion/libsvn_wc/merge.c new file mode 100644 index 000000000000..7cff3e4bd225 --- /dev/null +++ b/subversion/libsvn_wc/merge.c @@ -0,0 +1,1424 @@ +/* + * merge.c: merging changes into a working file + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "svn_wc.h" +#include "svn_diff.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_pools.h" + +#include "wc.h" +#include "adm_files.h" +#include "conflicts.h" +#include "translate.h" +#include "workqueue.h" + +#include "private/svn_skel.h" + +#include "svn_private_config.h" + +/* Contains some information on the merge target before merge, and some + information needed for the diff processing. */ +typedef struct merge_target_t +{ + svn_wc__db_t *db; /* The DB used to access target */ + const char *local_abspath; /* The absolute path to target */ + const char *wri_abspath; /* The working copy of target */ + + apr_hash_t *old_actual_props; /* The set of actual properties + before merging */ + const apr_array_header_t *prop_diff; /* The property changes */ + + const char *diff3_cmd; /* The diff3 command and options */ + const apr_array_header_t *merge_options; + +} merge_target_t; + + +/* Return a pointer to the svn_prop_t structure from PROP_DIFF + belonging to PROP_NAME, if any. NULL otherwise.*/ +static const svn_prop_t * +get_prop(const apr_array_header_t *prop_diff, + const char *prop_name) +{ + if (prop_diff) + { + int i; + for (i = 0; i < prop_diff->nelts; i++) + { + const svn_prop_t *elt = &APR_ARRAY_IDX(prop_diff, i, + svn_prop_t); + + if (strcmp(elt->name, prop_name) == 0) + return elt; + } + } + + return NULL; +} + + +/* Detranslate a working copy file MERGE_TARGET to achieve the effect of: + + 1. Detranslate + 2. Install new props + 3. Retranslate + 4. Detranslate + + in one pass, to get a file which can be compared with the left and right + files which are in repository normal form. + + Property changes make this a little complex though. Changes in + + - svn:mime-type + - svn:eol-style + - svn:keywords + - svn:special + + may change the way a file is translated. + + Effect for svn:mime-type: + + If svn:mime-type is considered 'binary', we ignore svn:eol-style (but + still translate keywords). + + I) both old and new mime-types are texty + -> just do the translation dance (as lined out below) + ### actually we do a shortcut with just one translation: + detranslate with the old keywords and ... eol-style + (the new re+detranslation is a no-op w.r.t. keywords [1]) + + II) the old one is texty, the new one is binary + -> detranslate with the old eol-style and keywords + (the new re+detranslation is a no-op [1]) + + III) the old one is binary, the new one texty + -> detranslate with the old keywords and new eol-style + (the old detranslation is a no-op w.r.t. eol, and + the new re+detranslation is a no-op w.r.t. keywords [1]) + + IV) the old and new ones are binary + -> detranslate with the old keywords + (the new re+detranslation is a no-op [1]) + + Effect for svn:eol-style + + I) On add or change of svn:eol-style, use the new value + + II) otherwise: use the old value (absent means 'no translation') + + Effect for svn:keywords + + Always use the old settings (re+detranslation are no-op [1]). + + [1] Translation of keywords from repository normal form to WC form and + back is normally a no-op, but is not a no-op if text contains a kw + that is only enabled by the new props and is present in non- + contracted form (such as "$Rev: 1234 $"). If we want to catch this + case we should detranslate with both the old & the new keywords + together. + + Effect for svn:special + + Always use the old settings (re+detranslation are no-op). + + Sets *DETRANSLATED_ABSPATH to the path to the detranslated file, + this may be the same as SOURCE_ABSPATH if FORCE_COPY is FALSE and no + translation is required. + + If FORCE_COPY is FALSE and *DETRANSLATED_ABSPATH is a file distinct + from SOURCE_ABSPATH then the file will be deleted on RESULT_POOL + cleanup. + + If FORCE_COPY is TRUE then *DETRANSLATED_ABSPATH will always be a + new file distinct from SOURCE_ABSPATH and it will be the callers + responsibility to delete the file. + +*/ +static svn_error_t * +detranslate_wc_file(const char **detranslated_abspath, + const merge_target_t *mt, + svn_boolean_t force_copy, + const char *source_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t old_is_binary, new_is_binary; + svn_subst_eol_style_t style; + const char *eol; + apr_hash_t *keywords; + svn_boolean_t special; + + { + const char *old_mime_value + = svn_prop_get_value(mt->old_actual_props, SVN_PROP_MIME_TYPE); + const svn_prop_t *prop = get_prop(mt->prop_diff, SVN_PROP_MIME_TYPE); + const char *new_mime_value + = prop ? (prop->value ? prop->value->data : NULL) : old_mime_value; + + old_is_binary = old_mime_value && svn_mime_type_is_binary(old_mime_value); + new_is_binary = new_mime_value && svn_mime_type_is_binary(new_mime_value);; + } + + /* See what translations we want to do */ + if (old_is_binary && new_is_binary) + { + /* Case IV. Old and new props 'binary': detranslate keywords only */ + SVN_ERR(svn_wc__get_translate_info(NULL, NULL, &keywords, NULL, + mt->db, mt->local_abspath, + mt->old_actual_props, TRUE, + scratch_pool, scratch_pool)); + /* ### Why override 'special'? Elsewhere it has precedence. */ + special = FALSE; + eol = NULL; + style = svn_subst_eol_style_none; + } + else if (!old_is_binary && new_is_binary) + { + /* Case II. Old props indicate texty, new props indicate binary: + detranslate keywords and old eol-style */ + SVN_ERR(svn_wc__get_translate_info(&style, &eol, + &keywords, + &special, + mt->db, mt->local_abspath, + mt->old_actual_props, TRUE, + scratch_pool, scratch_pool)); + } + else + { + /* Case I & III. New props indicate texty, regardless of old props */ + + /* In case the file used to be special, detranslate specially */ + SVN_ERR(svn_wc__get_translate_info(&style, &eol, + &keywords, + &special, + mt->db, mt->local_abspath, + mt->old_actual_props, TRUE, + scratch_pool, scratch_pool)); + + if (special) + { + keywords = NULL; + eol = NULL; + style = svn_subst_eol_style_none; + } + else + { + const svn_prop_t *prop; + + /* In case a new eol style was set, use that for detranslation */ + if ((prop = get_prop(mt->prop_diff, SVN_PROP_EOL_STYLE)) && prop->value) + { + /* Value added or changed */ + svn_subst_eol_style_from_value(&style, &eol, prop->value->data); + } + else if (!old_is_binary) + { + /* Already fetched */ + } + else + { + eol = NULL; + style = svn_subst_eol_style_none; + } + } + } + + /* Now, detranslate with the settings we created above */ + + if (force_copy || keywords || eol || special) + { + const char *temp_dir_abspath; + const char *detranslated; + + /* Force a copy into the temporary wc area to avoid having + temporary files created below to appear in the actual wc. */ + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, mt->db, + mt->wri_abspath, + scratch_pool, scratch_pool)); + + /* ### svn_subst_copy_and_translate4() also creates a tempfile + ### internally. Anyway to piggyback on that? */ + SVN_ERR(svn_io_open_unique_file3(NULL, &detranslated, temp_dir_abspath, + (force_copy + ? svn_io_file_del_none + : svn_io_file_del_on_pool_cleanup), + result_pool, scratch_pool)); + + /* Always 'repair' EOLs here, so that we can apply a diff that + changes from inconsistent newlines and no 'svn:eol-style' to + consistent newlines and 'svn:eol-style' set. */ + + if (style == svn_subst_eol_style_native) + eol = SVN_SUBST_NATIVE_EOL_STR; + else if (style != svn_subst_eol_style_fixed + && style != svn_subst_eol_style_none) + return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL); + + SVN_ERR(svn_subst_copy_and_translate4(source_abspath, + detranslated, + eol, + TRUE /* repair */, + keywords, + FALSE /* contract keywords */, + special, + cancel_func, cancel_baton, + scratch_pool)); + + SVN_ERR(svn_dirent_get_absolute(detranslated_abspath, detranslated, + result_pool)); + } + else + *detranslated_abspath = apr_pstrdup(result_pool, source_abspath); + + return SVN_NO_ERROR; +} + +/* Updates (by copying and translating) the eol style in + OLD_TARGET_ABSPATH returning the filename containing the + correct eol style in NEW_TARGET_ABSPATH, if an eol style + change is contained in PROP_DIFF. */ +static svn_error_t * +maybe_update_target_eols(const char **new_target_abspath, + const apr_array_header_t *prop_diff, + const char *old_target_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const svn_prop_t *prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE); + + if (prop && prop->value) + { + const char *eol; + const char *tmp_new; + + svn_subst_eol_style_from_value(NULL, &eol, prop->value->data); + SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_new, NULL, + svn_io_file_del_on_pool_cleanup, + result_pool, scratch_pool)); + + /* Always 'repair' EOLs here, so that we can apply a diff that + changes from inconsistent newlines and no 'svn:eol-style' to + consistent newlines and 'svn:eol-style' set. */ + SVN_ERR(svn_subst_copy_and_translate4(old_target_abspath, + tmp_new, + eol, + TRUE /* repair */, + NULL /* keywords */, + FALSE /* expand */, + FALSE /* special */, + cancel_func, cancel_baton, + scratch_pool)); + *new_target_abspath = apr_pstrdup(result_pool, tmp_new); + } + else + *new_target_abspath = apr_pstrdup(result_pool, old_target_abspath); + + return SVN_NO_ERROR; +} + + +/* Set *TARGET_MARKER, *LEFT_MARKER and *RIGHT_MARKER to strings suitable + for delimiting the alternative texts in a text conflict. Include in each + marker a string that may be given by TARGET_LABEL, LEFT_LABEL and + RIGHT_LABEL respectively or a default value where any of those are NULL. + + Allocate the results in POOL or statically. */ +static void +init_conflict_markers(const char **target_marker, + const char **left_marker, + const char **right_marker, + const char *target_label, + const char *left_label, + const char *right_label, + apr_pool_t *pool) +{ + /* Labels fall back to sensible defaults if not specified. */ + if (target_label) + *target_marker = apr_psprintf(pool, "<<<<<<< %s", target_label); + else + *target_marker = "<<<<<<< .working"; + + if (left_label) + *left_marker = apr_psprintf(pool, "||||||| %s", left_label); + else + *left_marker = "||||||| .old"; + + if (right_label) + *right_marker = apr_psprintf(pool, ">>>>>>> %s", right_label); + else + *right_marker = ">>>>>>> .new"; +} + +/* Do a 3-way merge of the files at paths LEFT, DETRANSLATED_TARGET, + * and RIGHT, using diff options provided in MERGE_OPTIONS. Store the merge + * result in the file RESULT_F. + * If there are conflicts, set *CONTAINS_CONFLICTS to true, and use + * TARGET_LABEL, LEFT_LABEL, and RIGHT_LABEL as labels for conflict + * markers. Else, set *CONTAINS_CONFLICTS to false. + * Do all allocations in POOL. */ +static svn_error_t * +do_text_merge(svn_boolean_t *contains_conflicts, + apr_file_t *result_f, + const apr_array_header_t *merge_options, + const char *detranslated_target, + const char *left, + const char *right, + const char *target_label, + const char *left_label, + const char *right_label, + apr_pool_t *pool) +{ + svn_diff_t *diff; + svn_stream_t *ostream; + const char *target_marker; + const char *left_marker; + const char *right_marker; + svn_diff_file_options_t *diff3_options; + + diff3_options = svn_diff_file_options_create(pool); + + if (merge_options) + SVN_ERR(svn_diff_file_options_parse(diff3_options, + merge_options, pool)); + + + init_conflict_markers(&target_marker, &left_marker, &right_marker, + target_label, left_label, right_label, pool); + + SVN_ERR(svn_diff_file_diff3_2(&diff, left, detranslated_target, right, + diff3_options, pool)); + + ostream = svn_stream_from_aprfile2(result_f, TRUE, pool); + + SVN_ERR(svn_diff_file_output_merge2(ostream, diff, + left, detranslated_target, right, + left_marker, + target_marker, + right_marker, + "=======", /* separator */ + svn_diff_conflict_display_modified_latest, + pool)); + SVN_ERR(svn_stream_close(ostream)); + + *contains_conflicts = svn_diff_contains_conflicts(diff); + + return SVN_NO_ERROR; +} + +/* Same as do_text_merge() above, but use the external diff3 + * command DIFF3_CMD to perform the merge. Pass MERGE_OPTIONS + * to the diff3 command. Do all allocations in POOL. */ +static svn_error_t * +do_text_merge_external(svn_boolean_t *contains_conflicts, + apr_file_t *result_f, + const char *diff3_cmd, + const apr_array_header_t *merge_options, + const char *detranslated_target, + const char *left_abspath, + const char *right_abspath, + const char *target_label, + const char *left_label, + const char *right_label, + apr_pool_t *scratch_pool) +{ + int exit_code; + + SVN_ERR(svn_io_run_diff3_3(&exit_code, ".", + detranslated_target, left_abspath, right_abspath, + target_label, left_label, right_label, + result_f, diff3_cmd, + merge_options, scratch_pool)); + + *contains_conflicts = exit_code == 1; + + return SVN_NO_ERROR; +} + +/* Preserve the three pre-merge files. + + Create three empty files, with unique names that each include the + basename of TARGET_ABSPATH and one of LEFT_LABEL, RIGHT_LABEL and + TARGET_LABEL, in the directory that contains TARGET_ABSPATH. Typical + names are "foo.c.r37" or "foo.c.2.mine". Set *LEFT_COPY, *RIGHT_COPY and + *TARGET_COPY to their absolute paths. + + Set *WORK_ITEMS to a list of new work items that will write copies of + LEFT_ABSPATH, RIGHT_ABSPATH and TARGET_ABSPATH into the three files, + translated to working-copy form. + + The translation to working-copy form will be done according to the + versioned properties of TARGET_ABSPATH that are current when the work + queue items are executed. + + If target_abspath is not versioned use detranslated_target_abspath + as the target file. + ### NOT IMPLEMENTED -- 'detranslated_target_abspath' is not used. +*/ +static svn_error_t * +preserve_pre_merge_files(svn_skel_t **work_items, + const char **left_copy, + const char **right_copy, + const char **target_copy, + const merge_target_t *mt, + const char *left_abspath, + const char *right_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + const char *detranslated_target_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *tmp_left, *tmp_right, *detranslated_target_copy; + const char *dir_abspath, *target_name; + const char *wcroot_abspath, *temp_dir_abspath; + svn_skel_t *work_item, *last_items = NULL; + + *work_items = NULL; + + svn_dirent_split(&dir_abspath, &target_name, mt->local_abspath, + scratch_pool); + + SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, mt->db, mt->wri_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, mt->db, + mt->wri_abspath, + scratch_pool, scratch_pool)); + + /* Create three empty files in DIR_ABSPATH, naming them with unique names + that each include TARGET_NAME and one of {LEFT,RIGHT,TARGET}_LABEL, + and set *{LEFT,RIGHT,TARGET}_COPY to those names. */ + SVN_ERR(svn_io_open_uniquely_named( + NULL, left_copy, dir_abspath, target_name, left_label, + svn_io_file_del_none, result_pool, scratch_pool)); + SVN_ERR(svn_io_open_uniquely_named( + NULL, right_copy, dir_abspath, target_name, right_label, + svn_io_file_del_none, result_pool, scratch_pool)); + SVN_ERR(svn_io_open_uniquely_named( + NULL, target_copy, dir_abspath, target_name, target_label, + svn_io_file_del_none, result_pool, scratch_pool)); + + /* We preserve all the files with keywords expanded and line + endings in local (working) form. */ + + /* The workingqueue requires its paths to be in the subtree + relative to the wcroot path they are executed in. + + Make our LEFT and RIGHT files 'local' if they aren't... */ + if (! svn_dirent_is_ancestor(wcroot_abspath, left_abspath)) + { + SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_left, temp_dir_abspath, + svn_io_file_del_none, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_copy_file(left_abspath, tmp_left, TRUE, scratch_pool)); + + /* And create a wq item to remove the file later */ + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath, + tmp_left, + result_pool, scratch_pool)); + + last_items = svn_wc__wq_merge(last_items, work_item, result_pool); + } + else + tmp_left = left_abspath; + + if (! svn_dirent_is_ancestor(wcroot_abspath, right_abspath)) + { + SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_right, temp_dir_abspath, + svn_io_file_del_none, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_copy_file(right_abspath, tmp_right, TRUE, scratch_pool)); + + /* And create a wq item to remove the file later */ + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath, + tmp_right, + result_pool, scratch_pool)); + + last_items = svn_wc__wq_merge(last_items, work_item, result_pool); + } + else + tmp_right = right_abspath; + + /* NOTE: Callers must ensure that the svn:eol-style and + svn:keywords property values are correct in the currently + installed props. With 'svn merge', it's no big deal. But + when 'svn up' calls this routine, it needs to make sure that + this routine is using the newest property values that may + have been received *during* the update. Since this routine + will be run from within a log-command, merge_file() + needs to make sure that a previous log-command to 'install + latest props' has already executed first. Ben and I just + checked, and that is indeed the order in which the log items + are written, so everything should be fine. Really. */ + + /* Create LEFT and RIGHT backup files, in expanded form. + We use TARGET_ABSPATH's current properties to do the translation. */ + /* Derive the basenames of the 3 backup files. */ + SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item, + mt->db, mt->local_abspath, + tmp_left, *left_copy, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item, + mt->db, mt->local_abspath, + tmp_right, *right_copy, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + /* Back up TARGET_ABSPATH through detranslation/retranslation: + the new translation properties may not match the current ones */ + SVN_ERR(detranslate_wc_file(&detranslated_target_copy, mt, TRUE, + mt->local_abspath, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__wq_build_file_copy_translated(&work_item, + mt->db, mt->local_abspath, + detranslated_target_copy, + *target_copy, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + /* And maybe delete some tempfiles */ + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, wcroot_abspath, + detranslated_target_copy, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + *work_items = svn_wc__wq_merge(*work_items, last_items, result_pool); + + return SVN_NO_ERROR; +} + +/* Attempt a trivial merge of LEFT_ABSPATH and RIGHT_ABSPATH to + * the target file at TARGET_ABSPATH. + * + * These are the inherently trivial cases: + * + * left == right == target => no-op + * left != right, left == target => target := right + * + * This case is also treated as trivial: + * + * left != right, right == target => no-op + * + * ### Strictly, this case is a conflict, and the no-op outcome is only + * one of the possible resolutions. + * + * TODO: Raise a conflict at this level and implement the 'no-op' + * resolution of that conflict at a higher level, in preparation for + * being able to support stricter conflict detection. + * + * This case is inherently trivial but not currently handled here: + * + * left == right != target => no-op + * + * The files at LEFT_ABSPATH and RIGHT_ABSPATH are in repository normal + * form. The file at DETRANSLATED_TARGET_ABSPATH is a copy of the target, + * 'detranslated' to repository normal form, or may be the target file + * itself if no translation is necessary. + * + * When this function updates the target file, it translates to working copy + * form. + * + * On success, set *MERGE_OUTCOME to SVN_WC_MERGE_MERGED in case the + * target was changed, or to SVN_WC_MERGE_UNCHANGED if the target was not + * changed. Install work queue items allocated in RESULT_POOL in *WORK_ITEMS. + * On failure, set *MERGE_OUTCOME to SVN_WC_MERGE_NO_MERGE. + */ +static svn_error_t * +merge_file_trivial(svn_skel_t **work_items, + enum svn_wc_merge_outcome_t *merge_outcome, + const char *left_abspath, + const char *right_abspath, + const char *target_abspath, + const char *detranslated_target_abspath, + svn_boolean_t dry_run, + svn_wc__db_t *db, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *work_item; + svn_boolean_t same_left_right; + svn_boolean_t same_right_target; + svn_boolean_t same_left_target; + svn_node_kind_t kind; + svn_boolean_t is_special; + + /* If the target is not a normal file, do not attempt a trivial merge. */ + SVN_ERR(svn_io_check_special_path(target_abspath, &kind, &is_special, + scratch_pool)); + if (kind != svn_node_file || is_special) + { + *merge_outcome = svn_wc_merge_no_merge; + return SVN_NO_ERROR; + } + + /* Check the files */ + SVN_ERR(svn_io_files_contents_three_same_p(&same_left_right, + &same_right_target, + &same_left_target, + left_abspath, + right_abspath, + detranslated_target_abspath, + scratch_pool)); + + /* If the LEFT side of the merge is equal to WORKING, then we can + * copy RIGHT directly. */ + if (same_left_target) + { + /* If the left side equals the right side, there is no change to merge + * so we leave the target unchanged. */ + if (same_left_right) + { + *merge_outcome = svn_wc_merge_unchanged; + } + else + { + *merge_outcome = svn_wc_merge_merged; + if (!dry_run) + { + const char *wcroot_abspath; + svn_boolean_t delete_src = FALSE; + + /* The right_abspath might be outside our working copy. In that + case we should copy the file to a safe location before + installing to avoid breaking the workqueue. + + This matches the behavior in preserve_pre_merge_files */ + + SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, + db, target_abspath, + scratch_pool, scratch_pool)); + + if (!svn_dirent_is_child(wcroot_abspath, right_abspath, NULL)) + { + svn_stream_t *tmp_src; + svn_stream_t *tmp_dst; + + SVN_ERR(svn_stream_open_readonly(&tmp_src, right_abspath, + scratch_pool, + scratch_pool)); + + SVN_ERR(svn_wc__open_writable_base(&tmp_dst, &right_abspath, + NULL, NULL, + db, target_abspath, + scratch_pool, + scratch_pool)); + + SVN_ERR(svn_stream_copy3(tmp_src, tmp_dst, + cancel_func, cancel_baton, + scratch_pool)); + + delete_src = TRUE; + } + + SVN_ERR(svn_wc__wq_build_file_install( + &work_item, db, target_abspath, right_abspath, + FALSE /* use_commit_times */, + FALSE /* record_fileinfo */, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, + result_pool); + + if (delete_src) + { + SVN_ERR(svn_wc__wq_build_file_remove( + &work_item, db, wcroot_abspath, + right_abspath, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, + result_pool); + } + } + } + + return SVN_NO_ERROR; + } + else + { + /* If the locally existing, changed file equals the incoming 'right' + * file, there is no conflict. For binary files, we historically + * conflicted them needlessly, while merge_text_file figured it out + * eventually and returned svn_wc_merge_unchanged for them, which + * is what we do here. */ + if (same_right_target) + { + *merge_outcome = svn_wc_merge_unchanged; + return SVN_NO_ERROR; + } + } + + *merge_outcome = svn_wc_merge_no_merge; + return SVN_NO_ERROR; +} + + +/* Handle a non-trivial merge of 'text' files. (Assume that a trivial + * merge was not possible.) + * + * Set *WORK_ITEMS, *CONFLICT_SKEL and *MERGE_OUTCOME according to the + * result -- to install the merged file, or to indicate a conflict. + * + * On successful merge, leave the result in a temporary file and set + * *WORK_ITEMS to hold work items that will translate and install that + * file into its proper form and place (unless DRY_RUN) and delete the + * temporary file (in any case). Set *MERGE_OUTCOME to 'merged' or + * 'unchanged'. + * + * If a conflict occurs, set *MERGE_OUTCOME to 'conflicted', and (unless + * DRY_RUN) set *WORK_ITEMS and *CONFLICT_SKEL to record the conflict + * and copies of the pre-merge files. See preserve_pre_merge_files() + * for details. + * + * On entry, all of the output pointers must be non-null and *CONFLICT_SKEL + * must either point to an existing conflict skel or be NULL. + */ +static svn_error_t* +merge_text_file(svn_skel_t **work_items, + svn_skel_t **conflict_skel, + enum svn_wc_merge_outcome_t *merge_outcome, + const merge_target_t *mt, + const char *left_abspath, + const char *right_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + svn_boolean_t dry_run, + const char *detranslated_target_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_pool_t *pool = scratch_pool; /* ### temporary rename */ + svn_boolean_t contains_conflicts; + apr_file_t *result_f; + const char *result_target; + const char *base_name; + const char *temp_dir; + svn_skel_t *work_item; + + *work_items = NULL; + + base_name = svn_dirent_basename(mt->local_abspath, scratch_pool); + + /* Open a second temporary file for writing; this is where diff3 + will write the merged results. We want to use a tempfile + with a name that reflects the original, in case this + ultimately winds up in a conflict resolution editor. */ + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir, mt->db, mt->wri_abspath, + pool, pool)); + SVN_ERR(svn_io_open_uniquely_named(&result_f, &result_target, + temp_dir, base_name, ".tmp", + svn_io_file_del_none, pool, pool)); + + /* Run the external or internal merge, as requested. */ + if (mt->diff3_cmd) + SVN_ERR(do_text_merge_external(&contains_conflicts, + result_f, + mt->diff3_cmd, + mt->merge_options, + detranslated_target_abspath, + left_abspath, + right_abspath, + target_label, + left_label, + right_label, + pool)); + else /* Use internal merge. */ + SVN_ERR(do_text_merge(&contains_conflicts, + result_f, + mt->merge_options, + detranslated_target_abspath, + left_abspath, + right_abspath, + target_label, + left_label, + right_label, + pool)); + + SVN_ERR(svn_io_file_close(result_f, pool)); + + /* Determine the MERGE_OUTCOME, and record any conflict. */ + if (contains_conflicts && ! dry_run) + { + *merge_outcome = svn_wc_merge_conflict; + if (*merge_outcome == svn_wc_merge_conflict) + { + const char *left_copy, *right_copy, *target_copy; + + /* Preserve the three conflict files */ + SVN_ERR(preserve_pre_merge_files( + &work_item, + &left_copy, &right_copy, &target_copy, + mt, left_abspath, right_abspath, + left_label, right_label, target_label, + detranslated_target_abspath, + cancel_func, cancel_baton, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + /* Track the conflict marker files in the metadata. */ + + if (!*conflict_skel) + *conflict_skel = svn_wc__conflict_skel_create(result_pool); + + SVN_ERR(svn_wc__conflict_skel_add_text_conflict(*conflict_skel, + mt->db, mt->local_abspath, + target_copy, + left_copy, + right_copy, + result_pool, + scratch_pool)); + } + + if (*merge_outcome == svn_wc_merge_merged) + goto done; + } + else if (contains_conflicts && dry_run) + *merge_outcome = svn_wc_merge_conflict; + else + { + svn_boolean_t same, special; + + /* If 'special', then use the detranslated form of the + target file. This is so we don't try to follow symlinks, + but the same treatment is probably also appropriate for + whatever special file types we may invent in the future. */ + SVN_ERR(svn_wc__get_translate_info(NULL, NULL, NULL, + &special, mt->db, mt->local_abspath, + mt->old_actual_props, TRUE, + pool, pool)); + SVN_ERR(svn_io_files_contents_same_p(&same, result_target, + (special ? + detranslated_target_abspath : + mt->local_abspath), + pool)); + + *merge_outcome = same ? svn_wc_merge_unchanged : svn_wc_merge_merged; + } + + if (*merge_outcome != svn_wc_merge_unchanged && ! dry_run) + { + /* replace TARGET_ABSPATH with the new merged file, expanding. */ + SVN_ERR(svn_wc__wq_build_file_install(&work_item, + mt->db, mt->local_abspath, + result_target, + FALSE /* use_commit_times */, + FALSE /* record_fileinfo */, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + } + +done: + /* Remove the tempfile after use */ + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, mt->db, mt->local_abspath, + result_target, + result_pool, scratch_pool)); + + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + + return SVN_NO_ERROR; +} + +/* Handle a non-trivial merge of 'binary' files: don't actually merge, just + * flag a conflict. (Assume that a trivial merge was not possible.) + * + * Copy* the files at LEFT_ABSPATH and RIGHT_ABSPATH into the same directory + * as the target file, giving them unique names that start with the target + * file's name and end with LEFT_LABEL and RIGHT_LABEL respectively. + * If the merge target has been 'detranslated' to repository normal form, + * move the detranslated file similarly to a unique name ending with + * TARGET_LABEL. + * + * ### * Why do we copy the left and right temp files when we could (maybe + * not always?) move them? + * + * On entry, all of the output pointers must be non-null and *CONFLICT_SKEL + * must either point to an existing conflict skel or be NULL. + * + * Set *WORK_ITEMS, *CONFLICT_SKEL and *MERGE_OUTCOME to indicate the + * conflict. + * + * ### Why do we not use preserve_pre_merge_files() in here? The + * behaviour would be slightly different, more consistent: the + * preserved 'left' and 'right' files would be translated to working + * copy form, which may make a difference when a binary file + * contains keyword expansions or when some versions of the file are + * not 'binary' even though we're merging in 'binary files' mode. + */ +static svn_error_t * +merge_binary_file(svn_skel_t **work_items, + svn_skel_t **conflict_skel, + enum svn_wc_merge_outcome_t *merge_outcome, + const merge_target_t *mt, + const char *left_abspath, + const char *right_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + svn_boolean_t dry_run, + const char *detranslated_target_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_pool_t *pool = scratch_pool; /* ### temporary rename */ + /* ### when making the binary-file backups, should we be honoring + keywords and eol stuff? */ + const char *left_copy, *right_copy; + const char *merge_dirpath, *merge_filename; + const char *conflict_wrk; + + *work_items = NULL; + + svn_dirent_split(&merge_dirpath, &merge_filename, mt->local_abspath, pool); + + if (dry_run) + { + *merge_outcome = svn_wc_merge_conflict; + return SVN_NO_ERROR; + } + + /* reserve names for backups of left and right fulltexts */ + SVN_ERR(svn_io_open_uniquely_named(NULL, + &left_copy, + merge_dirpath, + merge_filename, + left_label, + svn_io_file_del_none, + pool, pool)); + + SVN_ERR(svn_io_open_uniquely_named(NULL, + &right_copy, + merge_dirpath, + merge_filename, + right_label, + svn_io_file_del_none, + pool, pool)); + + /* create the backup files */ + SVN_ERR(svn_io_copy_file(left_abspath, left_copy, TRUE, pool)); + SVN_ERR(svn_io_copy_file(right_abspath, right_copy, TRUE, pool)); + + /* Was the merge target detranslated? */ + if (strcmp(mt->local_abspath, detranslated_target_abspath) != 0) + { + /* Create a .mine file too */ + SVN_ERR(svn_io_open_uniquely_named(NULL, + &conflict_wrk, + merge_dirpath, + merge_filename, + target_label, + svn_io_file_del_none, + pool, pool)); + SVN_ERR(svn_wc__wq_build_file_move(work_items, mt->db, + mt->local_abspath, + detranslated_target_abspath, + conflict_wrk, + pool, result_pool)); + } + else + { + conflict_wrk = NULL; + } + + /* Mark target_abspath's entry as "Conflicted", and start tracking + the backup files in the entry as well. */ + if (!*conflict_skel) + *conflict_skel = svn_wc__conflict_skel_create(result_pool); + + SVN_ERR(svn_wc__conflict_skel_add_text_conflict(*conflict_skel, + mt->db, mt->local_abspath, + conflict_wrk, + left_copy, + right_copy, + result_pool, scratch_pool)); + + *merge_outcome = svn_wc_merge_conflict; /* a conflict happened */ + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__internal_merge(svn_skel_t **work_items, + svn_skel_t **conflict_skel, + enum svn_wc_merge_outcome_t *merge_outcome, + svn_wc__db_t *db, + const char *left_abspath, + const char *right_abspath, + const char *target_abspath, + const char *wri_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + apr_hash_t *old_actual_props, + svn_boolean_t dry_run, + const char *diff3_cmd, + const apr_array_header_t *merge_options, + const apr_array_header_t *prop_diff, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *detranslated_target_abspath; + svn_boolean_t is_binary = FALSE; + const svn_prop_t *mimeprop; + svn_skel_t *work_item; + merge_target_t mt; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(left_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(right_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath)); + + *work_items = NULL; + + /* Fill the merge target baton */ + mt.db = db; + mt.local_abspath = target_abspath; + mt.wri_abspath = wri_abspath; + mt.old_actual_props = old_actual_props; + mt.prop_diff = prop_diff; + mt.diff3_cmd = diff3_cmd; + mt.merge_options = merge_options; + + /* Decide if the merge target is a text or binary file. */ + if ((mimeprop = get_prop(prop_diff, SVN_PROP_MIME_TYPE)) + && mimeprop->value) + is_binary = svn_mime_type_is_binary(mimeprop->value->data); + else + { + const char *value = svn_prop_get_value(mt.old_actual_props, + SVN_PROP_MIME_TYPE); + + is_binary = value && svn_mime_type_is_binary(value); + } + + SVN_ERR(detranslate_wc_file(&detranslated_target_abspath, &mt, + (! is_binary) && diff3_cmd != NULL, + target_abspath, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + + /* We cannot depend on the left file to contain the same eols as the + right file. If the merge target has mods, this will mark the entire + file as conflicted, so we need to compensate. */ + SVN_ERR(maybe_update_target_eols(&left_abspath, prop_diff, left_abspath, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + + SVN_ERR(merge_file_trivial(work_items, merge_outcome, + left_abspath, right_abspath, + target_abspath, detranslated_target_abspath, + dry_run, db, cancel_func, cancel_baton, + result_pool, scratch_pool)); + if (*merge_outcome == svn_wc_merge_no_merge) + { + /* We have a non-trivial merge. If we classify it as a merge of + * 'binary' files we'll just raise a conflict, otherwise we'll do + * the actual merge of 'text' file contents. */ + if (is_binary) + { + /* Raise a text conflict */ + SVN_ERR(merge_binary_file(work_items, + conflict_skel, + merge_outcome, + &mt, + left_abspath, + right_abspath, + left_label, + right_label, + target_label, + dry_run, + detranslated_target_abspath, + result_pool, scratch_pool)); + } + else + { + SVN_ERR(merge_text_file(work_items, + conflict_skel, + merge_outcome, + &mt, + left_abspath, + right_abspath, + left_label, + right_label, + target_label, + dry_run, + detranslated_target_abspath, + cancel_func, cancel_baton, + result_pool, scratch_pool)); + } + } + + /* Merging is complete. Regardless of text or binariness, we might + need to tweak the executable bit on the new working file, and + possibly make it read-only. */ + if (! dry_run) + { + SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, + target_abspath, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_merge5(enum svn_wc_merge_outcome_t *merge_content_outcome, + enum svn_wc_notify_state_t *merge_props_outcome, + svn_wc_context_t *wc_ctx, + const char *left_abspath, + const char *right_abspath, + const char *target_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + svn_boolean_t dry_run, + const char *diff3_cmd, + const apr_array_header_t *merge_options, + apr_hash_t *original_props, + const apr_array_header_t *prop_diff, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const char *dir_abspath = svn_dirent_dirname(target_abspath, scratch_pool); + svn_skel_t *work_items; + svn_skel_t *conflict_skel = NULL; + apr_hash_t *pristine_props = NULL; + apr_hash_t *old_actual_props; + apr_hash_t *new_actual_props = NULL; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(left_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(right_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath)); + + /* Before we do any work, make sure we hold a write lock. */ + if (!dry_run) + SVN_ERR(svn_wc__write_check(wc_ctx->db, dir_abspath, scratch_pool)); + + /* Sanity check: the merge target must be a file under revision control */ + { + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_boolean_t had_props; + svn_boolean_t props_mod; + svn_boolean_t conflicted; + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + &conflicted, NULL, &had_props, &props_mod, + NULL, NULL, NULL, + wc_ctx->db, target_abspath, + scratch_pool, scratch_pool)); + + if (kind != svn_node_file || (status != svn_wc__db_status_normal + && status != svn_wc__db_status_added)) + { + *merge_content_outcome = svn_wc_merge_no_merge; + if (merge_props_outcome) + *merge_props_outcome = svn_wc_notify_state_unchanged; + return SVN_NO_ERROR; + } + + if (conflicted) + { + svn_boolean_t text_conflicted; + svn_boolean_t prop_conflicted; + svn_boolean_t tree_conflicted; + + SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, + &prop_conflicted, + &tree_conflicted, + wc_ctx->db, target_abspath, + scratch_pool)); + + /* We can't install two prop conflicts on a single node, so + avoid even checking that we have to merge it */ + if (text_conflicted || prop_conflicted || tree_conflicted) + { + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Can't merge into conflicted node '%s'"), + svn_dirent_local_style(target_abspath, + scratch_pool)); + } + /* else: Conflict was resolved by removing markers */ + } + + if (merge_props_outcome && had_props) + { + SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, + wc_ctx->db, target_abspath, + scratch_pool, scratch_pool)); + } + else if (merge_props_outcome) + pristine_props = apr_hash_make(scratch_pool); + + if (props_mod) + { + SVN_ERR(svn_wc__db_read_props(&old_actual_props, + wc_ctx->db, target_abspath, + scratch_pool, scratch_pool)); + } + else if (pristine_props) + old_actual_props = pristine_props; + else + old_actual_props = apr_hash_make(scratch_pool); + } + + /* Merge the properties, if requested. We merge the properties first + * because the properties can affect the text (EOL style, keywords). */ + if (merge_props_outcome) + { + int i; + + /* The PROPCHANGES may not have non-"normal" properties in it. If entry + or wc props were allowed, then the following code would install them + into the BASE and/or WORKING properties(!). */ + for (i = prop_diff->nelts; i--; ) + { + const svn_prop_t *change = &APR_ARRAY_IDX(prop_diff, i, svn_prop_t); + + if (!svn_wc_is_normal_prop(change->name)) + return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL, + _("The property '%s' may not be merged " + "into '%s'."), + change->name, + svn_dirent_local_style(target_abspath, + scratch_pool)); + } + + SVN_ERR(svn_wc__merge_props(&conflict_skel, + merge_props_outcome, + &new_actual_props, + wc_ctx->db, target_abspath, + original_props, pristine_props, old_actual_props, + prop_diff, + scratch_pool, scratch_pool)); + } + + /* Merge the text. */ + SVN_ERR(svn_wc__internal_merge(&work_items, + &conflict_skel, + merge_content_outcome, + wc_ctx->db, + left_abspath, + right_abspath, + target_abspath, + target_abspath, + left_label, right_label, target_label, + old_actual_props, + dry_run, + diff3_cmd, + merge_options, + prop_diff, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + + /* If this isn't a dry run, then update the DB, run the work, and + * call the conflict resolver callback. */ + if (!dry_run) + { + if (conflict_skel) + { + svn_skel_t *work_item; + + SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel, + left_version, + right_version, + scratch_pool, + scratch_pool)); + + SVN_ERR(svn_wc__conflict_create_markers(&work_item, + wc_ctx->db, target_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + } + + if (new_actual_props) + SVN_ERR(svn_wc__db_op_set_props(wc_ctx->db, target_abspath, + new_actual_props, + svn_wc__has_magic_property(prop_diff), + conflict_skel, work_items, + scratch_pool)); + else if (conflict_skel) + SVN_ERR(svn_wc__db_op_mark_conflict(wc_ctx->db, target_abspath, + conflict_skel, work_items, + scratch_pool)); + else if (work_items) + SVN_ERR(svn_wc__db_wq_add(wc_ctx->db, target_abspath, work_items, + scratch_pool)); + + if (work_items) + SVN_ERR(svn_wc__wq_run(wc_ctx->db, target_abspath, + cancel_func, cancel_baton, + scratch_pool)); + + if (conflict_skel && conflict_func) + { + svn_boolean_t text_conflicted, prop_conflicted; + + SVN_ERR(svn_wc__conflict_invoke_resolver( + wc_ctx->db, target_abspath, + conflict_skel, merge_options, + conflict_func, conflict_baton, + cancel_func, cancel_baton, + scratch_pool)); + + /* Reset *MERGE_CONTENT_OUTCOME etc. if a conflict was resolved. */ + SVN_ERR(svn_wc__internal_conflicted_p( + &text_conflicted, &prop_conflicted, NULL, + wc_ctx->db, target_abspath, scratch_pool)); + if (*merge_props_outcome == svn_wc_notify_state_conflicted + && ! prop_conflicted) + *merge_props_outcome = svn_wc_notify_state_merged; + if (*merge_content_outcome == svn_wc_merge_conflict + && ! text_conflicted) + *merge_content_outcome = svn_wc_merge_merged; + } + } + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/node.c b/subversion/libsvn_wc/node.c new file mode 100644 index 000000000000..a1d6b02f5083 --- /dev/null +++ b/subversion/libsvn_wc/node.c @@ -0,0 +1,1418 @@ +/* + * node.c: routines for getting information about nodes in the working copy. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* A note about these functions: + + We aren't really sure yet which bits of data libsvn_client needs about + nodes. In wc-1, we just grab the entry, and then use whatever we want + from it. Such a pattern is Bad. + + This file is intended to hold functions which retrieve specific bits of + information about a node, and will hopefully give us a better idea about + what data libsvn_client needs, and how to best provide that data in 1.7 + final. As such, these functions should only be called from outside + libsvn_wc; any internal callers are encouraged to use the appropriate + information fetching function, such as svn_wc__db_read_info(). +*/ + +#include <apr_pools.h> +#include <apr_time.h> + +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" +#include "svn_types.h" + +#include "wc.h" +#include "props.h" +#include "entries.h" +#include "wc_db.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" + + +/* Set *CHILDREN_ABSPATHS to a new array of the full paths formed by joining + * each name in REL_CHILDREN onto DIR_ABSPATH. If SHOW_HIDDEN is false then + * omit any paths that are reported as 'hidden' by svn_wc__db_node_hidden(). + * + * Allocate the output array and its elements in RESULT_POOL. */ +static svn_error_t * +filter_and_make_absolute(const apr_array_header_t **children_abspaths, + svn_wc_context_t *wc_ctx, + const char *dir_abspath, + const apr_array_header_t *rel_children, + svn_boolean_t show_hidden, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *children; + int i; + + children = apr_array_make(result_pool, rel_children->nelts, + sizeof(const char *)); + for (i = 0; i < rel_children->nelts; i++) + { + const char *child_abspath = svn_dirent_join(dir_abspath, + APR_ARRAY_IDX(rel_children, + i, + const char *), + result_pool); + + /* Don't add hidden nodes to *CHILDREN if we don't want them. */ + if (!show_hidden) + { + svn_boolean_t child_is_hidden; + + SVN_ERR(svn_wc__db_node_hidden(&child_is_hidden, wc_ctx->db, + child_abspath, scratch_pool)); + if (child_is_hidden) + continue; + } + + APR_ARRAY_PUSH(children, const char *) = child_abspath; + } + + *children_abspaths = children; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__node_get_children_of_working_node(const apr_array_header_t **children, + svn_wc_context_t *wc_ctx, + const char *dir_abspath, + svn_boolean_t show_hidden, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const apr_array_header_t *rel_children; + + SVN_ERR(svn_wc__db_read_children_of_working_node(&rel_children, + wc_ctx->db, dir_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(filter_and_make_absolute(children, wc_ctx, dir_abspath, + rel_children, show_hidden, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__node_get_children(const apr_array_header_t **children, + svn_wc_context_t *wc_ctx, + const char *dir_abspath, + svn_boolean_t show_hidden, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const apr_array_header_t *rel_children; + + SVN_ERR(svn_wc__db_read_children(&rel_children, wc_ctx->db, dir_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(filter_and_make_absolute(children, wc_ctx, dir_abspath, + rel_children, show_hidden, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__internal_get_repos_info(svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_boolean_t have_work; + + SVN_ERR(svn_wc__db_read_info(&status, NULL, revision, repos_relpath, + repos_root_url, repos_uuid, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, &have_work, + db, local_abspath, + result_pool, scratch_pool)); + + if ((repos_relpath ? *repos_relpath != NULL : TRUE) + && (repos_root_url ? *repos_root_url != NULL: TRUE) + && (repos_uuid ? *repos_uuid != NULL : TRUE)) + return SVN_NO_ERROR; /* We got the requested information */ + + if (!have_work) /* not-present, (server-)excluded? */ + { + return SVN_NO_ERROR; /* Can't fetch more */ + } + + if (status == svn_wc__db_status_deleted) + { + const char *base_del_abspath, *wrk_del_abspath; + + SVN_ERR(svn_wc__db_scan_deletion(&base_del_abspath, NULL, + &wrk_del_abspath, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + if (base_del_abspath) + { + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, repos_relpath, + repos_root_url, repos_uuid, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, base_del_abspath, + result_pool, scratch_pool)); + + /* If we have a repos_relpath, it is of the op-root */ + if (repos_relpath) + *repos_relpath = svn_relpath_join(*repos_relpath, + svn_dirent_skip_ancestor(base_del_abspath, + local_abspath), + result_pool); + /* We keep revision as SVN_INVALID_REVNUM */ + } + else if (wrk_del_abspath) + { + const char *op_root_abspath = NULL; + + SVN_ERR(svn_wc__db_scan_addition(NULL, repos_relpath + ? &op_root_abspath : NULL, + repos_relpath, repos_root_url, + repos_uuid, NULL, NULL, NULL, NULL, + db, svn_dirent_dirname( + wrk_del_abspath, + scratch_pool), + result_pool, scratch_pool)); + + /* If we have a repos_relpath, it is of the op-root */ + if (repos_relpath) + *repos_relpath = svn_relpath_join( + *repos_relpath, + svn_dirent_skip_ancestor(op_root_abspath, + local_abspath), + result_pool); + } + } + else /* added, or WORKING incomplete */ + { + const char *op_root_abspath = NULL; + + /* We have an addition. scan_addition() will find the intended + repository location by scanning up the tree. */ + SVN_ERR(svn_wc__db_scan_addition(NULL, repos_relpath + ? &op_root_abspath : NULL, + repos_relpath, repos_root_url, + repos_uuid, NULL, NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool)); + } + + SVN_ERR_ASSERT(repos_root_url == NULL || *repos_root_url != NULL); + SVN_ERR_ASSERT(repos_uuid == NULL || *repos_uuid != NULL); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__node_get_repos_info(svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__internal_get_repos_info(revision, + repos_relpath, + repos_root_url, + repos_uuid, + wc_ctx->db, local_abspath, + result_pool, scratch_pool)); +} + +/* Convert DB_KIND into the appropriate NODE_KIND value. + * If SHOW_HIDDEN is TRUE, report the node kind as found in the DB + * even if DB_STATUS indicates that the node is hidden. + * Else, return svn_node_none for such nodes. + * + * ### This is a bit ugly. We should consider promoting svn_kind_t + * ### to the de-facto node kind type instead of converting between them + * ### in non-backwards compat code. + * ### See also comments at the definition of svn_kind_t. + * + * ### In reality, the previous comment is out of date, as there is + * ### now only one enumeration for node kinds, and that is + * ### svn_node_kind_t (svn_kind_t was merged with that). But it's + * ### still ugly. + */ +static svn_error_t * +convert_db_kind_to_node_kind(svn_node_kind_t *node_kind, + svn_node_kind_t db_kind, + svn_wc__db_status_t db_status, + svn_boolean_t show_hidden) +{ + *node_kind = db_kind; + + /* Make sure hidden nodes return svn_node_none. */ + if (! show_hidden) + switch (db_status) + { + case svn_wc__db_status_not_present: + case svn_wc__db_status_server_excluded: + case svn_wc__db_status_excluded: + *node_kind = svn_node_none; + + default: + break; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_read_kind2(svn_node_kind_t *kind, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t show_deleted, + svn_boolean_t show_hidden, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t db_kind; + + SVN_ERR(svn_wc__db_read_kind(&db_kind, + wc_ctx->db, local_abspath, + TRUE, + show_deleted, + show_hidden, + scratch_pool)); + + if (db_kind == svn_node_dir) + *kind = svn_node_dir; + else if (db_kind == svn_node_file || db_kind == svn_node_symlink) + *kind = svn_node_file; + else + *kind = svn_node_none; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__node_get_depth(svn_depth_t *depth, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, depth, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, scratch_pool, + scratch_pool)); +} + +svn_error_t * +svn_wc__node_get_changed_info(svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, changed_rev, + changed_date, changed_author, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, result_pool, + scratch_pool)); +} + +svn_error_t * +svn_wc__node_get_url(const char **url, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__db_read_url(url, wc_ctx->db, local_abspath, + result_pool, scratch_pool)); +} + +/* A recursive node-walker, helper for svn_wc__internal_walk_children(). + * + * Call WALK_CALLBACK with WALK_BATON on all children (recursively) of + * DIR_ABSPATH in DB, but not on DIR_ABSPATH itself. DIR_ABSPATH must be a + * versioned directory. If SHOW_HIDDEN is true, visit hidden nodes, else + * ignore them. Restrict the depth of the walk to DEPTH. + * + * ### Is it possible for a subdirectory to be hidden and known to be a + * directory? If so, and if show_hidden is true, this will try to + * recurse into it. */ +static svn_error_t * +walker_helper(svn_wc__db_t *db, + const char *dir_abspath, + svn_boolean_t show_hidden, + const apr_hash_t *changelist_filter, + svn_wc__node_found_func_t walk_callback, + void *walk_baton, + svn_depth_t depth, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + apr_hash_t *rel_children_info; + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + if (depth == svn_depth_empty) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__db_read_children_walker_info(&rel_children_info, db, + dir_abspath, scratch_pool, + scratch_pool)); + + + iterpool = svn_pool_create(scratch_pool); + for (hi = apr_hash_first(scratch_pool, rel_children_info); + hi; + hi = apr_hash_next(hi)) + { + const char *child_name = svn__apr_hash_index_key(hi); + struct svn_wc__db_walker_info_t *wi = svn__apr_hash_index_val(hi); + svn_node_kind_t child_kind = wi->kind; + svn_wc__db_status_t child_status = wi->status; + const char *child_abspath; + + svn_pool_clear(iterpool); + + /* See if someone wants to cancel this operation. */ + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + child_abspath = svn_dirent_join(dir_abspath, child_name, iterpool); + + if (!show_hidden) + switch (child_status) + { + case svn_wc__db_status_not_present: + case svn_wc__db_status_server_excluded: + case svn_wc__db_status_excluded: + continue; + default: + break; + } + + /* Return the child, if appropriate. */ + if ( (child_kind == svn_node_file + || depth >= svn_depth_immediates) + && svn_wc__internal_changelist_match(db, child_abspath, + changelist_filter, + scratch_pool) ) + { + svn_node_kind_t kind; + + SVN_ERR(convert_db_kind_to_node_kind(&kind, child_kind, + child_status, show_hidden)); + /* ### We might want to pass child_status as well because at least + * ### one callee is asking for it. + * ### But is it OK to use an svn_wc__db type in this API? + * ### Not yet, we need to get the node walker + * ### libsvn_wc-internal first. -hkw */ + SVN_ERR(walk_callback(child_abspath, kind, walk_baton, iterpool)); + } + + /* Recurse into this directory, if appropriate. */ + if (child_kind == svn_node_dir + && depth >= svn_depth_immediates) + { + svn_depth_t depth_below_here = depth; + + if (depth == svn_depth_immediates) + depth_below_here = svn_depth_empty; + + SVN_ERR(walker_helper(db, child_abspath, show_hidden, + changelist_filter, + walk_callback, walk_baton, + depth_below_here, + cancel_func, cancel_baton, + iterpool)); + } + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__internal_walk_children(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t show_hidden, + const apr_array_header_t *changelist_filter, + svn_wc__node_found_func_t walk_callback, + void *walk_baton, + svn_depth_t walk_depth, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t db_kind; + svn_node_kind_t kind; + svn_wc__db_status_t status; + apr_hash_t *changelist_hash = NULL; + + SVN_ERR_ASSERT(walk_depth >= svn_depth_empty + && walk_depth <= svn_depth_infinity); + + if (changelist_filter && changelist_filter->nelts) + SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, + scratch_pool)); + + /* Check if the node exists before the first callback */ + SVN_ERR(svn_wc__db_read_info(&status, &db_kind, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool)); + + SVN_ERR(convert_db_kind_to_node_kind(&kind, db_kind, status, show_hidden)); + + if (svn_wc__internal_changelist_match(db, local_abspath, + changelist_hash, scratch_pool)) + SVN_ERR(walk_callback(local_abspath, kind, walk_baton, scratch_pool)); + + if (db_kind == svn_node_file + || status == svn_wc__db_status_not_present + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_server_excluded) + return SVN_NO_ERROR; + + if (db_kind == svn_node_dir) + { + return svn_error_trace( + walker_helper(db, local_abspath, show_hidden, changelist_hash, + walk_callback, walk_baton, + walk_depth, cancel_func, cancel_baton, scratch_pool)); + } + + return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL, + _("'%s' has an unrecognized node kind"), + svn_dirent_local_style(local_abspath, + scratch_pool)); +} + +svn_error_t * +svn_wc__node_is_status_deleted(svn_boolean_t *is_deleted, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + + SVN_ERR(svn_wc__db_read_info(&status, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + *is_deleted = (status == svn_wc__db_status_deleted); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__node_get_deleted_ancestor(const char **deleted_ancestor_abspath, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + + *deleted_ancestor_abspath = NULL; + + SVN_ERR(svn_wc__db_read_info(&status, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + if (status == svn_wc__db_status_deleted) + SVN_ERR(svn_wc__db_scan_deletion(deleted_ancestor_abspath, NULL, NULL, + NULL, wc_ctx->db, local_abspath, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__node_is_not_present(svn_boolean_t *is_not_present, + svn_boolean_t *is_excluded, + svn_boolean_t *is_server_excluded, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t base_only, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + + if (base_only) + { + SVN_ERR(svn_wc__db_base_get_info(&status, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + } + else + { + SVN_ERR(svn_wc__db_read_info(&status, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + } + + if (is_not_present) + *is_not_present = (status == svn_wc__db_status_not_present); + + if (is_excluded) + *is_excluded = (status == svn_wc__db_status_excluded); + + if (is_server_excluded) + *is_server_excluded = (status == svn_wc__db_status_server_excluded); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__node_is_added(svn_boolean_t *is_added, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + + SVN_ERR(svn_wc__db_read_info(&status, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + *is_added = (status == svn_wc__db_status_added); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__node_has_working(svn_boolean_t *has_working, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + + SVN_ERR(svn_wc__db_read_info(&status, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, has_working, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__node_get_base(svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + const char **lock_token, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t ignore_enoent, + svn_boolean_t show_hidden, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_wc__db_status_t status; + svn_wc__db_lock_t *lock; + svn_node_kind_t db_kind; + + err = svn_wc__db_base_get_info(&status, &db_kind, revision, repos_relpath, + repos_root_url, repos_uuid, NULL, + NULL, NULL, NULL, NULL, NULL, + lock_token ? &lock : NULL, + NULL, NULL, NULL, + wc_ctx->db, local_abspath, + result_pool, scratch_pool); + + if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + else if (err + || (!err && !show_hidden + && (status != svn_wc__db_status_normal + && status != svn_wc__db_status_incomplete))) + { + if (!ignore_enoent) + { + if (err) + return svn_error_trace(err); + else + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + svn_error_clear(err); + + if (kind) + *kind = svn_node_unknown; + if (revision) + *revision = SVN_INVALID_REVNUM; + if (repos_relpath) + *repos_relpath = NULL; + if (repos_root_url) + *repos_root_url = NULL; + if (repos_uuid) + *repos_uuid = NULL; + if (lock_token) + *lock_token = NULL; + return SVN_NO_ERROR; + } + + if (kind) + *kind = db_kind; + if (lock_token) + *lock_token = lock ? lock->token : NULL; + + SVN_ERR_ASSERT(!revision || SVN_IS_VALID_REVNUM(*revision)); + SVN_ERR_ASSERT(!repos_relpath || *repos_relpath); + SVN_ERR_ASSERT(!repos_root_url || *repos_root_url); + SVN_ERR_ASSERT(!repos_uuid || *repos_uuid); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__node_get_pre_ng_status_data(svn_revnum_t *revision, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_boolean_t have_base, have_more_work, have_work; + + SVN_ERR(svn_wc__db_read_info(&status, NULL, revision, NULL, NULL, NULL, + changed_rev, changed_date, changed_author, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &have_base, &have_more_work, &have_work, + wc_ctx->db, local_abspath, + result_pool, scratch_pool)); + + if (!have_work + || ((!changed_rev || SVN_IS_VALID_REVNUM(*changed_rev)) + && (!revision || SVN_IS_VALID_REVNUM(*revision))) + || ((status != svn_wc__db_status_added) + && (status != svn_wc__db_status_deleted))) + { + return SVN_NO_ERROR; /* We got everything we need */ + } + + if (have_base && !have_more_work) + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, revision, NULL, NULL, NULL, + changed_rev, changed_date, changed_author, + NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + result_pool, scratch_pool)); + else if (status == svn_wc__db_status_deleted) + /* Check the information below a WORKING delete */ + SVN_ERR(svn_wc__db_read_pristine_info(NULL, NULL, changed_rev, + changed_date, changed_author, NULL, + NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__internal_node_get_schedule(svn_wc_schedule_t *schedule, + svn_boolean_t *copied, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_boolean_t op_root; + svn_boolean_t have_base; + svn_boolean_t have_work; + svn_boolean_t have_more_work; + const char *copyfrom_relpath; + + if (schedule) + *schedule = svn_wc_schedule_normal; + if (copied) + *copied = FALSE; + + SVN_ERR(svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, ©from_relpath, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &op_root, NULL, NULL, + &have_base, &have_more_work, &have_work, + db, local_abspath, scratch_pool, scratch_pool)); + + switch (status) + { + case svn_wc__db_status_not_present: + case svn_wc__db_status_server_excluded: + case svn_wc__db_status_excluded: + /* We used status normal in the entries world. */ + if (schedule) + *schedule = svn_wc_schedule_normal; + break; + case svn_wc__db_status_normal: + case svn_wc__db_status_incomplete: + break; + + case svn_wc__db_status_deleted: + { + if (schedule) + *schedule = svn_wc_schedule_delete; + + if (!copied) + break; + + if (have_more_work || !have_base) + *copied = TRUE; + else + { + const char *work_del_abspath; + + /* Find out details of our deletion. */ + SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, + &work_del_abspath, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + if (work_del_abspath) + *copied = TRUE; /* Working deletion */ + } + break; + } + case svn_wc__db_status_added: + { + if (!op_root) + { + if (copied) + *copied = TRUE; + + if (schedule) + *schedule = svn_wc_schedule_normal; + + break; + } + + if (copied) + *copied = (copyfrom_relpath != NULL); + + if (schedule) + *schedule = svn_wc_schedule_add; + else + break; + + /* Check for replaced */ + if (have_base || have_more_work) + { + svn_wc__db_status_t below_working; + SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work, + &below_working, + db, local_abspath, + scratch_pool)); + + /* If the node is not present or deleted (read: not present + in working), then the node is not a replacement */ + if (below_working != svn_wc__db_status_not_present + && below_working != svn_wc__db_status_deleted) + { + *schedule = svn_wc_schedule_replace; + break; + } + } + break; + } + default: + SVN_ERR_MALFUNCTION(); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__node_get_schedule(svn_wc_schedule_t *schedule, + svn_boolean_t *copied, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__internal_node_get_schedule(schedule, + copied, + wc_ctx->db, + local_abspath, + scratch_pool)); +} + +svn_error_t * +svn_wc__node_clear_dav_cache_recursive(svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__db_base_clear_dav_cache_recursive( + wc_ctx->db, local_abspath, scratch_pool)); +} + + +svn_error_t * +svn_wc__node_get_lock_tokens_recursive(apr_hash_t **lock_tokens, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__db_base_get_lock_tokens_recursive( + lock_tokens, wc_ctx->db, local_abspath, + result_pool, scratch_pool)); +} + +svn_error_t * +svn_wc__get_excluded_subtrees(apr_hash_t **server_excluded_subtrees, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__db_get_excluded_subtrees(server_excluded_subtrees, + wc_ctx->db, + local_abspath, + result_pool, + scratch_pool)); +} + +svn_error_t * +svn_wc__internal_get_origin(svn_boolean_t *is_copy, + svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + const char **copy_root_abspath, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t scan_deleted, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *original_repos_relpath; + const char *original_repos_root_url; + const char *original_repos_uuid; + svn_revnum_t original_revision; + svn_wc__db_status_t status; + + const char *tmp_repos_relpath; + + if (!repos_relpath) + repos_relpath = &tmp_repos_relpath; + + SVN_ERR(svn_wc__db_read_info(&status, NULL, revision, repos_relpath, + repos_root_url, repos_uuid, NULL, NULL, NULL, + NULL, NULL, NULL, + &original_repos_relpath, + &original_repos_root_url, + &original_repos_uuid, &original_revision, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, is_copy, + db, local_abspath, result_pool, scratch_pool)); + + if (*repos_relpath) + { + return SVN_NO_ERROR; /* Returned BASE information */ + } + + if (status == svn_wc__db_status_deleted && !scan_deleted) + { + if (is_copy) + *is_copy = FALSE; /* Deletes are stored in working; default to FALSE */ + + return SVN_NO_ERROR; /* No info */ + } + + if (original_repos_relpath) + { + *repos_relpath = original_repos_relpath; + if (revision) + *revision = original_revision; + if (repos_root_url) + *repos_root_url = original_repos_root_url; + if (repos_uuid) + *repos_uuid = original_repos_uuid; + + if (copy_root_abspath == NULL) + return SVN_NO_ERROR; + } + + { + svn_boolean_t scan_working = FALSE; + + if (status == svn_wc__db_status_added) + scan_working = TRUE; + else if (status == svn_wc__db_status_deleted) + { + svn_boolean_t have_base; + /* Is this a BASE or a WORKING delete? */ + SVN_ERR(svn_wc__db_info_below_working(&have_base, &scan_working, + &status, db, local_abspath, + scratch_pool)); + } + + if (scan_working) + { + const char *op_root_abspath; + + SVN_ERR(svn_wc__db_scan_addition(&status, &op_root_abspath, NULL, + NULL, NULL, &original_repos_relpath, + repos_root_url, + repos_uuid, revision, + db, local_abspath, + result_pool, scratch_pool)); + + if (status == svn_wc__db_status_added) + { + if (is_copy) + *is_copy = FALSE; + return SVN_NO_ERROR; /* Local addition */ + } + + /* We don't know how the following error condition can be fulfilled + * but we have seen that happening in the wild. Better to create + * an error than a SEGFAULT. */ + if (status == svn_wc__db_status_incomplete && !original_repos_relpath) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Incomplete copy information on path '%s'."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + *repos_relpath = svn_relpath_join( + original_repos_relpath, + svn_dirent_skip_ancestor(op_root_abspath, + local_abspath), + result_pool); + if (copy_root_abspath) + *copy_root_abspath = op_root_abspath; + } + else /* Deleted, excluded, not-present, server-excluded, ... */ + { + if (is_copy) + *is_copy = FALSE; + + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, revision, repos_relpath, + repos_root_url, repos_uuid, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool)); + } + + return SVN_NO_ERROR; + } +} + +svn_error_t * +svn_wc__node_get_origin(svn_boolean_t *is_copy, + svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + const char **copy_root_abspath, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t scan_deleted, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__internal_get_origin(is_copy, revision, + repos_relpath, repos_root_url, repos_uuid, + copy_root_abspath, + wc_ctx->db, local_abspath, scan_deleted, + result_pool, scratch_pool)); +} + +svn_error_t * +svn_wc__node_get_commit_status(svn_boolean_t *added, + svn_boolean_t *deleted, + svn_boolean_t *is_replace_root, + svn_boolean_t *is_op_root, + svn_revnum_t *revision, + svn_revnum_t *original_revision, + const char **original_repos_relpath, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_boolean_t have_base; + svn_boolean_t have_more_work; + svn_boolean_t op_root; + + /* ### All of this should be handled inside a single read transaction */ + SVN_ERR(svn_wc__db_read_info(&status, NULL, revision, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + original_repos_relpath, NULL, NULL, + original_revision, NULL, NULL, NULL, + NULL, NULL, + &op_root, NULL, NULL, + &have_base, &have_more_work, NULL, + wc_ctx->db, local_abspath, + result_pool, scratch_pool)); + + if (added) + *added = (status == svn_wc__db_status_added); + if (deleted) + *deleted = (status == svn_wc__db_status_deleted); + if (is_op_root) + *is_op_root = op_root; + + if (is_replace_root) + { + if (status == svn_wc__db_status_added + && op_root + && (have_base || have_more_work)) + SVN_ERR(svn_wc__db_node_check_replace(is_replace_root, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool)); + else + *is_replace_root = FALSE; + } + + /* Retrieve some information from BASE which is needed for replacing + and/or deleting BASE nodes. */ + if (have_base + && !have_more_work + && op_root + && (revision && !SVN_IS_VALID_REVNUM(*revision))) + { + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, revision, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__node_get_md5_from_sha1(const svn_checksum_t **md5_checksum, + svn_wc_context_t *wc_ctx, + const char *wri_abspath, + const svn_checksum_t *sha1_checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__db_pristine_get_md5(md5_checksum, + wc_ctx->db, + wri_abspath, + sha1_checksum, + result_pool, + scratch_pool)); +} + +svn_error_t * +svn_wc__get_not_present_descendants(const apr_array_header_t **descendants, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__db_get_not_present_descendants(descendants, + wc_ctx->db, + local_abspath, + result_pool, + scratch_pool)); +} + +svn_error_t * +svn_wc__rename_wc(svn_wc_context_t *wc_ctx, + const char *from_abspath, + const char *dst_abspath, + apr_pool_t *scratch_pool) +{ + const char *wcroot_abspath; + SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, wc_ctx->db, from_abspath, + scratch_pool, scratch_pool)); + + if (! strcmp(from_abspath, wcroot_abspath)) + { + SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, wcroot_abspath, scratch_pool)); + + SVN_ERR(svn_io_file_rename(from_abspath, dst_abspath, scratch_pool)); + } + else + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' is not the root of the working copy '%s'"), + svn_dirent_local_style(from_abspath, scratch_pool), + svn_dirent_local_style(wcroot_abspath, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__check_for_obstructions(svn_wc_notify_state_t *obstruction_state, + svn_node_kind_t *kind, + svn_boolean_t *deleted, + svn_boolean_t *excluded, + svn_depth_t *parent_depth, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t no_wcroot_check, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t db_kind; + svn_node_kind_t disk_kind; + svn_error_t *err; + + *obstruction_state = svn_wc_notify_state_inapplicable; + if (kind) + *kind = svn_node_none; + if (deleted) + *deleted = FALSE; + if (excluded) + *excluded = FALSE; + if (parent_depth) + *parent_depth = svn_depth_unknown; + + SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); + + err = svn_wc__db_read_info(&status, &db_kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + + if (disk_kind != svn_node_none) + { + /* Nothing in the DB, but something on disk */ + *obstruction_state = svn_wc_notify_state_obstructed; + return SVN_NO_ERROR; + } + + err = svn_wc__db_read_info(&status, &db_kind, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, parent_depth, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, + wc_ctx->db, svn_dirent_dirname(local_abspath, + scratch_pool), + scratch_pool, scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + /* No versioned parent; we can't add a node here */ + *obstruction_state = svn_wc_notify_state_obstructed; + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + if (db_kind != svn_node_dir + || (status != svn_wc__db_status_normal + && status != svn_wc__db_status_added)) + { + /* The parent doesn't allow nodes to be added below it */ + *obstruction_state = svn_wc_notify_state_obstructed; + } + + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + /* Check for obstructing working copies */ + if (!no_wcroot_check + && db_kind == svn_node_dir + && status == svn_wc__db_status_normal) + { + svn_boolean_t is_root; + SVN_ERR(svn_wc__db_is_wcroot(&is_root, wc_ctx->db, local_abspath, + scratch_pool)); + + if (is_root) + { + /* Callers should handle this as unversioned */ + *obstruction_state = svn_wc_notify_state_obstructed; + return SVN_NO_ERROR; + } + } + + if (kind) + SVN_ERR(convert_db_kind_to_node_kind(kind, db_kind, status, FALSE)); + + switch (status) + { + case svn_wc__db_status_deleted: + if (deleted) + *deleted = TRUE; + /* Fall through to svn_wc__db_status_not_present */ + case svn_wc__db_status_not_present: + if (disk_kind != svn_node_none) + *obstruction_state = svn_wc_notify_state_obstructed; + break; + + case svn_wc__db_status_excluded: + case svn_wc__db_status_server_excluded: + if (excluded) + *excluded = TRUE; + /* fall through */ + case svn_wc__db_status_incomplete: + *obstruction_state = svn_wc_notify_state_missing; + break; + + case svn_wc__db_status_added: + case svn_wc__db_status_normal: + if (disk_kind == svn_node_none) + *obstruction_state = svn_wc_notify_state_missing; + else + { + svn_node_kind_t expected_kind; + + SVN_ERR(convert_db_kind_to_node_kind(&expected_kind, db_kind, + status, FALSE)); + + if (disk_kind != expected_kind) + *obstruction_state = svn_wc_notify_state_obstructed; + } + break; + default: + SVN_ERR_MALFUNCTION(); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__node_was_moved_away(const char **moved_to_abspath, + const char **op_root_abspath, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t is_deleted; + + if (moved_to_abspath) + *moved_to_abspath = NULL; + if (op_root_abspath) + *op_root_abspath = NULL; + + SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, wc_ctx, local_abspath, + scratch_pool)); + if (is_deleted) + SVN_ERR(svn_wc__db_scan_deletion(NULL, moved_to_abspath, NULL, + op_root_abspath, wc_ctx->db, + local_abspath, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__node_was_moved_here(const char **moved_from_abspath, + const char **delete_op_root_abspath, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + + if (moved_from_abspath) + *moved_from_abspath = NULL; + if (delete_op_root_abspath) + *delete_op_root_abspath = NULL; + + err = svn_wc__db_scan_moved(moved_from_abspath, NULL, NULL, + delete_op_root_abspath, + wc_ctx->db, local_abspath, + result_pool, scratch_pool); + + if (err) + { + /* Return error for not added nodes */ + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + + /* Path not moved here */ + svn_error_clear(err); + return SVN_NO_ERROR; + } + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/old-and-busted.c b/subversion/libsvn_wc/old-and-busted.c new file mode 100644 index 000000000000..20f7c6ca9c7e --- /dev/null +++ b/subversion/libsvn_wc/old-and-busted.c @@ -0,0 +1,1340 @@ +/* + * old-and-busted.c: routines for reading pre-1.7 working copies. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include "svn_time.h" +#include "svn_xml.h" +#include "svn_dirent_uri.h" +#include "svn_hash.h" +#include "svn_path.h" +#include "svn_ctype.h" +#include "svn_pools.h" + +#include "wc.h" +#include "adm_files.h" +#include "entries.h" +#include "lock.h" + +#include "private/svn_wc_private.h" +#include "svn_private_config.h" + + +/* Within the (old) entries file, boolean values have a specific string + value (thus, TRUE), or they are missing (for FALSE). Below are the + values for each of the booleans stored. */ +#define ENTRIES_BOOL_COPIED "copied" +#define ENTRIES_BOOL_DELETED "deleted" +#define ENTRIES_BOOL_ABSENT "absent" +#define ENTRIES_BOOL_INCOMPLETE "incomplete" +#define ENTRIES_BOOL_KEEP_LOCAL "keep-local" + +/* Tag names used in our old XML entries file. */ +#define ENTRIES_TAG_ENTRY "entry" + +/* Attribute names used in our old XML entries file. */ +#define ENTRIES_ATTR_NAME "name" +#define ENTRIES_ATTR_REPOS "repos" +#define ENTRIES_ATTR_UUID "uuid" +#define ENTRIES_ATTR_INCOMPLETE "incomplete" +#define ENTRIES_ATTR_LOCK_TOKEN "lock-token" +#define ENTRIES_ATTR_LOCK_OWNER "lock-owner" +#define ENTRIES_ATTR_LOCK_COMMENT "lock-comment" +#define ENTRIES_ATTR_LOCK_CREATION_DATE "lock-creation-date" +#define ENTRIES_ATTR_DELETED "deleted" +#define ENTRIES_ATTR_ABSENT "absent" +#define ENTRIES_ATTR_CMT_REV "committed-rev" +#define ENTRIES_ATTR_CMT_DATE "committed-date" +#define ENTRIES_ATTR_CMT_AUTHOR "last-author" +#define ENTRIES_ATTR_REVISION "revision" +#define ENTRIES_ATTR_URL "url" +#define ENTRIES_ATTR_KIND "kind" +#define ENTRIES_ATTR_SCHEDULE "schedule" +#define ENTRIES_ATTR_COPIED "copied" +#define ENTRIES_ATTR_COPYFROM_URL "copyfrom-url" +#define ENTRIES_ATTR_COPYFROM_REV "copyfrom-rev" +#define ENTRIES_ATTR_CHECKSUM "checksum" +#define ENTRIES_ATTR_WORKING_SIZE "working-size" +#define ENTRIES_ATTR_TEXT_TIME "text-time" +#define ENTRIES_ATTR_CONFLICT_OLD "conflict-old" /* saved old file */ +#define ENTRIES_ATTR_CONFLICT_NEW "conflict-new" /* saved new file */ +#define ENTRIES_ATTR_CONFLICT_WRK "conflict-wrk" /* saved wrk file */ +#define ENTRIES_ATTR_PREJFILE "prop-reject-file" + +/* Attribute values used in our old XML entries file. */ +#define ENTRIES_VALUE_FILE "file" +#define ENTRIES_VALUE_DIR "dir" +#define ENTRIES_VALUE_ADD "add" +#define ENTRIES_VALUE_DELETE "delete" +#define ENTRIES_VALUE_REPLACE "replace" + + +/* */ +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; +} + + + +/* Read an escaped byte on the form 'xHH' from [*BUF, END), placing + the byte in *RESULT. Advance *BUF to point after the escape + sequence. */ +static svn_error_t * +read_escaped(char *result, char **buf, const char *end) +{ + apr_uint64_t val; + char digits[3]; + + if (end - *buf < 3 || **buf != 'x' || ! svn_ctype_isxdigit((*buf)[1]) + || ! svn_ctype_isxdigit((*buf)[2])) + return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid escape sequence")); + (*buf)++; + digits[0] = *((*buf)++); + digits[1] = *((*buf)++); + digits[2] = 0; + if ((val = apr_strtoi64(digits, NULL, 16)) == 0) + return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid escaped character")); + *result = (char) val; + return SVN_NO_ERROR; +} + +/* Read a field, possibly with escaped bytes, from [*BUF, END), + stopping at the terminator. Place the read string in *RESULT, or set + *RESULT to NULL if it is the empty string. Allocate the returned string + in POOL. Advance *BUF to point after the terminator. */ +static svn_error_t * +read_str(const char **result, + char **buf, const char *end, + apr_pool_t *pool) +{ + svn_stringbuf_t *s = NULL; + const char *start; + if (*buf == end) + return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, + _("Unexpected end of entry")); + if (**buf == '\n') + { + *result = NULL; + (*buf)++; + return SVN_NO_ERROR; + } + + start = *buf; + while (*buf != end && **buf != '\n') + { + if (**buf == '\\') + { + char c; + if (! s) + s = svn_stringbuf_ncreate(start, *buf - start, pool); + else + svn_stringbuf_appendbytes(s, start, *buf - start); + (*buf)++; + SVN_ERR(read_escaped(&c, buf, end)); + svn_stringbuf_appendbyte(s, c); + start = *buf; + } + else + (*buf)++; + } + + if (*buf == end) + return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, + _("Unexpected end of entry")); + + if (s) + { + svn_stringbuf_appendbytes(s, start, *buf - start); + *result = s->data; + } + else + *result = apr_pstrndup(pool, start, *buf - start); + (*buf)++; + return SVN_NO_ERROR; +} + +/* This is wrapper around read_str() (which see for details); it + simply asks svn_path_is_canonical() of the string it reads, + returning an error if the test fails. + ### It seems this is only called for entrynames now + */ +static svn_error_t * +read_path(const char **result, + char **buf, const char *end, + apr_pool_t *pool) +{ + SVN_ERR(read_str(result, buf, end, pool)); + if (*result && **result && !svn_relpath_is_canonical(*result)) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Entry contains non-canonical path '%s'"), + *result); + return SVN_NO_ERROR; +} + +/* This is read_path() for urls. This function does not do the is_canonical + test for entries from working copies older than version 10, as since that + version the canonicalization of urls has been changed. See issue #2475. + If the test is done and fails, read_url returs an error. */ +static svn_error_t * +read_url(const char **result, + char **buf, const char *end, + int wc_format, + apr_pool_t *pool) +{ + SVN_ERR(read_str(result, buf, end, pool)); + + /* Always canonicalize the url, as we have stricter canonicalization rules + in 1.7+ then before */ + if (*result && **result) + *result = svn_uri_canonicalize(*result, pool); + + return SVN_NO_ERROR; +} + +/* Read a field from [*BUF, END), terminated by a newline character. + The field may not contain escape sequences. The field is not + copied and the buffer is modified in place, by replacing the + terminator with a NUL byte. Make *BUF point after the original + terminator. */ +static svn_error_t * +read_val(const char **result, + char **buf, const char *end) +{ + const char *start = *buf; + + if (*buf == end) + return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, + _("Unexpected end of entry")); + if (**buf == '\n') + { + (*buf)++; + *result = NULL; + return SVN_NO_ERROR; + } + + while (*buf != end && **buf != '\n') + (*buf)++; + if (*buf == end) + return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, + _("Unexpected end of entry")); + **buf = '\0'; + *result = start; + (*buf)++; + return SVN_NO_ERROR; +} + +/* Read a boolean field from [*BUF, END), placing the result in + *RESULT. If there is no boolean value (just a terminator), it + defaults to false. Else, the value must match FIELD_NAME, in which + case *RESULT will be set to true. Advance *BUF to point after the + terminator. */ +static svn_error_t * +read_bool(svn_boolean_t *result, const char *field_name, + char **buf, const char *end) +{ + const char *val; + SVN_ERR(read_val(&val, buf, end)); + if (val) + { + if (strcmp(val, field_name) != 0) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid value for field '%s'"), + field_name); + *result = TRUE; + } + else + *result = FALSE; + return SVN_NO_ERROR; +} + +/* Read a revision number from [*BUF, END) stopping at the + terminator. Set *RESULT to the revision number, or + SVN_INVALID_REVNUM if there is none. Use POOL for temporary + allocations. Make *BUF point after the terminator. */ +static svn_error_t * +read_revnum(svn_revnum_t *result, + char **buf, + const char *end, + apr_pool_t *pool) +{ + const char *val; + + SVN_ERR(read_val(&val, buf, end)); + + if (val) + *result = SVN_STR_TO_REV(val); + else + *result = SVN_INVALID_REVNUM; + + return SVN_NO_ERROR; +} + +/* Read a timestamp from [*BUF, END) stopping at the terminator. + Set *RESULT to the resulting timestamp, or 0 if there is none. Use + POOL for temporary allocations. Make *BUF point after the + terminator. */ +static svn_error_t * +read_time(apr_time_t *result, + char **buf, const char *end, + apr_pool_t *pool) +{ + const char *val; + + SVN_ERR(read_val(&val, buf, end)); + if (val) + SVN_ERR(svn_time_from_cstring(result, val, pool)); + else + *result = 0; + + return SVN_NO_ERROR; +} + +/** + * Parse the string at *STR as an revision and save the result in + * *OPT_REV. After returning successfully, *STR points at next + * character in *STR where further parsing can be done. + */ +static svn_error_t * +string_to_opt_revision(svn_opt_revision_t *opt_rev, + const char **str, + apr_pool_t *pool) +{ + const char *s = *str; + + SVN_ERR_ASSERT(opt_rev); + + while (*s && *s != ':') + ++s; + + /* Should not find a \0. */ + if (!*s) + return svn_error_createf + (SVN_ERR_INCORRECT_PARAMS, NULL, + _("Found an unexpected \\0 in the file external '%s'"), *str); + + if (0 == strncmp(*str, "HEAD:", 5)) + { + opt_rev->kind = svn_opt_revision_head; + } + else + { + svn_revnum_t rev; + const char *endptr; + + SVN_ERR(svn_revnum_parse(&rev, *str, &endptr)); + SVN_ERR_ASSERT(endptr == s); + opt_rev->kind = svn_opt_revision_number; + opt_rev->value.number = rev; + } + + *str = s + 1; + + return SVN_NO_ERROR; +} + +/** + * Given a revision, return a string for the revision, either "HEAD" + * or a string representation of the revision value. All other + * revision kinds return an error. + */ +static svn_error_t * +opt_revision_to_string(const char **str, + const char *path, + const svn_opt_revision_t *rev, + apr_pool_t *pool) +{ + switch (rev->kind) + { + case svn_opt_revision_head: + *str = apr_pstrmemdup(pool, "HEAD", 4); + break; + case svn_opt_revision_number: + *str = apr_ltoa(pool, rev->value.number); + break; + default: + return svn_error_createf + (SVN_ERR_INCORRECT_PARAMS, NULL, + _("Illegal file external revision kind %d for path '%s'"), + rev->kind, path); + break; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__unserialize_file_external(const char **path_result, + svn_opt_revision_t *peg_rev_result, + svn_opt_revision_t *rev_result, + const char *str, + apr_pool_t *pool) +{ + if (str) + { + svn_opt_revision_t peg_rev; + svn_opt_revision_t op_rev; + const char *s = str; + + SVN_ERR(string_to_opt_revision(&peg_rev, &s, pool)); + SVN_ERR(string_to_opt_revision(&op_rev, &s, pool)); + + *path_result = apr_pstrdup(pool, s); + *peg_rev_result = peg_rev; + *rev_result = op_rev; + } + else + { + *path_result = NULL; + peg_rev_result->kind = svn_opt_revision_unspecified; + rev_result->kind = svn_opt_revision_unspecified; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__serialize_file_external(const char **str, + const char *path, + const svn_opt_revision_t *peg_rev, + const svn_opt_revision_t *rev, + apr_pool_t *pool) +{ + const char *s; + + if (path) + { + const char *s1; + const char *s2; + + SVN_ERR(opt_revision_to_string(&s1, path, peg_rev, pool)); + SVN_ERR(opt_revision_to_string(&s2, path, rev, pool)); + + s = apr_pstrcat(pool, s1, ":", s2, ":", path, (char *)NULL); + } + else + s = NULL; + + *str = s; + + return SVN_NO_ERROR; +} + +/* Allocate an entry from POOL and read it from [*BUF, END). The + buffer may be modified in place while parsing. Return the new + entry in *NEW_ENTRY. Advance *BUF to point at the end of the entry + record. + The entries file format should be provided in ENTRIES_FORMAT. */ +static svn_error_t * +read_entry(svn_wc_entry_t **new_entry, + char **buf, const char *end, + int entries_format, + apr_pool_t *pool) +{ + svn_wc_entry_t *entry = alloc_entry(pool); + const char *name; + +#define MAYBE_DONE if (**buf == '\f') goto done + + /* Find the name and set up the entry under that name. */ + SVN_ERR(read_path(&name, buf, end, pool)); + entry->name = name ? name : SVN_WC_ENTRY_THIS_DIR; + + /* Set up kind. */ + { + const char *kindstr; + SVN_ERR(read_val(&kindstr, buf, end)); + if (kindstr) + { + if (strcmp(kindstr, ENTRIES_VALUE_FILE) == 0) + entry->kind = svn_node_file; + else if (strcmp(kindstr, ENTRIES_VALUE_DIR) == 0) + entry->kind = svn_node_dir; + else + return svn_error_createf + (SVN_ERR_NODE_UNKNOWN_KIND, NULL, + _("Entry '%s' has invalid node kind"), + (name ? name : SVN_WC_ENTRY_THIS_DIR)); + } + else + entry->kind = svn_node_none; + } + MAYBE_DONE; + + /* Attempt to set revision (resolve_to_defaults may do it later, too) */ + SVN_ERR(read_revnum(&entry->revision, buf, end, pool)); + MAYBE_DONE; + + /* Attempt to set up url path (again, see resolve_to_defaults). */ + SVN_ERR(read_url(&entry->url, buf, end, entries_format, pool)); + MAYBE_DONE; + + /* Set up repository root. Make sure it is a prefix of url. */ + SVN_ERR(read_url(&entry->repos, buf, end, entries_format, pool)); + if (entry->repos && entry->url + && ! svn_uri__is_ancestor(entry->repos, entry->url)) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Entry for '%s' has invalid repository " + "root"), + name ? name : SVN_WC_ENTRY_THIS_DIR); + MAYBE_DONE; + + /* Look for a schedule attribute on this entry. */ + { + const char *schedulestr; + SVN_ERR(read_val(&schedulestr, buf, end)); + entry->schedule = svn_wc_schedule_normal; + if (schedulestr) + { + if (strcmp(schedulestr, ENTRIES_VALUE_ADD) == 0) + entry->schedule = svn_wc_schedule_add; + else if (strcmp(schedulestr, ENTRIES_VALUE_DELETE) == 0) + entry->schedule = svn_wc_schedule_delete; + else if (strcmp(schedulestr, ENTRIES_VALUE_REPLACE) == 0) + entry->schedule = svn_wc_schedule_replace; + else + return svn_error_createf( + SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL, + _("Entry '%s' has invalid 'schedule' value"), + name ? name : SVN_WC_ENTRY_THIS_DIR); + } + } + MAYBE_DONE; + + /* Attempt to set up text timestamp. */ + SVN_ERR(read_time(&entry->text_time, buf, end, pool)); + MAYBE_DONE; + + /* Checksum. */ + SVN_ERR(read_str(&entry->checksum, buf, end, pool)); + MAYBE_DONE; + + /* Setup last-committed values. */ + SVN_ERR(read_time(&entry->cmt_date, buf, end, pool)); + MAYBE_DONE; + + SVN_ERR(read_revnum(&entry->cmt_rev, buf, end, pool)); + MAYBE_DONE; + + SVN_ERR(read_str(&entry->cmt_author, buf, end, pool)); + MAYBE_DONE; + + /* has-props, has-prop-mods, cachable-props, present-props are all + deprecated. Read any values that may be in the 'entries' file, but + discard them, and just put default values into the entry. */ + { + const char *unused_value; + + /* has-props flag. */ + SVN_ERR(read_val(&unused_value, buf, end)); + entry->has_props = FALSE; + MAYBE_DONE; + + /* has-prop-mods flag. */ + SVN_ERR(read_val(&unused_value, buf, end)); + entry->has_prop_mods = FALSE; + MAYBE_DONE; + + /* Use the empty string for cachable_props, indicating that we no + longer attempt to cache any properties. An empty string for + present_props means that no cachable props are present. */ + + /* cachable-props string. */ + SVN_ERR(read_val(&unused_value, buf, end)); + entry->cachable_props = ""; + MAYBE_DONE; + + /* present-props string. */ + SVN_ERR(read_val(&unused_value, buf, end)); + entry->present_props = ""; + MAYBE_DONE; + } + + /* Is this entry in a state of mental torment (conflict)? */ + { + SVN_ERR(read_path(&entry->prejfile, buf, end, pool)); + MAYBE_DONE; + SVN_ERR(read_path(&entry->conflict_old, buf, end, pool)); + MAYBE_DONE; + SVN_ERR(read_path(&entry->conflict_new, buf, end, pool)); + MAYBE_DONE; + SVN_ERR(read_path(&entry->conflict_wrk, buf, end, pool)); + MAYBE_DONE; + } + + /* Is this entry copied? */ + SVN_ERR(read_bool(&entry->copied, ENTRIES_BOOL_COPIED, buf, end)); + MAYBE_DONE; + + SVN_ERR(read_url(&entry->copyfrom_url, buf, end, entries_format, pool)); + MAYBE_DONE; + SVN_ERR(read_revnum(&entry->copyfrom_rev, buf, end, pool)); + MAYBE_DONE; + + /* Is this entry deleted? */ + SVN_ERR(read_bool(&entry->deleted, ENTRIES_BOOL_DELETED, buf, end)); + MAYBE_DONE; + + /* Is this entry absent? */ + SVN_ERR(read_bool(&entry->absent, ENTRIES_BOOL_ABSENT, buf, end)); + MAYBE_DONE; + + /* Is this entry incomplete? */ + SVN_ERR(read_bool(&entry->incomplete, ENTRIES_BOOL_INCOMPLETE, buf, end)); + MAYBE_DONE; + + /* UUID. */ + SVN_ERR(read_str(&entry->uuid, buf, end, pool)); + MAYBE_DONE; + + /* Lock token. */ + SVN_ERR(read_str(&entry->lock_token, buf, end, pool)); + MAYBE_DONE; + + /* Lock owner. */ + SVN_ERR(read_str(&entry->lock_owner, buf, end, pool)); + MAYBE_DONE; + + /* Lock comment. */ + SVN_ERR(read_str(&entry->lock_comment, buf, end, pool)); + MAYBE_DONE; + + /* Lock creation date. */ + SVN_ERR(read_time(&entry->lock_creation_date, buf, end, pool)); + MAYBE_DONE; + + /* Changelist. */ + SVN_ERR(read_str(&entry->changelist, buf, end, pool)); + MAYBE_DONE; + + /* Keep entry in working copy after deletion? */ + SVN_ERR(read_bool(&entry->keep_local, ENTRIES_BOOL_KEEP_LOCAL, buf, end)); + MAYBE_DONE; + + /* Translated size */ + { + const char *val; + + /* read_val() returns NULL on an empty (e.g. default) entry line, + and entry has already been initialized accordingly already */ + SVN_ERR(read_val(&val, buf, end)); + if (val) + entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0); + } + MAYBE_DONE; + + /* Depth. */ + { + const char *result; + SVN_ERR(read_val(&result, buf, end)); + if (result) + { + svn_boolean_t invalid; + svn_boolean_t is_this_dir; + + entry->depth = svn_depth_from_word(result); + + /* Verify the depth value: + THIS_DIR should not have an excluded value and SUB_DIR should only + have excluded value. Remember that infinity value is not stored and + should not show up here. Otherwise, something bad may have + happened. However, infinity value itself will always be okay. */ + is_this_dir = !name; + /* '!=': XOR */ + invalid = is_this_dir != (entry->depth != svn_depth_exclude); + if (entry->depth != svn_depth_infinity && invalid) + return svn_error_createf( + SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL, + _("Entry '%s' has invalid 'depth' value"), + name ? name : SVN_WC_ENTRY_THIS_DIR); + } + else + entry->depth = svn_depth_infinity; + + } + MAYBE_DONE; + + /* Tree conflict data. */ + SVN_ERR(read_str(&entry->tree_conflict_data, buf, end, pool)); + MAYBE_DONE; + + /* File external URL and revision. */ + { + const char *str; + SVN_ERR(read_str(&str, buf, end, pool)); + SVN_ERR(svn_wc__unserialize_file_external(&entry->file_external_path, + &entry->file_external_peg_rev, + &entry->file_external_rev, + str, + pool)); + } + MAYBE_DONE; + + done: + *new_entry = entry; + return SVN_NO_ERROR; +} + + +/* If attribute ATTR_NAME appears in hash ATTS, set *ENTRY_FLAG to its + boolean value, else set *ENTRY_FLAG false. ENTRY_NAME is the name + of the WC-entry. */ +static svn_error_t * +do_bool_attr(svn_boolean_t *entry_flag, + apr_hash_t *atts, const char *attr_name, + const char *entry_name) +{ + const char *str = svn_hash_gets(atts, attr_name); + + *entry_flag = FALSE; + if (str) + { + if (strcmp(str, "true") == 0) + *entry_flag = TRUE; + else if (strcmp(str, "false") == 0 || strcmp(str, "") == 0) + *entry_flag = FALSE; + else + return svn_error_createf + (SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL, + _("Entry '%s' has invalid '%s' value"), + (entry_name ? entry_name : SVN_WC_ENTRY_THIS_DIR), attr_name); + } + return SVN_NO_ERROR; +} + + +/* */ +static const char * +extract_string(apr_hash_t *atts, + const char *att_name, + apr_pool_t *result_pool) +{ + const char *value = svn_hash_gets(atts, att_name); + + if (value == NULL) + return NULL; + + return apr_pstrdup(result_pool, value); +} + + +/* Like extract_string(), but normalizes empty strings to NULL. */ +static const char * +extract_string_normalize(apr_hash_t *atts, + const char *att_name, + apr_pool_t *result_pool) +{ + const char *value = svn_hash_gets(atts, att_name); + + if (value == NULL) + return NULL; + + if (*value == '\0') + return NULL; + + return apr_pstrdup(result_pool, value); +} + + +/* NOTE: this is used for upgrading old XML-based entries file. Be wary of + removing items. + + ### many attributes are no longer used within the old-style log files. + ### These attrs need to be recognized for old entries, however. For these + ### cases, the code will parse the attribute, but not set *MODIFY_FLAGS + ### for that particular field. MODIFY_FLAGS is *only* used by the + ### log-based entry modification system, and will go way once we + ### completely move away from loggy. + + Set *NEW_ENTRY to a new entry, taking attributes from ATTS, whose + keys and values are both char *. Allocate the entry and copy + attributes into POOL as needed. */ +static svn_error_t * +atts_to_entry(svn_wc_entry_t **new_entry, + apr_hash_t *atts, + apr_pool_t *pool) +{ + svn_wc_entry_t *entry = alloc_entry(pool); + const char *name; + + /* Find the name and set up the entry under that name. */ + name = svn_hash_gets(atts, ENTRIES_ATTR_NAME); + entry->name = name ? apr_pstrdup(pool, name) : SVN_WC_ENTRY_THIS_DIR; + + /* Attempt to set revision (resolve_to_defaults may do it later, too) + + ### not used by loggy; no need to set MODIFY_FLAGS */ + { + const char *revision_str + = svn_hash_gets(atts, ENTRIES_ATTR_REVISION); + + if (revision_str) + entry->revision = SVN_STR_TO_REV(revision_str); + else + entry->revision = SVN_INVALID_REVNUM; + } + + /* Attempt to set up url path (again, see resolve_to_defaults). + + ### not used by loggy; no need to set MODIFY_FLAGS */ + entry->url = extract_string(atts, ENTRIES_ATTR_URL, pool); + + /* Set up repository root. Make sure it is a prefix of url. + + ### not used by loggy; no need to set MODIFY_FLAGS */ + entry->repos = extract_string(atts, ENTRIES_ATTR_REPOS, pool); + + if (entry->url && entry->repos + && !svn_uri__is_ancestor(entry->repos, entry->url)) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Entry for '%s' has invalid repository " + "root"), + name ? name : SVN_WC_ENTRY_THIS_DIR); + + /* Set up kind. */ + /* ### not used by loggy; no need to set MODIFY_FLAGS */ + { + const char *kindstr + = svn_hash_gets(atts, ENTRIES_ATTR_KIND); + + entry->kind = svn_node_none; + if (kindstr) + { + if (strcmp(kindstr, ENTRIES_VALUE_FILE) == 0) + entry->kind = svn_node_file; + else if (strcmp(kindstr, ENTRIES_VALUE_DIR) == 0) + entry->kind = svn_node_dir; + else + return svn_error_createf + (SVN_ERR_NODE_UNKNOWN_KIND, NULL, + _("Entry '%s' has invalid node kind"), + (name ? name : SVN_WC_ENTRY_THIS_DIR)); + } + } + + /* Look for a schedule attribute on this entry. */ + /* ### not used by loggy; no need to set MODIFY_FLAGS */ + { + const char *schedulestr + = svn_hash_gets(atts, ENTRIES_ATTR_SCHEDULE); + + entry->schedule = svn_wc_schedule_normal; + if (schedulestr) + { + if (strcmp(schedulestr, ENTRIES_VALUE_ADD) == 0) + entry->schedule = svn_wc_schedule_add; + else if (strcmp(schedulestr, ENTRIES_VALUE_DELETE) == 0) + entry->schedule = svn_wc_schedule_delete; + else if (strcmp(schedulestr, ENTRIES_VALUE_REPLACE) == 0) + entry->schedule = svn_wc_schedule_replace; + else if (strcmp(schedulestr, "") == 0) + entry->schedule = svn_wc_schedule_normal; + else + return svn_error_createf( + SVN_ERR_ENTRY_ATTRIBUTE_INVALID, NULL, + _("Entry '%s' has invalid 'schedule' value"), + (name ? name : SVN_WC_ENTRY_THIS_DIR)); + } + } + + /* Is this entry in a state of mental torment (conflict)? */ + entry->prejfile = extract_string_normalize(atts, + ENTRIES_ATTR_PREJFILE, + pool); + entry->conflict_old = extract_string_normalize(atts, + ENTRIES_ATTR_CONFLICT_OLD, + pool); + entry->conflict_new = extract_string_normalize(atts, + ENTRIES_ATTR_CONFLICT_NEW, + pool); + entry->conflict_wrk = extract_string_normalize(atts, + ENTRIES_ATTR_CONFLICT_WRK, + pool); + + /* Is this entry copied? */ + /* ### not used by loggy; no need to set MODIFY_FLAGS */ + SVN_ERR(do_bool_attr(&entry->copied, atts, ENTRIES_ATTR_COPIED, name)); + + /* ### not used by loggy; no need to set MODIFY_FLAGS */ + entry->copyfrom_url = extract_string(atts, ENTRIES_ATTR_COPYFROM_URL, pool); + + /* ### not used by loggy; no need to set MODIFY_FLAGS */ + { + const char *revstr; + + revstr = svn_hash_gets(atts, ENTRIES_ATTR_COPYFROM_REV); + if (revstr) + entry->copyfrom_rev = SVN_STR_TO_REV(revstr); + } + + /* Is this entry deleted? + + ### not used by loggy; no need to set MODIFY_FLAGS */ + SVN_ERR(do_bool_attr(&entry->deleted, atts, ENTRIES_ATTR_DELETED, name)); + + /* Is this entry absent? + + ### not used by loggy; no need to set MODIFY_FLAGS */ + SVN_ERR(do_bool_attr(&entry->absent, atts, ENTRIES_ATTR_ABSENT, name)); + + /* Is this entry incomplete? + + ### not used by loggy; no need to set MODIFY_FLAGS */ + SVN_ERR(do_bool_attr(&entry->incomplete, atts, ENTRIES_ATTR_INCOMPLETE, + name)); + + /* Attempt to set up timestamps. */ + /* ### not used by loggy; no need to set MODIFY_FLAGS */ + { + const char *text_timestr; + + text_timestr = svn_hash_gets(atts, ENTRIES_ATTR_TEXT_TIME); + if (text_timestr) + SVN_ERR(svn_time_from_cstring(&entry->text_time, text_timestr, pool)); + + /* Note: we do not persist prop_time, so there is no need to attempt + to parse a new prop_time value from the log. Certainly, on any + recent working copy, there will not be a log record to alter + the prop_time value. */ + } + + /* Checksum. */ + /* ### not used by loggy; no need to set MODIFY_FLAGS */ + entry->checksum = extract_string(atts, ENTRIES_ATTR_CHECKSUM, pool); + + /* UUID. + + ### not used by loggy; no need to set MODIFY_FLAGS */ + entry->uuid = extract_string(atts, ENTRIES_ATTR_UUID, pool); + + /* Setup last-committed values. */ + { + const char *cmt_datestr, *cmt_revstr; + + cmt_datestr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_DATE); + if (cmt_datestr) + { + SVN_ERR(svn_time_from_cstring(&entry->cmt_date, cmt_datestr, pool)); + } + else + entry->cmt_date = 0; + + cmt_revstr = svn_hash_gets(atts, ENTRIES_ATTR_CMT_REV); + if (cmt_revstr) + { + entry->cmt_rev = SVN_STR_TO_REV(cmt_revstr); + } + else + entry->cmt_rev = SVN_INVALID_REVNUM; + + entry->cmt_author = extract_string(atts, ENTRIES_ATTR_CMT_AUTHOR, pool); + } + + /* ### not used by loggy; no need to set MODIFY_FLAGS */ + entry->lock_token = extract_string(atts, ENTRIES_ATTR_LOCK_TOKEN, pool); + entry->lock_owner = extract_string(atts, ENTRIES_ATTR_LOCK_OWNER, pool); + entry->lock_comment = extract_string(atts, ENTRIES_ATTR_LOCK_COMMENT, pool); + { + const char *cdate_str = + svn_hash_gets(atts, ENTRIES_ATTR_LOCK_CREATION_DATE); + if (cdate_str) + { + SVN_ERR(svn_time_from_cstring(&entry->lock_creation_date, + cdate_str, pool)); + } + } + /* ----- end of lock handling. */ + + /* Note: if there are attributes for the (deprecated) has_props, + has_prop_mods, cachable_props, or present_props, then we're just + going to ignore them. */ + + /* Translated size */ + /* ### not used by loggy; no need to set MODIFY_FLAGS */ + { + const char *val = svn_hash_gets(atts, ENTRIES_ATTR_WORKING_SIZE); + if (val) + { + /* Cast to off_t; it's safe: we put in an off_t to start with... */ + entry->working_size = (apr_off_t)apr_strtoi64(val, NULL, 0); + } + } + + *new_entry = entry; + return SVN_NO_ERROR; +} + +/* Used when reading an entries file in XML format. */ +struct entries_accumulator +{ + /* Keys are entry names, vals are (struct svn_wc_entry_t *)'s. */ + apr_hash_t *entries; + + /* The parser that's parsing it, for signal_expat_bailout(). */ + svn_xml_parser_t *parser; + + /* Don't leave home without one. */ + apr_pool_t *pool; + + /* Cleared before handling each entry. */ + apr_pool_t *scratch_pool; +}; + + + +/* Called whenever we find an <open> tag of some kind. */ +static void +handle_start_tag(void *userData, const char *tagname, const char **atts) +{ + struct entries_accumulator *accum = userData; + apr_hash_t *attributes; + svn_wc_entry_t *entry; + svn_error_t *err; + + /* We only care about the `entry' tag; all other tags, such as `xml' + and `wc-entries', are ignored. */ + if (strcmp(tagname, ENTRIES_TAG_ENTRY)) + return; + + svn_pool_clear(accum->scratch_pool); + /* Make an entry from the attributes. */ + attributes = svn_xml_make_att_hash(atts, accum->scratch_pool); + err = atts_to_entry(&entry, attributes, accum->pool); + if (err) + { + svn_xml_signal_bailout(err, accum->parser); + return; + } + + /* Find the name and set up the entry under that name. This + should *NOT* be NULL, since svn_wc__atts_to_entry() should + have made it into SVN_WC_ENTRY_THIS_DIR. */ + svn_hash_sets(accum->entries, entry->name, entry); +} + +/* Parse BUF of size SIZE as an entries file in XML format, storing the parsed + entries in ENTRIES. Use SCRATCH_POOL for temporary allocations and + RESULT_POOL for the returned entries. */ +static svn_error_t * +parse_entries_xml(const char *dir_abspath, + apr_hash_t *entries, + const char *buf, + apr_size_t size, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_xml_parser_t *svn_parser; + struct entries_accumulator accum; + + /* Set up userData for the XML parser. */ + accum.entries = entries; + accum.pool = result_pool; + accum.scratch_pool = svn_pool_create(scratch_pool); + + /* Create the XML parser */ + svn_parser = svn_xml_make_parser(&accum, + handle_start_tag, + NULL, + NULL, + scratch_pool); + + /* Store parser in its own userdata, so callbacks can call + svn_xml_signal_bailout() */ + accum.parser = svn_parser; + + /* Parse. */ + SVN_ERR_W(svn_xml_parse(svn_parser, buf, size, TRUE), + apr_psprintf(scratch_pool, + _("XML parser failed in '%s'"), + svn_dirent_local_style(dir_abspath, scratch_pool))); + + svn_pool_destroy(accum.scratch_pool); + + /* Clean up the XML parser */ + svn_xml_free_parser(svn_parser); + + 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; + } +} + +/* Resolve any missing information in ENTRIES by deducing from the + directory's own entry (which must already be present in ENTRIES). */ +static svn_error_t * +resolve_to_defaults(apr_hash_t *entries, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + svn_wc_entry_t *default_entry + = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR); + + /* First check the dir's own entry for consistency. */ + if (! default_entry) + return svn_error_create(SVN_ERR_ENTRY_NOT_FOUND, + NULL, + _("Missing default entry")); + + if (default_entry->revision == SVN_INVALID_REVNUM) + return svn_error_create(SVN_ERR_ENTRY_MISSING_REVISION, + NULL, + _("Default entry has no revision number")); + + if (! default_entry->url) + return svn_error_create(SVN_ERR_ENTRY_MISSING_URL, + NULL, + _("Default entry is missing URL")); + + + /* Then use it to fill in missing information in other entries. */ + for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) + { + svn_wc_entry_t *this_entry = svn__apr_hash_index_val(hi); + + if (this_entry == default_entry) + /* THIS_DIR already has all the information it can possibly + have. */ + continue; + + if (this_entry->kind == svn_node_dir) + /* Entries that are directories have everything but their + name, kind, and state stored in the THIS_DIR entry of the + directory itself. However, we are disallowing the perusing + of any entries outside of the current entries file. If a + caller wants more info about a directory, it should look in + the entries file in the directory. */ + continue; + + if (this_entry->kind == svn_node_file) + /* For file nodes that do not explicitly have their ancestry + stated, this can be derived from the default entry of the + directory in which those files reside. */ + take_from_entry(default_entry, this_entry, pool); + } + + return SVN_NO_ERROR; +} + + + +/* Read and parse an old-style 'entries' file in the administrative area + of PATH, filling in ENTRIES with the contents. The results will be + allocated in RESULT_POOL, and temporary allocations will be made in + SCRATCH_POOL. */ +svn_error_t * +svn_wc__read_entries_old(apr_hash_t **entries, + const char *dir_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + char *curp; + const char *endp; + svn_wc_entry_t *entry; + svn_stream_t *stream; + svn_string_t *buf; + + *entries = apr_hash_make(result_pool); + + /* Open the entries file. */ + SVN_ERR(svn_wc__open_adm_stream(&stream, dir_abspath, SVN_WC__ADM_ENTRIES, + scratch_pool, scratch_pool)); + SVN_ERR(svn_string_from_stream(&buf, stream, scratch_pool, scratch_pool)); + + /* We own the returned data; it is modifiable, so cast away... */ + curp = (char *)buf->data; + endp = buf->data + buf->len; + + /* If the first byte of the file is not a digit, then it is probably in XML + format. */ + if (curp != endp && !svn_ctype_isdigit(*curp)) + SVN_ERR(parse_entries_xml(dir_abspath, *entries, buf->data, buf->len, + result_pool, scratch_pool)); + else + { + int entryno, entries_format; + const char *val; + + /* Read the format line from the entries file. In case we're in the + middle of upgrading a working copy, this line will contain the + original format pre-upgrade. */ + SVN_ERR(read_val(&val, &curp, endp)); + if (val) + entries_format = (int)apr_strtoi64(val, NULL, 0); + else + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid version line in entries file " + "of '%s'"), + svn_dirent_local_style(dir_abspath, + scratch_pool)); + entryno = 1; + + while (curp != endp) + { + svn_error_t *err = read_entry(&entry, &curp, endp, + entries_format, result_pool); + if (! err) + { + /* We allow extra fields at the end of the line, for + extensibility. */ + curp = memchr(curp, '\f', endp - curp); + if (! curp) + err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL, + _("Missing entry terminator")); + if (! err && (curp == endp || *(++curp) != '\n')) + err = svn_error_create(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid entry terminator")); + } + if (err) + return svn_error_createf(err->apr_err, err, + _("Error at entry %d in entries file for " + "'%s':"), + entryno, + svn_dirent_local_style(dir_abspath, + scratch_pool)); + + ++curp; + ++entryno; + + svn_hash_sets(*entries, entry->name, entry); + } + } + + /* Fill in any implied fields. */ + return svn_error_trace(resolve_to_defaults(*entries, result_pool)); +} + + +/* For non-directory PATHs full entry information is obtained by reading + * the entries for the parent directory of PATH and then extracting PATH's + * entry. If PATH is a directory then only abrieviated information is + * available in the parent directory, more complete information is + * available by reading the entries for PATH itself. + * + * Note: There is one bit of information about directories that is only + * available in the parent directory, that is the "deleted" state. If PATH + * is a versioned directory then the "deleted" state information will not + * be returned in ENTRY. This means some bits of the code (e.g. revert) + * need to obtain it by directly extracting the directory entry from the + * parent directory's entries. I wonder if this function should handle + * that? + */ +svn_error_t * +svn_wc_entry(const svn_wc_entry_t **entry, + const char *path, + svn_wc_adm_access_t *adm_access, + svn_boolean_t show_hidden, + apr_pool_t *pool) +{ + svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); + const char *local_abspath; + svn_wc_adm_access_t *dir_access; + const char *entry_name; + apr_hash_t *entries; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); + + /* Does the provided path refer to a directory with an associated + access baton? */ + dir_access = svn_wc__adm_retrieve_internal2(db, local_abspath, pool); + if (dir_access == NULL) + { + /* Damn. Okay. Assume the path is to a child, and let's look for + a baton associated with its parent. */ + + const char *dir_abspath; + + svn_dirent_split(&dir_abspath, &entry_name, local_abspath, pool); + + dir_access = svn_wc__adm_retrieve_internal2(db, dir_abspath, pool); + } + else + { + /* Woo! Got one. Look for "this dir" in the entries hash. */ + entry_name = ""; + } + + if (dir_access == NULL) + { + /* Early exit. */ + *entry = NULL; + return SVN_NO_ERROR; + } + + /* Load an entries hash, and cache it into DIR_ACCESS. Go ahead and + fetch all entries here (optimization) since we know how to filter + out a "hidden" node. */ + SVN_ERR(svn_wc__entries_read_internal(&entries, dir_access, TRUE, pool)); + *entry = svn_hash_gets(entries, entry_name); + + if (!show_hidden && *entry != NULL) + { + svn_boolean_t hidden; + + SVN_ERR(svn_wc__entry_is_hidden(&hidden, *entry)); + if (hidden) + *entry = NULL; + } + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/props.c b/subversion/libsvn_wc/props.c new file mode 100644 index 000000000000..a7b2339b0107 --- /dev/null +++ b/subversion/libsvn_wc/props.c @@ -0,0 +1,2344 @@ +/* + * props.c : routines dealing with properties in the working copy + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <stdlib.h> +#include <string.h> + +#include <apr_pools.h> +#include <apr_hash.h> +#include <apr_tables.h> +#include <apr_file_io.h> +#include <apr_strings.h> +#include <apr_general.h> + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_error.h" +#include "svn_props.h" +#include "svn_io.h" +#include "svn_hash.h" +#include "svn_mergeinfo.h" +#include "svn_wc.h" +#include "svn_utf.h" +#include "svn_diff.h" +#include "svn_sorts.h" + +#include "private/svn_wc_private.h" +#include "private/svn_mergeinfo_private.h" +#include "private/svn_skel.h" +#include "private/svn_string_private.h" +#include "private/svn_subr_private.h" + +#include "wc.h" +#include "props.h" +#include "translate.h" +#include "workqueue.h" +#include "conflicts.h" + +#include "svn_private_config.h" + +/* Forward declaration. */ +static svn_error_t * +prop_conflict_from_skel(const svn_string_t **conflict_desc, + const svn_skel_t *skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Given a *SINGLE* property conflict in PROP_SKEL, generate a description + for it, and write it to STREAM, along with a trailing EOL sequence. + + See prop_conflict_from_skel() for details on PROP_SKEL. */ +static svn_error_t * +append_prop_conflict(svn_stream_t *stream, + const svn_skel_t *prop_skel, + apr_pool_t *pool) +{ + /* TODO: someday, perhaps prefix each conflict_description with a + timestamp or something? */ + const svn_string_t *conflict_desc; + + SVN_ERR(prop_conflict_from_skel(&conflict_desc, prop_skel, pool, pool)); + + return svn_stream_puts(stream, conflict_desc->data); +} + +/*---------------------------------------------------------------------*/ + +/*** Merging propchanges into the working copy ***/ + + +/* Parse FROM_PROP_VAL and TO_PROP_VAL into mergeinfo hashes, and + calculate the deltas between them. */ +static svn_error_t * +diff_mergeinfo_props(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added, + const svn_string_t *from_prop_val, + const svn_string_t *to_prop_val, apr_pool_t *pool) +{ + if (svn_string_compare(from_prop_val, to_prop_val)) + { + /* Don't bothering parsing identical mergeinfo. */ + *deleted = apr_hash_make(pool); + *added = apr_hash_make(pool); + } + else + { + svn_mergeinfo_t from, to; + SVN_ERR(svn_mergeinfo_parse(&from, from_prop_val->data, pool)); + SVN_ERR(svn_mergeinfo_parse(&to, to_prop_val->data, pool)); + SVN_ERR(svn_mergeinfo_diff2(deleted, added, from, to, + TRUE, pool, pool)); + } + return SVN_NO_ERROR; +} + +/* Parse the mergeinfo from PROP_VAL1 and PROP_VAL2, combine it, then + reconstitute it into *OUTPUT. Call when the WC's mergeinfo has + been modified to combine it with incoming mergeinfo from the + repos. */ +static svn_error_t * +combine_mergeinfo_props(const svn_string_t **output, + const svn_string_t *prop_val1, + const svn_string_t *prop_val2, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_mergeinfo_t mergeinfo1, mergeinfo2; + svn_string_t *mergeinfo_string; + + SVN_ERR(svn_mergeinfo_parse(&mergeinfo1, prop_val1->data, scratch_pool)); + SVN_ERR(svn_mergeinfo_parse(&mergeinfo2, prop_val2->data, scratch_pool)); + SVN_ERR(svn_mergeinfo_merge2(mergeinfo1, mergeinfo2, scratch_pool, + scratch_pool)); + SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, mergeinfo1, result_pool)); + *output = mergeinfo_string; + return SVN_NO_ERROR; +} + +/* Perform a 3-way merge operation on mergeinfo. FROM_PROP_VAL is + the "base" property value, WORKING_PROP_VAL is the current value, + and TO_PROP_VAL is the new value. */ +static svn_error_t * +combine_forked_mergeinfo_props(const svn_string_t **output, + const svn_string_t *from_prop_val, + const svn_string_t *working_prop_val, + const svn_string_t *to_prop_val, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_mergeinfo_t from_mergeinfo, l_deleted, l_added, r_deleted, r_added; + svn_string_t *mergeinfo_string; + + /* ### OPTIMIZE: Use from_mergeinfo when diff'ing. */ + SVN_ERR(diff_mergeinfo_props(&l_deleted, &l_added, from_prop_val, + working_prop_val, scratch_pool)); + SVN_ERR(diff_mergeinfo_props(&r_deleted, &r_added, from_prop_val, + to_prop_val, scratch_pool)); + SVN_ERR(svn_mergeinfo_merge2(l_deleted, r_deleted, + scratch_pool, scratch_pool)); + SVN_ERR(svn_mergeinfo_merge2(l_added, r_added, + scratch_pool, scratch_pool)); + + /* Apply the combined deltas to the base. */ + SVN_ERR(svn_mergeinfo_parse(&from_mergeinfo, from_prop_val->data, + scratch_pool)); + SVN_ERR(svn_mergeinfo_merge2(from_mergeinfo, l_added, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_mergeinfo_remove2(&from_mergeinfo, l_deleted, from_mergeinfo, + TRUE, scratch_pool, scratch_pool)); + + SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, from_mergeinfo, + result_pool)); + *output = mergeinfo_string; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_merge_props3(svn_wc_notify_state_t *state, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + const svn_wc_conflict_version_t *left_version, + const svn_wc_conflict_version_t *right_version, + apr_hash_t *baseprops, + const apr_array_header_t *propchanges, + svn_boolean_t dry_run, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + int i; + svn_wc__db_status_t status; + svn_node_kind_t kind; + apr_hash_t *pristine_props = NULL; + apr_hash_t *actual_props; + apr_hash_t *new_actual_props; + svn_boolean_t had_props, props_mod; + svn_boolean_t have_base; + svn_boolean_t conflicted; + svn_skel_t *work_items = NULL; + svn_skel_t *conflict_skel = NULL; + svn_wc__db_t *db = wc_ctx->db; + + /* IMPORTANT: svn_wc_merge_prop_diffs relies on the fact that baseprops + may be NULL. */ + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, &conflicted, NULL, + &had_props, &props_mod, &have_base, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + /* Checks whether the node exists and returns the hidden flag */ + if (status == svn_wc__db_status_not_present + || status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded) + { + return svn_error_createf( + SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, scratch_pool)); + } + else if (status != svn_wc__db_status_normal + && status != svn_wc__db_status_added + && status != svn_wc__db_status_incomplete) + { + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("The node '%s' does not have properties in this state."), + svn_dirent_local_style(local_abspath, scratch_pool)); + } + else if (conflicted) + { + svn_boolean_t text_conflicted; + svn_boolean_t prop_conflicted; + svn_boolean_t tree_conflicted; + + SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, + &prop_conflicted, + &tree_conflicted, + db, local_abspath, + scratch_pool)); + + /* We can't install two text/prop conflicts on a single node, so + avoid even checking that we have to merge it */ + if (text_conflicted || prop_conflicted || tree_conflicted) + { + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Can't merge into conflicted node '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + /* else: Conflict was resolved by removing markers */ + } + + /* The PROPCHANGES may not have non-"normal" properties in it. If entry + or wc props were allowed, then the following code would install them + into the BASE and/or WORKING properties(!). */ + for (i = propchanges->nelts; i--; ) + { + const svn_prop_t *change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t); + + if (!svn_wc_is_normal_prop(change->name)) + return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL, + _("The property '%s' may not be merged " + "into '%s'."), + change->name, + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + if (had_props) + SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, local_abspath, + scratch_pool, scratch_pool)); + if (pristine_props == NULL) + pristine_props = apr_hash_make(scratch_pool); + + if (props_mod) + SVN_ERR(svn_wc__get_actual_props(&actual_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + actual_props = pristine_props; + + /* Note that while this routine does the "real" work, it's only + prepping tempfiles and writing log commands. */ + SVN_ERR(svn_wc__merge_props(&conflict_skel, state, + &new_actual_props, + db, local_abspath, + baseprops /* server_baseprops */, + pristine_props, + actual_props, + propchanges, + scratch_pool, scratch_pool)); + + if (dry_run) + { + return SVN_NO_ERROR; + } + + { + const char *dir_abspath; + + if (kind == svn_node_dir) + dir_abspath = local_abspath; + else + dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + /* Verify that we're holding this directory's write lock. */ + SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool)); + } + + if (conflict_skel) + { + svn_skel_t *work_item; + SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel, + left_version, + right_version, + scratch_pool, + scratch_pool)); + + SVN_ERR(svn_wc__conflict_create_markers(&work_item, + db, local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + } + + /* After a (not-dry-run) merge, we ALWAYS have props to save. */ + SVN_ERR_ASSERT(new_actual_props != NULL); + + SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, new_actual_props, + svn_wc__has_magic_property(propchanges), + conflict_skel, + work_items, + scratch_pool)); + + if (work_items != NULL) + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + + /* If there is a conflict, try to resolve it. */ + if (conflict_skel && conflict_func) + { + svn_boolean_t prop_conflicted; + + SVN_ERR(svn_wc__conflict_invoke_resolver(db, local_abspath, conflict_skel, + NULL /* merge_options */, + conflict_func, conflict_baton, + cancel_func, cancel_baton, + scratch_pool)); + + /* Reset *STATE if all prop conflicts were resolved. */ + SVN_ERR(svn_wc__internal_conflicted_p( + NULL, &prop_conflicted, NULL, + wc_ctx->db, local_abspath, scratch_pool)); + if (! prop_conflicted) + *state = svn_wc_notify_state_merged; + } + + return SVN_NO_ERROR; +} + + +/* Generate a message to describe the property conflict among these four + values. + + Note that this function (currently) interprets the property values as + strings, but they could actually be binary values. We'll keep the + types as svn_string_t in case we fix this in the future. */ +static svn_stringbuf_t * +generate_conflict_message(const char *propname, + const svn_string_t *original, + const svn_string_t *mine, + const svn_string_t *incoming, + const svn_string_t *incoming_base, + apr_pool_t *result_pool) +{ + if (incoming_base == NULL) + { + /* Attempting to add the value INCOMING. */ + SVN_ERR_ASSERT_NO_RETURN(incoming != NULL); + + if (mine) + { + /* To have a conflict, these must be different. */ + SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(mine, incoming)); + + /* Note that we don't care whether MINE is locally-added or + edited, or just something different that is a copy of the + pristine ORIGINAL. */ + return svn_stringbuf_createf(result_pool, + _("Trying to add new property '%s'\n" + "but the property already exists.\n"), + propname); + } + + /* To have a conflict, we must have an ORIGINAL which has been + locally-deleted. */ + SVN_ERR_ASSERT_NO_RETURN(original != NULL); + return svn_stringbuf_createf(result_pool, + _("Trying to add new property '%s'\n" + "but the property has been locally " + "deleted.\n"), + propname); + } + + if (incoming == NULL) + { + /* Attempting to delete the value INCOMING_BASE. */ + SVN_ERR_ASSERT_NO_RETURN(incoming_base != NULL); + + /* Are we trying to delete a local addition? */ + if (original == NULL && mine != NULL) + return svn_stringbuf_createf(result_pool, + _("Trying to delete property '%s'\n" + "but the property has been locally " + "added.\n"), + propname); + + /* A conflict can only occur if we originally had the property; + otherwise, we would have merged the property-delete into the + non-existent property. */ + SVN_ERR_ASSERT_NO_RETURN(original != NULL); + + if (svn_string_compare(original, incoming_base)) + { + if (mine) + /* We were trying to delete the correct property, but an edit + caused the conflict. */ + return svn_stringbuf_createf(result_pool, + _("Trying to delete property '%s'\n" + "but the property has been locally " + "modified.\n"), + propname); + } + else if (mine == NULL) + { + /* We were trying to delete the property, but we have locally + deleted the same property, but with a different value. */ + return svn_stringbuf_createf(result_pool, + _("Trying to delete property '%s'\n" + "but the property has been locally " + "deleted and had a different " + "value.\n"), + propname); + } + + /* We were trying to delete INCOMING_BASE but our ORIGINAL is + something else entirely. */ + SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base)); + + return svn_stringbuf_createf(result_pool, + _("Trying to delete property '%s'\n" + "but the local property value is " + "different.\n"), + propname); + } + + /* Attempting to change the property from INCOMING_BASE to INCOMING. */ + + /* If we have a (current) property value, then it should be different + from the INCOMING_BASE; otherwise, the incoming change would have + been applied to it. */ + SVN_ERR_ASSERT_NO_RETURN(!mine || !svn_string_compare(mine, incoming_base)); + + if (original && mine && svn_string_compare(original, mine)) + { + /* We have an unchanged property, so the original values must + have been different. */ + SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base)); + return svn_stringbuf_createf(result_pool, + _("Trying to change property '%s'\n" + "but the local property value conflicts " + "with the incoming change.\n"), + propname); + } + + if (original && mine) + return svn_stringbuf_createf(result_pool, + _("Trying to change property '%s'\n" + "but the property has already been locally " + "changed to a different value.\n"), + propname); + + if (original) + return svn_stringbuf_createf(result_pool, + _("Trying to change property '%s'\nbut " + "the property has been locally deleted.\n"), + propname); + + if (mine) + return svn_stringbuf_createf(result_pool, + _("Trying to change property '%s'\nbut the " + "property has been locally added with a " + "different value.\n"), + propname); + + return svn_stringbuf_createf(result_pool, + _("Trying to change property '%s'\nbut " + "the property does not exist locally.\n"), + propname); +} + + +/* SKEL will be one of: + + () + (VALUE) + + Return NULL for the former (the particular property value was not + present), and VALUE for the second. */ +static const svn_string_t * +maybe_prop_value(const svn_skel_t *skel, + apr_pool_t *result_pool) +{ + if (skel->children == NULL) + return NULL; + + return svn_string_ncreate(skel->children->data, + skel->children->len, + result_pool); +} + + +/* Parse a property conflict description from the provided SKEL. + The result includes a descriptive message (see generate_conflict_message) + and maybe a diff of property values containing conflict markers. + The result will be allocated in RESULT_POOL. + + Note: SKEL is a single property conflict of the form: + + ("prop" ([ORIGINAL]) ([MINE]) ([INCOMING]) ([INCOMING_BASE])) + + See notes/wc-ng/conflict-storage for more information. */ +static svn_error_t * +prop_conflict_from_skel(const svn_string_t **conflict_desc, + const svn_skel_t *skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const svn_string_t *original; + const svn_string_t *mine; + const svn_string_t *incoming; + const svn_string_t *incoming_base; + const char *propname; + svn_diff_t *diff; + svn_diff_file_options_t *diff_opts; + svn_stringbuf_t *buf; + svn_boolean_t original_is_binary; + svn_boolean_t mine_is_binary; + svn_boolean_t incoming_is_binary; + + /* Navigate to the property name. */ + skel = skel->children->next; + + /* We need to copy these into SCRATCH_POOL in order to nul-terminate + the values. */ + propname = apr_pstrmemdup(scratch_pool, skel->data, skel->len); + original = maybe_prop_value(skel->next, scratch_pool); + mine = maybe_prop_value(skel->next->next, scratch_pool); + incoming = maybe_prop_value(skel->next->next->next, scratch_pool); + incoming_base = maybe_prop_value(skel->next->next->next->next, scratch_pool); + + buf = generate_conflict_message(propname, original, mine, incoming, + incoming_base, scratch_pool); + + if (mine == NULL) + mine = svn_string_create_empty(scratch_pool); + if (incoming == NULL) + incoming = svn_string_create_empty(scratch_pool); + + /* Pick a suitable base for the conflict diff. + * The incoming value is always a change, + * but the local value might not have changed. */ + if (original == NULL) + { + if (incoming_base) + original = incoming_base; + else + original = svn_string_create_empty(scratch_pool); + } + else if (incoming_base && svn_string_compare(original, mine)) + original = incoming_base; + + /* If any of the property values involved in the diff is binary data, + * do not generate a diff. */ + original_is_binary = svn_io_is_binary_data(original->data, original->len); + mine_is_binary = svn_io_is_binary_data(mine->data, mine->len); + incoming_is_binary = svn_io_is_binary_data(incoming->data, incoming->len); + + if (!(original_is_binary || mine_is_binary || incoming_is_binary)) + { + diff_opts = svn_diff_file_options_create(scratch_pool); + diff_opts->ignore_space = svn_diff_file_ignore_space_none; + diff_opts->ignore_eol_style = FALSE; + diff_opts->show_c_function = FALSE; + SVN_ERR(svn_diff_mem_string_diff3(&diff, original, mine, incoming, + diff_opts, scratch_pool)); + if (svn_diff_contains_conflicts(diff)) + { + svn_stream_t *stream; + svn_diff_conflict_display_style_t style; + const char *mine_marker = _("<<<<<<< (local property value)"); + const char *incoming_marker = _(">>>>>>> (incoming property value)"); + const char *separator = "======="; + svn_string_t *original_ascii = + svn_string_create(svn_utf_cstring_from_utf8_fuzzy(original->data, + scratch_pool), + scratch_pool); + svn_string_t *mine_ascii = + svn_string_create(svn_utf_cstring_from_utf8_fuzzy(mine->data, + scratch_pool), + scratch_pool); + svn_string_t *incoming_ascii = + svn_string_create(svn_utf_cstring_from_utf8_fuzzy(incoming->data, + scratch_pool), + scratch_pool); + + style = svn_diff_conflict_display_modified_latest; + stream = svn_stream_from_stringbuf(buf, scratch_pool); + SVN_ERR(svn_stream_skip(stream, buf->len)); + SVN_ERR(svn_diff_mem_string_output_merge2(stream, diff, + original_ascii, + mine_ascii, + incoming_ascii, + NULL, mine_marker, + incoming_marker, separator, + style, scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + + *conflict_desc = svn_string_create_from_buf(buf, result_pool); + return SVN_NO_ERROR; + } + } + + /* If we could not print a conflict diff just print full values . */ + if (mine->len > 0) + { + svn_stringbuf_appendcstr(buf, _("Local property value:\n")); + if (mine_is_binary) + svn_stringbuf_appendcstr(buf, _("Cannot display: property value is " + "binary data\n")); + else + svn_stringbuf_appendbytes(buf, mine->data, mine->len); + svn_stringbuf_appendcstr(buf, "\n"); + } + + if (incoming->len > 0) + { + svn_stringbuf_appendcstr(buf, _("Incoming property value:\n")); + if (incoming_is_binary) + svn_stringbuf_appendcstr(buf, _("Cannot display: property value is " + "binary data\n")); + else + svn_stringbuf_appendbytes(buf, incoming->data, incoming->len); + svn_stringbuf_appendcstr(buf, "\n"); + } + + *conflict_desc = svn_string_create_from_buf(buf, result_pool); + return SVN_NO_ERROR; +} + + +/* Create a property conflict file at PREJFILE based on the property + conflicts in CONFLICT_SKEL. */ +svn_error_t * +svn_wc__create_prejfile(const char **tmp_prejfile_abspath, + svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *tempdir_abspath; + svn_stream_t *stream; + const char *temp_abspath; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + const svn_skel_t *scan; + + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tempdir_abspath, + db, local_abspath, + iterpool, iterpool)); + + SVN_ERR(svn_stream_open_unique(&stream, &temp_abspath, + tempdir_abspath, svn_io_file_del_none, + scratch_pool, iterpool)); + + for (scan = conflict_skel->children->next; scan != NULL; scan = scan->next) + { + svn_pool_clear(iterpool); + + SVN_ERR(append_prop_conflict(stream, scan, iterpool)); + } + + SVN_ERR(svn_stream_close(stream)); + + svn_pool_destroy(iterpool); + + *tmp_prejfile_abspath = apr_pstrdup(result_pool, temp_abspath); + return SVN_NO_ERROR; +} + + +/* Set the value of *STATE to NEW_VALUE if STATE is not NULL + * and NEW_VALUE is a higer order value than *STATE's current value + * using this ordering (lower order first): + * + * - unknown, unchanged, inapplicable + * - changed + * - merged + * - missing + * - obstructed + * - conflicted + * + */ +static void +set_prop_merge_state(svn_wc_notify_state_t *state, + svn_wc_notify_state_t new_value) +{ + static char ordering[] = + { svn_wc_notify_state_unknown, + svn_wc_notify_state_unchanged, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_changed, + svn_wc_notify_state_merged, + svn_wc_notify_state_obstructed, + svn_wc_notify_state_conflicted }; + int state_pos = 0, i; + + if (! state) + return; + + /* Find *STATE in our ordering */ + for (i = 0; i < sizeof(ordering); i++) + { + if (*state == ordering[i]) + { + state_pos = i; + break; + } + } + + /* Find NEW_VALUE in our ordering + * We don't need to look further than where we found *STATE though: + * If we find our value, it's order is too low. + * If we don't find it, we'll want to set it, no matter its order. + */ + + for (i = 0; i <= state_pos; i++) + { + if (new_value == ordering[i]) + return; + } + + *state = new_value; +} + +/* Apply the addition of a property with name PROPNAME and value NEW_VAL to + * the existing property with value WORKING_VAL, that originally had value + * PRISTINE_VAL. + * + * Sets *RESULT_VAL to the resulting value. + * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict. + * Sets *DID_MERGE to true if the result is caused by a merge + */ +static svn_error_t * +apply_single_prop_add(const svn_string_t **result_val, + svn_boolean_t *conflict_remains, + svn_boolean_t *did_merge, + const char *propname, + const svn_string_t *pristine_val, + const svn_string_t *new_val, + const svn_string_t *working_val, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) + +{ + *conflict_remains = FALSE; + + if (working_val) + { + /* the property already exists in actual_props... */ + + if (svn_string_compare(working_val, new_val)) + /* The value we want is already there, so it's a merge. */ + *did_merge = TRUE; + + else + { + svn_boolean_t merged_prop = FALSE; + + /* The WC difference doesn't match the new value. + We only merge mergeinfo; other props conflict */ + if (strcmp(propname, SVN_PROP_MERGEINFO) == 0) + { + const svn_string_t *merged_val; + svn_error_t *err = combine_mergeinfo_props(&merged_val, + working_val, + new_val, + result_pool, + scratch_pool); + + /* Issue #3896 'mergeinfo syntax errors should be treated + gracefully': If bogus mergeinfo is present we can't + merge intelligently, so raise a conflict instead. */ + if (err) + { + if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) + svn_error_clear(err); + else + return svn_error_trace(err); + } + else + { + merged_prop = TRUE; + *result_val = merged_val; + *did_merge = TRUE; + } + } + + if (!merged_prop) + *conflict_remains = TRUE; + } + } + else if (pristine_val) + *conflict_remains = TRUE; + else /* property doesn't yet exist in actual_props... */ + /* so just set it */ + *result_val = new_val; + + return SVN_NO_ERROR; +} + + +/* Apply the deletion of a property to the existing + * property with value WORKING_VAL, that originally had value PRISTINE_VAL. + * + * Sets *RESULT_VAL to the resulting value. + * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict. + * Sets *DID_MERGE to true if the result is caused by a merge + */ +static svn_error_t * +apply_single_prop_delete(const svn_string_t **result_val, + svn_boolean_t *conflict_remains, + svn_boolean_t *did_merge, + const svn_string_t *base_val, + const svn_string_t *old_val, + const svn_string_t *working_val) +{ + *conflict_remains = FALSE; + + if (! base_val) + { + if (working_val + && !svn_string_compare(working_val, old_val)) + { + /* We are trying to delete a locally-added prop. */ + *conflict_remains = TRUE; + } + else + { + *result_val = NULL; + if (old_val) + /* This is a merge, merging a delete into non-existent + property or a local addition of same prop value. */ + *did_merge = TRUE; + } + } + + else if (svn_string_compare(base_val, old_val)) + { + if (working_val) + { + if (svn_string_compare(working_val, old_val)) + /* they have the same values, so it's an update */ + *result_val = NULL; + else + *conflict_remains = TRUE; + } + else + /* The property is locally deleted from the same value, so it's + a merge */ + *did_merge = TRUE; + } + + else + *conflict_remains = TRUE; + + return SVN_NO_ERROR; +} + + +/* Merge a change to the mergeinfo property. Similar to + apply_single_prop_change(), except that the property name is always + SVN_PROP_MERGEINFO. */ +/* ### This function is extracted straight from the previous all-in-one + version of apply_single_prop_change() by removing the code paths that + were not followed for this property, but with no attempt to rationalize + the remainder. */ +static svn_error_t * +apply_single_mergeinfo_prop_change(const svn_string_t **result_val, + svn_boolean_t *conflict_remains, + svn_boolean_t *did_merge, + const svn_string_t *base_val, + const svn_string_t *old_val, + const svn_string_t *new_val, + const svn_string_t *working_val, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if ((working_val && ! base_val) + || (! working_val && base_val) + || (working_val && base_val + && !svn_string_compare(working_val, base_val))) + { + /* Locally changed property */ + if (working_val) + { + if (svn_string_compare(working_val, new_val)) + /* The new value equals the changed value: a no-op merge */ + *did_merge = TRUE; + else + { + /* We have base, WC, and new values. Discover + deltas between base <-> WC, and base <-> + incoming. Combine those deltas, and apply + them to base to get the new value. */ + SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val, + working_val, + new_val, + result_pool, + scratch_pool)); + *result_val = new_val; + *did_merge = TRUE; + } + } + else + { + /* There is a base_val but no working_val */ + *conflict_remains = TRUE; + } + } + + else if (! working_val) /* means !working_val && !base_val due + to conditions above: no prop at all */ + { + /* Discover any mergeinfo additions in the + incoming value relative to the base, and + "combine" those with the empty WC value. */ + svn_mergeinfo_t deleted_mergeinfo, added_mergeinfo; + svn_string_t *mergeinfo_string; + + SVN_ERR(diff_mergeinfo_props(&deleted_mergeinfo, + &added_mergeinfo, + old_val, new_val, scratch_pool)); + SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, + added_mergeinfo, result_pool)); + *result_val = mergeinfo_string; + } + + else /* means working && base && svn_string_compare(working, base) */ + { + if (svn_string_compare(old_val, base_val)) + *result_val = new_val; + else + { + /* We have base, WC, and new values. Discover + deltas between base <-> WC, and base <-> + incoming. Combine those deltas, and apply + them to base to get the new value. */ + SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val, + working_val, + new_val, result_pool, + scratch_pool)); + *result_val = new_val; + *did_merge = TRUE; + } + } + + return SVN_NO_ERROR; +} + +/* Merge a change to a property, using the rule that if the working value + is equal to the new value then there is nothing we need to do. Else, if + the working value is the same as the old value then apply the change as a + simple update (replacement), otherwise invoke maybe_generate_propconflict(). + The definition of the arguments and behaviour is the same as + apply_single_prop_change(). */ +static svn_error_t * +apply_single_generic_prop_change(const svn_string_t **result_val, + svn_boolean_t *conflict_remains, + svn_boolean_t *did_merge, + const svn_string_t *old_val, + const svn_string_t *new_val, + const svn_string_t *working_val) +{ + SVN_ERR_ASSERT(old_val != NULL); + + /* If working_val is the same as new_val already then there is + * nothing to do */ + if (working_val && new_val + && svn_string_compare(working_val, new_val)) + { + /* All values identical is a trivial, non-notifiable merge */ + if (! old_val || ! svn_string_compare(old_val, new_val)) + *did_merge = TRUE; + } + /* If working_val is the same as old_val... */ + else if (working_val && old_val + && svn_string_compare(working_val, old_val)) + { + /* A trivial update: change it to new_val. */ + *result_val = new_val; + } + else + { + /* Merge the change. */ + *conflict_remains = TRUE; + } + + return SVN_NO_ERROR; +} + +/* Change the property with name PROPNAME, setting *RESULT_VAL, + * *CONFLICT_REMAINS and *DID_MERGE according to the merge outcome. + * + * BASE_VAL contains the working copy base property value. (May be null.) + * + * OLD_VAL contains the value of the property the server + * thinks it's overwriting. (Not null.) + * + * NEW_VAL contains the value to be set. (Not null.) + * + * WORKING_VAL contains the working copy actual value. (May be null.) + */ +static svn_error_t * +apply_single_prop_change(const svn_string_t **result_val, + svn_boolean_t *conflict_remains, + svn_boolean_t *did_merge, + const char *propname, + const svn_string_t *base_val, + const svn_string_t *old_val, + const svn_string_t *new_val, + const svn_string_t *working_val, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t merged_prop = FALSE; + + *conflict_remains = FALSE; + + /* Note: The purpose is to apply the change (old_val -> new_val) onto + (working_val). There is no need for base_val to be involved in the + process except as a bit of context to help the user understand and + resolve any conflict. */ + + /* Decide how to merge, based on whether we know anything special about + the property. */ + if (strcmp(propname, SVN_PROP_MERGEINFO) == 0) + { + /* We know how to merge any mergeinfo property change... + + ...But Issue #3896 'mergeinfo syntax errors should be treated + gracefully' might thwart us. If bogus mergeinfo is present we + can't merge intelligently, so let the standard method deal with + it instead. */ + svn_error_t *err = apply_single_mergeinfo_prop_change(result_val, + conflict_remains, + did_merge, + base_val, + old_val, + new_val, + working_val, + result_pool, + scratch_pool); + if (err) + { + if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) + svn_error_clear(err); + else + return svn_error_trace(err); + } + else + { + merged_prop = TRUE; + } + } + + if (!merged_prop) + { + /* The standard method: perform a simple update automatically, but + pass any other kind of merge to maybe_generate_propconflict(). */ + + SVN_ERR(apply_single_generic_prop_change(result_val, conflict_remains, + did_merge, + old_val, new_val, working_val)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__merge_props(svn_skel_t **conflict_skel, + svn_wc_notify_state_t *state, + apr_hash_t **new_actual_props, + svn_wc__db_t *db, + const char *local_abspath, + apr_hash_t *server_baseprops, + apr_hash_t *pristine_props, + apr_hash_t *actual_props, + const apr_array_header_t *propchanges, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + int i; + apr_hash_t *conflict_props = NULL; + apr_hash_t *their_props; + + SVN_ERR_ASSERT(pristine_props != NULL); + SVN_ERR_ASSERT(actual_props != NULL); + + *new_actual_props = apr_hash_copy(result_pool, actual_props); + + if (!server_baseprops) + server_baseprops = pristine_props; + + their_props = apr_hash_copy(scratch_pool, server_baseprops); + + if (state) + { + /* Start out assuming no changes or conflicts. Don't bother to + examine propchanges->nelts yet; even if we knew there were + propchanges, we wouldn't yet know if they are "normal" props, + as opposed wc or entry props. */ + *state = svn_wc_notify_state_unchanged; + } + + /* Looping over the array of incoming propchanges we want to apply: */ + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < propchanges->nelts; i++) + { + const svn_prop_t *incoming_change + = &APR_ARRAY_IDX(propchanges, i, svn_prop_t); + const char *propname = incoming_change->name; + const svn_string_t *base_val /* Pristine in WC */ + = svn_hash_gets(pristine_props, propname); + const svn_string_t *from_val /* Merge left */ + = svn_hash_gets(server_baseprops, propname); + const svn_string_t *to_val /* Merge right */ + = incoming_change->value; + const svn_string_t *working_val /* Mine */ + = svn_hash_gets(actual_props, propname); + const svn_string_t *result_val; + svn_boolean_t conflict_remains; + svn_boolean_t did_merge = FALSE; + + svn_pool_clear(iterpool); + + to_val = to_val ? svn_string_dup(to_val, result_pool) : NULL; + + svn_hash_sets(their_props, propname, to_val); + + + /* We already know that state is at least `changed', so mark + that, but remember that we may later upgrade to `merged' or + even `conflicted'. */ + set_prop_merge_state(state, svn_wc_notify_state_changed); + + result_val = working_val; + + if (! from_val) /* adding a new property */ + SVN_ERR(apply_single_prop_add(&result_val, &conflict_remains, + &did_merge, propname, + base_val, to_val, working_val, + result_pool, iterpool)); + + else if (! to_val) /* delete an existing property */ + SVN_ERR(apply_single_prop_delete(&result_val, &conflict_remains, + &did_merge, + base_val, from_val, working_val)); + + else /* changing an existing property */ + SVN_ERR(apply_single_prop_change(&result_val, &conflict_remains, + &did_merge, propname, + base_val, from_val, to_val, working_val, + result_pool, iterpool)); + + if (result_val != working_val) + svn_hash_sets(*new_actual_props, propname, result_val); + if (did_merge) + set_prop_merge_state(state, svn_wc_notify_state_merged); + + /* merging logic complete, now we need to possibly log conflict + data to tmpfiles. */ + + if (conflict_remains) + { + set_prop_merge_state(state, svn_wc_notify_state_conflicted); + + if (!conflict_props) + conflict_props = apr_hash_make(scratch_pool); + + svn_hash_sets(conflict_props, propname, ""); + } + + } /* foreach propchange ... */ + svn_pool_destroy(iterpool); + + /* Finished applying all incoming propchanges to our hashes! */ + + if (conflict_props != NULL) + { + /* Ok, we got some conflict. Lets store all the property knowledge we + have for resolving later */ + + if (!*conflict_skel) + *conflict_skel = svn_wc__conflict_skel_create(result_pool); + + SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(*conflict_skel, + db, local_abspath, + NULL /* reject_path */, + actual_props, + server_baseprops, + their_props, + conflict_props, + result_pool, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +/* Set a single 'wcprop' NAME to VALUE for versioned object LOCAL_ABSPATH. + If VALUE is null, remove property NAME. */ +static svn_error_t * +wcprop_set(svn_wc__db_t *db, + const char *local_abspath, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool) +{ + apr_hash_t *prophash; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* Note: this is not well-transacted. But... meh. This is merely a cache, + and if two processes are trying to modify this one entry at the same + time, then fine: we can let one be a winner, and one a loser. Of course, + if there are *other* state changes afoot, then the lack of a txn could + be a real issue, but we cannot solve that here. */ + + SVN_ERR(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath, + scratch_pool, scratch_pool)); + + if (prophash == NULL) + prophash = apr_hash_make(scratch_pool); + + svn_hash_sets(prophash, name, value); + return svn_error_trace(svn_wc__db_base_set_dav_cache(db, local_abspath, + prophash, + scratch_pool)); +} + + +svn_error_t * +svn_wc__get_actual_props(apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(props != NULL); + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* ### perform some state checking. for example, locally-deleted nodes + ### should not have any ACTUAL props. */ + + return svn_error_trace(svn_wc__db_read_props(props, db, local_abspath, + result_pool, scratch_pool)); +} + + +svn_error_t * +svn_wc_prop_list2(apr_hash_t **props, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__get_actual_props(props, + wc_ctx->db, + local_abspath, + result_pool, + scratch_pool)); +} + +struct propname_filter_baton_t { + svn_wc__proplist_receiver_t receiver_func; + void *receiver_baton; + const char *propname; +}; + +static svn_error_t * +propname_filter_receiver(void *baton, + const char *local_abspath, + apr_hash_t *props, + apr_pool_t *scratch_pool) +{ + struct propname_filter_baton_t *pfb = baton; + const svn_string_t *propval = svn_hash_gets(props, pfb->propname); + + if (propval) + { + props = apr_hash_make(scratch_pool); + svn_hash_sets(props, pfb->propname, propval); + + SVN_ERR(pfb->receiver_func(pfb->receiver_baton, local_abspath, props, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__prop_list_recursive(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *propname, + svn_depth_t depth, + svn_boolean_t pristine, + const apr_array_header_t *changelists, + svn_wc__proplist_receiver_t receiver_func, + void *receiver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__proplist_receiver_t receiver = receiver_func; + void *baton = receiver_baton; + struct propname_filter_baton_t pfb; + + pfb.receiver_func = receiver_func; + pfb.receiver_baton = receiver_baton; + pfb.propname = propname; + + SVN_ERR_ASSERT(receiver_func); + + if (propname) + { + baton = &pfb; + receiver = propname_filter_receiver; + } + + switch (depth) + { + case svn_depth_empty: + { + apr_hash_t *props; + apr_hash_t *changelist_hash = NULL; + + if (changelists && changelists->nelts) + SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, + changelists, scratch_pool)); + + if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath, + changelist_hash, scratch_pool)) + break; + + if (pristine) + SVN_ERR(svn_wc__db_read_pristine_props(&props, wc_ctx->db, + local_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(svn_wc__db_read_props(&props, wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + if (props && apr_hash_count(props) > 0) + SVN_ERR(receiver(baton, local_abspath, props, scratch_pool)); + } + break; + case svn_depth_files: + case svn_depth_immediates: + case svn_depth_infinity: + { + SVN_ERR(svn_wc__db_read_props_streamily(wc_ctx->db, local_abspath, + depth, pristine, + changelists, receiver, baton, + cancel_func, cancel_baton, + scratch_pool)); + } + break; + default: + SVN_ERR_MALFUNCTION(); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__prop_retrieve_recursive(apr_hash_t **values, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__db_prop_retrieve_recursive(values, + wc_ctx->db, + local_abspath, + propname, + result_pool, scratch_pool)); +} + +svn_error_t * +svn_wc_get_pristine_props(apr_hash_t **props, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(props != NULL); + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* Certain node stats do not have properties defined on them. Check the + state, and return NULL for these situations. */ + + err = svn_wc__db_read_pristine_props(props, wc_ctx->db, local_abspath, + result_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + + svn_error_clear(err); + + /* Documented behavior is to set *PROPS to NULL */ + *props = NULL; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_prop_get2(const svn_string_t **value, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *name, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + enum svn_prop_kind kind = svn_property_kind2(name); + svn_error_t *err; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (kind == svn_prop_entry_kind) + { + /* we don't do entry properties here */ + return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL, + _("Property '%s' is an entry property"), name); + } + + err = svn_wc__internal_propget(value, wc_ctx->db, local_abspath, name, + result_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + + svn_error_clear(err); + /* Documented behavior is to set *VALUE to NULL */ + *value = NULL; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__internal_propget(const svn_string_t **value, + svn_wc__db_t *db, + const char *local_abspath, + const char *name, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *prophash = NULL; + enum svn_prop_kind kind = svn_property_kind2(name); + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(kind != svn_prop_entry_kind); + + if (kind == svn_prop_wc_kind) + { + SVN_ERR_W(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath, + result_pool, scratch_pool), + _("Failed to load properties")); + } + else + { + /* regular prop */ + SVN_ERR_W(svn_wc__get_actual_props(&prophash, db, local_abspath, + result_pool, scratch_pool), + _("Failed to load properties")); + } + + if (prophash) + *value = svn_hash_gets(prophash, name); + else + *value = NULL; + + return SVN_NO_ERROR; +} + + +/* The special Subversion properties are not valid for all node kinds. + Return an error if NAME is an invalid Subversion property for PATH which + is of kind NODE_KIND. NAME must be in the "svn:" name space. + + Note that we only disallow the property if we're sure it's one that + already has a meaning for a different node kind. We don't disallow + setting an *unknown* svn: prop here, at this level; a higher level + should disallow that if desired. + */ +static svn_error_t * +validate_prop_against_node_kind(const char *name, + const char *path, + svn_node_kind_t node_kind, + apr_pool_t *pool) +{ + const char *path_display + = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool); + + switch (node_kind) + { + case svn_node_dir: + if (! svn_prop_is_known_svn_dir_prop(name) + && svn_prop_is_known_svn_file_prop(name)) + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Cannot set '%s' on a directory ('%s')"), + name, path_display); + break; + case svn_node_file: + if (! svn_prop_is_known_svn_file_prop(name) + && svn_prop_is_known_svn_dir_prop(name)) + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Cannot set '%s' on a file ('%s')"), + name, + path_display); + break; + default: + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("'%s' is not a file or directory"), + path_display); + } + + return SVN_NO_ERROR; +} + + +struct getter_baton { + const svn_string_t *mime_type; + const char *local_abspath; +}; + + +/* Provide the MIME_TYPE and/or push the content to STREAM for the file + * referenced by (getter_baton *) BATON. + * + * Implements svn_wc_canonicalize_svn_prop_get_file_t. */ +static svn_error_t * +get_file_for_validation(const svn_string_t **mime_type, + svn_stream_t *stream, + void *baton, + apr_pool_t *pool) +{ + struct getter_baton *gb = baton; + + if (mime_type) + *mime_type = gb->mime_type; + + if (stream) + { + svn_stream_t *read_stream; + + /* Copy the text of GB->LOCAL_ABSPATH into STREAM. */ + SVN_ERR(svn_stream_open_readonly(&read_stream, gb->local_abspath, + pool, pool)); + SVN_ERR(svn_stream_copy3(read_stream, svn_stream_disown(stream, pool), + NULL, NULL, pool)); + } + + return SVN_NO_ERROR; +} + + +/* Validate that a file has a 'non-binary' MIME type and contains + * self-consistent line endings. If not, then return an error. + * + * Call GETTER (which must not be NULL) with GETTER_BATON to get the + * file's MIME type and/or content. If the MIME type is non-null and + * is categorized as 'binary' then return an error and do not request + * the file content. + * + * Use PATH (a local path or a URL) only for error messages. + */ +static svn_error_t * +validate_eol_prop_against_file(const char *path, + svn_wc_canonicalize_svn_prop_get_file_t getter, + void *getter_baton, + apr_pool_t *pool) +{ + svn_stream_t *translating_stream; + svn_error_t *err; + const svn_string_t *mime_type; + const char *path_display + = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool); + + /* First just ask the "getter" for the MIME type. */ + SVN_ERR(getter(&mime_type, NULL, getter_baton, pool)); + + /* See if this file has been determined to be binary. */ + if (mime_type && svn_mime_type_is_binary(mime_type->data)) + return svn_error_createf + (SVN_ERR_ILLEGAL_TARGET, NULL, + _("Can't set '%s': " + "file '%s' has binary mime type property"), + SVN_PROP_EOL_STYLE, path_display); + + /* Now ask the getter for the contents of the file; this will do a + newline translation. All we really care about here is whether or + not the function fails on inconsistent line endings. The + function is "translating" to an empty stream. This is + sneeeeeeeeeeeaky. */ + translating_stream = svn_subst_stream_translated(svn_stream_empty(pool), + "", FALSE, NULL, FALSE, + pool); + + err = getter(NULL, translating_stream, getter_baton, pool); + + err = svn_error_compose_create(err, svn_stream_close(translating_stream)); + + if (err && err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL) + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, err, + _("File '%s' has inconsistent newlines"), + path_display); + + return svn_error_trace(err); +} + +static svn_error_t * +do_propset(svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + const char *name, + const svn_string_t *value, + svn_boolean_t skip_checks, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + apr_hash_t *prophash; + svn_wc_notify_action_t notify_action; + svn_skel_t *work_item = NULL; + svn_boolean_t clear_recorded_info = FALSE; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR_W(svn_wc__db_read_props(&prophash, db, local_abspath, + scratch_pool, scratch_pool), + _("Failed to load current properties")); + + /* Setting an inappropriate property is not allowed (unless + overridden by 'skip_checks', in some circumstances). Deleting an + inappropriate property is allowed, however, since older clients + allowed (and other clients possibly still allow) setting it in + the first place. */ + if (value && svn_prop_is_svn_prop(name)) + { + const svn_string_t *new_value; + struct getter_baton gb; + + gb.mime_type = svn_hash_gets(prophash, SVN_PROP_MIME_TYPE); + gb.local_abspath = local_abspath; + + SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, name, value, + local_abspath, kind, + skip_checks, + get_file_for_validation, &gb, + scratch_pool)); + value = new_value; + } + + if (kind == svn_node_file + && (strcmp(name, SVN_PROP_EXECUTABLE) == 0 + || strcmp(name, SVN_PROP_NEEDS_LOCK) == 0)) + { + SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath, + scratch_pool, scratch_pool)); + } + + /* If we're changing this file's list of expanded keywords, then + * we'll need to invalidate its text timestamp, since keyword + * expansion affects the comparison of working file to text base. + * + * Here we retrieve the old list of expanded keywords; after the + * property is set, we'll grab the new list and see if it differs + * from the old one. + */ + if (kind == svn_node_file && strcmp(name, SVN_PROP_KEYWORDS) == 0) + { + svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_KEYWORDS); + apr_hash_t *old_keywords, *new_keywords; + + if (old_value) + SVN_ERR(svn_wc__expand_keywords(&old_keywords, + db, local_abspath, NULL, + old_value->data, TRUE, + scratch_pool, scratch_pool)); + else + old_keywords = apr_hash_make(scratch_pool); + + if (value) + SVN_ERR(svn_wc__expand_keywords(&new_keywords, + db, local_abspath, NULL, + value->data, TRUE, + scratch_pool, scratch_pool)); + else + new_keywords = apr_hash_make(scratch_pool); + + if (svn_subst_keywords_differ2(old_keywords, new_keywords, FALSE, + scratch_pool)) + { + /* If the keywords have changed, then the translation of the file + may be different. We should invalidate the RECORDED_SIZE + and RECORDED_TIME on this node. + + Note that we don't immediately re-translate the file. But a + "has it changed?" check in the future will do a translation + from the pristine, and it will want to compare the (new) + resulting RECORDED_SIZE against the working copy file. + + Also, when this file is (de)translated with the new keywords, + then it could be different, relative to the pristine. We want + to ensure the RECORDED_TIME is different, to indicate that + a full detranslate/compare is performed. */ + clear_recorded_info = TRUE; + } + } + else if (kind == svn_node_file && strcmp(name, SVN_PROP_EOL_STYLE) == 0) + { + svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_EOL_STYLE); + + if (((value == NULL) != (old_value == NULL)) + || (value && ! svn_string_compare(value, old_value))) + { + clear_recorded_info = TRUE; + } + } + + /* Find out what type of property change we are doing: add, modify, or + delete. */ + if (svn_hash_gets(prophash, name) == NULL) + { + if (value == NULL) + /* Deleting a non-existent property. */ + notify_action = svn_wc_notify_property_deleted_nonexistent; + else + /* Adding a property. */ + notify_action = svn_wc_notify_property_added; + } + else + { + if (value == NULL) + /* Deleting the property. */ + notify_action = svn_wc_notify_property_deleted; + else + /* Modifying property. */ + notify_action = svn_wc_notify_property_modified; + } + + /* Now we have all the properties in our hash. Simply merge the new + property into it. */ + svn_hash_sets(prophash, name, value); + + /* Drop it right into the db.. */ + SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, prophash, + clear_recorded_info, NULL, work_item, + scratch_pool)); + + /* Run our workqueue item for sync'ing flags with props. */ + if (work_item) + SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool)); + + if (notify_func) + { + svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, + notify_action, + scratch_pool); + notify->prop_name = name; + notify->kind = kind; + + (*notify_func)(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; +} + +/* A baton for propset_walk_cb. */ +struct propset_walk_baton +{ + const char *propname; /* The name of the property to set. */ + const svn_string_t *propval; /* The value to set. */ + svn_wc__db_t *db; /* Database for the tree being walked. */ + svn_boolean_t force; /* True iff force was passed. */ + svn_wc_notify_func2_t notify_func; + void *notify_baton; +}; + +/* An node-walk callback for svn_wc_prop_set4(). + * + * For LOCAL_ABSPATH, set the property named wb->PROPNAME to the value + * wb->PROPVAL, where "wb" is the WALK_BATON of type "struct + * propset_walk_baton *". + */ +static svn_error_t * +propset_walk_cb(const char *local_abspath, + svn_node_kind_t kind, + void *walk_baton, + apr_pool_t *scratch_pool) +{ + struct propset_walk_baton *wb = walk_baton; + svn_error_t *err; + + err = do_propset(wb->db, local_abspath, kind, wb->propname, wb->propval, + wb->force, wb->notify_func, wb->notify_baton, scratch_pool); + if (err && (err->apr_err == SVN_ERR_ILLEGAL_TARGET + || err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS)) + { + svn_error_clear(err); + err = SVN_NO_ERROR; + } + + return svn_error_trace(err); +} + +svn_error_t * +svn_wc_prop_set4(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *name, + const svn_string_t *value, + svn_depth_t depth, + svn_boolean_t skip_checks, + const apr_array_header_t *changelist_filter, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + enum svn_prop_kind prop_kind = svn_property_kind2(name); + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_wc__db_t *db = wc_ctx->db; + + /* we don't do entry properties here */ + if (prop_kind == svn_prop_entry_kind) + return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL, + _("Property '%s' is an entry property"), name); + + /* Check to see if we're setting the dav cache. */ + if (prop_kind == svn_prop_wc_kind) + { + SVN_ERR_ASSERT(depth == svn_depth_empty); + return svn_error_trace(wcprop_set(wc_ctx->db, local_abspath, + name, value, scratch_pool)); + } + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + if (status != svn_wc__db_status_normal + && status != svn_wc__db_status_added + && status != svn_wc__db_status_incomplete) + { + return svn_error_createf(SVN_ERR_WC_INVALID_SCHEDULE, NULL, + _("Can't set properties on '%s':" + " invalid status for updating properties."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + /* We have to do this little DIR_ABSPATH dance for backwards compat. + But from 1.7 onwards, all locks are of infinite depth, and from 1.6 + backward we never call this API with depth > empty, so we only need + to do the write check once per call, here (and not for every node in + the node walker). + + ### Note that we could check for a write lock on local_abspath first + ### if we would want to. And then justy check for kind if that fails. + ### ... but we need kind for the "svn:" property checks anyway */ + { + const char *dir_abspath; + + if (kind == svn_node_dir) + dir_abspath = local_abspath; + else + dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + /* Verify that we're holding this directory's write lock. */ + SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool)); + } + + if (depth == svn_depth_empty || kind != svn_node_dir) + { + apr_hash_t *changelist_hash = NULL; + + if (changelist_filter && changelist_filter->nelts) + SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, + scratch_pool)); + + if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath, + changelist_hash, scratch_pool)) + return SVN_NO_ERROR; + + SVN_ERR(do_propset(wc_ctx->db, local_abspath, + kind == svn_node_dir + ? svn_node_dir + : svn_node_file, + name, value, skip_checks, + notify_func, notify_baton, scratch_pool)); + + } + else + { + struct propset_walk_baton wb; + + wb.propname = name; + wb.propval = value; + wb.db = wc_ctx->db; + wb.force = skip_checks; + wb.notify_func = notify_func; + wb.notify_baton = notify_baton; + + SVN_ERR(svn_wc__internal_walk_children(wc_ctx->db, local_abspath, + FALSE, changelist_filter, + propset_walk_cb, &wb, + depth, + cancel_func, cancel_baton, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* Check that NAME names a regular prop. Return an error if it names an + * entry prop or a WC prop. */ +static svn_error_t * +ensure_prop_is_regular_kind(const char *name) +{ + enum svn_prop_kind prop_kind = svn_property_kind2(name); + + /* we don't do entry properties here */ + if (prop_kind == svn_prop_entry_kind) + return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL, + _("Property '%s' is an entry property"), name); + + /* Check to see if we're setting the dav cache. */ + if (prop_kind == svn_prop_wc_kind) + return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL, + _("Property '%s' is a WC property, not " + "a regular property"), name); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__canonicalize_props(apr_hash_t **prepared_props, + const char *local_abspath, + svn_node_kind_t node_kind, + const apr_hash_t *props, + svn_boolean_t skip_some_checks, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const svn_string_t *mime_type; + struct getter_baton gb; + apr_hash_index_t *hi; + + /* While we allocate new parts of *PREPARED_PROPS in RESULT_POOL, we + don't promise to deep-copy the unchanged keys and values. */ + *prepared_props = apr_hash_make(result_pool); + + /* Before we can canonicalize svn:eol-style we need to know svn:mime-type, + * so process that first. */ + mime_type = svn_hash_gets((apr_hash_t *)props, SVN_PROP_MIME_TYPE); + if (mime_type) + { + SVN_ERR(svn_wc_canonicalize_svn_prop( + &mime_type, SVN_PROP_MIME_TYPE, mime_type, + local_abspath, node_kind, skip_some_checks, + NULL, NULL, scratch_pool)); + svn_hash_sets(*prepared_props, SVN_PROP_MIME_TYPE, mime_type); + } + + /* Set up the context for canonicalizing the other properties. */ + gb.mime_type = mime_type; + gb.local_abspath = local_abspath; + + /* Check and canonicalize the other properties. */ + for (hi = apr_hash_first(scratch_pool, (apr_hash_t *)props); hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + const svn_string_t *value = svn__apr_hash_index_val(hi); + + if (strcmp(name, SVN_PROP_MIME_TYPE) == 0) + continue; + + SVN_ERR(ensure_prop_is_regular_kind(name)); + SVN_ERR(svn_wc_canonicalize_svn_prop( + &value, name, value, + local_abspath, node_kind, skip_some_checks, + get_file_for_validation, &gb, scratch_pool)); + svn_hash_sets(*prepared_props, name, value); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p, + const char *propname, + const svn_string_t *propval, + const char *path, + svn_node_kind_t kind, + svn_boolean_t skip_some_checks, + svn_wc_canonicalize_svn_prop_get_file_t getter, + void *getter_baton, + apr_pool_t *pool) +{ + svn_stringbuf_t *new_value = NULL; + + /* Keep this static, it may get stored (for read-only purposes) in a + hash that outlives this function. */ + static const svn_string_t boolean_value = + { + SVN_PROP_BOOLEAN_TRUE, + sizeof(SVN_PROP_BOOLEAN_TRUE) - 1 + }; + + SVN_ERR(validate_prop_against_node_kind(propname, path, kind, pool)); + + /* This code may place the new prop val in either NEW_VALUE or PROPVAL. */ + if (!skip_some_checks && (strcmp(propname, SVN_PROP_EOL_STYLE) == 0)) + { + svn_subst_eol_style_t eol_style; + const char *ignored_eol; + new_value = svn_stringbuf_create_from_string(propval, pool); + svn_stringbuf_strip_whitespace(new_value); + svn_subst_eol_style_from_value(&eol_style, &ignored_eol, new_value->data); + if (eol_style == svn_subst_eol_style_unknown) + return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL, + _("Unrecognized line ending style '%s' for '%s'"), + new_value->data, + svn_dirent_local_style(path, pool)); + SVN_ERR(validate_eol_prop_against_file(path, getter, getter_baton, + pool)); + } + else if (!skip_some_checks && (strcmp(propname, SVN_PROP_MIME_TYPE) == 0)) + { + new_value = svn_stringbuf_create_from_string(propval, pool); + svn_stringbuf_strip_whitespace(new_value); + SVN_ERR(svn_mime_type_validate(new_value->data, pool)); + } + else if (strcmp(propname, SVN_PROP_IGNORE) == 0 + || strcmp(propname, SVN_PROP_EXTERNALS) == 0 + || strcmp(propname, SVN_PROP_INHERITABLE_IGNORES) == 0 + || strcmp(propname, SVN_PROP_INHERITABLE_AUTO_PROPS) == 0) + { + /* Make sure that the last line ends in a newline */ + if (propval->len == 0 + || propval->data[propval->len - 1] != '\n') + { + new_value = svn_stringbuf_create_from_string(propval, pool); + svn_stringbuf_appendbyte(new_value, '\n'); + } + + /* Make sure this is a valid externals property. Do not + allow 'skip_some_checks' to override, as there is no circumstance in + which this is proper (because there is no circumstance in + which Subversion can handle it). */ + if (strcmp(propname, SVN_PROP_EXTERNALS) == 0) + { + /* We don't allow "." nor ".." as target directories in + an svn:externals line. As it happens, our parse code + checks for this, so all we have to is invoke it -- + we're not interested in the parsed result, only in + whether or not the parsing errored. */ + apr_array_header_t *externals = NULL; + apr_array_header_t *duplicate_targets = NULL; + SVN_ERR(svn_wc_parse_externals_description3(&externals, path, + propval->data, FALSE, + /*scratch_*/pool)); + SVN_ERR(svn_wc__externals_find_target_dups(&duplicate_targets, + externals, + /*scratch_*/pool, + /*scratch_*/pool)); + if (duplicate_targets && duplicate_targets->nelts > 0) + { + const char *more_str = ""; + if (duplicate_targets->nelts > 1) + { + more_str = apr_psprintf(/*scratch_*/pool, + _(" (%d more duplicate targets found)"), + duplicate_targets->nelts - 1); + } + return svn_error_createf( + SVN_ERR_WC_DUPLICATE_EXTERNALS_TARGET, NULL, + _("Invalid %s property on '%s': " + "target '%s' appears more than once%s"), + SVN_PROP_EXTERNALS, + svn_dirent_local_style(path, pool), + APR_ARRAY_IDX(duplicate_targets, 0, const char*), + more_str); + } + } + } + else if (strcmp(propname, SVN_PROP_KEYWORDS) == 0) + { + new_value = svn_stringbuf_create_from_string(propval, pool); + svn_stringbuf_strip_whitespace(new_value); + } + else if (svn_prop_is_boolean(propname)) + { + /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */ + propval = &boolean_value; + } + else if (strcmp(propname, SVN_PROP_MERGEINFO) == 0) + { + apr_hash_t *mergeinfo; + svn_string_t *new_value_str; + + SVN_ERR(svn_mergeinfo_parse(&mergeinfo, propval->data, pool)); + + /* Non-inheritable mergeinfo is only valid on directories. */ + if (kind != svn_node_dir + && svn_mergeinfo__is_noninheritable(mergeinfo, pool)) + return svn_error_createf( + SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Cannot set non-inheritable mergeinfo on a non-directory ('%s')"), + svn_dirent_local_style(path, pool)); + + SVN_ERR(svn_mergeinfo_to_string(&new_value_str, mergeinfo, pool)); + propval = new_value_str; + } + + if (new_value) + *propval_p = svn_stringbuf__morph_into_string(new_value); + else + *propval_p = propval; + + return SVN_NO_ERROR; +} + + +svn_boolean_t +svn_wc_is_normal_prop(const char *name) +{ + enum svn_prop_kind kind = svn_property_kind2(name); + return (kind == svn_prop_regular_kind); +} + + +svn_boolean_t +svn_wc_is_wc_prop(const char *name) +{ + enum svn_prop_kind kind = svn_property_kind2(name); + return (kind == svn_prop_wc_kind); +} + + +svn_boolean_t +svn_wc_is_entry_prop(const char *name) +{ + enum svn_prop_kind kind = svn_property_kind2(name); + return (kind == svn_prop_entry_kind); +} + + +svn_error_t * +svn_wc__props_modified(svn_boolean_t *modified_p, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, modified_p, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_props_modified_p2(svn_boolean_t *modified_p, + svn_wc_context_t* wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__props_modified(modified_p, + wc_ctx->db, + local_abspath, + scratch_pool)); +} + +svn_error_t * +svn_wc__internal_propdiff(apr_array_header_t **propchanges, + apr_hash_t **original_props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *baseprops; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* ### if pristines are not defined, then should this raise an error, + ### or use an empty set? */ + SVN_ERR(svn_wc__db_read_pristine_props(&baseprops, db, local_abspath, + result_pool, scratch_pool)); + + if (original_props != NULL) + *original_props = baseprops; + + if (propchanges != NULL) + { + apr_hash_t *actual_props; + + /* Some nodes do not have pristine props, so let's just use an empty + set here. Thus, any ACTUAL props are additions. */ + if (baseprops == NULL) + baseprops = apr_hash_make(scratch_pool); + + SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath, + result_pool, scratch_pool)); + /* ### be wary. certain nodes don't have ACTUAL props either. we + ### may want to raise an error. or maybe that is a deletion of + ### any potential pristine props? */ + + SVN_ERR(svn_prop_diffs(propchanges, actual_props, baseprops, + result_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_get_prop_diffs2(apr_array_header_t **propchanges, + apr_hash_t **original_props, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__internal_propdiff(propchanges, + original_props, wc_ctx->db, local_abspath, + result_pool, scratch_pool)); +} + +svn_boolean_t +svn_wc__has_magic_property(const apr_array_header_t *properties) +{ + int i; + + for (i = 0; i < properties->nelts; i++) + { + const svn_prop_t *property = &APR_ARRAY_IDX(properties, i, svn_prop_t); + + if (strcmp(property->name, SVN_PROP_EXECUTABLE) == 0 + || strcmp(property->name, SVN_PROP_KEYWORDS) == 0 + || strcmp(property->name, SVN_PROP_EOL_STYLE) == 0 + || strcmp(property->name, SVN_PROP_SPECIAL) == 0 + || strcmp(property->name, SVN_PROP_NEEDS_LOCK) == 0) + return TRUE; + } + return FALSE; +} + +svn_error_t * +svn_wc__get_iprops(apr_array_header_t **inherited_props, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__db_read_inherited_props(inherited_props, NULL, + wc_ctx->db, local_abspath, + propname, + result_pool, scratch_pool)); +} + +svn_error_t * +svn_wc__get_cached_iprop_children(apr_hash_t **iprop_paths, + svn_depth_t depth, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_wc__db_get_children_with_cached_iprops(iprop_paths, + depth, + local_abspath, + wc_ctx->db, + result_pool, + scratch_pool)); + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/props.h b/subversion/libsvn_wc/props.h new file mode 100644 index 000000000000..c648e3c42ff9 --- /dev/null +++ b/subversion/libsvn_wc/props.h @@ -0,0 +1,154 @@ +/* + * props.h : properties + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#ifndef SVN_LIBSVN_WC_PROPS_H +#define SVN_LIBSVN_WC_PROPS_H + +#include <apr_pools.h> + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_props.h" + +#include "wc_db.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Internal function for diffing props. See svn_wc_get_prop_diffs2(). */ +svn_error_t * +svn_wc__internal_propdiff(apr_array_header_t **propchanges, + apr_hash_t **original_props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Internal function for fetching a property. See svn_wc_prop_get2(). */ +svn_error_t * +svn_wc__internal_propget(const svn_string_t **value, + svn_wc__db_t *db, + const char *local_abspath, + const char *name, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Validate and canonicalize the PROPS like svn_wc_prop_set4() does; + * see that function for details of the SKIP_SOME_CHECKS option. + * + * The properties are checked against the node at LOCAL_ABSPATH (which + * need not be under version control) of kind KIND. This text of this + * node may be read (if it is a file) in order to validate the + * svn:eol-style property. + * + * Only regular props are accepted; WC props and entry props raise an error + * (unlike svn_wc_prop_set4() which accepts WC props). + * + * Set *PREPARED_PROPS to the resulting canonicalized properties, + * allocating any new data in RESULT_POOL but making shallow copies of + * keys and unchanged values from PROPS. + */ +svn_error_t * +svn_wc__canonicalize_props(apr_hash_t **prepared_props, + const char *local_abspath, + svn_node_kind_t node_kind, + const apr_hash_t *props, + svn_boolean_t skip_some_checks, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Given LOCAL_ABSPATH/DB and an array of PROPCHANGES based on + SERVER_BASEPROPS, calculate what changes should be applied to the working + copy. + + We return the new property collections to the caller, so the caller + can combine the property update with other operations. + + If SERVER_BASEPROPS is NULL then use the pristine props as PROPCHANGES + base. + + Return the new set of actual properties in *NEW_ACTUAL_PROPS. + + Append any conflicts of the actual props to *CONFLICT_SKEL. (First + allocate *CONFLICT_SKEL from RESULT_POOL if it is initially NULL. + CONFLICT_SKEL itself must not be NULL.) + + If STATE is non-null, set *STATE to the state of the local properties + after the merge, one of: + + svn_wc_notify_state_unchanged + svn_wc_notify_state_changed + svn_wc_notify_state_merged + svn_wc_notify_state_conflicted + */ +svn_error_t * +svn_wc__merge_props(svn_skel_t **conflict_skel, + svn_wc_notify_state_t *state, + apr_hash_t **new_actual_props, + svn_wc__db_t *db, + const char *local_abspath, + /*const*/ apr_hash_t *server_baseprops, + /*const*/ apr_hash_t *pristine_props, + /*const*/ apr_hash_t *actual_props, + const apr_array_header_t *propchanges, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Given PROPERTIES is array of @c svn_prop_t structures. Returns TRUE if any + of the PROPERTIES are the known "magic" ones that might require + changing the working file. */ +svn_boolean_t svn_wc__has_magic_property(const apr_array_header_t *properties); + +/* Set *MODIFIED_P TRUE if the props for LOCAL_ABSPATH have been modified. */ +svn_error_t * +svn_wc__props_modified(svn_boolean_t *modified_p, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* Internal version of svn_wc_prop_list2(). */ +svn_error_t * +svn_wc__get_actual_props(apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +svn_error_t * +svn_wc__create_prejfile(const char **tmp_prejfile_abspath, + svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_WC_PROPS_H */ diff --git a/subversion/libsvn_wc/questions.c b/subversion/libsvn_wc/questions.c new file mode 100644 index 000000000000..c2a42b6ad935 --- /dev/null +++ b/subversion/libsvn_wc/questions.c @@ -0,0 +1,621 @@ +/* + * questions.c: routines for asking questions about working copies + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <string.h> + +#include <apr_pools.h> +#include <apr_file_io.h> +#include <apr_file_info.h> +#include <apr_time.h> + +#include "svn_pools.h" +#include "svn_types.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_time.h" +#include "svn_io.h" +#include "svn_props.h" + +#include "wc.h" +#include "conflicts.h" +#include "translate.h" +#include "wc_db.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" + + + +/*** svn_wc_text_modified_p ***/ + +/* svn_wc_text_modified_p answers the question: + + "Are the contents of F different than the contents of + .svn/text-base/F.svn-base or .svn/tmp/text-base/F.svn-base?" + + In the first case, we're looking to see if a user has made local + modifications to a file since the last update or commit. In the + second, the file may not be versioned yet (it doesn't exist in + entries). Support for the latter case came about to facilitate + forced checkouts, updates, and switches, where an unversioned file + may obstruct a file about to be added. + + Note: Assuming that F lives in a directory D at revision V, please + notice that we are *NOT* answering the question, "are the contents + of F different than revision V of F?" While F may be at a different + revision number than its parent directory, but we're only looking + for local edits on F, not for consistent directory revisions. + + TODO: the logic of the routines on this page might change in the + future, as they bear some relation to the user interface. For + example, if a file is removed -- without telling subversion about + it -- how should subversion react? Should it copy the file back + out of text-base? Should it ask whether one meant to officially + mark it for removal? +*/ + + +/* Set *MODIFIED_P to TRUE if (after translation) VERSIONED_FILE_ABSPATH + * (of VERSIONED_FILE_SIZE bytes) differs from PRISTINE_STREAM (of + * PRISTINE_SIZE bytes), else to FALSE if not. + * + * If EXACT_COMPARISON is FALSE, translate VERSIONED_FILE_ABSPATH's EOL + * style and keywords to repository-normal form according to its properties, + * and compare the result with PRISTINE_STREAM. If EXACT_COMPARISON is + * TRUE, translate PRISTINE_STREAM's EOL style and keywords to working-copy + * form according to VERSIONED_FILE_ABSPATH's properties, and compare the + * result with VERSIONED_FILE_ABSPATH. + * + * HAS_PROPS should be TRUE if the file had properties when it was not + * modified, otherwise FALSE. + * + * PROPS_MOD should be TRUE if the file's properties have been changed, + * otherwise FALSE. + * + * PRISTINE_STREAM will be closed before a successful return. + * + * DB is a wc_db; use SCRATCH_POOL for temporary allocation. + */ +static svn_error_t * +compare_and_verify(svn_boolean_t *modified_p, + svn_wc__db_t *db, + const char *versioned_file_abspath, + svn_filesize_t versioned_file_size, + svn_stream_t *pristine_stream, + svn_filesize_t pristine_size, + svn_boolean_t has_props, + svn_boolean_t props_mod, + svn_boolean_t exact_comparison, + apr_pool_t *scratch_pool) +{ + svn_boolean_t same; + svn_subst_eol_style_t eol_style; + const char *eol_str; + apr_hash_t *keywords; + svn_boolean_t special = FALSE; + svn_boolean_t need_translation; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_file_abspath)); + + if (props_mod) + has_props = TRUE; /* Maybe it didn't have properties; but it has now */ + + if (has_props) + { + SVN_ERR(svn_wc__get_translate_info(&eol_style, &eol_str, + &keywords, + &special, + db, versioned_file_abspath, NULL, + !exact_comparison, + scratch_pool, scratch_pool)); + + need_translation = svn_subst_translation_required(eol_style, eol_str, + keywords, special, + TRUE); + } + else + need_translation = FALSE; + + if (! need_translation + && (versioned_file_size != pristine_size)) + { + *modified_p = TRUE; + + /* ### Why did we open the pristine? */ + return svn_error_trace(svn_stream_close(pristine_stream)); + } + + /* ### Other checks possible? */ + + if (need_translation) + { + /* Reading files is necessary. */ + svn_stream_t *v_stream; /* versioned_file */ + + if (special) + { + SVN_ERR(svn_subst_read_specialfile(&v_stream, versioned_file_abspath, + scratch_pool, scratch_pool)); + } + else + { + SVN_ERR(svn_stream_open_readonly(&v_stream, versioned_file_abspath, + scratch_pool, scratch_pool)); + + if (!exact_comparison && need_translation) + { + if (eol_style == svn_subst_eol_style_native) + eol_str = SVN_SUBST_NATIVE_EOL_STR; + else if (eol_style != svn_subst_eol_style_fixed + && eol_style != svn_subst_eol_style_none) + return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, + svn_stream_close(v_stream), NULL); + + /* Wrap file stream to detranslate into normal form, + * "repairing" the EOL style if it is inconsistent. */ + v_stream = svn_subst_stream_translated(v_stream, + eol_str, + TRUE /* repair */, + keywords, + FALSE /* expand */, + scratch_pool); + } + else if (need_translation) + { + /* Wrap base stream to translate into working copy form, and + * arrange to throw an error if its EOL style is inconsistent. */ + pristine_stream = svn_subst_stream_translated(pristine_stream, + eol_str, FALSE, + keywords, TRUE, + scratch_pool); + } + } + + SVN_ERR(svn_stream_contents_same2(&same, pristine_stream, v_stream, + scratch_pool)); + } + else + { + /* Translation would be a no-op, so compare the original file. */ + svn_stream_t *v_stream; /* versioned_file */ + + SVN_ERR(svn_stream_open_readonly(&v_stream, versioned_file_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_stream_contents_same2(&same, pristine_stream, v_stream, + scratch_pool)); + } + + *modified_p = (! same); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__internal_file_modified_p(svn_boolean_t *modified_p, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t exact_comparison, + apr_pool_t *scratch_pool) +{ + svn_stream_t *pristine_stream; + svn_filesize_t pristine_size; + svn_wc__db_status_t status; + svn_node_kind_t kind; + const svn_checksum_t *checksum; + svn_filesize_t recorded_size; + apr_time_t recorded_mod_time; + svn_boolean_t has_props; + svn_boolean_t props_mod; + const svn_io_dirent2_t *dirent; + + /* Read the relevant info */ + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &checksum, NULL, NULL, NULL, + NULL, NULL, NULL, + &recorded_size, &recorded_mod_time, + NULL, NULL, NULL, &has_props, &props_mod, + NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + /* If we don't have a pristine or the node has a status that allows a + pristine, just say that the node is modified */ + if (!checksum + || (kind != svn_node_file) + || ((status != svn_wc__db_status_normal) + && (status != svn_wc__db_status_added))) + { + *modified_p = TRUE; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE, + scratch_pool, scratch_pool)); + + if (dirent->kind != svn_node_file) + { + /* There is no file on disk, so the text is missing, not modified. */ + *modified_p = FALSE; + return SVN_NO_ERROR; + } + + if (! exact_comparison) + { + /* We're allowed to use a heuristic to determine whether files may + have changed. The heuristic has these steps: + + 1. Compare the working file's size + with the size cached in the entries file + 2. If they differ, do a full file compare + 3. Compare the working file's timestamp + with the timestamp cached in the entries file + 4. If they differ, do a full file compare + 5. Otherwise, return indicating an unchanged file. + + There are 2 problematic situations which may occur: + + 1. The cached working size is missing + --> In this case, we forget we ever tried to compare + and skip to the timestamp comparison. This is + because old working copies do not contain cached sizes + + 2. The cached timestamp is missing + --> In this case, we forget we ever tried to compare + and skip to full file comparison. This is because + the timestamp will be removed when the library + updates a locally changed file. (ie, this only happens + when the file was locally modified.) + + */ + + /* Compare the sizes, if applicable */ + if (recorded_size != SVN_INVALID_FILESIZE + && dirent->filesize != recorded_size) + goto compare_them; + + /* Compare the timestamps + + Note: recorded_mod_time == 0 means not available, + which also means the timestamps won't be equal, + so there's no need to explicitly check the 'absent' value. */ + if (recorded_mod_time != dirent->mtime) + goto compare_them; + + *modified_p = FALSE; + return SVN_NO_ERROR; + } + + compare_them: + SVN_ERR(svn_wc__db_pristine_read(&pristine_stream, &pristine_size, + db, local_abspath, checksum, + scratch_pool, scratch_pool)); + + /* Check all bytes, and verify checksum if requested. */ + { + svn_error_t *err; + err = compare_and_verify(modified_p, db, + local_abspath, dirent->filesize, + pristine_stream, pristine_size, + has_props, props_mod, + exact_comparison, + scratch_pool); + + /* At this point we already opened the pristine file, so we know that + the access denied applies to the working copy path */ + if (err && APR_STATUS_IS_EACCES(err->apr_err)) + return svn_error_create(SVN_ERR_WC_PATH_ACCESS_DENIED, err, NULL); + else + SVN_ERR(err); + } + + if (!*modified_p) + { + svn_boolean_t own_lock; + + /* The timestamp is missing or "broken" so "repair" it if we can. */ + SVN_ERR(svn_wc__db_wclock_owns_lock(&own_lock, db, local_abspath, FALSE, + scratch_pool)); + if (own_lock) + SVN_ERR(svn_wc__db_global_record_fileinfo(db, local_abspath, + dirent->filesize, + dirent->mtime, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_text_modified_p2(svn_boolean_t *modified_p, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t unused, + apr_pool_t *scratch_pool) +{ + return svn_wc__internal_file_modified_p(modified_p, wc_ctx->db, + local_abspath, FALSE, scratch_pool); +} + + + +static svn_error_t * +internal_conflicted_p(svn_boolean_t *text_conflicted_p, + svn_boolean_t *prop_conflicted_p, + svn_boolean_t *tree_conflicted_p, + svn_boolean_t *ignore_move_edit_p, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t kind; + svn_skel_t *conflicts; + svn_boolean_t resolved_text = FALSE; + svn_boolean_t resolved_props = FALSE; + + SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + scratch_pool, scratch_pool)); + + if (!conflicts) + { + if (text_conflicted_p) + *text_conflicted_p = FALSE; + if (prop_conflicted_p) + *prop_conflicted_p = FALSE; + if (tree_conflicted_p) + *tree_conflicted_p = FALSE; + if (ignore_move_edit_p) + *ignore_move_edit_p = FALSE; + + return SVN_NO_ERROR; + } + + SVN_ERR(svn_wc__conflict_read_info(NULL, NULL, text_conflicted_p, + prop_conflicted_p, tree_conflicted_p, + db, local_abspath, conflicts, + scratch_pool, scratch_pool)); + + if (text_conflicted_p && *text_conflicted_p) + { + const char *mine_abspath; + const char *their_old_abspath; + const char *their_abspath; + svn_boolean_t done = FALSE; + + /* Look for any text conflict, exercising only as much effort as + necessary to obtain a definitive answer. This only applies to + files, but we don't have to explicitly check that entry is a + file, since these attributes would never be set on a directory + anyway. A conflict file entry notation only counts if the + conflict file still exists on disk. */ + + SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath, + &their_old_abspath, + &their_abspath, + db, local_abspath, conflicts, + scratch_pool, scratch_pool)); + + if (mine_abspath) + { + SVN_ERR(svn_io_check_path(mine_abspath, &kind, scratch_pool)); + + *text_conflicted_p = (kind == svn_node_file); + + if (*text_conflicted_p) + done = TRUE; + } + + if (!done && their_abspath) + { + SVN_ERR(svn_io_check_path(their_abspath, &kind, scratch_pool)); + + *text_conflicted_p = (kind == svn_node_file); + + if (*text_conflicted_p) + done = TRUE; + } + + if (!done && their_old_abspath) + { + SVN_ERR(svn_io_check_path(their_old_abspath, &kind, scratch_pool)); + + *text_conflicted_p = (kind == svn_node_file); + + if (*text_conflicted_p) + done = TRUE; + } + + if (!done && (mine_abspath || their_abspath || their_old_abspath)) + resolved_text = TRUE; /* Remove in-db conflict marker */ + } + + if (prop_conflicted_p && *prop_conflicted_p) + { + const char *prej_abspath; + + SVN_ERR(svn_wc__conflict_read_prop_conflict(&prej_abspath, + NULL, NULL, NULL, NULL, + db, local_abspath, conflicts, + scratch_pool, scratch_pool)); + + if (prej_abspath) + { + SVN_ERR(svn_io_check_path(prej_abspath, &kind, scratch_pool)); + + *prop_conflicted_p = (kind == svn_node_file); + + if (! *prop_conflicted_p) + resolved_props = TRUE; /* Remove in-db conflict marker */ + } + } + + if (ignore_move_edit_p) + { + *ignore_move_edit_p = FALSE; + if (tree_conflicted_p && *tree_conflicted_p) + { + svn_wc_conflict_reason_t reason; + svn_wc_conflict_action_t action; + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, NULL, + db, local_abspath, + conflicts, + scratch_pool, + scratch_pool)); + + if (reason == svn_wc_conflict_reason_moved_away + && action == svn_wc_conflict_action_edit) + { + *tree_conflicted_p = FALSE; + *ignore_move_edit_p = TRUE; + } + } + } + + if (resolved_text || resolved_props) + { + svn_boolean_t own_lock; + + /* The marker files are missing, so "repair" wc.db if we can */ + SVN_ERR(svn_wc__db_wclock_owns_lock(&own_lock, db, local_abspath, FALSE, + scratch_pool)); + if (own_lock) + SVN_ERR(svn_wc__db_op_mark_resolved(db, local_abspath, + resolved_text, + resolved_props, + FALSE /* resolved_tree */, + NULL /* work_items */, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__internal_conflicted_p(svn_boolean_t *text_conflicted_p, + svn_boolean_t *prop_conflicted_p, + svn_boolean_t *tree_conflicted_p, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + SVN_ERR(internal_conflicted_p(text_conflicted_p, prop_conflicted_p, + tree_conflicted_p, NULL, + db, local_abspath, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__conflicted_for_update_p(svn_boolean_t *conflicted_p, + svn_boolean_t *conflict_ignored_p, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t tree_only, + apr_pool_t *scratch_pool) +{ + svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted; + svn_boolean_t conflict_ignored; + + if (!conflict_ignored_p) + conflict_ignored_p = &conflict_ignored; + + SVN_ERR(internal_conflicted_p(tree_only ? NULL: &text_conflicted, + tree_only ? NULL: &prop_conflicted, + &tree_conflicted, conflict_ignored_p, + db, local_abspath, scratch_pool)); + if (tree_only) + *conflicted_p = tree_conflicted; + else + *conflicted_p = text_conflicted || prop_conflicted || tree_conflicted; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_conflicted_p3(svn_boolean_t *text_conflicted_p, + svn_boolean_t *prop_conflicted_p, + svn_boolean_t *tree_conflicted_p, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__internal_conflicted_p(text_conflicted_p, + prop_conflicted_p, + tree_conflicted_p, + wc_ctx->db, + local_abspath, + scratch_pool)); +} + +svn_error_t * +svn_wc__min_max_revisions(svn_revnum_t *min_revision, + svn_revnum_t *max_revision, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_boolean_t committed, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__db_min_max_revisions(min_revision, + max_revision, + wc_ctx->db, + local_abspath, + committed, + scratch_pool)); +} + + +svn_error_t * +svn_wc__has_switched_subtrees(svn_boolean_t *is_switched, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *trail_url, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__db_has_switched_subtrees(is_switched, + wc_ctx->db, + local_abspath, + trail_url, + scratch_pool)); +} + + +svn_error_t * +svn_wc__has_local_mods(svn_boolean_t *is_modified, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__db_has_local_mods(is_modified, + wc_ctx->db, + local_abspath, + cancel_func, + cancel_baton, + scratch_pool)); +} diff --git a/subversion/libsvn_wc/relocate.c b/subversion/libsvn_wc/relocate.c new file mode 100644 index 000000000000..4a9df678aaf1 --- /dev/null +++ b/subversion/libsvn_wc/relocate.c @@ -0,0 +1,170 @@ +/* + * relocate.c: do wc repos relocation + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include "svn_wc.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" + +#include "wc.h" +#include "props.h" + +#include "svn_private_config.h" + + +/* If the components of RELPATH exactly match (after being + URI-encoded) the final components of URL, return a copy of URL + minus those components allocated in RESULT_POOL; otherwise, return + NULL. */ +static const char * +url_remove_final_relpath(const char *url, + const char *relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + char *result = apr_pstrdup(result_pool, url); + char *result_end; + const char *relpath_end; + + SVN_ERR_ASSERT_NO_RETURN(svn_path_is_url(url)); + SVN_ERR_ASSERT_NO_RETURN(svn_relpath_is_canonical(relpath)); + + if (relpath[0] == 0) + return result; + + relpath = svn_path_uri_encode(relpath, scratch_pool); + result_end = result + strlen(result) - 1; + relpath_end = relpath + strlen(relpath) - 1; + + while (relpath_end >= relpath) + { + if (*result_end != *relpath_end) + return NULL; + + relpath_end--; + result_end--; + } + + if (*result_end != '/') + return NULL; + + *result_end = 0; + + return result; +} + +svn_error_t * +svn_wc_relocate4(svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *from, + const char *to, + svn_wc_relocation_validator3_t validator, + void *validator_baton, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t kind; + const char *repos_relpath; + const char *old_repos_root, *old_url; + const char *new_repos_root, *new_url; + size_t from_len; + size_t old_url_len; + const char *uuid; + svn_boolean_t is_wc_root; + + SVN_ERR(svn_wc__is_wcroot(&is_wc_root, wc_ctx, local_abspath, + scratch_pool)); + if (! is_wc_root) + { + const char *wcroot_abspath; + svn_error_t *err; + + err = svn_wc__db_get_wcroot(&wcroot_abspath, wc_ctx->db, + local_abspath, scratch_pool, scratch_pool); + if (err) + { + svn_error_clear(err); + return svn_error_createf( + SVN_ERR_WC_INVALID_OP_ON_CWD, NULL, + _("Cannot relocate '%s' as it is not the root of a working copy"), + svn_dirent_local_style(local_abspath, scratch_pool)); + } + else + { + return svn_error_createf( + SVN_ERR_WC_INVALID_OP_ON_CWD, NULL, + _("Cannot relocate '%s' as it is not the root of a working copy; " + "try relocating '%s' instead"), + svn_dirent_local_style(local_abspath, scratch_pool), + svn_dirent_local_style(wcroot_abspath, scratch_pool)); + } + } + + SVN_ERR(svn_wc__db_read_info(NULL, &kind, NULL, &repos_relpath, + &old_repos_root, &uuid, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + wc_ctx->db, local_abspath, scratch_pool, + scratch_pool)); + + if (kind != svn_node_dir) + return svn_error_create(SVN_ERR_CLIENT_INVALID_RELOCATION, NULL, + _("Cannot relocate a single file")); + + old_url = svn_path_url_add_component2(old_repos_root, repos_relpath, + scratch_pool); + old_url_len = strlen(old_url); + from_len = strlen(from); + if ((from_len > old_url_len) || (strncmp(old_url, from, strlen(from)) != 0)) + return svn_error_createf(SVN_ERR_WC_INVALID_RELOCATION, NULL, + _("Invalid source URL prefix: '%s' (does not " + "overlap target's URL '%s')"), + from, old_url); + + if (old_url_len == from_len) + new_url = to; + else + new_url = apr_pstrcat(scratch_pool, to, old_url + from_len, (char *)NULL); + if (! svn_path_is_url(new_url)) + return svn_error_createf(SVN_ERR_WC_INVALID_RELOCATION, NULL, + _("Invalid relocation destination: '%s' " + "(not a URL)"), new_url); + + new_repos_root = url_remove_final_relpath(new_url, repos_relpath, + scratch_pool, scratch_pool); + if (!new_repos_root) + return svn_error_createf(SVN_ERR_WC_INVALID_RELOCATION, NULL, + _("Invalid relocation destination: '%s' " + "(does not point to target)" ), new_url); + + SVN_ERR(validator(validator_baton, uuid, new_url, new_repos_root, + scratch_pool)); + + return svn_error_trace(svn_wc__db_global_relocate(wc_ctx->db, local_abspath, + new_repos_root, + scratch_pool)); +} diff --git a/subversion/libsvn_wc/revert.c b/subversion/libsvn_wc/revert.c new file mode 100644 index 000000000000..5e190e89c403 --- /dev/null +++ b/subversion/libsvn_wc/revert.c @@ -0,0 +1,886 @@ +/* + * revert.c: Handling of the in-wc side of the revert operation + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <string.h> +#include <stdlib.h> + +#include <apr_pools.h> +#include <apr_tables.h> + +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" +#include "svn_wc.h" +#include "svn_io.h" + +#include "wc.h" +#include "adm_files.h" +#include "workqueue.h" + +#include "svn_private_config.h" +#include "private/svn_io_private.h" +#include "private/svn_wc_private.h" + +/* Thoughts on Reversion. + + What does is mean to revert a given PATH in a tree? We'll + consider things by their modifications. + + Adds + + - For files, svn_wc_remove_from_revision_control(), baby. + + - Added directories may contain nothing but added children, and + reverting the addition of a directory necessarily means reverting + the addition of all the directory's children. Again, + svn_wc_remove_from_revision_control() should do the trick. + + Deletes + + - Restore properties to their unmodified state. + + - For files, restore the pristine contents, and reset the schedule + to 'normal'. + + - For directories, reset the schedule to 'normal'. All children + of a directory marked for deletion must also be marked for + deletion, but it's okay for those children to remain deleted even + if their parent directory is restored. That's what the + recursive flag is for. + + Replaces + + - Restore properties to their unmodified state. + + - For files, restore the pristine contents, and reset the schedule + to 'normal'. + + - For directories, reset the schedule to normal. A replaced + directory can have deleted children (left over from the initial + deletion), replaced children (children of the initial deletion + now re-added), and added children (new entries under the + replaced directory). Since this is technically an addition, it + necessitates recursion. + + Modifications + + - Restore properties and, for files, contents to their unmodified + state. + +*/ + + +/* Remove conflict file CONFLICT_ABSPATH, which may not exist, and set + * *NOTIFY_REQUIRED to TRUE if the file was present and removed. */ +static svn_error_t * +remove_conflict_file(svn_boolean_t *notify_required, + const char *conflict_abspath, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + if (conflict_abspath) + { + svn_error_t *err = svn_io_remove_file2(conflict_abspath, FALSE, + scratch_pool); + if (err) + svn_error_clear(err); + else + *notify_required = TRUE; + } + + return SVN_NO_ERROR; +} + + +/* Sort copied children obtained from the revert list based on + * their paths in descending order (longest paths first). */ +static int +compare_revert_list_copied_children(const void *a, const void *b) +{ + const svn_wc__db_revert_list_copied_child_info_t * const *ca = a; + const svn_wc__db_revert_list_copied_child_info_t * const *cb = b; + int i; + + i = svn_path_compare_paths(ca[0]->abspath, cb[0]->abspath); + + /* Reverse the result of svn_path_compare_paths() to achieve + * descending order. */ + return -i; +} + + +/* Remove all reverted copied children from the directory at LOCAL_ABSPATH. + * If REMOVE_SELF is TRUE, try to remove LOCAL_ABSPATH itself (REMOVE_SELF + * should be set if LOCAL_ABSPATH is itself a reverted copy). + * + * If REMOVED_SELF is not NULL, indicate in *REMOVED_SELF whether + * LOCAL_ABSPATH itself was removed. + * + * All reverted copied file children are removed from disk. Reverted copied + * directories left empty as a result are also removed from disk. + */ +static svn_error_t * +revert_restore_handle_copied_dirs(svn_boolean_t *removed_self, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t remove_self, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const apr_array_header_t *copied_children; + svn_wc__db_revert_list_copied_child_info_t *child_info; + int i; + svn_node_kind_t on_disk; + apr_pool_t *iterpool; + svn_error_t *err; + + if (removed_self) + *removed_self = FALSE; + + SVN_ERR(svn_wc__db_revert_list_read_copied_children(&copied_children, + db, local_abspath, + scratch_pool, + scratch_pool)); + iterpool = svn_pool_create(scratch_pool); + + /* Remove all copied file children. */ + for (i = 0; i < copied_children->nelts; i++) + { + child_info = APR_ARRAY_IDX( + copied_children, i, + svn_wc__db_revert_list_copied_child_info_t *); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + if (child_info->kind != svn_node_file) + continue; + + svn_pool_clear(iterpool); + + /* Make sure what we delete from disk is really a file. */ + SVN_ERR(svn_io_check_path(child_info->abspath, &on_disk, iterpool)); + if (on_disk != svn_node_file) + continue; + + SVN_ERR(svn_io_remove_file2(child_info->abspath, TRUE, iterpool)); + } + + /* Delete every empty child directory. + * We cannot delete children recursively since we want to keep any files + * that still exist on disk (e.g. unversioned files within the copied tree). + * So sort the children list such that longest paths come first and try to + * remove each child directory in order. */ + qsort(copied_children->elts, copied_children->nelts, + sizeof(svn_wc__db_revert_list_copied_child_info_t *), + compare_revert_list_copied_children); + for (i = 0; i < copied_children->nelts; i++) + { + child_info = APR_ARRAY_IDX( + copied_children, i, + svn_wc__db_revert_list_copied_child_info_t *); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + if (child_info->kind != svn_node_dir) + continue; + + svn_pool_clear(iterpool); + + err = svn_io_dir_remove_nonrecursive(child_info->abspath, iterpool); + if (err) + { + if (APR_STATUS_IS_ENOENT(err->apr_err) || + SVN__APR_STATUS_IS_ENOTDIR(err->apr_err) || + APR_STATUS_IS_ENOTEMPTY(err->apr_err)) + svn_error_clear(err); + else + return svn_error_trace(err); + } + } + + if (remove_self) + { + /* Delete LOCAL_ABSPATH itself if no children are left. */ + err = svn_io_dir_remove_nonrecursive(local_abspath, iterpool); + if (err) + { + if (APR_STATUS_IS_ENOTEMPTY(err->apr_err)) + svn_error_clear(err); + else + return svn_error_trace(err); + } + else if (removed_self) + *removed_self = TRUE; + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Make the working tree under LOCAL_ABSPATH to depth DEPTH match the + versioned tree. This function is called after svn_wc__db_op_revert + has done the database revert and created the revert list. Notifies + for all paths equal to or below LOCAL_ABSPATH that are reverted. + + REVERT_ROOT is true for explicit revert targets and FALSE for targets + reached via recursion. + */ +static svn_error_t * +revert_restore(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t use_commit_times, + svn_boolean_t revert_root, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_node_kind_t on_disk; + svn_boolean_t notify_required; + const apr_array_header_t *conflict_files; + svn_filesize_t recorded_size; + apr_time_t recorded_time; + apr_finfo_t finfo; +#ifdef HAVE_SYMLINK + svn_boolean_t special; +#endif + svn_boolean_t copied_here; + svn_node_kind_t reverted_kind; + svn_boolean_t is_wcroot; + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool)); + if (is_wcroot && !revert_root) + { + /* Issue #4162: Obstructing working copy. We can't access the working + copy data from the parent working copy for this node by just using + local_abspath */ + + if (notify_func) + { + svn_wc_notify_t *notify = svn_wc_create_notify( + local_abspath, + svn_wc_notify_update_skip_obstruction, + scratch_pool); + + notify_func(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; /* We don't revert obstructing working copies */ + } + + SVN_ERR(svn_wc__db_revert_list_read(¬ify_required, + &conflict_files, + &copied_here, &reverted_kind, + db, local_abspath, + scratch_pool, scratch_pool)); + + err = svn_wc__db_read_info(&status, &kind, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &recorded_size, &recorded_time, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + + if (!copied_here) + { + if (notify_func && notify_required) + notify_func(notify_baton, + svn_wc_create_notify(local_abspath, + svn_wc_notify_revert, + scratch_pool), + scratch_pool); + + if (notify_func) + SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton, + db, local_abspath, + scratch_pool)); + return SVN_NO_ERROR; + } + else + { + /* ### Initialise to values which prevent the code below from + * ### trying to restore anything to disk. + * ### 'status' should be status_unknown but that doesn't exist. */ + status = svn_wc__db_status_normal; + kind = svn_node_unknown; + recorded_size = SVN_INVALID_FILESIZE; + recorded_time = 0; + } + } + else if (err) + return svn_error_trace(err); + + err = svn_io_stat(&finfo, local_abspath, + APR_FINFO_TYPE | APR_FINFO_LINK + | APR_FINFO_SIZE | APR_FINFO_MTIME + | SVN__APR_FINFO_EXECUTABLE + | SVN__APR_FINFO_READONLY, + scratch_pool); + + if (err && (APR_STATUS_IS_ENOENT(err->apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) + { + svn_error_clear(err); + on_disk = svn_node_none; +#ifdef HAVE_SYMLINK + special = FALSE; +#endif + } + else if (!err) + { + if (finfo.filetype == APR_REG || finfo.filetype == APR_LNK) + on_disk = svn_node_file; + else if (finfo.filetype == APR_DIR) + on_disk = svn_node_dir; + else + on_disk = svn_node_unknown; + +#ifdef HAVE_SYMLINK + special = (finfo.filetype == APR_LNK); +#endif + } + else + return svn_error_trace(err); + + if (copied_here) + { + /* The revert target itself is the op-root of a copy. */ + if (reverted_kind == svn_node_file && on_disk == svn_node_file) + { + SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool)); + on_disk = svn_node_none; + } + else if (reverted_kind == svn_node_dir && on_disk == svn_node_dir) + { + svn_boolean_t removed; + + SVN_ERR(revert_restore_handle_copied_dirs(&removed, db, + local_abspath, TRUE, + cancel_func, cancel_baton, + scratch_pool)); + if (removed) + on_disk = svn_node_none; + } + } + + /* If we expect a versioned item to be present then check that any + item on disk matches the versioned item, if it doesn't match then + fix it or delete it. */ + if (on_disk != svn_node_none + && status != svn_wc__db_status_server_excluded + && status != svn_wc__db_status_deleted + && status != svn_wc__db_status_excluded + && status != svn_wc__db_status_not_present) + { + if (on_disk == svn_node_dir && kind != svn_node_dir) + { + SVN_ERR(svn_io_remove_dir2(local_abspath, FALSE, + cancel_func, cancel_baton, scratch_pool)); + on_disk = svn_node_none; + } + else if (on_disk == svn_node_file && kind != svn_node_file) + { +#ifdef HAVE_SYMLINK + /* Preserve symlinks pointing at directories. Changes on the + * directory node have been reverted. The symlink should remain. */ + if (!(special && kind == svn_node_dir)) +#endif + { + SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool)); + on_disk = svn_node_none; + } + } + else if (on_disk == svn_node_file) + { + svn_boolean_t modified; + apr_hash_t *props; +#ifdef HAVE_SYMLINK + svn_string_t *special_prop; +#endif + + SVN_ERR(svn_wc__db_read_pristine_props(&props, db, local_abspath, + scratch_pool, scratch_pool)); + +#ifdef HAVE_SYMLINK + special_prop = svn_hash_gets(props, SVN_PROP_SPECIAL); + + if ((special_prop != NULL) != special) + { + /* File/symlink mismatch. */ + SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, scratch_pool)); + on_disk = svn_node_none; + } + else +#endif + { + /* Issue #1663 asserts that we should compare a file in its + working copy format here, but before r1101473 we would only + do that if the file was already unequal to its recorded + information. + + r1101473 removes the option of asking for a working format + compare but *also* check the recorded information first, as + that combination doesn't guarantee a stable behavior. + (See the revert_test.py: revert_reexpand_keyword) + + But to have the same issue #1663 behavior for revert as we + had in <=1.6 we only have to check the recorded information + ourselves. And we already have everything we need, because + we called stat ourselves. */ + if (recorded_size != SVN_INVALID_FILESIZE + && recorded_time != 0 + && recorded_size == finfo.size + && recorded_time == finfo.mtime) + { + modified = FALSE; + } + else + SVN_ERR(svn_wc__internal_file_modified_p(&modified, + db, local_abspath, + TRUE, scratch_pool)); + + if (modified) + { + SVN_ERR(svn_io_remove_file2(local_abspath, FALSE, + scratch_pool)); + on_disk = svn_node_none; + } + else + { + if (status == svn_wc__db_status_normal) + { + svn_boolean_t read_only; + svn_string_t *needs_lock_prop; + + SVN_ERR(svn_io__is_finfo_read_only(&read_only, &finfo, + scratch_pool)); + + needs_lock_prop = svn_hash_gets(props, + SVN_PROP_NEEDS_LOCK); + if (needs_lock_prop && !read_only) + { + SVN_ERR(svn_io_set_file_read_only(local_abspath, + FALSE, + scratch_pool)); + notify_required = TRUE; + } + else if (!needs_lock_prop && read_only) + { + SVN_ERR(svn_io_set_file_read_write(local_abspath, + FALSE, + scratch_pool)); + notify_required = TRUE; + } + } + +#if !defined(WIN32) && !defined(__OS2__) +#ifdef HAVE_SYMLINK + if (!special) +#endif + { + svn_boolean_t executable; + svn_string_t *executable_prop; + + SVN_ERR(svn_io__is_finfo_executable(&executable, &finfo, + scratch_pool)); + executable_prop = svn_hash_gets(props, + SVN_PROP_EXECUTABLE); + if (executable_prop && !executable) + { + SVN_ERR(svn_io_set_file_executable(local_abspath, + TRUE, FALSE, + scratch_pool)); + notify_required = TRUE; + } + else if (!executable_prop && executable) + { + SVN_ERR(svn_io_set_file_executable(local_abspath, + FALSE, FALSE, + scratch_pool)); + notify_required = TRUE; + } + } +#endif + } + } + } + } + + /* If we expect a versioned item to be present and there is nothing + on disk then recreate it. */ + if (on_disk == svn_node_none + && status != svn_wc__db_status_server_excluded + && status != svn_wc__db_status_deleted + && status != svn_wc__db_status_excluded + && status != svn_wc__db_status_not_present) + { + if (kind == svn_node_dir) + SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool)); + + if (kind == svn_node_file) + { + svn_skel_t *work_item; + + /* ### Get the checksum from read_info above and pass in here? */ + SVN_ERR(svn_wc__wq_build_file_install(&work_item, db, local_abspath, + NULL, use_commit_times, TRUE, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_wq_add(db, local_abspath, work_item, + scratch_pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + } + notify_required = TRUE; + } + + if (conflict_files) + { + int i; + for (i = 0; i < conflict_files->nelts; i++) + { + SVN_ERR(remove_conflict_file(¬ify_required, + APR_ARRAY_IDX(conflict_files, i, + const char *), + local_abspath, scratch_pool)); + } + } + + if (notify_func && notify_required) + notify_func(notify_baton, + svn_wc_create_notify(local_abspath, svn_wc_notify_revert, + scratch_pool), + scratch_pool); + + if (depth == svn_depth_infinity && kind == svn_node_dir) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + const apr_array_header_t *children; + int i; + + SVN_ERR(revert_restore_handle_copied_dirs(NULL, db, local_abspath, FALSE, + cancel_func, cancel_baton, + iterpool)); + + SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, + local_abspath, + scratch_pool, + iterpool)); + for (i = 0; i < children->nelts; ++i) + { + const char *child_abspath; + + svn_pool_clear(iterpool); + + child_abspath = svn_dirent_join(local_abspath, + APR_ARRAY_IDX(children, i, + const char *), + iterpool); + + SVN_ERR(revert_restore(db, child_abspath, depth, + use_commit_times, FALSE /* revert root */, + cancel_func, cancel_baton, + notify_func, notify_baton, + iterpool)); + } + + svn_pool_destroy(iterpool); + } + + if (notify_func) + SVN_ERR(svn_wc__db_revert_list_notify(notify_func, notify_baton, + db, local_abspath, scratch_pool)); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__revert_internal(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t use_commit_times, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(depth == svn_depth_empty || depth == svn_depth_infinity); + + /* We should have a write lock on the parent of local_abspath, except + when local_abspath is the working copy root. */ + { + const char *dir_abspath; + svn_boolean_t is_wcroot; + + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, scratch_pool)); + + if (! is_wcroot) + dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + else + dir_abspath = local_abspath; + + SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool)); + } + + err = svn_wc__db_op_revert(db, local_abspath, depth, + scratch_pool, scratch_pool); + + if (!err) + err = revert_restore(db, local_abspath, depth, + use_commit_times, TRUE /* revert root */, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool); + + err = svn_error_compose_create(err, + svn_wc__db_revert_list_done(db, + local_abspath, + scratch_pool)); + + return err; +} + + +/* Revert files in LOCAL_ABSPATH to depth DEPTH that match + CHANGELIST_HASH and notify for all reverts. */ +static svn_error_t * +revert_changelist(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t use_commit_times, + apr_hash_t *changelist_hash, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + const apr_array_header_t *children; + int i; + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + /* Revert this node (depth=empty) if it matches one of the changelists. */ + if (svn_wc__internal_changelist_match(db, local_abspath, changelist_hash, + scratch_pool)) + SVN_ERR(svn_wc__revert_internal(db, local_abspath, + svn_depth_empty, use_commit_times, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); + + if (depth == svn_depth_empty) + return SVN_NO_ERROR; + + iterpool = svn_pool_create(scratch_pool); + + /* We can handle both depth=files and depth=immediates by setting + depth=empty here. We don't need to distinguish files and + directories when making the recursive call because directories + can never match a changelist, so making the recursive call for + directories when asked for depth=files is a no-op. */ + if (depth == svn_depth_files || depth == svn_depth_immediates) + depth = svn_depth_empty; + + SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, + local_abspath, + scratch_pool, + iterpool)); + for (i = 0; i < children->nelts; ++i) + { + const char *child_abspath; + + svn_pool_clear(iterpool); + + child_abspath = svn_dirent_join(local_abspath, + APR_ARRAY_IDX(children, i, + const char *), + iterpool); + + SVN_ERR(revert_changelist(db, child_abspath, depth, + use_commit_times, changelist_hash, + cancel_func, cancel_baton, + notify_func, notify_baton, + iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Does a partially recursive revert of LOCAL_ABSPATH to depth DEPTH + (which must be either svn_depth_files or svn_depth_immediates) by + doing a non-recursive revert on each permissible path. Notifies + all reverted paths. + + ### This won't revert a copied dir with one level of children since + ### the non-recursive revert on the dir will fail. Not sure how a + ### partially recursive revert should handle actual-only nodes. */ +static svn_error_t * +revert_partial(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t use_commit_times, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + const apr_array_header_t *children; + int i; + + SVN_ERR_ASSERT(depth == svn_depth_files || depth == svn_depth_immediates); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + iterpool = svn_pool_create(scratch_pool); + + /* Revert the root node itself (depth=empty), then move on to the + children. */ + SVN_ERR(svn_wc__revert_internal(db, local_abspath, svn_depth_empty, + use_commit_times, cancel_func, cancel_baton, + notify_func, notify_baton, iterpool)); + + SVN_ERR(svn_wc__db_read_children_of_working_node(&children, db, + local_abspath, + scratch_pool, + iterpool)); + for (i = 0; i < children->nelts; ++i) + { + const char *child_abspath; + + svn_pool_clear(iterpool); + + child_abspath = svn_dirent_join(local_abspath, + APR_ARRAY_IDX(children, i, const char *), + iterpool); + + /* For svn_depth_files: don't revert non-files. */ + if (depth == svn_depth_files) + { + svn_node_kind_t kind; + + SVN_ERR(svn_wc__db_read_kind(&kind, db, child_abspath, + FALSE /* allow_missing */, + TRUE /* show_deleted */, + FALSE /* show_hidden */, + iterpool)); + if (kind != svn_node_file) + continue; + } + + /* Revert just this node (depth=empty). */ + SVN_ERR(svn_wc__revert_internal(db, child_abspath, + svn_depth_empty, use_commit_times, + cancel_func, cancel_baton, + notify_func, notify_baton, + iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_revert4(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t use_commit_times, + const apr_array_header_t *changelist_filter, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + if (changelist_filter && changelist_filter->nelts) + { + apr_hash_t *changelist_hash; + + SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter, + scratch_pool)); + return svn_error_trace(revert_changelist(wc_ctx->db, local_abspath, + depth, use_commit_times, + changelist_hash, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); + } + + if (depth == svn_depth_empty || depth == svn_depth_infinity) + return svn_error_trace(svn_wc__revert_internal(wc_ctx->db, local_abspath, + depth, use_commit_times, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); + + /* The user may expect svn_depth_files/svn_depth_immediates to work + on copied dirs with one level of children. It doesn't, the user + will get an error and will need to invoke an infinite revert. If + we identified those cases where svn_depth_infinity would not + revert too much we could invoke the recursive call above. */ + + if (depth == svn_depth_files || depth == svn_depth_immediates) + return svn_error_trace(revert_partial(wc_ctx->db, local_abspath, + depth, use_commit_times, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool)); + + /* Bogus depth. Tell the caller. */ + return svn_error_create(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, NULL); +} diff --git a/subversion/libsvn_wc/revision_status.c b/subversion/libsvn_wc/revision_status.c new file mode 100644 index 000000000000..a4b9bea81ea1 --- /dev/null +++ b/subversion/libsvn_wc/revision_status.c @@ -0,0 +1,67 @@ +/* + * revision_status.c: report the revision range and status of a working copy + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "svn_wc.h" +#include "svn_dirent_uri.h" +#include "wc_db.h" +#include "wc.h" +#include "props.h" + +#include "private/svn_wc_private.h" + +#include "svn_private_config.h" + +svn_error_t * +svn_wc_revision_status2(svn_wc_revision_status_t **result_p, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + const char *trail_url, + svn_boolean_t committed, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_revision_status_t *result = apr_pcalloc(result_pool, sizeof(*result)); + + *result_p = result; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* set result as nil */ + result->min_rev = SVN_INVALID_REVNUM; + result->max_rev = SVN_INVALID_REVNUM; + result->switched = FALSE; + result->modified = FALSE; + result->sparse_checkout = FALSE; + + SVN_ERR(svn_wc__db_revision_status(&result->min_rev, &result->max_rev, + &result->sparse_checkout, + &result->modified, + &result->switched, + wc_ctx->db, local_abspath, trail_url, + committed, cancel_func, cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/status.c b/subversion/libsvn_wc/status.c new file mode 100644 index 000000000000..1440b2ee5e4a --- /dev/null +++ b/subversion/libsvn_wc/status.c @@ -0,0 +1,3047 @@ +/* + * status.c: construct a status structure from an entry structure + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <assert.h> +#include <string.h> + +#include <apr_pools.h> +#include <apr_file_io.h> +#include <apr_hash.h> + +#include "svn_pools.h" +#include "svn_types.h" +#include "svn_delta.h" +#include "svn_string.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_io.h" +#include "svn_config.h" +#include "svn_time.h" +#include "svn_hash.h" +#include "svn_sorts.h" + +#include "svn_private_config.h" + +#include "wc.h" +#include "props.h" +#include "entries.h" +#include "translate.h" +#include "tree_conflicts.h" + +#include "private/svn_wc_private.h" +#include "private/svn_fspath.h" +#include "private/svn_editor.h" + + + +/*** Baton used for walking the local status */ +struct walk_status_baton +{ + /* The DB handle for managing the working copy state. */ + svn_wc__db_t *db; + + /*** External handling ***/ + /* Target of the status */ + const char *target_abspath; + + /* Should we ignore text modifications? */ + svn_boolean_t ignore_text_mods; + + /* Externals info harvested during the status run. */ + apr_hash_t *externals; + + /*** Repository lock handling ***/ + /* The repository root URL, if set. */ + const char *repos_root; + + /* Repository locks, if set. */ + apr_hash_t *repos_locks; +}; + +/*** Editor batons ***/ + +struct edit_baton +{ + /* For status, the "destination" of the edit. */ + const char *anchor_abspath; + const char *target_abspath; + const char *target_basename; + + /* The DB handle for managing the working copy state. */ + svn_wc__db_t *db; + svn_wc_context_t *wc_ctx; + + /* The overall depth of this edit (a dir baton may override this). + * + * If this is svn_depth_unknown, the depths found in the working + * copy will govern the edit; or if the edit depth indicates a + * descent deeper than the found depths are capable of, the found + * depths also govern, of course (there's no point descending into + * something that's not there). + */ + svn_depth_t default_depth; + + /* Do we want all statuses (instead of just the interesting ones) ? */ + svn_boolean_t get_all; + + /* Ignore the svn:ignores. */ + svn_boolean_t no_ignore; + + /* The comparison revision in the repository. This is a reference + because this editor returns this rev to the driver directly, as + well as in each statushash entry. */ + svn_revnum_t *target_revision; + + /* Status function/baton. */ + svn_wc_status_func4_t status_func; + void *status_baton; + + /* Cancellation function/baton. */ + svn_cancel_func_t cancel_func; + void *cancel_baton; + + /* The configured set of default ignores. */ + const apr_array_header_t *ignores; + + /* Status item for the path represented by the anchor of the edit. */ + svn_wc_status3_t *anchor_status; + + /* Was open_root() called for this edit drive? */ + svn_boolean_t root_opened; + + /* The local status baton */ + struct walk_status_baton wb; +}; + + +struct dir_baton +{ + /* The path to this directory. */ + const char *local_abspath; + + /* Basename of this directory. */ + const char *name; + + /* The global edit baton. */ + struct edit_baton *edit_baton; + + /* Baton for this directory's parent, or NULL if this is the root + directory. */ + struct dir_baton *parent_baton; + + /* The ambient requested depth below this point in the edit. This + can differ from the parent baton's depth (with the edit baton + considered the ultimate parent baton). For example, if the + parent baton has svn_depth_immediates, then here we should have + svn_depth_empty, because there would be no further recursion, not + even to file children. */ + svn_depth_t depth; + + /* Is this directory filtered out due to depth? (Note that if this + is TRUE, the depth field is undefined.) */ + svn_boolean_t excluded; + + /* 'svn status' shouldn't print status lines for things that are + added; we're only interest in asking if objects that the user + *already* has are up-to-date or not. Thus if this flag is set, + the next two will be ignored. :-) */ + svn_boolean_t added; + + /* Gets set iff there's a change to this directory's properties, to + guide us when syncing adm files later. */ + svn_boolean_t prop_changed; + + /* This means (in terms of 'svn status') that some child was deleted + or added to the directory */ + svn_boolean_t text_changed; + + /* Working copy status structures for children of this directory. + This hash maps const char * abspaths to svn_wc_status3_t * + status items. */ + apr_hash_t *statii; + + /* The pool in which this baton itself is allocated. */ + apr_pool_t *pool; + + /* The repository root relative path to this item in the repository. */ + const char *repos_relpath; + + /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */ + svn_node_kind_t ood_kind; + svn_revnum_t ood_changed_rev; + apr_time_t ood_changed_date; + const char *ood_changed_author; +}; + + +struct file_baton +{ +/* Absolute local path to this file */ + const char *local_abspath; + + /* The global edit baton. */ + struct edit_baton *edit_baton; + + /* Baton for this file's parent directory. */ + struct dir_baton *dir_baton; + + /* Pool specific to this file_baton. */ + apr_pool_t *pool; + + /* Basename of this file */ + const char *name; + + /* 'svn status' shouldn't print status lines for things that are + added; we're only interest in asking if objects that the user + *already* has are up-to-date or not. Thus if this flag is set, + the next two will be ignored. :-) */ + svn_boolean_t added; + + /* This gets set if the file underwent a text change, which guides + the code that syncs up the adm dir and working copy. */ + svn_boolean_t text_changed; + + /* This gets set if the file underwent a prop change, which guides + the code that syncs up the adm dir and working copy. */ + svn_boolean_t prop_changed; + + /* The repository root relative path to this item in the repository. */ + const char *repos_relpath; + + /* out-of-date info corresponding to ood_* fields in svn_wc_status3_t. */ + svn_node_kind_t ood_kind; + svn_revnum_t ood_changed_rev; + apr_time_t ood_changed_date; + + const char *ood_changed_author; +}; + + +/** Code **/ + +/* Fill in *INFO with the information it would contain if it were + obtained from svn_wc__db_read_children_info. */ +static svn_error_t * +read_info(const struct svn_wc__db_info_t **info, + const char *local_abspath, + svn_wc__db_t *db, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct svn_wc__db_info_t *mtb = apr_pcalloc(result_pool, sizeof(*mtb)); + const svn_checksum_t *checksum; + const char *original_repos_relpath; + + SVN_ERR(svn_wc__db_read_info(&mtb->status, &mtb->kind, + &mtb->revnum, &mtb->repos_relpath, + &mtb->repos_root_url, &mtb->repos_uuid, + &mtb->changed_rev, &mtb->changed_date, + &mtb->changed_author, &mtb->depth, + &checksum, NULL, &original_repos_relpath, NULL, + NULL, NULL, &mtb->lock, &mtb->recorded_size, + &mtb->recorded_time, &mtb->changelist, + &mtb->conflicted, &mtb->op_root, + &mtb->had_props, &mtb->props_mod, + &mtb->have_base, &mtb->have_more_work, NULL, + db, local_abspath, + result_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_wclocked(&mtb->locked, db, local_abspath, scratch_pool)); + + /* Maybe we have to get some shadowed lock from BASE to make our test suite + happy... (It might be completely unrelated, but...) */ + if (mtb->have_base + && (mtb->status == svn_wc__db_status_added + || mtb->status == svn_wc__db_status_deleted + || mtb->kind == svn_node_file)) + { + svn_boolean_t update_root; + svn_wc__db_lock_t **lock_arg = NULL; + + if (mtb->status == svn_wc__db_status_added + || mtb->status == svn_wc__db_status_deleted) + lock_arg = &mtb->lock; + + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + lock_arg, NULL, NULL, &update_root, + db, local_abspath, + result_pool, scratch_pool)); + + mtb->file_external = (update_root && mtb->kind == svn_node_file); + + if (mtb->status == svn_wc__db_status_deleted) + { + const char *moved_to_abspath; + const char *moved_to_op_root_abspath; + + /* NOTE: we can't use op-root-ness as a condition here since a base + * node can be the root of a move and still not be an explicit + * op-root (having a working node with op_depth == pathelements). + * + * Both these (almost identical) situations showcase this: + * svn mv a/b bb + * svn del a + * and + * svn mv a aa + * svn mv aa/b bb + * In both, 'bb' is moved from 'a/b', but 'a/b' has no op_depth>0 + * node at all, as its parent 'a' is locally deleted. */ + + SVN_ERR(svn_wc__db_scan_deletion(NULL, + &moved_to_abspath, + NULL, + &moved_to_op_root_abspath, + db, local_abspath, + scratch_pool, scratch_pool)); + if (moved_to_abspath != NULL + && moved_to_op_root_abspath != NULL + && strcmp(moved_to_abspath, moved_to_op_root_abspath) == 0) + { + mtb->moved_to_abspath = apr_pstrdup(result_pool, + moved_to_abspath); + } + /* ### ^^^ THIS SUCKS. For at least two reasons: + * 1) We scan the node deletion and that's technically not necessary. + * We'd be fine to know if this is an actual root of a move. + * 2) From the elaborately calculated results, we backwards-guess + * whether this is a root. + * It works ok, and this code only gets called when a node is an + * explicit target of a 'status'. But it would be better to do this + * differently. + * We could return moved-to via svn_wc__db_base_get_info() (called + * just above), but as moved-to is only intended to be returned for + * roots of a move, that doesn't fit too well. */ + } + } + + /* ### svn_wc__db_read_info() could easily return the moved-here flag. But + * for now... (The per-dir query for recursive status is far more optimal.) + * Note that this actually scans around to get the full path, for a bool. + * This bool then gets returned, later is evaluated, and if true leads to + * the same paths being scanned again. We'd want to obtain this bool here as + * cheaply as svn_wc__db_read_children_info() does. */ + if (mtb->status == svn_wc__db_status_added) + { + svn_wc__db_status_t status; + + SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool)); + + mtb->moved_here = (status == svn_wc__db_status_moved_here); + mtb->incomplete = (status == svn_wc__db_status_incomplete); + } + + mtb->has_checksum = (checksum != NULL); + mtb->copied = (original_repos_relpath != NULL); + +#ifdef HAVE_SYMLINK + if (mtb->kind == svn_node_file + && (mtb->had_props || mtb->props_mod)) + { + apr_hash_t *properties; + + if (mtb->props_mod) + SVN_ERR(svn_wc__db_read_props(&properties, db, local_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(svn_wc__db_read_pristine_props(&properties, db, local_abspath, + scratch_pool, scratch_pool)); + + mtb->special = (NULL != svn_hash_gets(properties, SVN_PROP_SPECIAL)); + } +#endif + *info = mtb; + + return SVN_NO_ERROR; +} + +/* Return *REPOS_RELPATH and *REPOS_ROOT_URL for LOCAL_ABSPATH using + information in INFO if available, falling back on + PARENT_REPOS_RELPATH and PARENT_REPOS_ROOT_URL if available, and + finally falling back on querying DB. */ +static svn_error_t * +get_repos_root_url_relpath(const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + const struct svn_wc__db_info_t *info, + const char *parent_repos_relpath, + const char *parent_repos_root_url, + const char *parent_repos_uuid, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (info->repos_relpath && info->repos_root_url) + { + *repos_relpath = apr_pstrdup(result_pool, info->repos_relpath); + *repos_root_url = apr_pstrdup(result_pool, info->repos_root_url); + *repos_uuid = apr_pstrdup(result_pool, info->repos_uuid); + } + else if (parent_repos_relpath && parent_repos_root_url) + { + *repos_relpath = svn_relpath_join(parent_repos_relpath, + svn_dirent_basename(local_abspath, + NULL), + result_pool); + *repos_root_url = apr_pstrdup(result_pool, parent_repos_root_url); + *repos_uuid = apr_pstrdup(result_pool, parent_repos_uuid); + } + else if (info->status == svn_wc__db_status_added) + { + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, + repos_relpath, repos_root_url, + repos_uuid, NULL, NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool)); + } + else if (info->have_base) + { + SVN_ERR(svn_wc__db_scan_base_repos(repos_relpath, repos_root_url, + repos_uuid, + db, local_abspath, + result_pool, scratch_pool)); + } + else + { + *repos_relpath = NULL; + *repos_root_url = NULL; + *repos_uuid = NULL; + } + return SVN_NO_ERROR; +} + +static svn_error_t * +internal_status(svn_wc_status3_t **status, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Fill in *STATUS for LOCAL_ABSPATH, using DB. Allocate *STATUS in + RESULT_POOL and use SCRATCH_POOL for temporary allocations. + + PARENT_REPOS_ROOT_URL and PARENT_REPOS_RELPATH are the repository root + and repository relative path of the parent of LOCAL_ABSPATH or NULL if + LOCAL_ABSPATH doesn't have a versioned parent directory. + + DIRENT is the local representation of LOCAL_ABSPATH in the working copy or + NULL if the node does not exist on disk. + + If GET_ALL is FALSE, and LOCAL_ABSPATH is not locally modified, then + *STATUS will be set to NULL. If GET_ALL is non-zero, then *STATUS will be + allocated and returned no matter what. If IGNORE_TEXT_MODS is TRUE then + don't check for text mods, assume there are none and set and *STATUS + returned to reflect that assumption. + + The status struct's repos_lock field will be set to REPOS_LOCK. +*/ +static svn_error_t * +assemble_status(svn_wc_status3_t **status, + svn_wc__db_t *db, + const char *local_abspath, + const char *parent_repos_root_url, + const char *parent_repos_relpath, + const char *parent_repos_uuid, + const struct svn_wc__db_info_t *info, + const svn_io_dirent2_t *dirent, + svn_boolean_t get_all, + svn_boolean_t ignore_text_mods, + const svn_lock_t *repos_lock, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_status3_t *stat; + svn_boolean_t switched_p = FALSE; + svn_boolean_t copied = FALSE; + svn_boolean_t conflicted; + const char *moved_from_abspath = NULL; + svn_filesize_t filesize = (dirent && (dirent->kind == svn_node_file)) + ? dirent->filesize + : SVN_INVALID_FILESIZE; + + /* Defaults for two main variables. */ + enum svn_wc_status_kind node_status = svn_wc_status_normal; + enum svn_wc_status_kind text_status = svn_wc_status_normal; + enum svn_wc_status_kind prop_status = svn_wc_status_none; + + + if (!info) + SVN_ERR(read_info(&info, local_abspath, db, result_pool, scratch_pool)); + + if (!info->repos_relpath || !parent_repos_relpath) + switched_p = FALSE; + else + { + /* A node is switched if it doesn't have the implied repos_relpath */ + const char *name = svn_relpath_skip_ancestor(parent_repos_relpath, + info->repos_relpath); + switched_p = !name || (strcmp(name, + svn_dirent_basename(local_abspath, NULL)) + != 0); + } + + if (info->status == svn_wc__db_status_incomplete || info->incomplete) + { + /* Highest precedence. */ + node_status = svn_wc_status_incomplete; + } + else if (info->status == svn_wc__db_status_deleted) + { + node_status = svn_wc_status_deleted; + + if (!info->have_base || info->have_more_work || info->copied) + copied = TRUE; + else if (!info->have_more_work && info->have_base) + copied = FALSE; + else + { + const char *work_del_abspath; + + /* Find out details of our deletion. */ + SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, + &work_del_abspath, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + if (work_del_abspath) + copied = TRUE; /* Working deletion */ + } + } + else + { + /* Examine whether our target is missing or obstructed. To detect + * obstructions, we have to look at the on-disk status in DIRENT. */ + svn_node_kind_t expected_kind = (info->kind == svn_node_dir) + ? svn_node_dir + : svn_node_file; + + if (!dirent || dirent->kind != expected_kind) + { + /* A present or added node should be on disk, so it is + reported missing or obstructed. */ + if (!dirent || dirent->kind == svn_node_none) + node_status = svn_wc_status_missing; + else + node_status = svn_wc_status_obstructed; + } + } + + /* Does the node have props? */ + if (info->status != svn_wc__db_status_deleted) + { + if (info->props_mod) + prop_status = svn_wc_status_modified; + else if (info->had_props) + prop_status = svn_wc_status_normal; + } + + /* If NODE_STATUS is still normal, after the above checks, then + we should proceed to refine the status. + + If it was changed, then the subdir is incomplete or missing/obstructed. + */ + if (info->kind != svn_node_dir + && node_status == svn_wc_status_normal) + { + svn_boolean_t text_modified_p = FALSE; + + /* Implement predecence rules: */ + + /* 1. Set the two main variables to "discovered" values first (M, C). + Together, these two stati are of lowest precedence, and C has + precedence over M. */ + + /* If the entry is a file, check for textual modifications */ + if ((info->kind == svn_node_file + || info->kind == svn_node_symlink) +#ifdef HAVE_SYMLINK + && (info->special == (dirent && dirent->special)) +#endif /* HAVE_SYMLINK */ + ) + { + /* If the on-disk dirent exactly matches the expected state + skip all operations in svn_wc__internal_text_modified_p() + to avoid an extra filestat for every file, which can be + expensive on network drives as a filestat usually can't + be cached there */ + if (!info->has_checksum) + text_modified_p = TRUE; /* Local addition -> Modified */ + else if (ignore_text_mods + ||(dirent + && info->recorded_size != SVN_INVALID_FILESIZE + && info->recorded_time != 0 + && info->recorded_size == dirent->filesize + && info->recorded_time == dirent->mtime)) + text_modified_p = FALSE; + else + { + svn_error_t *err; + err = svn_wc__internal_file_modified_p(&text_modified_p, + db, local_abspath, + FALSE, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_ACCESS_DENIED) + return svn_error_trace(err); + + /* An access denied is very common on Windows when another + application has the file open. Previously we ignored + this error in svn_wc__text_modified_internal_p, where it + should have really errored. */ + svn_error_clear(err); + text_modified_p = TRUE; + } + } + } +#ifdef HAVE_SYMLINK + else if (info->special != (dirent && dirent->special)) + node_status = svn_wc_status_obstructed; +#endif /* HAVE_SYMLINK */ + + if (text_modified_p) + text_status = svn_wc_status_modified; + } + + conflicted = info->conflicted; + if (conflicted) + { + svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted; + + /* ### Check if the conflict was resolved by removing the marker files. + ### This should really be moved to the users of this API */ + SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted, &prop_conflicted, + &tree_conflicted, + db, local_abspath, scratch_pool)); + + if (!text_conflicted && !prop_conflicted && !tree_conflicted) + conflicted = FALSE; + } + + if (node_status == svn_wc_status_normal) + { + /* 2. Possibly overwrite the text_status variable with "scheduled" + states from the entry (A, D, R). As a group, these states are + of medium precedence. They also override any C or M that may + be in the prop_status field at this point, although they do not + override a C text status.*/ + if (info->status == svn_wc__db_status_added) + { + copied = info->copied; + if (!info->op_root) + { /* Keep status normal */ } + else if (!info->have_base && !info->have_more_work) + { + /* Simple addition or copy, no replacement */ + node_status = svn_wc_status_added; + } + else + { + svn_wc__db_status_t below_working; + svn_boolean_t have_base, have_work; + + SVN_ERR(svn_wc__db_info_below_working(&have_base, &have_work, + &below_working, + db, local_abspath, + scratch_pool)); + + /* If the node is not present or deleted (read: not present + in working), then the node is not a replacement */ + if (below_working != svn_wc__db_status_not_present + && below_working != svn_wc__db_status_deleted) + { + node_status = svn_wc_status_replaced; + } + else + node_status = svn_wc_status_added; + } + + /* Get moved-from info (only for potential op-roots of a move). */ + if (info->moved_here && info->op_root) + { + svn_error_t *err; + err = svn_wc__db_scan_moved(&moved_from_abspath, NULL, NULL, NULL, + db, local_abspath, + result_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + + svn_error_clear(err); + /* We are no longer moved... So most likely we are somehow + changing the db for things like resolving conflicts. */ + + moved_from_abspath = NULL; + } + } + } + } + + + if (node_status == svn_wc_status_normal) + node_status = text_status; + + if (node_status == svn_wc_status_normal + && prop_status != svn_wc_status_none) + node_status = prop_status; + + /* 5. Easy out: unless we're fetching -every- entry, don't bother + to allocate a struct for an uninteresting entry. */ + + if (! get_all) + if (((node_status == svn_wc_status_none) + || (node_status == svn_wc_status_normal)) + + && (! switched_p) + && (! info->locked ) + && (! info->lock) + && (! repos_lock) + && (! info->changelist) + && (! conflicted)) + { + *status = NULL; + return SVN_NO_ERROR; + } + + /* 6. Build and return a status structure. */ + + stat = apr_pcalloc(result_pool, sizeof(**status)); + + switch (info->kind) + { + case svn_node_dir: + stat->kind = svn_node_dir; + break; + case svn_node_file: + case svn_node_symlink: + stat->kind = svn_node_file; + break; + case svn_node_unknown: + default: + stat->kind = svn_node_unknown; + } + stat->depth = info->depth; + stat->filesize = filesize; + stat->node_status = node_status; + stat->text_status = text_status; + stat->prop_status = prop_status; + stat->repos_node_status = svn_wc_status_none; /* default */ + stat->repos_text_status = svn_wc_status_none; /* default */ + stat->repos_prop_status = svn_wc_status_none; /* default */ + stat->switched = switched_p; + stat->copied = copied; + stat->repos_lock = repos_lock; + stat->revision = info->revnum; + stat->changed_rev = info->changed_rev; + if (info->changed_author) + stat->changed_author = apr_pstrdup(result_pool, info->changed_author); + stat->changed_date = info->changed_date; + + stat->ood_kind = svn_node_none; + stat->ood_changed_rev = SVN_INVALID_REVNUM; + stat->ood_changed_date = 0; + stat->ood_changed_author = NULL; + + SVN_ERR(get_repos_root_url_relpath(&stat->repos_relpath, + &stat->repos_root_url, + &stat->repos_uuid, info, + parent_repos_relpath, + parent_repos_root_url, + parent_repos_uuid, + db, local_abspath, + result_pool, scratch_pool)); + + if (info->lock) + { + svn_lock_t *lck = svn_lock_create(result_pool); + lck->path = stat->repos_relpath; + lck->token = info->lock->token; + lck->owner = info->lock->owner; + lck->comment = info->lock->comment; + lck->creation_date = info->lock->date; + stat->lock = lck; + } + else + stat->lock = NULL; + + stat->locked = info->locked; + stat->conflicted = conflicted; + stat->versioned = TRUE; + if (info->changelist) + stat->changelist = apr_pstrdup(result_pool, info->changelist); + + stat->moved_from_abspath = moved_from_abspath; + if (info->moved_to_abspath) + stat->moved_to_abspath = apr_pstrdup(result_pool, info->moved_to_abspath); + + stat->file_external = info->file_external; + + *status = stat; + + return SVN_NO_ERROR; +} + +/* Fill in *STATUS for the unversioned path LOCAL_ABSPATH, using data + available in DB. Allocate *STATUS in POOL. Use SCRATCH_POOL for + temporary allocations. + + If IS_IGNORED is non-zero and this is a non-versioned entity, set + the node_status to svn_wc_status_none. Otherwise set the + node_status to svn_wc_status_unversioned. + */ +static svn_error_t * +assemble_unversioned(svn_wc_status3_t **status, + svn_wc__db_t *db, + const char *local_abspath, + const svn_io_dirent2_t *dirent, + svn_boolean_t tree_conflicted, + svn_boolean_t is_ignored, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_status3_t *stat; + + /* return a fairly blank structure. */ + stat = apr_pcalloc(result_pool, sizeof(*stat)); + + /*stat->versioned = FALSE;*/ + stat->kind = svn_node_unknown; /* not versioned */ + stat->depth = svn_depth_unknown; + stat->filesize = (dirent && dirent->kind == svn_node_file) + ? dirent->filesize + : SVN_INVALID_FILESIZE; + stat->node_status = svn_wc_status_none; + stat->text_status = svn_wc_status_none; + stat->prop_status = svn_wc_status_none; + stat->repos_node_status = svn_wc_status_none; + stat->repos_text_status = svn_wc_status_none; + stat->repos_prop_status = svn_wc_status_none; + + /* If this path has no entry, but IS present on disk, it's + unversioned. If this file is being explicitly ignored (due + to matching an ignore-pattern), the node_status is set to + svn_wc_status_ignored. Otherwise the node_status is set to + svn_wc_status_unversioned. */ + if (dirent && dirent->kind != svn_node_none) + { + if (is_ignored) + stat->node_status = svn_wc_status_ignored; + else + stat->node_status = svn_wc_status_unversioned; + } + else if (tree_conflicted) + { + /* If this path has no entry, is NOT present on disk, and IS a + tree conflict victim, report it as conflicted. */ + stat->node_status = svn_wc_status_conflicted; + } + + stat->revision = SVN_INVALID_REVNUM; + stat->changed_rev = SVN_INVALID_REVNUM; + stat->ood_changed_rev = SVN_INVALID_REVNUM; + stat->ood_kind = svn_node_none; + + /* For the case of an incoming delete to a locally deleted path during + an update, we get a tree conflict. */ + stat->conflicted = tree_conflicted; + stat->changelist = NULL; + + *status = stat; + return SVN_NO_ERROR; +} + + +/* Given an ENTRY object representing PATH, build a status structure + and pass it off to the STATUS_FUNC/STATUS_BATON. All other + arguments are the same as those passed to assemble_status(). */ +static svn_error_t * +send_status_structure(const struct walk_status_baton *wb, + const char *local_abspath, + const char *parent_repos_root_url, + const char *parent_repos_relpath, + const char *parent_repos_uuid, + const struct svn_wc__db_info_t *info, + const svn_io_dirent2_t *dirent, + svn_boolean_t get_all, + svn_wc_status_func4_t status_func, + void *status_baton, + apr_pool_t *scratch_pool) +{ + svn_wc_status3_t *statstruct; + const svn_lock_t *repos_lock = NULL; + + /* Check for a repository lock. */ + if (wb->repos_locks) + { + const char *repos_relpath, *repos_root_url, *repos_uuid; + + SVN_ERR(get_repos_root_url_relpath(&repos_relpath, &repos_root_url, + &repos_uuid, + info, parent_repos_relpath, + parent_repos_root_url, + parent_repos_uuid, + wb->db, local_abspath, + scratch_pool, scratch_pool)); + if (repos_relpath) + { + /* repos_lock still uses the deprecated filesystem absolute path + format */ + repos_lock = svn_hash_gets(wb->repos_locks, + svn_fspath__join("/", repos_relpath, + scratch_pool)); + } + } + + SVN_ERR(assemble_status(&statstruct, wb->db, local_abspath, + parent_repos_root_url, parent_repos_relpath, + parent_repos_uuid, + info, dirent, get_all, wb->ignore_text_mods, + repos_lock, scratch_pool, scratch_pool)); + + if (statstruct && status_func) + return svn_error_trace((*status_func)(status_baton, local_abspath, + statstruct, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* Store in *PATTERNS a list of ignores collected from svn:ignore properties + on LOCAL_ABSPATH and svn:global-ignores on LOCAL_ABSPATH and its + repository ancestors (as cached in the working copy), including the default + ignores passed in as IGNORES. + + Upon return, *PATTERNS will contain zero or more (const char *) + patterns from the value of the SVN_PROP_IGNORE property set on + the working directory path. + + IGNORES is a list of patterns to include; typically this will + be the default ignores as, for example, specified in a config file. + + DB, LOCAL_ABSPATH is used to access the working copy. + + Allocate results in RESULT_POOL, temporary stuffs in SCRATCH_POOL. + + None of the arguments may be NULL. +*/ +static svn_error_t * +collect_ignore_patterns(apr_array_header_t **patterns, + svn_wc__db_t *db, + const char *local_abspath, + const apr_array_header_t *ignores, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i; + apr_hash_t *props; + apr_array_header_t *inherited_props; + svn_error_t *err; + + /* ### assert we are passed a directory? */ + + *patterns = apr_array_make(result_pool, 1, sizeof(const char *)); + + /* Copy default ignores into the local PATTERNS array. */ + for (i = 0; i < ignores->nelts; i++) + { + const char *ignore = APR_ARRAY_IDX(ignores, i, const char *); + APR_ARRAY_PUSH(*patterns, const char *) = apr_pstrdup(result_pool, + ignore); + } + + err = svn_wc__db_read_inherited_props(&inherited_props, &props, + db, local_abspath, + SVN_PROP_INHERITABLE_IGNORES, + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + + svn_error_clear(err); + return SVN_NO_ERROR; + } + + if (props) + { + const svn_string_t *value; + + value = svn_hash_gets(props, SVN_PROP_IGNORE); + if (value) + svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE, + result_pool); + + value = svn_hash_gets(props, SVN_PROP_INHERITABLE_IGNORES); + if (value) + svn_cstring_split_append(*patterns, value->data, "\n\r", FALSE, + result_pool); + } + + for (i = 0; i < inherited_props->nelts; i++) + { + svn_prop_inherited_item_t *elt = APR_ARRAY_IDX( + inherited_props, i, svn_prop_inherited_item_t *); + const svn_string_t *value; + + value = svn_hash_gets(elt->prop_hash, SVN_PROP_INHERITABLE_IGNORES); + + if (value) + svn_cstring_split_append(*patterns, value->data, + "\n\r", FALSE, result_pool); + } + + return SVN_NO_ERROR; +} + + +/* Compare LOCAL_ABSPATH with items in the EXTERNALS hash to see if + LOCAL_ABSPATH is the drop location for, or an intermediate directory + of the drop location for, an externals definition. Use SCRATCH_POOL + for scratchwork. */ +static svn_boolean_t +is_external_path(apr_hash_t *externals, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + /* First try: does the path exist as a key in the hash? */ + if (svn_hash_gets(externals, local_abspath)) + return TRUE; + + /* Failing that, we need to check if any external is a child of + LOCAL_ABSPATH. */ + for (hi = apr_hash_first(scratch_pool, externals); + hi; + hi = apr_hash_next(hi)) + { + const char *external_abspath = svn__apr_hash_index_key(hi); + + if (svn_dirent_is_child(local_abspath, external_abspath, NULL)) + return TRUE; + } + + return FALSE; +} + + +/* Assuming that LOCAL_ABSPATH is unversioned, send a status structure + for it through STATUS_FUNC/STATUS_BATON unless this path is being + ignored. This function should never be called on a versioned entry. + + LOCAL_ABSPATH is the path to the unversioned file whose status is being + requested. PATH_KIND is the node kind of NAME as determined by the + caller. PATH_SPECIAL is the special status of the path, also determined + by the caller. + PATTERNS points to a list of filename patterns which are marked as ignored. + None of these parameter may be NULL. + + If NO_IGNORE is TRUE, the item will be added regardless of + whether it is ignored; otherwise we will only add the item if it + does not match any of the patterns in PATTERN or INHERITED_IGNORES. + + Allocate everything in POOL. +*/ +static svn_error_t * +send_unversioned_item(const struct walk_status_baton *wb, + const char *local_abspath, + const svn_io_dirent2_t *dirent, + svn_boolean_t tree_conflicted, + const apr_array_header_t *patterns, + svn_boolean_t no_ignore, + svn_wc_status_func4_t status_func, + void *status_baton, + apr_pool_t *scratch_pool) +{ + svn_boolean_t is_ignored; + svn_boolean_t is_external; + svn_wc_status3_t *status; + const char *base_name = svn_dirent_basename(local_abspath, NULL); + + is_ignored = svn_wc_match_ignore_list(base_name, patterns, scratch_pool); + SVN_ERR(assemble_unversioned(&status, + wb->db, local_abspath, + dirent, tree_conflicted, + is_ignored, + scratch_pool, scratch_pool)); + + is_external = is_external_path(wb->externals, local_abspath, scratch_pool); + if (is_external) + status->node_status = svn_wc_status_external; + + /* We can have a tree conflict on an unversioned path, i.e. an incoming + * delete on a locally deleted path during an update. Don't ever ignore + * those! */ + if (status->conflicted) + is_ignored = FALSE; + + /* If we aren't ignoring it, or if it's an externals path, pass this + entry to the status func. */ + if (no_ignore + || !is_ignored + || is_external) + return svn_error_trace((*status_func)(status_baton, local_abspath, + status, scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +get_dir_status(const struct walk_status_baton *wb, + const char *local_abspath, + svn_boolean_t skip_this_dir, + const char *parent_repos_root_url, + const char *parent_repos_relpath, + const char *parent_repos_uuid, + const struct svn_wc__db_info_t *dir_info, + const svn_io_dirent2_t *dirent, + const apr_array_header_t *ignore_patterns, + svn_depth_t depth, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* Send out a status structure according to the information gathered on one + * child node. (Basically this function is the guts of the loop in + * get_dir_status() and of get_child_status().) + * + * Send a status structure of LOCAL_ABSPATH. PARENT_ABSPATH must be the + * dirname of LOCAL_ABSPATH. + * + * INFO should reflect the information on LOCAL_ABSPATH; LOCAL_ABSPATH must + * be an unversioned file or dir, or a versioned file. For versioned + * directories use get_dir_status() instead. + * + * INFO may be NULL for an unversioned node. If such node has a tree conflict, + * UNVERSIONED_TREE_CONFLICTED may be set to TRUE. If INFO is non-NULL, + * UNVERSIONED_TREE_CONFLICTED is ignored. + * + * DIRENT should reflect LOCAL_ABSPATH's dirent information. + * + * DIR_REPOS_* should reflect LOCAL_ABSPATH's parent URL, i.e. LOCAL_ABSPATH's + * URL treated with svn_uri_dirname(). ### TODO verify this (externals) + * + * If *COLLECTED_IGNORE_PATTERNS is NULL and ignore patterns are needed in this + * call, then *COLLECTED_IGNORE_PATTERNS will be set to an apr_array_header_t* + * containing all ignore patterns, as returned by collect_ignore_patterns() on + * PARENT_ABSPATH and IGNORE_PATTERNS. If *COLLECTED_IGNORE_PATTERNS is passed + * non-NULL, it is assumed it already holds those results. + * This speeds up repeated calls with the same PARENT_ABSPATH. + * + * *COLLECTED_IGNORE_PATTERNS will be allocated in RESULT_POOL. All other + * allocations are made in SCRATCH_POOL. + * + * The remaining parameters correspond to get_dir_status(). */ +static svn_error_t * +one_child_status(const struct walk_status_baton *wb, + const char *local_abspath, + const char *parent_abspath, + const struct svn_wc__db_info_t *info, + const svn_io_dirent2_t *dirent, + const char *dir_repos_root_url, + const char *dir_repos_relpath, + const char *dir_repos_uuid, + svn_boolean_t unversioned_tree_conflicted, + apr_array_header_t **collected_ignore_patterns, + const apr_array_header_t *ignore_patterns, + svn_depth_t depth, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t conflicted = info ? info->conflicted + : unversioned_tree_conflicted; + + if (info + && info->status != svn_wc__db_status_not_present + && info->status != svn_wc__db_status_excluded + && info->status != svn_wc__db_status_server_excluded + && !(info->kind == svn_node_unknown + && info->status == svn_wc__db_status_normal)) + { + if (depth == svn_depth_files + && info->kind == svn_node_dir) + { + return SVN_NO_ERROR; + } + + SVN_ERR(send_status_structure(wb, local_abspath, + dir_repos_root_url, + dir_repos_relpath, + dir_repos_uuid, + info, dirent, get_all, + status_func, status_baton, + scratch_pool)); + + /* Descend in subdirectories. */ + if (depth == svn_depth_infinity + && info->kind == svn_node_dir) + { + SVN_ERR(get_dir_status(wb, local_abspath, TRUE, + dir_repos_root_url, dir_repos_relpath, + dir_repos_uuid, info, + dirent, ignore_patterns, + svn_depth_infinity, get_all, + no_ignore, + status_func, status_baton, + cancel_func, cancel_baton, + scratch_pool)); + } + + return SVN_NO_ERROR; + } + + /* If conflicted, fall right through to unversioned. + * With depth_files, show all conflicts, even if their report is only + * about directories. A tree conflict may actually report two different + * kinds, so it's not so easy to define what depth=files means. We could go + * look up the kinds in the conflict ... just show all. */ + if (! conflicted) + { + /* Selected node, but not found */ + if (dirent == NULL) + return SVN_NO_ERROR; + + if (depth == svn_depth_files && dirent->kind == svn_node_dir) + return SVN_NO_ERROR; + + if (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL), + scratch_pool)) + return SVN_NO_ERROR; + } + + /* The node exists on disk but there is no versioned information about it, + * or it doesn't exist but is a tree conflicted path or should be + * reported not-present. */ + + /* Why pass ignore patterns on a tree conflicted node, even if it should + * always show up in clients' status reports anyway? Because the calling + * client decides whether to ignore, and thus this flag needs to be + * determined. For example, in 'svn status', plain unversioned nodes show + * as '? C', where ignored ones show as 'I C'. */ + + if (ignore_patterns && ! *collected_ignore_patterns) + SVN_ERR(collect_ignore_patterns(collected_ignore_patterns, + wb->db, parent_abspath, ignore_patterns, + result_pool, scratch_pool)); + + SVN_ERR(send_unversioned_item(wb, + local_abspath, + dirent, + conflicted, + *collected_ignore_patterns, + no_ignore, + status_func, status_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Send svn_wc_status3_t * structures for the directory LOCAL_ABSPATH and + for all its child nodes (according to DEPTH) through STATUS_FUNC / + STATUS_BATON. + + If SKIP_THIS_DIR is TRUE, the directory's own status will not be reported. + All subdirs reached by recursion will be reported regardless of this + parameter's value. + + PARENT_REPOS_* parameters can be set to refer to LOCAL_ABSPATH's parent's + URL, i.e. the URL the WC reflects at the dirname of LOCAL_ABSPATH, to avoid + retrieving them again. Otherwise they must be NULL. + + DIR_INFO can be set to the information of LOCAL_ABSPATH, to avoid retrieving + it again. Otherwise it must be NULL. + + DIRENT is LOCAL_ABSPATH's own dirent and is only needed if it is reported, + so if SKIP_THIS_DIR is TRUE, DIRENT can be left NULL. + + Other arguments are the same as those passed to + svn_wc_get_status_editor5(). */ +static svn_error_t * +get_dir_status(const struct walk_status_baton *wb, + const char *local_abspath, + svn_boolean_t skip_this_dir, + const char *parent_repos_root_url, + const char *parent_repos_relpath, + const char *parent_repos_uuid, + const struct svn_wc__db_info_t *dir_info, + const svn_io_dirent2_t *dirent, + const apr_array_header_t *ignore_patterns, + svn_depth_t depth, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const char *dir_repos_root_url; + const char *dir_repos_relpath; + const char *dir_repos_uuid; + apr_hash_t *dirents, *nodes, *conflicts, *all_children; + apr_array_header_t *sorted_children; + apr_array_header_t *collected_ignore_patterns = NULL; + apr_pool_t *iterpool; + svn_error_t *err; + int i; + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + if (depth == svn_depth_unknown) + depth = svn_depth_infinity; + + iterpool = svn_pool_create(scratch_pool); + + err = svn_io_get_dirents3(&dirents, local_abspath, FALSE, scratch_pool, + iterpool); + if (err + && (APR_STATUS_IS_ENOENT(err->apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) + { + svn_error_clear(err); + dirents = apr_hash_make(scratch_pool); + } + else + SVN_ERR(err); + + if (!dir_info) + SVN_ERR(read_info(&dir_info, local_abspath, wb->db, + scratch_pool, iterpool)); + + SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url, + &dir_repos_uuid, dir_info, + parent_repos_relpath, + parent_repos_root_url, parent_repos_uuid, + wb->db, local_abspath, + scratch_pool, iterpool)); + + /* Create a hash containing all children. The source hashes + don't all map the same types, but only the keys of the result + hash are subsequently used. */ + SVN_ERR(svn_wc__db_read_children_info(&nodes, &conflicts, + wb->db, local_abspath, + scratch_pool, iterpool)); + + all_children = apr_hash_overlay(scratch_pool, nodes, dirents); + if (apr_hash_count(conflicts) > 0) + all_children = apr_hash_overlay(scratch_pool, conflicts, all_children); + + /* Handle "this-dir" first. */ + if (! skip_this_dir) + { + /* This code is not conditional on HAVE_SYMLINK as some systems that do + not allow creating symlinks (!HAVE_SYMLINK) can still encounter + symlinks (or in case of Windows also 'Junctions') created by other + methods. + + Without this block a working copy in the root of a junction is + reported as an obstruction, because the junction itself is reported as + special. + + Systems that have no symlink support at all, would always see + dirent->special as FALSE, so even there enabling this code shouldn't + produce problems. + */ + if (dirent->special) + { + svn_io_dirent2_t *this_dirent = svn_io_dirent2_dup(dirent, iterpool); + + /* We're being pointed to "this-dir" via a symlink. + * Get the real node kind and pretend the path is not a symlink. + * This prevents send_status_structure() from treating this-dir + * as a directory obstructed by a file. */ + SVN_ERR(svn_io_check_resolved_path(local_abspath, + &this_dirent->kind, iterpool)); + this_dirent->special = FALSE; + SVN_ERR(send_status_structure(wb, local_abspath, + parent_repos_root_url, + parent_repos_relpath, + parent_repos_uuid, + dir_info, this_dirent, get_all, + status_func, status_baton, + iterpool)); + } + else + SVN_ERR(send_status_structure(wb, local_abspath, + parent_repos_root_url, + parent_repos_relpath, + parent_repos_uuid, + dir_info, dirent, get_all, + status_func, status_baton, + iterpool)); + } + + /* If the requested depth is empty, we only need status on this-dir. */ + if (depth == svn_depth_empty) + return SVN_NO_ERROR; + + /* Walk all the children of this directory. */ + sorted_children = svn_sort__hash(all_children, + svn_sort_compare_items_lexically, + scratch_pool); + for (i = 0; i < sorted_children->nelts; i++) + { + const void *key; + apr_ssize_t klen; + svn_sort__item_t item; + const char *child_abspath; + svn_io_dirent2_t *child_dirent; + const struct svn_wc__db_info_t *child_info; + + svn_pool_clear(iterpool); + + item = APR_ARRAY_IDX(sorted_children, i, svn_sort__item_t); + key = item.key; + klen = item.klen; + + child_abspath = svn_dirent_join(local_abspath, key, iterpool); + child_dirent = apr_hash_get(dirents, key, klen); + child_info = apr_hash_get(nodes, key, klen); + + SVN_ERR(one_child_status(wb, + child_abspath, + local_abspath, + child_info, + child_dirent, + dir_repos_root_url, + dir_repos_relpath, + dir_repos_uuid, + apr_hash_get(conflicts, key, klen) != NULL, + &collected_ignore_patterns, + ignore_patterns, + depth, + get_all, + no_ignore, + status_func, + status_baton, + cancel_func, + cancel_baton, + scratch_pool, + iterpool)); + } + + /* Destroy our subpools. */ + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Send an svn_wc_status3_t * structure for the versioned file, or for the + * unversioned file or directory, LOCAL_ABSPATH, which is not ignored (an + * explicit target). Does not recurse. + * + * INFO should reflect LOCAL_ABSPATH's information, but should be NULL for + * unversioned nodes. An unversioned and tree-conflicted node however should + * pass a non-NULL INFO as returned by read_info() (INFO->CONFLICTED = TRUE). + * + * DIRENT should reflect LOCAL_ABSPATH. + * + * All allocations made in SCRATCH_POOL. + * + * The remaining parameters correspond to get_dir_status(). */ +static svn_error_t * +get_child_status(const struct walk_status_baton *wb, + const char *local_abspath, + const struct svn_wc__db_info_t *info, + const svn_io_dirent2_t *dirent, + const apr_array_header_t *ignore_patterns, + svn_boolean_t get_all, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const char *dir_repos_root_url; + const char *dir_repos_relpath; + const char *dir_repos_uuid; + const struct svn_wc__db_info_t *dir_info; + apr_array_header_t *collected_ignore_patterns = NULL; + const char *parent_abspath = svn_dirent_dirname(local_abspath, + scratch_pool); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + if (dirent->kind == svn_node_none) + dirent = NULL; + + SVN_ERR(read_info(&dir_info, parent_abspath, wb->db, + scratch_pool, scratch_pool)); + + SVN_ERR(get_repos_root_url_relpath(&dir_repos_relpath, &dir_repos_root_url, + &dir_repos_uuid, dir_info, + NULL, NULL, NULL, + wb->db, parent_abspath, + scratch_pool, scratch_pool)); + + /* An unversioned node with a tree conflict will see an INFO != NULL here, + * in which case the FALSE passed for UNVERSIONED_TREE_CONFLICTED has no + * effect and INFO->CONFLICTED counts. + * ### Maybe svn_wc__db_read_children_info() and read_info() should be more + * ### alike? */ + SVN_ERR(one_child_status(wb, + local_abspath, + parent_abspath, + info, + dirent, + dir_repos_root_url, + dir_repos_relpath, + dir_repos_uuid, + FALSE, /* unversioned_tree_conflicted */ + &collected_ignore_patterns, + ignore_patterns, + svn_depth_empty, + get_all, + TRUE, /* no_ignore. This is an explicit target. */ + status_func, + status_baton, + cancel_func, + cancel_baton, + scratch_pool, + scratch_pool)); + return SVN_NO_ERROR; +} + + + +/*** Helpers ***/ + +/* A faux status callback function for stashing STATUS item in an hash + (which is the BATON), keyed on PATH. This implements the + svn_wc_status_func4_t interface. */ +static svn_error_t * +hash_stash(void *baton, + const char *path, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + apr_hash_t *stat_hash = baton; + apr_pool_t *hash_pool = apr_hash_pool_get(stat_hash); + assert(! svn_hash_gets(stat_hash, path)); + svn_hash_sets(stat_hash, apr_pstrdup(hash_pool, path), + svn_wc_dup_status3(status, hash_pool)); + + return SVN_NO_ERROR; +} + + +/* Look up the key PATH in BATON->STATII. IS_DIR_BATON indicates whether + baton is a struct *dir_baton or struct *file_baton. If the value doesn't + yet exist, and the REPOS_NODE_STATUS indicates that this is an addition, + create a new status struct using the hash's pool. + + If IS_DIR_BATON is true, THIS_DIR_BATON is a *dir_baton cotaining the out + of date (ood) information we want to set in BATON. This is necessary + because this function tweaks the status of out-of-date directories + (BATON == THIS_DIR_BATON) and out-of-date directories' parents + (BATON == THIS_DIR_BATON->parent_baton). In the latter case THIS_DIR_BATON + contains the ood info we want to bubble up to ancestor directories so these + accurately reflect the fact they have an ood descendant. + + Merge REPOS_NODE_STATUS, REPOS_TEXT_STATUS and REPOS_PROP_STATUS into the + status structure's "network" fields. + + Iff IS_DIR_BATON is true, DELETED_REV is used as follows, otherwise it + is ignored: + + If REPOS_NODE_STATUS is svn_wc_status_deleted then DELETED_REV is + optionally the revision path was deleted, in all other cases it must + be set to SVN_INVALID_REVNUM. If DELETED_REV is not + SVN_INVALID_REVNUM and REPOS_TEXT_STATUS is svn_wc_status_deleted, + then use DELETED_REV to set PATH's ood_last_cmt_rev field in BATON. + If DELETED_REV is SVN_INVALID_REVNUM and REPOS_NODE_STATUS is + svn_wc_status_deleted, set PATH's ood_last_cmt_rev to its parent's + ood_last_cmt_rev value - see comment below. + + If a new struct was added, set the repos_lock to REPOS_LOCK. */ +static svn_error_t * +tweak_statushash(void *baton, + void *this_dir_baton, + svn_boolean_t is_dir_baton, + svn_wc__db_t *db, + const char *local_abspath, + enum svn_wc_status_kind repos_node_status, + enum svn_wc_status_kind repos_text_status, + enum svn_wc_status_kind repos_prop_status, + svn_revnum_t deleted_rev, + const svn_lock_t *repos_lock, + apr_pool_t *scratch_pool) +{ + svn_wc_status3_t *statstruct; + apr_pool_t *pool; + apr_hash_t *statushash; + + if (is_dir_baton) + statushash = ((struct dir_baton *) baton)->statii; + else + statushash = ((struct file_baton *) baton)->dir_baton->statii; + pool = apr_hash_pool_get(statushash); + + /* Is PATH already a hash-key? */ + statstruct = svn_hash_gets(statushash, local_abspath); + + /* If not, make it so. */ + if (! statstruct) + { + /* If this item isn't being added, then we're most likely + dealing with a non-recursive (or at least partially + non-recursive) working copy. Due to bugs in how the client + reports the state of non-recursive working copies, the + repository can send back responses about paths that don't + even exist locally. Our best course here is just to ignore + those responses. After all, if the client had reported + correctly in the first, that path would either be mentioned + as an 'add' or not mentioned at all, depending on how we + eventually fix the bugs in non-recursivity. See issue + #2122 for details. */ + if (repos_node_status != svn_wc_status_added) + return SVN_NO_ERROR; + + /* Use the public API to get a statstruct, and put it into the hash. */ + SVN_ERR(internal_status(&statstruct, db, local_abspath, pool, + scratch_pool)); + statstruct->repos_lock = repos_lock; + svn_hash_sets(statushash, apr_pstrdup(pool, local_abspath), statstruct); + } + + /* Merge a repos "delete" + "add" into a single "replace". */ + if ((repos_node_status == svn_wc_status_added) + && (statstruct->repos_node_status == svn_wc_status_deleted)) + repos_node_status = svn_wc_status_replaced; + + /* Tweak the structure's repos fields. */ + if (repos_node_status) + statstruct->repos_node_status = repos_node_status; + if (repos_text_status) + statstruct->repos_text_status = repos_text_status; + if (repos_prop_status) + statstruct->repos_prop_status = repos_prop_status; + + /* Copy out-of-date info. */ + if (is_dir_baton) + { + struct dir_baton *b = this_dir_baton; + + if (!statstruct->repos_relpath && b->repos_relpath) + { + if (statstruct->repos_node_status == svn_wc_status_deleted) + { + /* When deleting PATH, BATON is for PATH's parent, + so we must construct PATH's real statstruct->url. */ + statstruct->repos_relpath = + svn_relpath_join(b->repos_relpath, + svn_dirent_basename(local_abspath, + NULL), + pool); + } + else + statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath); + + statstruct->repos_root_url = + b->edit_baton->anchor_status->repos_root_url; + statstruct->repos_uuid = + b->edit_baton->anchor_status->repos_uuid; + } + + /* The last committed date, and author for deleted items + isn't available. */ + if (statstruct->repos_node_status == svn_wc_status_deleted) + { + statstruct->ood_kind = statstruct->kind; + + /* Pre 1.5 servers don't provide the revision a path was deleted. + So we punt and use the last committed revision of the path's + parent, which has some chance of being correct. At worse it + is a higher revision than the path was deleted, but this is + better than nothing... */ + if (deleted_rev == SVN_INVALID_REVNUM) + statstruct->ood_changed_rev = + ((struct dir_baton *) baton)->ood_changed_rev; + else + statstruct->ood_changed_rev = deleted_rev; + } + else + { + statstruct->ood_kind = b->ood_kind; + statstruct->ood_changed_rev = b->ood_changed_rev; + statstruct->ood_changed_date = b->ood_changed_date; + if (b->ood_changed_author) + statstruct->ood_changed_author = + apr_pstrdup(pool, b->ood_changed_author); + } + + } + else + { + struct file_baton *b = baton; + statstruct->ood_changed_rev = b->ood_changed_rev; + statstruct->ood_changed_date = b->ood_changed_date; + if (!statstruct->repos_relpath && b->repos_relpath) + { + statstruct->repos_relpath = apr_pstrdup(pool, b->repos_relpath); + statstruct->repos_root_url = + b->edit_baton->anchor_status->repos_root_url; + statstruct->repos_uuid = + b->edit_baton->anchor_status->repos_uuid; + } + statstruct->ood_kind = b->ood_kind; + if (b->ood_changed_author) + statstruct->ood_changed_author = + apr_pstrdup(pool, b->ood_changed_author); + } + return SVN_NO_ERROR; +} + +/* Returns the URL for DB */ +static const char * +find_dir_repos_relpath(const struct dir_baton *db, apr_pool_t *pool) +{ + /* If we have no name, we're the root, return the anchor URL. */ + if (! db->name) + return db->edit_baton->anchor_status->repos_relpath; + else + { + const char *repos_relpath; + struct dir_baton *pb = db->parent_baton; + const svn_wc_status3_t *status = svn_hash_gets(pb->statii, + db->local_abspath); + /* Note that status->repos_relpath could be NULL in the case of a missing + * directory, which means we need to recurse up another level to get + * a useful relpath. */ + if (status && status->repos_relpath) + return status->repos_relpath; + + repos_relpath = find_dir_repos_relpath(pb, pool); + return svn_relpath_join(repos_relpath, db->name, pool); + } +} + + + +/* Create a new dir_baton for subdir PATH. */ +static svn_error_t * +make_dir_baton(void **dir_baton, + const char *path, + struct edit_baton *edit_baton, + struct dir_baton *parent_baton, + apr_pool_t *result_pool) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = edit_baton; + struct dir_baton *d; + const char *local_abspath; + const svn_wc_status3_t *status_in_parent; + apr_pool_t *dir_pool; + + if (parent_baton) + dir_pool = svn_pool_create(parent_baton->pool); + else + dir_pool = svn_pool_create(result_pool); + + d = apr_pcalloc(dir_pool, sizeof(*d)); + + SVN_ERR_ASSERT(path || (! pb)); + + /* Construct the absolute path of this directory. */ + if (pb) + local_abspath = svn_dirent_join(eb->anchor_abspath, path, dir_pool); + else + local_abspath = eb->anchor_abspath; + + /* Finish populating the baton members. */ + d->pool = dir_pool; + d->local_abspath = local_abspath; + d->name = path ? svn_dirent_basename(path, dir_pool) : NULL; + d->edit_baton = edit_baton; + d->parent_baton = parent_baton; + d->statii = apr_hash_make(dir_pool); + d->ood_changed_rev = SVN_INVALID_REVNUM; + d->ood_changed_date = 0; + d->repos_relpath = find_dir_repos_relpath(d, dir_pool); + d->ood_kind = svn_node_dir; + d->ood_changed_author = NULL; + + if (pb) + { + if (pb->excluded) + d->excluded = TRUE; + else if (pb->depth == svn_depth_immediates) + d->depth = svn_depth_empty; + else if (pb->depth == svn_depth_files || pb->depth == svn_depth_empty) + d->excluded = TRUE; + else if (pb->depth == svn_depth_unknown) + /* This is only tentative, it can be overridden from d's entry + later. */ + d->depth = svn_depth_unknown; + else + d->depth = svn_depth_infinity; + } + else + { + d->depth = eb->default_depth; + } + + /* Get the status for this path's children. Of course, we only want + to do this if the path is versioned as a directory. */ + if (pb) + status_in_parent = svn_hash_gets(pb->statii, d->local_abspath); + else + status_in_parent = eb->anchor_status; + + if (status_in_parent + && status_in_parent->versioned + && (status_in_parent->kind == svn_node_dir) + && (! d->excluded) + && (d->depth == svn_depth_unknown + || d->depth == svn_depth_infinity + || d->depth == svn_depth_files + || d->depth == svn_depth_immediates) + ) + { + const svn_wc_status3_t *this_dir_status; + const apr_array_header_t *ignores = eb->ignores; + + SVN_ERR(get_dir_status(&eb->wb, local_abspath, TRUE, + status_in_parent->repos_root_url, + NULL /*parent_repos_relpath*/, + status_in_parent->repos_uuid, + NULL, + NULL /* dirent */, ignores, + d->depth == svn_depth_files + ? svn_depth_files + : svn_depth_immediates, + TRUE, TRUE, + hash_stash, d->statii, + eb->cancel_func, eb->cancel_baton, + dir_pool)); + + /* If we found a depth here, it should govern. */ + this_dir_status = svn_hash_gets(d->statii, d->local_abspath); + if (this_dir_status && this_dir_status->versioned + && (d->depth == svn_depth_unknown + || d->depth > status_in_parent->depth)) + { + d->depth = this_dir_status->depth; + } + } + + *dir_baton = d; + return SVN_NO_ERROR; +} + + +/* Make a file baton, using a new subpool of PARENT_DIR_BATON's pool. + NAME is just one component, not a path. */ +static struct file_baton * +make_file_baton(struct dir_baton *parent_dir_baton, + const char *path, + apr_pool_t *pool) +{ + struct dir_baton *pb = parent_dir_baton; + struct edit_baton *eb = pb->edit_baton; + struct file_baton *f = apr_pcalloc(pool, sizeof(*f)); + + /* Finish populating the baton members. */ + f->local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool); + f->name = svn_dirent_basename(f->local_abspath, NULL); + f->pool = pool; + f->dir_baton = pb; + f->edit_baton = eb; + f->ood_changed_rev = SVN_INVALID_REVNUM; + f->ood_changed_date = 0; + f->repos_relpath = svn_relpath_join(find_dir_repos_relpath(pb, pool), + f->name, pool); + f->ood_kind = svn_node_file; + f->ood_changed_author = NULL; + return f; +} + + +/** + * Return a boolean answer to the question "Is @a status something that + * should be reported?". @a no_ignore and @a get_all are the same as + * svn_wc_get_status_editor4(). + */ +static svn_boolean_t +is_sendable_status(const svn_wc_status3_t *status, + svn_boolean_t no_ignore, + svn_boolean_t get_all) +{ + /* If the repository status was touched at all, it's interesting. */ + if (status->repos_node_status != svn_wc_status_none) + return TRUE; + + /* If there is a lock in the repository, send it. */ + if (status->repos_lock) + return TRUE; + + if (status->conflicted) + return TRUE; + + /* If the item is ignored, and we don't want ignores, skip it. */ + if ((status->node_status == svn_wc_status_ignored) && (! no_ignore)) + return FALSE; + + /* If we want everything, we obviously want this single-item subset + of everything. */ + if (get_all) + return TRUE; + + /* If the item is unversioned, display it. */ + if (status->node_status == svn_wc_status_unversioned) + return TRUE; + + /* If the text, property or tree state is interesting, send it. */ + if ((status->node_status != svn_wc_status_none + && (status->node_status != svn_wc_status_normal))) + return TRUE; + + /* If it's switched, send it. */ + if (status->switched) + return TRUE; + + /* If there is a lock token, send it. */ + if (status->versioned && status->lock) + return TRUE; + + /* If the entry is associated with a changelist, send it. */ + if (status->changelist) + return TRUE; + + /* Otherwise, don't send it. */ + return FALSE; +} + + +/* Baton for mark_status. */ +struct status_baton +{ + svn_wc_status_func4_t real_status_func; /* real status function */ + void *real_status_baton; /* real status baton */ +}; + +/* A status callback function which wraps the *real* status + function/baton. It simply sets the "repos_node_status" field of the + STATUS to svn_wc_status_deleted and passes it off to the real + status func/baton. Implements svn_wc_status_func4_t */ +static svn_error_t * +mark_deleted(void *baton, + const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + struct status_baton *sb = baton; + svn_wc_status3_t *new_status = svn_wc_dup_status3(status, scratch_pool); + new_status->repos_node_status = svn_wc_status_deleted; + return sb->real_status_func(sb->real_status_baton, local_abspath, + new_status, scratch_pool); +} + + +/* Handle a directory's STATII hash. EB is the edit baton. DIR_PATH + and DIR_ENTRY are the on-disk path and entry, respectively, for the + directory itself. Descend into subdirectories according to DEPTH. + Also, if DIR_WAS_DELETED is set, each status that is reported + through this function will have its repos_text_status field showing + a deletion. Use POOL for all allocations. */ +static svn_error_t * +handle_statii(struct edit_baton *eb, + const char *dir_repos_root_url, + const char *dir_repos_relpath, + const char *dir_repos_uuid, + apr_hash_t *statii, + svn_boolean_t dir_was_deleted, + svn_depth_t depth, + apr_pool_t *pool) +{ + const apr_array_header_t *ignores = eb->ignores; + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(pool); + svn_wc_status_func4_t status_func = eb->status_func; + void *status_baton = eb->status_baton; + struct status_baton sb; + + if (dir_was_deleted) + { + sb.real_status_func = eb->status_func; + sb.real_status_baton = eb->status_baton; + status_func = mark_deleted; + status_baton = &sb; + } + + /* Loop over all the statii still in our hash, handling each one. */ + for (hi = apr_hash_first(pool, statii); hi; hi = apr_hash_next(hi)) + { + const char *local_abspath = svn__apr_hash_index_key(hi); + svn_wc_status3_t *status = svn__apr_hash_index_val(hi); + + /* Clear the subpool. */ + svn_pool_clear(iterpool); + + /* Now, handle the status. We don't recurse for svn_depth_immediates + because we already have the subdirectories' statii. */ + if (status->versioned && status->kind == svn_node_dir + && (depth == svn_depth_unknown + || depth == svn_depth_infinity)) + { + SVN_ERR(get_dir_status(&eb->wb, + local_abspath, TRUE, + dir_repos_root_url, dir_repos_relpath, + dir_repos_uuid, + NULL, + NULL /* dirent */, + ignores, depth, eb->get_all, eb->no_ignore, + status_func, status_baton, + eb->cancel_func, eb->cancel_baton, + iterpool)); + } + if (dir_was_deleted) + status->repos_node_status = svn_wc_status_deleted; + if (is_sendable_status(status, eb->no_ignore, eb->get_all)) + SVN_ERR((eb->status_func)(eb->status_baton, local_abspath, status, + iterpool)); + } + + /* Destroy the subpool. */ + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/*----------------------------------------------------------------------*/ + +/*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/ + +/* An svn_delta_editor_t function. */ +static svn_error_t * +set_target_revision(void *edit_baton, + svn_revnum_t target_revision, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + *(eb->target_revision) = target_revision; + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +open_root(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **dir_baton) +{ + struct edit_baton *eb = edit_baton; + eb->root_opened = TRUE; + return make_dir_baton(dir_baton, NULL, eb, NULL, pool); +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t revision, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton *db = parent_baton; + struct edit_baton *eb = db->edit_baton; + const char *local_abspath = svn_dirent_join(eb->anchor_abspath, path, pool); + + /* Note: when something is deleted, it's okay to tweak the + statushash immediately. No need to wait until close_file or + close_dir, because there's no risk of having to honor the 'added' + flag. We already know this item exists in the working copy. */ + SVN_ERR(tweak_statushash(db, db, TRUE, eb->db, + local_abspath, + svn_wc_status_deleted, 0, 0, revision, NULL, pool)); + + /* Mark the parent dir -- it lost an entry (unless that parent dir + is the root node and we're not supposed to report on the root + node). */ + if (db->parent_baton && (! *eb->target_basename)) + SVN_ERR(tweak_statushash(db->parent_baton, db, TRUE,eb->db, + db->local_abspath, + svn_wc_status_modified, svn_wc_status_modified, + 0, SVN_INVALID_REVNUM, NULL, pool)); + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +add_directory(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct dir_baton *new_db; + + SVN_ERR(make_dir_baton(child_baton, path, eb, pb, pool)); + + /* Make this dir as added. */ + new_db = *child_baton; + new_db->added = TRUE; + + /* Mark the parent as changed; it gained an entry. */ + pb->text_changed = TRUE; + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +open_directory(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + return make_dir_baton(child_baton, path, pb->edit_baton, pb, pool); +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +change_dir_prop(void *dir_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + if (svn_wc_is_normal_prop(name)) + db->prop_changed = TRUE; + + /* Note any changes to the repository. */ + if (value != NULL) + { + if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0) + db->ood_changed_rev = SVN_STR_TO_REV(value->data); + else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0) + db->ood_changed_author = apr_pstrdup(db->pool, value->data); + else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0) + { + apr_time_t tm; + SVN_ERR(svn_time_from_cstring(&tm, value->data, db->pool)); + db->ood_changed_date = tm; + } + } + + return SVN_NO_ERROR; +} + + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +close_directory(void *dir_baton, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + struct dir_baton *pb = db->parent_baton; + struct edit_baton *eb = db->edit_baton; + apr_pool_t *scratch_pool = db->pool; + + /* If nothing has changed and directory has no out of + date descendants, return. */ + if (db->added || db->prop_changed || db->text_changed + || db->ood_changed_rev != SVN_INVALID_REVNUM) + { + enum svn_wc_status_kind repos_node_status; + enum svn_wc_status_kind repos_text_status; + enum svn_wc_status_kind repos_prop_status; + + /* If this is a new directory, add it to the statushash. */ + if (db->added) + { + repos_node_status = svn_wc_status_added; + repos_text_status = svn_wc_status_none; + repos_prop_status = db->prop_changed ? svn_wc_status_added + : svn_wc_status_none; + } + else + { + repos_node_status = (db->text_changed || db->prop_changed) + ? svn_wc_status_modified + : svn_wc_status_none; + repos_text_status = db->text_changed ? svn_wc_status_modified + : svn_wc_status_none; + repos_prop_status = db->prop_changed ? svn_wc_status_modified + : svn_wc_status_none; + } + + /* Maybe add this directory to its parent's status hash. Note + that tweak_statushash won't do anything if repos_text_status + is not svn_wc_status_added. */ + if (pb) + { + /* ### When we add directory locking, we need to find a + ### directory lock here. */ + SVN_ERR(tweak_statushash(pb, db, TRUE, eb->db, db->local_abspath, + repos_node_status, repos_text_status, + repos_prop_status, SVN_INVALID_REVNUM, NULL, + scratch_pool)); + } + else + { + /* We're editing the root dir of the WC. As its repos + status info isn't otherwise set, set it directly to + trigger invocation of the status callback below. */ + eb->anchor_status->repos_node_status = repos_node_status; + eb->anchor_status->repos_prop_status = repos_prop_status; + eb->anchor_status->repos_text_status = repos_text_status; + + /* If the root dir is out of date set the ood info directly too. */ + if (db->ood_changed_rev != eb->anchor_status->revision) + { + eb->anchor_status->ood_changed_rev = db->ood_changed_rev; + eb->anchor_status->ood_changed_date = db->ood_changed_date; + eb->anchor_status->ood_kind = db->ood_kind; + eb->anchor_status->ood_changed_author = + apr_pstrdup(pool, db->ood_changed_author); + } + } + } + + /* Handle this directory's statuses, and then note in the parent + that this has been done. */ + if (pb && ! db->excluded) + { + svn_boolean_t was_deleted = FALSE; + const svn_wc_status3_t *dir_status; + + /* See if the directory was deleted or replaced. */ + dir_status = svn_hash_gets(pb->statii, db->local_abspath); + if (dir_status && + ((dir_status->repos_node_status == svn_wc_status_deleted) + || (dir_status->repos_node_status == svn_wc_status_replaced))) + was_deleted = TRUE; + + /* Now do the status reporting. */ + SVN_ERR(handle_statii(eb, + dir_status ? dir_status->repos_root_url : NULL, + dir_status ? dir_status->repos_relpath : NULL, + dir_status ? dir_status->repos_uuid : NULL, + db->statii, was_deleted, db->depth, scratch_pool)); + if (dir_status && is_sendable_status(dir_status, eb->no_ignore, + eb->get_all)) + SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath, + dir_status, scratch_pool)); + svn_hash_sets(pb->statii, db->local_abspath, NULL); + } + else if (! pb) + { + /* If this is the top-most directory, and the operation had a + target, we should only report the target. */ + if (*eb->target_basename) + { + const svn_wc_status3_t *tgt_status; + + tgt_status = svn_hash_gets(db->statii, eb->target_abspath); + if (tgt_status) + { + if (tgt_status->versioned + && tgt_status->kind == svn_node_dir) + { + SVN_ERR(get_dir_status(&eb->wb, + eb->target_abspath, TRUE, + NULL, NULL, NULL, NULL, + NULL /* dirent */, + eb->ignores, + eb->default_depth, + eb->get_all, eb->no_ignore, + eb->status_func, eb->status_baton, + eb->cancel_func, eb->cancel_baton, + scratch_pool)); + } + if (is_sendable_status(tgt_status, eb->no_ignore, eb->get_all)) + SVN_ERR((eb->status_func)(eb->status_baton, eb->target_abspath, + tgt_status, scratch_pool)); + } + } + else + { + /* Otherwise, we report on all our children and ourself. + Note that our directory couldn't have been deleted, + because it is the root of the edit drive. */ + SVN_ERR(handle_statii(eb, + eb->anchor_status->repos_root_url, + eb->anchor_status->repos_relpath, + eb->anchor_status->repos_uuid, + db->statii, FALSE, eb->default_depth, + scratch_pool)); + if (is_sendable_status(eb->anchor_status, eb->no_ignore, + eb->get_all)) + SVN_ERR((eb->status_func)(eb->status_baton, db->local_abspath, + eb->anchor_status, scratch_pool)); + eb->anchor_status = NULL; + } + } + + svn_pool_clear(scratch_pool); /* Clear baton and its pool */ + + return SVN_NO_ERROR; +} + + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +add_file(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct file_baton *new_fb = make_file_baton(pb, path, pool); + + /* Mark parent dir as changed */ + pb->text_changed = TRUE; + + /* Make this file as added. */ + new_fb->added = TRUE; + + *file_baton = new_fb; + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +open_file(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct file_baton *new_fb = make_file_baton(pb, path, pool); + + *file_baton = new_fb; + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +apply_textdelta(void *file_baton, + const char *base_checksum, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct file_baton *fb = file_baton; + + /* Mark file as having textual mods. */ + fb->text_changed = TRUE; + + /* Send back a NULL window handler -- we don't need the actual diffs. */ + *handler_baton = NULL; + *handler = svn_delta_noop_window_handler; + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +change_file_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + if (svn_wc_is_normal_prop(name)) + fb->prop_changed = TRUE; + + /* Note any changes to the repository. */ + if (value != NULL) + { + if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0) + fb->ood_changed_rev = SVN_STR_TO_REV(value->data); + else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0) + fb->ood_changed_author = apr_pstrdup(fb->dir_baton->pool, + value->data); + else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0) + { + apr_time_t tm; + SVN_ERR(svn_time_from_cstring(&tm, value->data, + fb->dir_baton->pool)); + fb->ood_changed_date = tm; + } + } + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +close_file(void *file_baton, + const char *text_checksum, /* ignored, as we receive no data */ + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + enum svn_wc_status_kind repos_node_status; + enum svn_wc_status_kind repos_text_status; + enum svn_wc_status_kind repos_prop_status; + const svn_lock_t *repos_lock = NULL; + + /* If nothing has changed, return. */ + if (! (fb->added || fb->prop_changed || fb->text_changed)) + return SVN_NO_ERROR; + + /* If this is a new file, add it to the statushash. */ + if (fb->added) + { + repos_node_status = svn_wc_status_added; + repos_text_status = fb->text_changed ? svn_wc_status_modified + : 0 /* don't tweak */; + repos_prop_status = fb->prop_changed ? svn_wc_status_modified + : 0 /* don't tweak */; + + if (fb->edit_baton->wb.repos_locks) + { + const char *dir_repos_relpath = find_dir_repos_relpath(fb->dir_baton, + pool); + + /* repos_lock still uses the deprecated filesystem absolute path + format */ + const char *repos_relpath = svn_relpath_join(dir_repos_relpath, + fb->name, pool); + + repos_lock = svn_hash_gets(fb->edit_baton->wb.repos_locks, + svn_fspath__join("/", repos_relpath, + pool)); + } + } + else + { + repos_node_status = (fb->text_changed || fb->prop_changed) + ? svn_wc_status_modified + : 0 /* don't tweak */; + repos_text_status = fb->text_changed ? svn_wc_status_modified + : 0 /* don't tweak */; + repos_prop_status = fb->prop_changed ? svn_wc_status_modified + : 0 /* don't tweak */; + } + + return tweak_statushash(fb, NULL, FALSE, fb->edit_baton->db, + fb->local_abspath, repos_node_status, + repos_text_status, repos_prop_status, + SVN_INVALID_REVNUM, repos_lock, pool); +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +close_edit(void *edit_baton, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + + /* If we get here and the root was not opened as part of the edit, + we need to transmit statuses for everything. Otherwise, we + should be done. */ + if (eb->root_opened) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc_walk_status(eb->wc_ctx, + eb->target_abspath, + eb->default_depth, + eb->get_all, + eb->no_ignore, + FALSE, + eb->ignores, + eb->status_func, + eb->status_baton, + eb->cancel_func, + eb->cancel_baton, + pool)); + + return SVN_NO_ERROR; +} + + + +/*** Public API ***/ + +svn_error_t * +svn_wc__get_status_editor(const svn_delta_editor_t **editor, + void **edit_baton, + void **set_locks_baton, + svn_revnum_t *edit_revision, + svn_wc_context_t *wc_ctx, + const char *anchor_abspath, + const char *target_basename, + svn_depth_t depth, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + svn_boolean_t depth_as_sticky, + svn_boolean_t server_performs_filtering, + const apr_array_header_t *ignore_patterns, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb; + svn_delta_editor_t *tree_editor = svn_delta_default_editor(result_pool); + void *inner_baton; + struct svn_wc__shim_fetch_baton_t *sfb; + const svn_delta_editor_t *inner_editor; + svn_delta_shim_callbacks_t *shim_callbacks = + svn_delta_shim_callbacks_default(result_pool); + + /* Construct an edit baton. */ + eb = apr_pcalloc(result_pool, sizeof(*eb)); + eb->default_depth = depth; + eb->target_revision = edit_revision; + eb->db = wc_ctx->db; + eb->wc_ctx = wc_ctx; + eb->get_all = get_all; + eb->no_ignore = no_ignore; + eb->status_func = status_func; + eb->status_baton = status_baton; + eb->cancel_func = cancel_func; + eb->cancel_baton = cancel_baton; + eb->anchor_abspath = apr_pstrdup(result_pool, anchor_abspath); + eb->target_abspath = svn_dirent_join(anchor_abspath, target_basename, + result_pool); + + eb->target_basename = apr_pstrdup(result_pool, target_basename); + eb->root_opened = FALSE; + + eb->wb.db = wc_ctx->db; + eb->wb.target_abspath = eb->target_abspath; + eb->wb.ignore_text_mods = FALSE; + eb->wb.repos_locks = NULL; + eb->wb.repos_root = NULL; + + SVN_ERR(svn_wc__db_externals_defined_below(&eb->wb.externals, + wc_ctx->db, eb->target_abspath, + result_pool, scratch_pool)); + + /* Use the caller-provided ignore patterns if provided; the build-time + configured defaults otherwise. */ + if (ignore_patterns) + { + eb->ignores = ignore_patterns; + } + else + { + apr_array_header_t *ignores; + + SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, result_pool)); + eb->ignores = ignores; + } + + /* The edit baton's status structure maps to PATH, and the editor + have to be aware of whether that is the anchor or the target. */ + SVN_ERR(internal_status(&(eb->anchor_status), wc_ctx->db, anchor_abspath, + result_pool, scratch_pool)); + + /* Construct an editor. */ + tree_editor->set_target_revision = set_target_revision; + tree_editor->open_root = open_root; + tree_editor->delete_entry = delete_entry; + tree_editor->add_directory = add_directory; + tree_editor->open_directory = open_directory; + tree_editor->change_dir_prop = change_dir_prop; + tree_editor->close_directory = close_directory; + tree_editor->add_file = add_file; + tree_editor->open_file = open_file; + tree_editor->apply_textdelta = apply_textdelta; + tree_editor->change_file_prop = change_file_prop; + tree_editor->close_file = close_file; + tree_editor->close_edit = close_edit; + + inner_editor = tree_editor; + inner_baton = eb; + + if (!server_performs_filtering + && !depth_as_sticky) + SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor, + &inner_baton, + wc_ctx->db, + anchor_abspath, + target_basename, + inner_editor, + inner_baton, + result_pool)); + + /* Conjoin a cancellation editor with our status editor. */ + SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, + inner_editor, inner_baton, + editor, edit_baton, + result_pool)); + + if (set_locks_baton) + *set_locks_baton = eb; + + sfb = apr_palloc(result_pool, sizeof(*sfb)); + sfb->db = wc_ctx->db; + sfb->base_abspath = eb->anchor_abspath; + sfb->fetch_base = FALSE; + + shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func; + shim_callbacks->fetch_props_func = svn_wc__fetch_props_func; + shim_callbacks->fetch_base_func = svn_wc__fetch_base_func; + shim_callbacks->fetch_baton = sfb; + + SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, + NULL, NULL, shim_callbacks, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Like svn_io_stat_dirent, but works case sensitive inside working + copies. Before 1.8 we handled this with a selection filter inside + a directory */ +static svn_error_t * +stat_wc_dirent_case_sensitive(const svn_io_dirent2_t **dirent, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t is_wcroot; + + /* The wcroot is "" inside the wc; handle it as not in the wc, as + the case of the root is indifferent to us. */ + + /* Note that for performance this is really just a few hashtable lookups, + as we just used local_abspath for a db call in both our callers */ + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, + scratch_pool)); + + return svn_error_trace( + svn_io_stat_dirent2(dirent, local_abspath, + ! is_wcroot /* verify_truename */, + TRUE /* ignore_enoent */, + result_pool, scratch_pool)); +} + +svn_error_t * +svn_wc__internal_walk_status(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + svn_boolean_t ignore_text_mods, + const apr_array_header_t *ignore_patterns, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + struct walk_status_baton wb; + const svn_io_dirent2_t *dirent; + const struct svn_wc__db_info_t *info; + svn_error_t *err; + + wb.db = db; + wb.target_abspath = local_abspath; + wb.ignore_text_mods = ignore_text_mods; + wb.repos_root = NULL; + wb.repos_locks = NULL; + + /* Use the caller-provided ignore patterns if provided; the build-time + configured defaults otherwise. */ + if (!ignore_patterns) + { + apr_array_header_t *ignores; + + SVN_ERR(svn_wc_get_default_ignores(&ignores, NULL, scratch_pool)); + ignore_patterns = ignores; + } + + err = read_info(&info, local_abspath, db, scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + info = NULL; + } + else + return svn_error_trace(err); + + wb.externals = apr_hash_make(scratch_pool); + + SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE, + scratch_pool, scratch_pool)); + } + else + { + SVN_ERR(svn_wc__db_externals_defined_below(&wb.externals, + db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath, + scratch_pool, scratch_pool)); + } + + if (info + && info->kind == svn_node_dir + && info->status != svn_wc__db_status_not_present + && info->status != svn_wc__db_status_excluded + && info->status != svn_wc__db_status_server_excluded) + { + SVN_ERR(get_dir_status(&wb, + local_abspath, + FALSE /* skip_root */, + NULL, NULL, NULL, + info, + dirent, + ignore_patterns, + depth, + get_all, + no_ignore, + status_func, status_baton, + cancel_func, cancel_baton, + scratch_pool)); + } + else + { + /* It may be a file or an unversioned item. And this is an explicit + * target, so no ignoring. An unversioned item (file or dir) shows a + * status like '?', and can yield a tree conflicted path. */ + err = get_child_status(&wb, + local_abspath, + info, + dirent, + ignore_patterns, + get_all, + status_func, status_baton, + cancel_func, cancel_baton, + scratch_pool); + + if (!info && err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + /* The parent is also not versioned, but it is not nice to show + an error about a path a user didn't intend to touch. */ + svn_error_clear(err); + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + SVN_ERR(err); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc_walk_status(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + svn_boolean_t ignore_text_mods, + const apr_array_header_t *ignore_patterns, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + svn_wc__internal_walk_status(wc_ctx->db, + local_abspath, + depth, + get_all, + no_ignore, + ignore_text_mods, + ignore_patterns, + status_func, + status_baton, + cancel_func, + cancel_baton, + scratch_pool)); +} + + +svn_error_t * +svn_wc_status_set_repos_locks(void *edit_baton, + apr_hash_t *locks, + const char *repos_root, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + + eb->wb.repos_locks = locks; + eb->wb.repos_root = apr_pstrdup(pool, repos_root); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc_get_default_ignores(apr_array_header_t **patterns, + apr_hash_t *config, + apr_pool_t *pool) +{ + svn_config_t *cfg = config + ? svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG) + : NULL; + const char *val; + + /* Check the Subversion run-time configuration for global ignores. + If no configuration value exists, we fall back to our defaults. */ + svn_config_get(cfg, &val, SVN_CONFIG_SECTION_MISCELLANY, + SVN_CONFIG_OPTION_GLOBAL_IGNORES, + SVN_CONFIG_DEFAULT_GLOBAL_IGNORES); + *patterns = apr_array_make(pool, 16, sizeof(const char *)); + + /* Split the patterns on whitespace, and stuff them into *PATTERNS. */ + svn_cstring_split_append(*patterns, val, "\n\r\t\v ", FALSE, pool); + return SVN_NO_ERROR; +} + + +/* */ +static svn_error_t * +internal_status(svn_wc_status3_t **status, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const svn_io_dirent2_t *dirent; + svn_node_kind_t node_kind; + const char *parent_repos_relpath; + const char *parent_repos_root_url; + const char *parent_repos_uuid; + svn_wc__db_status_t node_status; + svn_boolean_t conflicted; + svn_boolean_t is_root = FALSE; + svn_error_t *err; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + err = svn_wc__db_read_info(&node_status, &node_kind, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, &conflicted, + NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + node_kind = svn_node_unknown; + /* Ensure conflicted is always set, but don't hide tree conflicts + on 'hidden' nodes. */ + conflicted = FALSE; + + SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE, + scratch_pool, scratch_pool)); + } + else + SVN_ERR(stat_wc_dirent_case_sensitive(&dirent, db, local_abspath, + scratch_pool, scratch_pool)); + + if (node_kind != svn_node_unknown + && (node_status == svn_wc__db_status_not_present + || node_status == svn_wc__db_status_server_excluded + || node_status == svn_wc__db_status_excluded)) + { + node_kind = svn_node_unknown; + } + + if (node_kind == svn_node_unknown) + return svn_error_trace(assemble_unversioned(status, + db, local_abspath, + dirent, conflicted, + FALSE /* is_ignored */, + result_pool, scratch_pool)); + + if (svn_dirent_is_root(local_abspath, strlen(local_abspath))) + is_root = TRUE; + else + SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool)); + + if (!is_root) + { + svn_wc__db_status_t parent_status; + const char *parent_abspath = svn_dirent_dirname(local_abspath, + scratch_pool); + + err = svn_wc__db_read_info(&parent_status, NULL, NULL, + &parent_repos_relpath, &parent_repos_root_url, + &parent_repos_uuid, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + db, parent_abspath, + result_pool, scratch_pool); + + if (err && (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND + || SVN_WC__ERR_IS_NOT_CURRENT_WC(err))) + { + svn_error_clear(err); + parent_repos_root_url = NULL; + parent_repos_relpath = NULL; + parent_repos_uuid = NULL; + } + else SVN_ERR(err); + } + else + { + parent_repos_root_url = NULL; + parent_repos_relpath = NULL; + parent_repos_uuid = NULL; + } + + return svn_error_trace(assemble_status(status, db, local_abspath, + parent_repos_root_url, + parent_repos_relpath, + parent_repos_uuid, + NULL, + dirent, + TRUE /* get_all */, + FALSE, + NULL /* repos_lock */, + result_pool, scratch_pool)); +} + + +svn_error_t * +svn_wc_status3(svn_wc_status3_t **status, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + internal_status(status, wc_ctx->db, local_abspath, result_pool, + scratch_pool)); +} + +svn_wc_status3_t * +svn_wc_dup_status3(const svn_wc_status3_t *orig_stat, + apr_pool_t *pool) +{ + svn_wc_status3_t *new_stat = apr_palloc(pool, sizeof(*new_stat)); + + /* Shallow copy all members. */ + *new_stat = *orig_stat; + + /* Now go back and dup the deep items into this pool. */ + if (orig_stat->repos_lock) + new_stat->repos_lock = svn_lock_dup(orig_stat->repos_lock, pool); + + if (orig_stat->changed_author) + new_stat->changed_author = apr_pstrdup(pool, orig_stat->changed_author); + + if (orig_stat->ood_changed_author) + new_stat->ood_changed_author + = apr_pstrdup(pool, orig_stat->ood_changed_author); + + if (orig_stat->lock) + new_stat->lock = svn_lock_dup(orig_stat->lock, pool); + + if (orig_stat->changelist) + new_stat->changelist + = apr_pstrdup(pool, orig_stat->changelist); + + if (orig_stat->repos_root_url) + new_stat->repos_root_url + = apr_pstrdup(pool, orig_stat->repos_root_url); + + if (orig_stat->repos_relpath) + new_stat->repos_relpath + = apr_pstrdup(pool, orig_stat->repos_relpath); + + if (orig_stat->repos_uuid) + new_stat->repos_uuid + = apr_pstrdup(pool, orig_stat->repos_uuid); + + if (orig_stat->moved_from_abspath) + new_stat->moved_from_abspath + = apr_pstrdup(pool, orig_stat->moved_from_abspath); + + if (orig_stat->moved_to_abspath) + new_stat->moved_to_abspath + = apr_pstrdup(pool, orig_stat->moved_to_abspath); + + /* Return the new hotness. */ + return new_stat; +} + +svn_error_t * +svn_wc_get_ignores2(apr_array_header_t **patterns, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_hash_t *config, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *default_ignores; + + SVN_ERR(svn_wc_get_default_ignores(&default_ignores, config, scratch_pool)); + return svn_error_trace(collect_ignore_patterns(patterns, wc_ctx->db, + local_abspath, + default_ignores, + result_pool, scratch_pool)); +} diff --git a/subversion/libsvn_wc/token-map.h b/subversion/libsvn_wc/token-map.h new file mode 100644 index 000000000000..9da12b8aaafc --- /dev/null +++ b/subversion/libsvn_wc/token-map.h @@ -0,0 +1,70 @@ +/** + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This header is parsed by transform-sql.py to allow SQLite + * statements to refer to string values by symbolic names. + */ + +#ifndef SVN_WC_TOKEN_MAP_H +#define SVN_WC_TOKEN_MAP_H + +#include "svn_types.h" +#include "wc_db.h" +#include "private/svn_token.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static const svn_token_map_t kind_map[] = { + { "file", svn_node_file }, /* MAP_FILE */ + { "dir", svn_node_dir }, /* MAP_DIR */ + { "symlink", svn_node_symlink }, /* MAP_SYMLINK */ + { "unknown", svn_node_unknown }, /* MAP_UNKNOWN */ + { NULL } +}; + +/* Note: we only decode presence values from the database. These are a + subset of all the status values. */ +static const svn_token_map_t presence_map[] = { + { "normal", svn_wc__db_status_normal }, /* MAP_NORMAL */ + { "server-excluded", svn_wc__db_status_server_excluded }, /* MAP_SERVER_EXCLUDED */ + { "excluded", svn_wc__db_status_excluded }, /* MAP_EXCLUDED */ + { "not-present", svn_wc__db_status_not_present }, /* MAP_NOT_PRESENT */ + { "incomplete", svn_wc__db_status_incomplete }, /* MAP_INCOMPLETE */ + { "base-deleted", svn_wc__db_status_base_deleted }, /* MAP_BASE_DELETED */ + { NULL } +}; + +/* The subset of svn_depth_t used in the database. */ +static const svn_token_map_t depth_map[] = { + { "unknown", svn_depth_unknown }, /* MAP_DEPTH_UNKNOWN */ + { "empty", svn_depth_empty }, + { "files", svn_depth_files }, + { "immediates", svn_depth_immediates }, + { "infinity", svn_depth_infinity }, /* MAP_DEPTH_INFINITY */ + { NULL } +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/subversion/libsvn_wc/translate.c b/subversion/libsvn_wc/translate.c new file mode 100644 index 000000000000..9e0b2656853a --- /dev/null +++ b/subversion/libsvn_wc/translate.c @@ -0,0 +1,452 @@ +/* + * translate.c : wc-specific eol/keyword substitution + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <stdlib.h> +#include <string.h> + +#include <apr_pools.h> +#include <apr_file_io.h> +#include <apr_strings.h> + +#include "svn_types.h" +#include "svn_string.h" +#include "svn_dirent_uri.h" +#include "svn_hash.h" +#include "svn_path.h" +#include "svn_error.h" +#include "svn_subst.h" +#include "svn_io.h" +#include "svn_props.h" + +#include "wc.h" +#include "adm_files.h" +#include "translate.h" +#include "props.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" + + + +/* */ +static svn_error_t * +read_handler_unsupported(void *baton, char *buffer, apr_size_t *len) +{ + SVN_ERR_MALFUNCTION(); +} + +/* */ +static svn_error_t * +write_handler_unsupported(void *baton, const char *buffer, apr_size_t *len) +{ + SVN_ERR_MALFUNCTION(); +} + +svn_error_t * +svn_wc__internal_translated_stream(svn_stream_t **stream, + svn_wc__db_t *db, + const char *local_abspath, + const char *versioned_abspath, + apr_uint32_t flags, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t special; + svn_boolean_t to_nf = flags & SVN_WC_TRANSLATE_TO_NF; + svn_subst_eol_style_t style; + const char *eol; + apr_hash_t *keywords; + svn_boolean_t repair_forced = flags & SVN_WC_TRANSLATE_FORCE_EOL_REPAIR; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_abspath)); + + SVN_ERR(svn_wc__get_translate_info(&style, &eol, + &keywords, + &special, + db, versioned_abspath, NULL, FALSE, + scratch_pool, scratch_pool)); + + if (special) + { + if (to_nf) + return svn_subst_read_specialfile(stream, local_abspath, result_pool, + scratch_pool); + + return svn_subst_create_specialfile(stream, local_abspath, result_pool, + scratch_pool); + } + + if (to_nf) + SVN_ERR(svn_stream_open_readonly(stream, local_abspath, result_pool, + scratch_pool)); + else + { + apr_file_t *file; + + /* We don't want the "open-exclusively" feature of the normal + svn_stream_open_writable interface. Do this manually. */ + SVN_ERR(svn_io_file_open(&file, local_abspath, + APR_CREATE | APR_WRITE | APR_BUFFERED, + APR_OS_DEFAULT, result_pool)); + *stream = svn_stream_from_aprfile2(file, FALSE, result_pool); + } + + if (svn_subst_translation_required(style, eol, keywords, special, TRUE)) + { + if (to_nf) + { + if (style == svn_subst_eol_style_native) + eol = SVN_SUBST_NATIVE_EOL_STR; + else if (style == svn_subst_eol_style_fixed) + repair_forced = TRUE; + else if (style != svn_subst_eol_style_none) + return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL); + + /* Wrap the stream to translate to normal form */ + *stream = svn_subst_stream_translated(*stream, + eol, + repair_forced, + keywords, + FALSE /* expand */, + result_pool); + + /* Enforce our contract. TO_NF streams are readonly */ + svn_stream_set_write(*stream, write_handler_unsupported); + } + else + { + *stream = svn_subst_stream_translated(*stream, eol, TRUE, + keywords, TRUE, result_pool); + + /* Enforce our contract. FROM_NF streams are write-only */ + svn_stream_set_read(*stream, read_handler_unsupported); + } + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__internal_translated_file(const char **xlated_abspath, + const char *src_abspath, + svn_wc__db_t *db, + const char *versioned_abspath, + apr_uint32_t flags, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_subst_eol_style_t style; + const char *eol; + apr_hash_t *keywords; + svn_boolean_t special; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_abspath)); + SVN_ERR(svn_wc__get_translate_info(&style, &eol, + &keywords, + &special, + db, versioned_abspath, NULL, FALSE, + scratch_pool, scratch_pool)); + + if (! svn_subst_translation_required(style, eol, keywords, special, TRUE) + && (! (flags & SVN_WC_TRANSLATE_FORCE_COPY))) + { + /* Translation would be a no-op, so return the original file. */ + *xlated_abspath = src_abspath; + } + else /* some translation (or copying) is necessary */ + { + const char *tmp_dir; + const char *tmp_vfile; + svn_boolean_t repair_forced + = (flags & SVN_WC_TRANSLATE_FORCE_EOL_REPAIR) != 0; + svn_boolean_t expand = (flags & SVN_WC_TRANSLATE_TO_NF) == 0; + + if (flags & SVN_WC_TRANSLATE_USE_GLOBAL_TMP) + tmp_dir = NULL; + else + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmp_dir, db, versioned_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_vfile, tmp_dir, + (flags & SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP) + ? svn_io_file_del_none + : svn_io_file_del_on_pool_cleanup, + result_pool, scratch_pool)); + + /* ### ugh. the repair behavior does NOT match the docstring. bleah. + ### all of these translation functions are crap and should go + ### away anyways. we'll just deprecate most of the functions and + ### properly document the survivors */ + + if (expand) + { + /* from normal form */ + + repair_forced = TRUE; + } + else + { + /* to normal form */ + + if (style == svn_subst_eol_style_native) + eol = SVN_SUBST_NATIVE_EOL_STR; + else if (style == svn_subst_eol_style_fixed) + repair_forced = TRUE; + else if (style != svn_subst_eol_style_none) + return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL); + } + + SVN_ERR(svn_subst_copy_and_translate4(src_abspath, tmp_vfile, + eol, repair_forced, + keywords, + expand, + special, + cancel_func, cancel_baton, + result_pool)); + + *xlated_abspath = tmp_vfile; + } + + return SVN_NO_ERROR; +} + +void +svn_wc__eol_value_from_string(const char **value, const char *eol) +{ + if (eol == NULL) + *value = NULL; + else if (! strcmp("\n", eol)) + *value = "LF"; + else if (! strcmp("\r", eol)) + *value = "CR"; + else if (! strcmp("\r\n", eol)) + *value = "CRLF"; + else + *value = NULL; +} + +svn_error_t * +svn_wc__get_translate_info(svn_subst_eol_style_t *style, + const char **eol, + apr_hash_t **keywords, + svn_boolean_t *special, + svn_wc__db_t *db, + const char *local_abspath, + apr_hash_t *props, + svn_boolean_t for_normalization, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *propval; + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (props == NULL) + SVN_ERR(svn_wc__get_actual_props(&props, db, local_abspath, + scratch_pool, scratch_pool)); + + if (eol) + { + propval = svn_prop_get_value(props, SVN_PROP_EOL_STYLE); + + svn_subst_eol_style_from_value(style, eol, propval); + } + + if (keywords) + { + propval = svn_prop_get_value(props, SVN_PROP_KEYWORDS); + + if (!propval || *propval == '\0') + *keywords = NULL; + else + SVN_ERR(svn_wc__expand_keywords(keywords, + db, local_abspath, NULL, + propval, for_normalization, + result_pool, scratch_pool)); + } + if (special) + { + propval = svn_prop_get_value(props, SVN_PROP_SPECIAL); + + *special = (propval != NULL); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__expand_keywords(apr_hash_t **keywords, + svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *keyword_list, + svn_boolean_t for_normalization, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + const char *url; + const char *repos_root_url; + + if (! for_normalization) + { + const char *repos_relpath; + + SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, &repos_relpath, + &repos_root_url, NULL, &changed_rev, + &changed_date, &changed_author, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + if (repos_relpath) + url = svn_path_url_add_component2(repos_root_url, repos_relpath, + scratch_pool); + else + SVN_ERR(svn_wc__db_read_url(&url, db, local_abspath, scratch_pool, + scratch_pool)); + } + else + { + url = ""; + changed_rev = SVN_INVALID_REVNUM; + changed_date = 0; + changed_author = ""; + repos_root_url = ""; + } + + SVN_ERR(svn_subst_build_keywords3(keywords, keyword_list, + apr_psprintf(scratch_pool, "%ld", + changed_rev), + url, repos_root_url, + changed_date, changed_author, + result_pool)); + + if (apr_hash_count(*keywords) == 0) + *keywords = NULL; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__sync_flags_with_props(svn_boolean_t *did_set, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_wc__db_lock_t *lock; + apr_hash_t *props = NULL; + svn_boolean_t had_props; + svn_boolean_t props_mod; + + if (did_set) + *did_set = FALSE; + + /* ### We'll consolidate these info gathering statements in a future + commit. */ + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, &lock, NULL, NULL, NULL, NULL, NULL, + &had_props, &props_mod, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + /* We actually only care about the following flags on files, so just + early-out for all other types. + + Also bail if there is no in-wc representation of the file. */ + if (kind != svn_node_file + || (status != svn_wc__db_status_normal + && status != svn_wc__db_status_added)) + return SVN_NO_ERROR; + + if (props_mod || had_props) + SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, scratch_pool, + scratch_pool)); + else + props = NULL; + + /* If we get this far, we're going to change *something*, so just set + the flag appropriately. */ + if (did_set) + *did_set = TRUE; + + /* Handle the read-write bit. */ + if (status != svn_wc__db_status_normal + || props == NULL + || ! svn_hash_gets(props, SVN_PROP_NEEDS_LOCK) + || lock) + { + SVN_ERR(svn_io_set_file_read_write(local_abspath, FALSE, scratch_pool)); + } + else + { + /* Special case: If we have an uncommitted svn:needs-lock, we don't + set the file read_only just yet. That happens upon commit. */ + apr_hash_t *pristine_props; + + if (! props_mod) + pristine_props = props; + else if (had_props) + SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, local_abspath, + scratch_pool, scratch_pool)); + else + pristine_props = NULL; + + if (pristine_props + && svn_hash_gets(pristine_props, SVN_PROP_NEEDS_LOCK) ) + /*&& props + && apr_hash_get(props, SVN_PROP_NEEDS_LOCK, APR_HASH_KEY_STRING) )*/ + SVN_ERR(svn_io_set_file_read_only(local_abspath, FALSE, scratch_pool)); + } + +/* Windows doesn't care about the execute bit. */ +#ifndef WIN32 + + if (props == NULL + || ! svn_hash_gets(props, SVN_PROP_EXECUTABLE)) + { + /* Turn off the execute bit */ + SVN_ERR(svn_io_set_file_executable(local_abspath, FALSE, FALSE, + scratch_pool)); + } + else + SVN_ERR(svn_io_set_file_executable(local_abspath, TRUE, FALSE, + scratch_pool)); +#endif + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/translate.h b/subversion/libsvn_wc/translate.h new file mode 100644 index 000000000000..c5203be42e8d --- /dev/null +++ b/subversion/libsvn_wc/translate.h @@ -0,0 +1,189 @@ +/* + * translate.h : eol and keyword translation + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#ifndef SVN_LIBSVN_WC_TRANSLATE_H +#define SVN_LIBSVN_WC_TRANSLATE_H + +#include <apr_pools.h> +#include "svn_types.h" +#include "svn_subst.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Newline and keyword translation properties */ + +/* If EOL is not-NULL query the SVN_PROP_EOL_STYLE property on file + LOCAL_ABSPATH in DB. If STYLE is non-null, set *STYLE to LOCAL_ABSPATH's + eol style. Set *EOL to + + - NULL for svn_subst_eol_style_none, or + + - a null-terminated C string containing the native eol marker + for this platform, for svn_subst_eol_style_native, or + + - a null-terminated C string containing the eol marker indicated + by the property value, for svn_subst_eol_style_fixed. + + If STYLE is null on entry, ignore it. If *EOL is non-null on exit, + it is a static string not allocated in POOL. + + If KEYWORDS is not NULL Expand keywords for the file at LOCAL_ABSPATH + in DB, by parsing a whitespace-delimited list of keywords. If any keywords + are found in the list, allocate *KEYWORDS from RESULT_POOL and populate it + with mappings from (const char *) keywords to their (svn_string_t *) + values (also allocated in RESULT_POOL). + + If a keyword is in the list, but no corresponding value is + available, do not create a hash entry for it. If no keywords are + found in the list, or if there is no list, set *KEYWORDS to NULL. + + If SPECIAL is not NULL determine if the svn:special flag is set on + LOCAL_ABSPATH in DB. If so, set SPECIAL to TRUE, if not, set it to FALSE. + + If PROPS is not NULL, use PROPS instead of the properties on LOCAL_ABSPATH. + + If WRI_ABSPATH is not NULL, retrieve the information for LOCAL_ABSPATH + from the working copy identified by WRI_ABSPATH. Falling back to file + external information if the file is not present as versioned node. + + If FOR_NORMALIZATION is TRUE, just return a list of keywords instead of + calculating their intended values. + + Use SCRATCH_POOL for temporary allocation, RESULT_POOL for allocating + *STYLE and *EOL. +*/ +svn_error_t * +svn_wc__get_translate_info(svn_subst_eol_style_t *style, + const char **eol, + apr_hash_t **keywords, + svn_boolean_t *special, + svn_wc__db_t *db, + const char *local_abspath, + apr_hash_t *props, + svn_boolean_t for_normalization, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Reverse parser. Given a real EOL string ("\n", "\r", or "\r\n"), + return an encoded *VALUE ("LF", "CR", "CRLF") that one might see in + the property value. */ +void svn_wc__eol_value_from_string(const char **value, + const char *eol); + +/* Expand keywords for the file at LOCAL_ABSPATH in DB, by parsing a + whitespace-delimited list of keywords KEYWORD_LIST. If any keywords + are found in the list, allocate *KEYWORDS from RESULT_POOL and populate + it with mappings from (const char *) keywords to their (svn_string_t *) + values (also allocated in RESULT_POOL). + + If a keyword is in the list, but no corresponding value is + available, do not create a hash entry for it. If no keywords are + found in the list, or if there is no list, set *KEYWORDS to NULL. + ### THIS LOOKS WRONG -- it creates a hash entry for every recognized kw + and expands each missing value as an empty string or "-1" or similar. + + Use LOCAL_ABSPATH to expand keyword values. + + If WRI_ABSPATH is not NULL, retrieve the information for LOCAL_ABSPATH + from the working copy identified by WRI_ABSPATH. Falling back to file + external information if the file is not present as versioned node. + ### THIS IS NOT IMPLEMENTED -- WRI_ABSPATH is ignored + + If FOR_NORMALIZATION is TRUE, just return a list of keywords instead of + calculating their intended values. + ### This would be better done by a separate API, since in this case + only the KEYWORD_LIST input parameter is needed. (And there is no + need to print "-1" as the revision value.) + + Use SCRATCH_POOL for any temporary allocations. +*/ +svn_error_t * +svn_wc__expand_keywords(apr_hash_t **keywords, + svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *keyword_list, + svn_boolean_t for_normalization, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Sync the write and execute bit for LOCAL_ABSPATH with what is currently + indicated by the properties in the database: + + * If the SVN_PROP_NEEDS_LOCK property is present and there is no + lock token for the file in the working copy, set LOCAL_ABSPATH to + read-only. + * If the SVN_PROP_EXECUTABLE property is present at all, then set + LOCAL_ABSPATH executable. + + If DID_SET is non-null, then liberally set *DID_SET to TRUE if we might + have change the permissions on LOCAL_ABSPATH. (A TRUE value in *DID_SET + does not guarantee that we changed the permissions, simply that more + investigation is warrented.) + + This function looks at the current values of the above properties, + including any scheduled-but-not-yet-committed changes. + + If LOCAL_ABSPATH is a directory, this function is a no-op. + + Use SCRATCH_POOL for any temporary allocations. + */ +svn_error_t * +svn_wc__sync_flags_with_props(svn_boolean_t *did_set, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* Internal version of svn_wc_translated_stream2(), which see. */ +svn_error_t * +svn_wc__internal_translated_stream(svn_stream_t **stream, + svn_wc__db_t *db, + const char *local_abspath, + const char *versioned_abspath, + apr_uint32_t flags, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Like svn_wc_translated_file2(), except the working copy database + * is used directly and the function assumes abspaths. */ +svn_error_t * +svn_wc__internal_translated_file(const char **xlated_abspath, + const char *src_abspath, + svn_wc__db_t *db, + const char *versioned_abspath, + apr_uint32_t flags, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_WC_TRANSLATE_H */ diff --git a/subversion/libsvn_wc/tree_conflicts.c b/subversion/libsvn_wc/tree_conflicts.c new file mode 100644 index 000000000000..4445c96ec09c --- /dev/null +++ b/subversion/libsvn_wc/tree_conflicts.c @@ -0,0 +1,513 @@ +/* + * tree_conflicts.c: Storage of tree conflict descriptions in the WC. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_types.h" +#include "svn_pools.h" + +#include "tree_conflicts.h" +#include "conflicts.h" +#include "wc.h" + +#include "private/svn_skel.h" +#include "private/svn_wc_private.h" +#include "private/svn_token.h" + +#include "svn_private_config.h" + +/* ### this should move to a more general location... */ +/* A map for svn_node_kind_t values. */ +/* FIXME: this mapping defines a different representation of + svn_node_unknown than the one defined in token-map.h */ +static const svn_token_map_t node_kind_map[] = +{ + { "none", svn_node_none }, + { "file", svn_node_file }, + { "dir", svn_node_dir }, + { "", svn_node_unknown }, + { NULL } +}; + +/* A map for svn_wc_operation_t values. */ +const svn_token_map_t svn_wc__operation_map[] = +{ + { "none", svn_wc_operation_none }, + { "update", svn_wc_operation_update }, + { "switch", svn_wc_operation_switch }, + { "merge", svn_wc_operation_merge }, + { NULL } +}; + +/* A map for svn_wc_conflict_action_t values. */ +const svn_token_map_t svn_wc__conflict_action_map[] = +{ + { "edited", svn_wc_conflict_action_edit }, + { "deleted", svn_wc_conflict_action_delete }, + { "added", svn_wc_conflict_action_add }, + { "replaced", svn_wc_conflict_action_replace }, + { NULL } +}; + +/* A map for svn_wc_conflict_reason_t values. */ +const svn_token_map_t svn_wc__conflict_reason_map[] = +{ + { "edited", svn_wc_conflict_reason_edited }, + { "deleted", svn_wc_conflict_reason_deleted }, + { "missing", svn_wc_conflict_reason_missing }, + { "obstructed", svn_wc_conflict_reason_obstructed }, + { "added", svn_wc_conflict_reason_added }, + { "replaced", svn_wc_conflict_reason_replaced }, + { "unversioned", svn_wc_conflict_reason_unversioned }, + { "moved-away", svn_wc_conflict_reason_moved_away }, + { "moved-here", svn_wc_conflict_reason_moved_here }, + { NULL } +}; + + +/* */ +static svn_boolean_t +is_valid_version_info_skel(const svn_skel_t *skel) +{ + return (svn_skel__list_length(skel) == 5 + && svn_skel__matches_atom(skel->children, "version") + && skel->children->next->is_atom + && skel->children->next->next->is_atom + && skel->children->next->next->next->is_atom + && skel->children->next->next->next->next->is_atom); +} + + +/* */ +static svn_boolean_t +is_valid_conflict_skel(const svn_skel_t *skel) +{ + int i; + + if (svn_skel__list_length(skel) != 8 + || !svn_skel__matches_atom(skel->children, "conflict")) + return FALSE; + + /* 5 atoms ... */ + skel = skel->children->next; + for (i = 5; i--; skel = skel->next) + if (!skel->is_atom) + return FALSE; + + /* ... and 2 version info skels. */ + return (is_valid_version_info_skel(skel) + && is_valid_version_info_skel(skel->next)); +} + + +/* Parse the enumeration value in VALUE into a plain + * 'int', using MAP to convert from strings to enumeration values. + * In MAP, a null .str field marks the end of the map. + */ +static svn_error_t * +read_enum_field(int *result, + const svn_token_map_t *map, + const svn_skel_t *skel) +{ + int value = svn_token__from_mem(map, skel->data, skel->len); + + if (value == SVN_TOKEN_UNKNOWN) + return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, + _("Unknown enumeration value in tree conflict " + "description")); + + *result = value; + + return SVN_NO_ERROR; +} + + +/* Parse the conflict info fields from SKEL into *VERSION_INFO. */ +static svn_error_t * +read_node_version_info(const svn_wc_conflict_version_t **version_info, + const svn_skel_t *skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int n; + const char *repos_root; + const char *repos_relpath; + svn_revnum_t peg_rev; + svn_node_kind_t kind; + + if (!is_valid_version_info_skel(skel)) + return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid version info in tree conflict " + "description")); + + repos_root = apr_pstrmemdup(scratch_pool, + skel->children->next->data, + skel->children->next->len); + if (*repos_root == '\0') + { + *version_info = NULL; + return SVN_NO_ERROR; + } + + /* Apply the Subversion 1.7+ url canonicalization rules to a pre 1.7 url */ + repos_root = svn_uri_canonicalize(repos_root, result_pool); + + peg_rev = SVN_STR_TO_REV(apr_pstrmemdup(scratch_pool, + skel->children->next->next->data, + skel->children->next->next->len)); + + repos_relpath = apr_pstrmemdup(result_pool, + skel->children->next->next->next->data, + skel->children->next->next->next->len); + + SVN_ERR(read_enum_field(&n, node_kind_map, + skel->children->next->next->next->next)); + kind = (svn_node_kind_t)n; + + *version_info = svn_wc_conflict_version_create2(repos_root, + NULL, + repos_relpath, + peg_rev, + kind, + result_pool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__deserialize_conflict(const svn_wc_conflict_description2_t **conflict, + const svn_skel_t *skel, + const char *dir_path, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *victim_basename; + const char *victim_abspath; + svn_node_kind_t node_kind; + svn_wc_operation_t operation; + svn_wc_conflict_action_t action; + svn_wc_conflict_reason_t reason; + const svn_wc_conflict_version_t *src_left_version; + const svn_wc_conflict_version_t *src_right_version; + int n; + svn_wc_conflict_description2_t *new_conflict; + + if (!is_valid_conflict_skel(skel)) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid conflict info '%s' in tree conflict " + "description"), + skel ? svn_skel__unparse(skel, scratch_pool)->data + : "(null)"); + + /* victim basename */ + victim_basename = apr_pstrmemdup(scratch_pool, + skel->children->next->data, + skel->children->next->len); + if (victim_basename[0] == '\0') + return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, + _("Empty 'victim' field in tree conflict " + "description")); + + /* node_kind */ + SVN_ERR(read_enum_field(&n, node_kind_map, skel->children->next->next)); + node_kind = (svn_node_kind_t)n; + if (node_kind != svn_node_file && node_kind != svn_node_dir) + return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid 'node_kind' field in tree conflict description")); + + /* operation */ + SVN_ERR(read_enum_field(&n, svn_wc__operation_map, + skel->children->next->next->next)); + operation = (svn_wc_operation_t)n; + + SVN_ERR(svn_dirent_get_absolute(&victim_abspath, + svn_dirent_join(dir_path, victim_basename, scratch_pool), + scratch_pool)); + + /* action */ + SVN_ERR(read_enum_field(&n, svn_wc__conflict_action_map, + skel->children->next->next->next->next)); + action = n; + + /* reason */ + SVN_ERR(read_enum_field(&n, svn_wc__conflict_reason_map, + skel->children->next->next->next->next->next)); + reason = n; + + /* Let's just make it a bit easier on ourself here... */ + skel = skel->children->next->next->next->next->next->next; + + /* src_left_version */ + SVN_ERR(read_node_version_info(&src_left_version, skel, + result_pool, scratch_pool)); + + /* src_right_version */ + SVN_ERR(read_node_version_info(&src_right_version, skel->next, + result_pool, scratch_pool)); + + new_conflict = svn_wc_conflict_description_create_tree2(victim_abspath, + node_kind, operation, src_left_version, src_right_version, + result_pool); + new_conflict->action = action; + new_conflict->reason = reason; + + *conflict = new_conflict; + + return SVN_NO_ERROR; +} + + +/* Prepend to SKEL the string corresponding to enumeration value N, as found + * in MAP. */ +static void +skel_prepend_enum(svn_skel_t *skel, + const svn_token_map_t *map, + int n, + apr_pool_t *result_pool) +{ + svn_skel__prepend(svn_skel__str_atom(svn_token__to_word(map, n), + result_pool), skel); +} + + +/* Prepend to PARENT_SKEL the several fields that represent VERSION_INFO, */ +static svn_error_t * +prepend_version_info_skel(svn_skel_t *parent_skel, + const svn_wc_conflict_version_t *version_info, + apr_pool_t *pool) +{ + svn_skel_t *skel = svn_skel__make_empty_list(pool); + + /* node_kind */ + skel_prepend_enum(skel, node_kind_map, version_info->node_kind, pool); + + /* path_in_repos */ + svn_skel__prepend(svn_skel__str_atom(version_info->path_in_repos + ? version_info->path_in_repos + : "", pool), skel); + + /* peg_rev */ + svn_skel__prepend(svn_skel__str_atom(apr_psprintf(pool, "%ld", + version_info->peg_rev), + pool), skel); + + /* repos_url */ + svn_skel__prepend(svn_skel__str_atom(version_info->repos_url + ? version_info->repos_url + : "", pool), skel); + + svn_skel__prepend(svn_skel__str_atom("version", pool), skel); + + SVN_ERR_ASSERT(is_valid_version_info_skel(skel)); + + svn_skel__prepend(skel, parent_skel); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__serialize_conflict(svn_skel_t **skel, + const svn_wc_conflict_description2_t *conflict, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + /* A conflict version struct with all fields null/invalid. */ + static const svn_wc_conflict_version_t null_version = { + NULL, SVN_INVALID_REVNUM, NULL, svn_node_unknown }; + svn_skel_t *c_skel = svn_skel__make_empty_list(result_pool); + const char *victim_basename; + + /* src_right_version */ + if (conflict->src_right_version) + SVN_ERR(prepend_version_info_skel(c_skel, conflict->src_right_version, + result_pool)); + else + SVN_ERR(prepend_version_info_skel(c_skel, &null_version, result_pool)); + + /* src_left_version */ + if (conflict->src_left_version) + SVN_ERR(prepend_version_info_skel(c_skel, conflict->src_left_version, + result_pool)); + else + SVN_ERR(prepend_version_info_skel(c_skel, &null_version, result_pool)); + + /* reason */ + skel_prepend_enum(c_skel, svn_wc__conflict_reason_map, conflict->reason, + result_pool); + + /* action */ + skel_prepend_enum(c_skel, svn_wc__conflict_action_map, conflict->action, + result_pool); + + /* operation */ + skel_prepend_enum(c_skel, svn_wc__operation_map, conflict->operation, + result_pool); + + /* node_kind */ + SVN_ERR_ASSERT(conflict->node_kind == svn_node_dir + || conflict->node_kind == svn_node_file); + skel_prepend_enum(c_skel, node_kind_map, conflict->node_kind, result_pool); + + /* Victim path (escaping separator chars). */ + victim_basename = svn_dirent_basename(conflict->local_abspath, result_pool); + SVN_ERR_ASSERT(victim_basename[0]); + svn_skel__prepend(svn_skel__str_atom(victim_basename, result_pool), c_skel); + + svn_skel__prepend(svn_skel__str_atom("conflict", result_pool), c_skel); + + SVN_ERR_ASSERT(is_valid_conflict_skel(c_skel)); + + *skel = c_skel; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__del_tree_conflict(svn_wc_context_t *wc_ctx, + const char *victim_abspath, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(svn_dirent_is_absolute(victim_abspath)); + + SVN_ERR(svn_wc__db_op_mark_resolved(wc_ctx->db, victim_abspath, + FALSE, FALSE, TRUE, NULL, + scratch_pool)); + + return SVN_NO_ERROR; + } + +svn_error_t * +svn_wc__add_tree_conflict(svn_wc_context_t *wc_ctx, + const svn_wc_conflict_description2_t *conflict, + apr_pool_t *scratch_pool) +{ + svn_boolean_t existing_conflict; + svn_skel_t *conflict_skel; + svn_error_t *err; + + SVN_ERR_ASSERT(conflict != NULL); + SVN_ERR_ASSERT(conflict->operation == svn_wc_operation_merge + || (conflict->reason != svn_wc_conflict_reason_moved_away + && conflict->reason != svn_wc_conflict_reason_moved_here) + ); + + /* Re-adding an existing tree conflict victim is an error. */ + err = svn_wc__internal_conflicted_p(NULL, NULL, &existing_conflict, + wc_ctx->db, conflict->local_abspath, + scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + } + else if (existing_conflict) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Attempt to add tree conflict that already " + "exists at '%s'"), + svn_dirent_local_style(conflict->local_abspath, + scratch_pool)); + else if (!conflict) + return SVN_NO_ERROR; + + conflict_skel = svn_wc__conflict_skel_create(scratch_pool); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(conflict_skel, wc_ctx->db, + conflict->local_abspath, + conflict->reason, + conflict->action, + NULL, + scratch_pool, scratch_pool)); + + switch(conflict->operation) + { + case svn_wc_operation_update: + default: + SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_skel, + conflict->src_left_version, + conflict->src_right_version, + scratch_pool, scratch_pool)); + break; + case svn_wc_operation_switch: + SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict_skel, + conflict->src_left_version, + conflict->src_right_version, + scratch_pool, scratch_pool)); + break; + case svn_wc_operation_merge: + SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel, + conflict->src_left_version, + conflict->src_right_version, + scratch_pool, scratch_pool)); + break; + } + + return svn_error_trace( + svn_wc__db_op_mark_conflict(wc_ctx->db, conflict->local_abspath, + conflict_skel, NULL, scratch_pool)); +} + + +svn_error_t * +svn_wc__get_tree_conflict(const svn_wc_conflict_description2_t **tree_conflict, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const apr_array_header_t *conflicts; + int i; + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__read_conflicts(&conflicts, + wc_ctx->db, local_abspath, FALSE, + scratch_pool, scratch_pool)); + + if (!conflicts || conflicts->nelts == 0) + { + *tree_conflict = NULL; + return SVN_NO_ERROR; + } + + for (i = 0; i < conflicts->nelts; i++) + { + const svn_wc_conflict_description2_t *desc; + + desc = APR_ARRAY_IDX(conflicts, i, svn_wc_conflict_description2_t *); + + if (desc->kind == svn_wc_conflict_kind_tree) + { + *tree_conflict = svn_wc__conflict_description2_dup(desc, + result_pool); + return SVN_NO_ERROR; + } + } + + *tree_conflict = NULL; + return SVN_NO_ERROR; +} + diff --git a/subversion/libsvn_wc/tree_conflicts.h b/subversion/libsvn_wc/tree_conflicts.h new file mode 100644 index 000000000000..68cd9f6066f5 --- /dev/null +++ b/subversion/libsvn_wc/tree_conflicts.h @@ -0,0 +1,93 @@ +/* + * tree_conflicts.h: declarations related to tree conflicts + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#ifndef SVN_LIBSVN_WC_TREE_CONFLICTS_H +#define SVN_LIBSVN_WC_TREE_CONFLICTS_H + +#include <apr_pools.h> +#include <apr_tables.h> + +#include "svn_string.h" +#include "svn_wc.h" + +#include "private/svn_token.h" +#include "private/svn_skel.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* + * See the notes/tree-conflicts/ directory for more information + * about tree conflicts in general. + * + * A given directory may contain potentially many tree conflicts. + * Each tree conflict is identified by the path of the file + * or directory (both a.k.a node) that it affects. + * We call this file or directory the "victim" of the tree conflict. + * + * For example, a file that is deleted by an update but locally + * modified by the user is a victim of a tree conflict. + * + * For now, tree conflict victims are always direct children of the + * directory in which the tree conflict is recorded. + * This may change once the way Subversion handles adm areas changes. + * + * If a directory has tree conflicts, the "tree-conflict-data" field + * in the entry for the directory contains one or more tree conflict + * descriptions stored using the "skel" format. + */ + + +svn_error_t * +svn_wc__serialize_conflict(svn_skel_t **skel, + const svn_wc_conflict_description2_t *conflict, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Parse a newly allocated svn_wc_conflict_description2_t object from the + * provided SKEL. Return the result in *CONFLICT, allocated in RESULT_POOL. + * DIR_PATH is the path to the WC directory whose conflicts are being read. + * Use SCRATCH_POOL for temporary allocations. + */ +svn_error_t * +svn_wc__deserialize_conflict(const svn_wc_conflict_description2_t **conflict, + const svn_skel_t *skel, + const char *dir_path, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Token mapping tables. */ +extern const svn_token_map_t svn_wc__operation_map[]; +extern const svn_token_map_t svn_wc__conflict_action_map[]; +extern const svn_token_map_t svn_wc__conflict_reason_map[]; + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_WC_TREE_CONFLICTS_H */ diff --git a/subversion/libsvn_wc/update_editor.c b/subversion/libsvn_wc/update_editor.c new file mode 100644 index 000000000000..617ad4775b90 --- /dev/null +++ b/subversion/libsvn_wc/update_editor.c @@ -0,0 +1,5486 @@ +/* + * update_editor.c : main editor for checkouts and updates + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <stdlib.h> +#include <string.h> + +#include <apr_pools.h> +#include <apr_hash.h> +#include <apr_md5.h> +#include <apr_tables.h> +#include <apr_strings.h> + +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_hash.h" +#include "svn_string.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_error.h" +#include "svn_io.h" +#include "svn_private_config.h" +#include "svn_time.h" + +#include "wc.h" +#include "adm_files.h" +#include "conflicts.h" +#include "translate.h" +#include "workqueue.h" + +#include "private/svn_subr_private.h" +#include "private/svn_wc_private.h" +#include "private/svn_editor.h" + +/* Checks whether a svn_wc__db_status_t indicates whether a node is + present in a working copy. Used by the editor implementation */ +#define IS_NODE_PRESENT(status) \ + ((status) != svn_wc__db_status_server_excluded &&\ + (status) != svn_wc__db_status_excluded && \ + (status) != svn_wc__db_status_not_present) + +static svn_error_t * +path_join_under_root(const char **result_path, + const char *base_path, + const char *add_path, + apr_pool_t *result_pool); + + +/* + * This code handles "checkout" and "update" and "switch". + * A checkout is similar to an update that is only adding new items. + * + * The intended behaviour of "update" and "switch", focusing on the checks + * to be made before applying a change, is: + * + * For each incoming change: + * if target is already in conflict or obstructed: + * skip this change + * else + * if this action will cause a tree conflict: + * record the tree conflict + * skip this change + * else: + * make this change + * + * In more detail: + * + * For each incoming change: + * + * 1. if # Incoming change is inside an item already in conflict: + * a. tree/text/prop change to node beneath tree-conflicted dir + * then # Skip all changes in this conflicted subtree [*1]: + * do not update the Base nor the Working + * notify "skipped because already in conflict" just once + * for the whole conflicted subtree + * + * if # Incoming change affects an item already in conflict: + * b. tree/text/prop change to tree-conflicted dir/file, or + * c. tree change to a text/prop-conflicted file/dir, or + * d. text/prop change to a text/prop-conflicted file/dir [*2], or + * e. tree change to a dir tree containing any conflicts, + * then # Skip this change [*1]: + * do not update the Base nor the Working + * notify "skipped because already in conflict" + * + * 2. if # Incoming change affects an item that's "obstructed": + * a. on-disk node kind doesn't match recorded Working node kind + * (including an absence/presence mis-match), + * then # Skip this change [*1]: + * do not update the Base nor the Working + * notify "skipped because obstructed" + * + * 3. if # Incoming change raises a tree conflict: + * a. tree/text/prop change to node beneath sched-delete dir, or + * b. tree/text/prop change to sched-delete dir/file, or + * c. text/prop change to tree-scheduled dir/file, + * then # Skip this change: + * do not update the Base nor the Working [*3] + * notify "tree conflict" + * + * 4. Apply the change: + * update the Base + * update the Working, possibly raising text/prop conflicts + * notify + * + * Notes: + * + * "Tree change" here refers to an add or delete of the target node, + * including the add or delete part of a copy or move or rename. + * + * [*1] We should skip changes to an entire node, as the base revision number + * applies to the entire node. Not sure how this affects attempts to + * handle text and prop changes separately. + * + * [*2] Details of which combinations of property and text changes conflict + * are not specified here. + * + * [*3] For now, we skip the update, and require the user to: + * - Modify the WC to be compatible with the incoming change; + * - Mark the conflict as resolved; + * - Repeat the update. + * Ideally, it would be possible to resolve any conflict without + * repeating the update. To achieve this, we would have to store the + * necessary data at conflict detection time, and delay the update of + * the Base until the time of resolving. + */ + + +/*** batons ***/ + +struct edit_baton +{ + /* For updates, the "destination" of the edit is ANCHOR_ABSPATH, the + directory containing TARGET_ABSPATH. If ANCHOR_ABSPATH itself is the + target, the values are identical. + + TARGET_BASENAME is the name of TARGET_ABSPATH in ANCHOR_ABSPATH, or "" if + ANCHOR_ABSPATH is the target */ + const char *target_basename; + + /* Absolute variants of ANCHOR and TARGET */ + const char *anchor_abspath; + const char *target_abspath; + + /* The DB handle for managing the working copy state. */ + svn_wc__db_t *db; + + /* Array of file extension patterns to preserve as extensions in + generated conflict files. */ + const apr_array_header_t *ext_patterns; + + /* Hash mapping const char * absolute working copy paths to depth-first + ordered arrays of svn_prop_inherited_item_t * structures representing + the properties inherited by the base node at that working copy path. + May be NULL. */ + apr_hash_t *wcroot_iprops; + + /* The revision we're targeting...or something like that. This + starts off as a pointer to the revision to which we are updating, + or SVN_INVALID_REVNUM, but by the end of the edit, should be + pointing to the final revision. */ + svn_revnum_t *target_revision; + + /* The requested depth of this edit. */ + svn_depth_t requested_depth; + + /* Is the requested depth merely an operational limitation, or is + also the new sticky ambient depth of the update target? */ + svn_boolean_t depth_is_sticky; + + /* Need to know if the user wants us to overwrite the 'now' times on + edited/added files with the last-commit-time. */ + svn_boolean_t use_commit_times; + + /* Was the root actually opened (was this a non-empty edit)? */ + svn_boolean_t root_opened; + + /* Was the update-target deleted? This is a special situation. */ + svn_boolean_t target_deleted; + + /* Allow unversioned obstructions when adding a path. */ + svn_boolean_t allow_unver_obstructions; + + /* Handle local additions as modifications of new nodes */ + svn_boolean_t adds_as_modification; + + /* If set, we check out into an empty directory. This allows for a number + of conflict checks to be omitted. */ + svn_boolean_t clean_checkout; + + /* If this is a 'switch' operation, the new relpath of target_abspath, + else NULL. */ + const char *switch_relpath; + + /* The URL to the root of the repository. */ + const char *repos_root; + + /* The UUID of the repos, or NULL. */ + const char *repos_uuid; + + /* External diff3 to use for merges (can be null, in which case + internal merge code is used). */ + const char *diff3_cmd; + + /* Externals handler */ + svn_wc_external_update_t external_func; + void *external_baton; + + /* This editor sends back notifications as it edits. */ + svn_wc_notify_func2_t notify_func; + void *notify_baton; + + /* This editor is normally wrapped in a cancellation editor anyway, + so it doesn't bother to check for cancellation itself. However, + it needs a cancel_func and cancel_baton available to pass to + long-running functions. */ + svn_cancel_func_t cancel_func; + void *cancel_baton; + + /* This editor will invoke a interactive conflict-resolution + callback, if available. */ + svn_wc_conflict_resolver_func2_t conflict_func; + void *conflict_baton; + + /* Subtrees that were skipped during the edit, and therefore shouldn't + have their revision/url info updated at the end. If a path is a + directory, its descendants will also be skipped. The keys are paths + relative to the working copy root and the values unspecified. */ + apr_hash_t *skipped_trees; + + /* A mapping from const char * repos_relpaths to the apr_hash_t * instances + returned from fetch_dirents_func for that repos_relpath. These + are used to avoid issue #3569 in specific update scenarios where a + restricted depth is used. */ + apr_hash_t *dir_dirents; + + /* Absolute path of the working copy root or NULL if not initialized yet */ + const char *wcroot_abspath; + + apr_pool_t *pool; +}; + + +/* Record in the edit baton EB that LOCAL_ABSPATH's base version is not being + * updated. + * + * Add to EB->skipped_trees a copy (allocated in EB->pool) of the string + * LOCAL_ABSPATH. + */ +static svn_error_t * +remember_skipped_tree(struct edit_baton *eb, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + svn_hash_sets(eb->skipped_trees, + apr_pstrdup(eb->pool, + svn_dirent_skip_ancestor(eb->wcroot_abspath, + local_abspath)), + (void *)1); + + return SVN_NO_ERROR; +} + +/* Per directory baton. Lives in its own subpool of the parent directory + or of the edit baton if there is no parent directory */ +struct dir_baton +{ + /* Basename of this directory. */ + const char *name; + + /* Absolute path of this directory */ + const char *local_abspath; + + /* The repository relative path this directory will correspond to. */ + const char *new_relpath; + + /* The revision of the directory before updating */ + svn_revnum_t old_revision; + + /* The repos_relpath before updating/switching */ + const char *old_repos_relpath; + + /* The global edit baton. */ + struct edit_baton *edit_baton; + + /* Baton for this directory's parent, or NULL if this is the root + directory. */ + struct dir_baton *parent_baton; + + /* Set if updates to this directory are skipped */ + svn_boolean_t skip_this; + + /* Set if there was a previous notification for this directory */ + svn_boolean_t already_notified; + + /* Set if this directory is being added during this editor drive. */ + svn_boolean_t adding_dir; + + /* Set on a node and its descendants are not present in the working copy + but should still be updated (not skipped). These nodes should all be + marked as deleted. */ + svn_boolean_t shadowed; + + /* Set on a node when the existing node is obstructed, and the edit operation + continues as semi-shadowed update */ + svn_boolean_t edit_obstructed; + + /* The (new) changed_* information, cached to avoid retrieving it later */ + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + + /* If not NULL, contains a mapping of const char* basenames of children that + have been deleted to their svn_skel_t* tree conflicts. + We store this hash to allow replacements to continue under a just + installed tree conflict. + + The add after the delete will then update the tree conflicts information + and reinstall it. */ + apr_hash_t *deletion_conflicts; + + /* A hash of file names (only the hash key matters) seen by add_file + and not yet added to the database by close_file. */ + apr_hash_t *not_present_files; + + /* Set if an unversioned dir of the same name already existed in + this directory. */ + svn_boolean_t obstruction_found; + + /* Set if a dir of the same name already exists and is + scheduled for addition without history. */ + svn_boolean_t add_existed; + + /* An array of svn_prop_t structures, representing all the property + changes to be applied to this directory. */ + apr_array_header_t *propchanges; + + /* A boolean indicating whether this node or one of its children has + received any 'real' changes. Used to avoid tree conflicts for simple + entryprop changes, like lock management */ + svn_boolean_t edited; + + /* The tree conflict to install once the node is really edited */ + svn_skel_t *edit_conflict; + + /* The bump information for this directory. */ + struct bump_dir_info *bump_info; + + /* The depth of the directory in the wc (or inferred if added). Not + used for filtering; we have a separate wrapping editor for that. */ + svn_depth_t ambient_depth; + + /* Was the directory marked as incomplete before the update? + (In other words, are we resuming an interrupted update?) + + If WAS_INCOMPLETE is set to TRUE we expect to receive all child nodes + and properties for/of the directory. If WAS_INCOMPLETE is FALSE then + we only receive the changes in/for children and properties.*/ + svn_boolean_t was_incomplete; + + /* The pool in which this baton itself is allocated. */ + apr_pool_t *pool; + + /* how many nodes are referring to baton? */ + int ref_count; + +}; + + +struct handler_baton +{ + svn_txdelta_window_handler_t apply_handler; + void *apply_baton; + apr_pool_t *pool; + struct file_baton *fb; + + /* Where we are assembling the new file. */ + const char *new_text_base_tmp_abspath; + + /* The expected source checksum of the text source or NULL if no base + checksum is available (MD5 if the server provides a checksum, SHA1 if + the server doesn't) */ + svn_checksum_t *expected_source_checksum; + + /* Why two checksums? + The editor currently provides an md5 which we use to detect corruption + during transmission. We use the sha1 inside libsvn_wc both for pristine + handling and corruption detection. In the future, the editor will also + provide a sha1, so we may not have to calculate both, but for the time + being, that's the way it is. */ + + /* The calculated checksum of the text source or NULL if the acual + checksum is not being calculated. The checksum kind is identical to the + kind of expected_source_checksum. */ + svn_checksum_t *actual_source_checksum; + + /* The stream used to calculate the source checksums */ + svn_stream_t *source_checksum_stream; + + /* A calculated MD5 digest of NEW_TEXT_BASE_TMP_ABSPATH. + This is initialized to all zeroes when the baton is created, then + populated with the MD5 digest of the resultant fulltext after the + last window is handled by the handler returned from + apply_textdelta(). */ + unsigned char new_text_base_md5_digest[APR_MD5_DIGESTSIZE]; + + /* A calculated SHA-1 of NEW_TEXT_BASE_TMP_ABSPATH, which we'll use for + eventually writing the pristine. */ + svn_checksum_t * new_text_base_sha1_checksum; +}; + + +/* Get an empty file in the temporary area for WRI_ABSPATH. The file will + not be set for automatic deletion, and the name will be returned in + TMP_FILENAME. + + This implementation creates a new empty file with a unique name. + + ### This is inefficient for callers that just want an empty file to read + ### from. There could be (and there used to be) a permanent, shared + ### empty file for this purpose. + + ### This is inefficient for callers that just want to reserve a unique + ### file name to create later. A better way may not be readily available. + */ +static svn_error_t * +get_empty_tmp_file(const char **tmp_filename, + svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *temp_dir_abspath; + + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, db, wri_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_open_unique_file3(NULL, tmp_filename, temp_dir_abspath, + svn_io_file_del_none, + scratch_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* An APR pool cleanup handler. This runs the working queue for an + editor baton. */ +static apr_status_t +cleanup_edit_baton(void *edit_baton) +{ + struct edit_baton *eb = edit_baton; + svn_error_t *err; + apr_pool_t *pool = apr_pool_parent_get(eb->pool); + + err = svn_wc__wq_run(eb->db, eb->wcroot_abspath, + NULL /* cancel_func */, NULL /* cancel_baton */, + pool); + + if (err) + { + apr_status_t apr_err = err->apr_err; + svn_error_clear(err); + return apr_err; + } + return APR_SUCCESS; +} + +/* Make a new dir baton in a subpool of PB->pool. PB is the parent baton. + If PATH and PB are NULL, this is the root directory of the edit; in this + case, make the new dir baton in a subpool of EB->pool. + ADDING should be TRUE if we are adding this directory. */ +static svn_error_t * +make_dir_baton(struct dir_baton **d_p, + const char *path, + struct edit_baton *eb, + struct dir_baton *pb, + svn_boolean_t adding, + apr_pool_t *scratch_pool) +{ + apr_pool_t *dir_pool; + struct dir_baton *d; + + if (pb != NULL) + dir_pool = svn_pool_create(pb->pool); + else + dir_pool = svn_pool_create(eb->pool); + + SVN_ERR_ASSERT(path || (! pb)); + + /* Okay, no easy out, so allocate and initialize a dir baton. */ + d = apr_pcalloc(dir_pool, sizeof(*d)); + + /* Construct the PATH and baseNAME of this directory. */ + if (path) + { + d->name = svn_dirent_basename(path, dir_pool); + SVN_ERR(path_join_under_root(&d->local_abspath, + pb->local_abspath, d->name, dir_pool)); + } + else + { + /* This is the root baton. */ + d->name = NULL; + d->local_abspath = eb->anchor_abspath; + } + + /* Figure out the new_relpath for this directory. */ + if (eb->switch_relpath) + { + /* Handle switches... */ + + if (pb == NULL) + { + if (*eb->target_basename == '\0') + { + /* No parent baton and target_basename=="" means that we are + the target of the switch. Thus, our NEW_RELPATH will be + the SWITCH_RELPATH. */ + d->new_relpath = eb->switch_relpath; + } + else + { + /* This node is NOT the target of the switch (one of our + children is the target); therefore, it must already exist. + Get its old REPOS_RELPATH, as it won't be changing. */ + SVN_ERR(svn_wc__db_scan_base_repos(&d->new_relpath, NULL, NULL, + eb->db, d->local_abspath, + dir_pool, scratch_pool)); + } + } + else + { + /* This directory is *not* the root (has a parent). If there is + no grandparent, then we may have anchored at the parent, + and self is the target. If we match the target, then set + NEW_RELPATH to the SWITCH_RELPATH. + + Otherwise, we simply extend NEW_RELPATH from the parent. */ + if (pb->parent_baton == NULL + && strcmp(eb->target_basename, d->name) == 0) + d->new_relpath = eb->switch_relpath; + else + d->new_relpath = svn_relpath_join(pb->new_relpath, d->name, + dir_pool); + } + } + else /* must be an update */ + { + /* If we are adding the node, then simply extend the parent's + relpath for our own. */ + if (adding) + { + SVN_ERR_ASSERT(pb != NULL); + d->new_relpath = svn_relpath_join(pb->new_relpath, d->name, + dir_pool); + } + else + { + SVN_ERR(svn_wc__db_scan_base_repos(&d->new_relpath, NULL, NULL, + eb->db, d->local_abspath, + dir_pool, scratch_pool)); + SVN_ERR_ASSERT(d->new_relpath); + } + } + + d->edit_baton = eb; + d->parent_baton = pb; + d->pool = dir_pool; + d->propchanges = apr_array_make(dir_pool, 1, sizeof(svn_prop_t)); + d->obstruction_found = FALSE; + d->add_existed = FALSE; + d->ref_count = 1; + d->old_revision = SVN_INVALID_REVNUM; + d->adding_dir = adding; + d->changed_rev = SVN_INVALID_REVNUM; + d->not_present_files = apr_hash_make(dir_pool); + + /* Copy some flags from the parent baton */ + if (pb) + { + d->skip_this = pb->skip_this; + d->shadowed = pb->shadowed || pb->edit_obstructed; + + /* the parent's bump info has one more referer */ + pb->ref_count++; + } + + /* The caller of this function needs to fill these in. */ + d->ambient_depth = svn_depth_unknown; + d->was_incomplete = FALSE; + + *d_p = d; + return SVN_NO_ERROR; +} + + +/* Forward declarations. */ +static svn_error_t * +already_in_a_tree_conflict(svn_boolean_t *conflicted, + svn_boolean_t *ignored, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + + +static void +do_notification(const struct edit_baton *eb, + const char *local_abspath, + svn_node_kind_t kind, + svn_wc_notify_action_t action, + apr_pool_t *scratch_pool) +{ + svn_wc_notify_t *notify; + + if (eb->notify_func == NULL) + return; + + notify = svn_wc_create_notify(local_abspath, action, scratch_pool); + notify->kind = kind; + + (*eb->notify_func)(eb->notify_baton, notify, scratch_pool); +} + +/* Decrement the directory's reference count. If it hits zero, + then this directory is "done". This means it is safe to clear its pool. + + In addition, when the directory is "done", we recurse to possible cleanup + the parent directory. +*/ +static svn_error_t * +maybe_release_dir_info(struct dir_baton *db) +{ + db->ref_count--; + + if (!db->ref_count) + { + struct dir_baton *pb = db->parent_baton; + + svn_pool_destroy(db->pool); + + if (pb) + SVN_ERR(maybe_release_dir_info(pb)); + } + + return SVN_NO_ERROR; +} + +/* Per file baton. Lives in its own subpool below the pool of the parent + directory */ +struct file_baton +{ + /* Pool specific to this file_baton. */ + apr_pool_t *pool; + + /* Name of this file (its entry in the directory). */ + const char *name; + + /* Absolute path to this file */ + const char *local_abspath; + + /* The repository relative path this file will correspond to. */ + const char *new_relpath; + + /* The revision of the file before updating */ + svn_revnum_t old_revision; + + /* The repos_relpath before updating/switching */ + const char *old_repos_relpath; + + /* The global edit baton. */ + struct edit_baton *edit_baton; + + /* The parent directory of this file. */ + struct dir_baton *dir_baton; + + /* Set if updates to this directory are skipped */ + svn_boolean_t skip_this; + + /* Set if there was a previous notification */ + svn_boolean_t already_notified; + + /* Set if this file is new. */ + svn_boolean_t adding_file; + + /* Set if an unversioned file of the same name already existed in + this directory. */ + svn_boolean_t obstruction_found; + + /* Set if a file of the same name already exists and is + scheduled for addition without history. */ + svn_boolean_t add_existed; + + /* Set if this file is being added in the BASE layer, but is not-present + in the working copy (replaced, deleted, etc.). */ + svn_boolean_t shadowed; + + /* Set on a node when the existing node is obstructed, and the edit operation + continues as semi-shadowed update */ + svn_boolean_t edit_obstructed; + + /* The (new) changed_* information, cached to avoid retrieving it later */ + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + + /* If there are file content changes, these are the checksums of the + resulting new text base, which is in the pristine store, else NULL. */ + const svn_checksum_t *new_text_base_md5_checksum; + const svn_checksum_t *new_text_base_sha1_checksum; + + /* The checksum of the file before the update */ + const svn_checksum_t *original_checksum; + + /* An array of svn_prop_t structures, representing all the property + changes to be applied to this file. Once a file baton is + initialized, this is never NULL, but it may have zero elements. */ + apr_array_header_t *propchanges; + + /* For existing files, whether there are local modifications. FALSE for added + files */ + svn_boolean_t local_prop_mods; + + /* Bump information for the directory this file lives in */ + struct bump_dir_info *bump_info; + + /* A boolean indicating whether this node or one of its children has + received any 'real' changes. Used to avoid tree conflicts for simple + entryprop changes, like lock management */ + svn_boolean_t edited; + + /* The tree conflict to install once the node is really edited */ + svn_skel_t *edit_conflict; +}; + + +/* Make a new file baton in a subpool of PB->pool. PB is the parent baton. + * PATH is relative to the root of the edit. ADDING tells whether this file + * is being added. */ +static svn_error_t * +make_file_baton(struct file_baton **f_p, + struct dir_baton *pb, + const char *path, + svn_boolean_t adding, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb = pb->edit_baton; + apr_pool_t *file_pool = svn_pool_create(pb->pool); + struct file_baton *f = apr_pcalloc(file_pool, sizeof(*f)); + + SVN_ERR_ASSERT(path); + + /* Make the file's on-disk name. */ + f->name = svn_dirent_basename(path, file_pool); + f->old_revision = SVN_INVALID_REVNUM; + SVN_ERR(path_join_under_root(&f->local_abspath, + pb->local_abspath, f->name, file_pool)); + + /* Figure out the new URL for this file. */ + if (eb->switch_relpath) + { + /* Handle switches... */ + + /* This file has a parent directory. If there is + no grandparent, then we may have anchored at the parent, + and self is the target. If we match the target, then set + NEW_RELPATH to the SWITCH_RELPATH. + + Otherwise, we simply extend NEW_RELPATH from the parent. */ + if (pb->parent_baton == NULL + && strcmp(eb->target_basename, f->name) == 0) + f->new_relpath = eb->switch_relpath; + else + f->new_relpath = svn_relpath_join(pb->new_relpath, f->name, + file_pool); + } + else /* must be an update */ + { + if (adding) + f->new_relpath = svn_relpath_join(pb->new_relpath, f->name, file_pool); + else + { + SVN_ERR(svn_wc__db_scan_base_repos(&f->new_relpath, NULL, NULL, + eb->db, f->local_abspath, + file_pool, scratch_pool)); + SVN_ERR_ASSERT(f->new_relpath); + } + } + + f->pool = file_pool; + f->edit_baton = pb->edit_baton; + f->propchanges = apr_array_make(file_pool, 1, sizeof(svn_prop_t)); + f->bump_info = pb->bump_info; + f->adding_file = adding; + f->obstruction_found = FALSE; + f->add_existed = FALSE; + f->skip_this = pb->skip_this; + f->shadowed = pb->shadowed || pb->edit_obstructed; + f->dir_baton = pb; + f->changed_rev = SVN_INVALID_REVNUM; + + /* the directory has one more referer now */ + pb->ref_count++; + + *f_p = f; + return SVN_NO_ERROR; +} + +/* Complete a conflict skel by describing the update. + * + * LOCAL_KIND is the node kind of the tree conflict victim in the + * working copy. + * + * All temporary allocations are be made in SCRATCH_POOL, while allocations + * needed for the returned conflict struct are made in RESULT_POOL. + */ +static svn_error_t * +complete_conflict(svn_skel_t *conflict, + const struct edit_baton *eb, + const char *local_abspath, + const char *old_repos_relpath, + svn_revnum_t old_revision, + const char *new_repos_relpath, + svn_node_kind_t local_kind, + svn_node_kind_t target_kind, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_conflict_version_t *original_version; + svn_wc_conflict_version_t *target_version; + svn_boolean_t is_complete; + + if (!conflict) + return SVN_NO_ERROR; /* Not conflicted */ + + SVN_ERR(svn_wc__conflict_skel_is_complete(&is_complete, conflict)); + + if (is_complete) + return SVN_NO_ERROR; /* Already completed */ + + if (old_repos_relpath) + original_version = svn_wc_conflict_version_create2(eb->repos_root, + eb->repos_uuid, + old_repos_relpath, + old_revision, + local_kind, + result_pool); + else + original_version = NULL; + + if (new_repos_relpath) + target_version = svn_wc_conflict_version_create2(eb->repos_root, + eb->repos_uuid, + new_repos_relpath, + *eb->target_revision, + target_kind, + result_pool); + else + target_version = NULL; + + if (eb->switch_relpath) + SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict, + original_version, + target_version, + result_pool, scratch_pool)); + else + SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict, + original_version, + target_version, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* Called when a directory is really edited, to avoid marking a + tree conflict on a node for a no-change edit */ +static svn_error_t * +mark_directory_edited(struct dir_baton *db, apr_pool_t *scratch_pool) +{ + if (db->edited) + return SVN_NO_ERROR; + + if (db->parent_baton) + SVN_ERR(mark_directory_edited(db->parent_baton, scratch_pool)); + + db->edited = TRUE; + + if (db->edit_conflict) + { + /* We have a (delayed) tree conflict to install */ + + SVN_ERR(complete_conflict(db->edit_conflict, db->edit_baton, + db->local_abspath, + db->old_repos_relpath, db->old_revision, + db->new_relpath, + svn_node_dir, svn_node_dir, + db->pool, scratch_pool)); + SVN_ERR(svn_wc__db_op_mark_conflict(db->edit_baton->db, + db->local_abspath, + db->edit_conflict, NULL, + scratch_pool)); + + do_notification(db->edit_baton, db->local_abspath, svn_node_dir, + svn_wc_notify_tree_conflict, scratch_pool); + db->already_notified = TRUE; + } + + return SVN_NO_ERROR; +} + +/* Called when a file is really edited, to avoid marking a + tree conflict on a node for a no-change edit */ +static svn_error_t * +mark_file_edited(struct file_baton *fb, apr_pool_t *scratch_pool) +{ + if (fb->edited) + return SVN_NO_ERROR; + + SVN_ERR(mark_directory_edited(fb->dir_baton, scratch_pool)); + + fb->edited = TRUE; + + if (fb->edit_conflict) + { + /* We have a (delayed) tree conflict to install */ + + SVN_ERR(complete_conflict(fb->edit_conflict, fb->edit_baton, + fb->local_abspath, fb->old_repos_relpath, + fb->old_revision, fb->new_relpath, + svn_node_file, svn_node_file, + fb->pool, scratch_pool)); + + SVN_ERR(svn_wc__db_op_mark_conflict(fb->edit_baton->db, + fb->local_abspath, + fb->edit_conflict, NULL, + scratch_pool)); + + do_notification(fb->edit_baton, fb->local_abspath, svn_node_file, + svn_wc_notify_tree_conflict, scratch_pool); + fb->already_notified = TRUE; + } + + return SVN_NO_ERROR; +} + + +/* Handle the next delta window of the file described by BATON. If it is + * the end (WINDOW == NULL), then check the checksum, store the text in the + * pristine store and write its details into BATON->fb->new_text_base_*. */ +static svn_error_t * +window_handler(svn_txdelta_window_t *window, void *baton) +{ + struct handler_baton *hb = baton; + struct file_baton *fb = hb->fb; + svn_wc__db_t *db = fb->edit_baton->db; + svn_error_t *err; + + /* Apply this window. We may be done at that point. */ + err = hb->apply_handler(window, hb->apply_baton); + if (window != NULL && !err) + return SVN_NO_ERROR; + + if (hb->expected_source_checksum) + { + /* Close the stream to calculate HB->actual_source_md5_checksum. */ + svn_error_t *err2 = svn_stream_close(hb->source_checksum_stream); + + if (!err2) + { + SVN_ERR_ASSERT(hb->expected_source_checksum->kind == + hb->actual_source_checksum->kind); + + if (!svn_checksum_match(hb->expected_source_checksum, + hb->actual_source_checksum)) + { + err = svn_error_createf(SVN_ERR_WC_CORRUPT_TEXT_BASE, err, + _("Checksum mismatch while updating '%s':\n" + " expected: %s\n" + " actual: %s\n"), + svn_dirent_local_style(fb->local_abspath, hb->pool), + svn_checksum_to_cstring(hb->expected_source_checksum, + hb->pool), + svn_checksum_to_cstring(hb->actual_source_checksum, + hb->pool)); + } + } + + err = svn_error_compose_create(err, err2); + } + + if (err) + { + /* We failed to apply the delta; clean up the temporary file. */ + svn_error_clear(svn_io_remove_file2(hb->new_text_base_tmp_abspath, TRUE, + hb->pool)); + } + else + { + /* Tell the file baton about the new text base's checksums. */ + fb->new_text_base_md5_checksum = + svn_checksum__from_digest_md5(hb->new_text_base_md5_digest, fb->pool); + fb->new_text_base_sha1_checksum = + svn_checksum_dup(hb->new_text_base_sha1_checksum, fb->pool); + + /* Store the new pristine text in the pristine store now. Later, in a + single transaction we will update the BASE_NODE to include a + reference to this pristine text's checksum. */ + SVN_ERR(svn_wc__db_pristine_install(db, hb->new_text_base_tmp_abspath, + fb->new_text_base_sha1_checksum, + fb->new_text_base_md5_checksum, + hb->pool)); + } + + svn_pool_destroy(hb->pool); + + return err; +} + + +/* Find the last-change info within ENTRY_PROPS, and return then in the + CHANGED_* parameters. Each parameter will be initialized to its "none" + value, and will contain the relavent info if found. + + CHANGED_AUTHOR will be allocated in RESULT_POOL. SCRATCH_POOL will be + used for some temporary allocations. +*/ +static svn_error_t * +accumulate_last_change(svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + const apr_array_header_t *entry_props, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i; + + *changed_rev = SVN_INVALID_REVNUM; + *changed_date = 0; + *changed_author = NULL; + + for (i = 0; i < entry_props->nelts; ++i) + { + const svn_prop_t *prop = &APR_ARRAY_IDX(entry_props, i, svn_prop_t); + + /* A prop value of NULL means the information was not + available. We don't remove this field from the entries + file; we have convention just leave it empty. So let's + just skip those entry props that have no values. */ + if (! prop->value) + continue; + + if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR)) + *changed_author = apr_pstrdup(result_pool, prop->value->data); + else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV)) + { + apr_int64_t rev; + SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data)); + *changed_rev = (svn_revnum_t)rev; + } + else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE)) + SVN_ERR(svn_time_from_cstring(changed_date, prop->value->data, + scratch_pool)); + + /* Starting with Subversion 1.7 we ignore the SVN_PROP_ENTRY_UUID + property here. */ + } + + return SVN_NO_ERROR; +} + + +/* Join ADD_PATH to BASE_PATH. If ADD_PATH is absolute, or if any ".." + * component of it resolves to a path above BASE_PATH, then return + * SVN_ERR_WC_OBSTRUCTED_UPDATE. + * + * This is to prevent the situation where the repository contains, + * say, "..\nastyfile". Although that's perfectly legal on some + * systems, when checked out onto Win32 it would cause "nastyfile" to + * be created in the parent of the current edit directory. + * + * (http://cve.mitre.org/cgi-bin/cvename.cgi?name=2007-3846) + */ +static svn_error_t * +path_join_under_root(const char **result_path, + const char *base_path, + const char *add_path, + apr_pool_t *pool) +{ + svn_boolean_t under_root; + + SVN_ERR(svn_dirent_is_under_root(&under_root, + result_path, base_path, add_path, pool)); + + if (! under_root) + { + return svn_error_createf( + SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Path '%s' is not in the working copy"), + svn_dirent_local_style(svn_dirent_join(base_path, add_path, pool), + pool)); + } + + /* This catches issue #3288 */ + if (strcmp(add_path, svn_dirent_basename(*result_path, NULL)) != 0) + { + return svn_error_createf( + SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("'%s' is not valid as filename in directory '%s'"), + svn_dirent_local_style(add_path, pool), + svn_dirent_local_style(base_path, pool)); + } + + return SVN_NO_ERROR; +} + + +/*** The callbacks we'll plug into an svn_delta_editor_t structure. ***/ + +/* An svn_delta_editor_t function. */ +static svn_error_t * +set_target_revision(void *edit_baton, + svn_revnum_t target_revision, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + + *(eb->target_revision) = target_revision; + return SVN_NO_ERROR; +} + +static svn_error_t * +check_tree_conflict(svn_skel_t **pconflict, + struct edit_baton *eb, + const char *local_abspath, + svn_wc__db_status_t working_status, + svn_boolean_t exists_in_repos, + svn_node_kind_t expected_kind, + svn_wc_conflict_action_t action, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* An svn_delta_editor_t function. */ +static svn_error_t * +open_root(void *edit_baton, + svn_revnum_t base_revision, /* This is ignored in co */ + apr_pool_t *pool, + void **dir_baton) +{ + struct edit_baton *eb = edit_baton; + struct dir_baton *db; + svn_boolean_t already_conflicted, conflict_ignored; + svn_error_t *err; + svn_wc__db_status_t status; + svn_wc__db_status_t base_status; + svn_node_kind_t kind; + svn_boolean_t have_work; + + /* Note that something interesting is actually happening in this + edit run. */ + eb->root_opened = TRUE; + + SVN_ERR(make_dir_baton(&db, NULL, eb, NULL, FALSE, pool)); + *dir_baton = db; + + err = already_in_a_tree_conflict(&already_conflicted, &conflict_ignored, + eb->db, db->local_abspath, pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + already_conflicted = conflict_ignored = FALSE; + } + else if (already_conflicted) + { + /* Record a skip of both the anchor and target in the skipped tree + as the anchor itself might not be updated */ + SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool)); + SVN_ERR(remember_skipped_tree(eb, eb->target_abspath, pool)); + + db->skip_this = TRUE; + db->already_notified = TRUE; + + /* Notify that we skipped the target, while we actually skipped + the anchor */ + do_notification(eb, eb->target_abspath, svn_node_unknown, + svn_wc_notify_skip_conflicted, pool); + + return SVN_NO_ERROR; + } + + + SVN_ERR(svn_wc__db_read_info(&status, &kind, &db->old_revision, + &db->old_repos_relpath, NULL, NULL, + &db->changed_rev, &db->changed_date, + &db->changed_author, &db->ambient_depth, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, &have_work, + eb->db, db->local_abspath, + db->pool, pool)); + + if (conflict_ignored) + { + db->shadowed = TRUE; + } + else if (have_work) + { + const char *move_src_root_abspath; + + SVN_ERR(svn_wc__db_base_moved_to(NULL, NULL, &move_src_root_abspath, + NULL, eb->db, db->local_abspath, + pool, pool)); + if (move_src_root_abspath || *eb->target_basename == '\0') + SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, + &db->old_revision, + &db->old_repos_relpath, NULL, NULL, + &db->changed_rev, &db->changed_date, + &db->changed_author, + &db->ambient_depth, + NULL, NULL, NULL, NULL, NULL, NULL, + eb->db, db->local_abspath, + db->pool, pool)); + + if (move_src_root_abspath) + { + /* This is an update anchored inside a move. We need to + raise a move-edit tree-conflict on the move root to + update the move destination. */ + svn_skel_t *tree_conflict = svn_wc__conflict_skel_create(pool); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict( + tree_conflict, eb->db, move_src_root_abspath, + svn_wc_conflict_reason_moved_away, + svn_wc_conflict_action_edit, + move_src_root_abspath, pool, pool)); + + if (strcmp(db->local_abspath, move_src_root_abspath)) + { + /* We are raising the tree-conflict on some parent of + the edit root, we won't be handling that path again + so raise the conflict now. */ + SVN_ERR(complete_conflict(tree_conflict, eb, + move_src_root_abspath, + db->old_repos_relpath, + db->old_revision, db->new_relpath, + svn_node_dir, svn_node_dir, + pool, pool)); + SVN_ERR(svn_wc__db_op_mark_conflict(eb->db, + move_src_root_abspath, + tree_conflict, + NULL, pool)); + do_notification(eb, move_src_root_abspath, svn_node_dir, + svn_wc_notify_tree_conflict, pool); + } + else + db->edit_conflict = tree_conflict; + } + + db->shadowed = TRUE; /* Needed for the close_directory() on the root, to + make sure it doesn't use the ACTUAL tree */ + } + else + base_status = status; + + if (*eb->target_basename == '\0') + { + /* For an update with a NULL target, this is equivalent to open_dir(): */ + + db->was_incomplete = (base_status == svn_wc__db_status_incomplete); + + /* ### TODO: Add some tree conflict and obstruction detection, etc. like + open_directory() does. + (or find a way to reuse that code here) + + ### BH 2013: I don't think we need all of the detection here, as the + user explicitly asked to update this node. So we don't + have to tell that it is a local replacement/delete. + */ + + SVN_ERR(svn_wc__db_temp_op_start_directory_update(eb->db, + db->local_abspath, + db->new_relpath, + *eb->target_revision, + pool)); + } + + return SVN_NO_ERROR; +} + + +/* ===================================================================== */ +/* Checking for local modifications. */ + +/* A baton for use with modcheck_found_entry(). */ +typedef struct modcheck_baton_t { + svn_wc__db_t *db; /* wc_db to access nodes */ + svn_boolean_t found_mod; /* whether a modification has been found */ + svn_boolean_t found_not_delete; /* Found a not-delete modification */ +} modcheck_baton_t; + +/* An implementation of svn_wc_status_func4_t. */ +static svn_error_t * +modcheck_callback(void *baton, + const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + modcheck_baton_t *mb = baton; + + switch (status->node_status) + { + case svn_wc_status_normal: + case svn_wc_status_incomplete: + case svn_wc_status_ignored: + case svn_wc_status_none: + case svn_wc_status_unversioned: + case svn_wc_status_external: + break; + + case svn_wc_status_deleted: + mb->found_mod = TRUE; + break; + + case svn_wc_status_missing: + case svn_wc_status_obstructed: + mb->found_mod = TRUE; + mb->found_not_delete = TRUE; + /* Exit from the status walker: We know what we want to know */ + return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); + + default: + case svn_wc_status_added: + case svn_wc_status_replaced: + case svn_wc_status_modified: + mb->found_mod = TRUE; + mb->found_not_delete = TRUE; + /* Exit from the status walker: We know what we want to know */ + return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); + } + + return SVN_NO_ERROR; +} + + +/* Set *MODIFIED to true iff there are any local modifications within the + * tree rooted at LOCAL_ABSPATH, using DB. If *MODIFIED + * is set to true and all the local modifications were deletes then set + * *ALL_EDITS_ARE_DELETES to true, set it to false otherwise. LOCAL_ABSPATH + * may be a file or a directory. */ +svn_error_t * +svn_wc__node_has_local_mods(svn_boolean_t *modified, + svn_boolean_t *all_edits_are_deletes, + svn_wc__db_t *db, + const char *local_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + modcheck_baton_t modcheck_baton = { NULL, FALSE, FALSE }; + svn_error_t *err; + + modcheck_baton.db = db; + + /* Walk the WC tree for status with depth infinity, looking for any local + * modifications. If it's a "sparse" directory, that's OK: there can be + * no local mods in the pieces that aren't present in the WC. */ + + err = svn_wc__internal_walk_status(db, local_abspath, + svn_depth_infinity, + FALSE, FALSE, FALSE, NULL, + modcheck_callback, &modcheck_baton, + cancel_func, cancel_baton, + scratch_pool); + + if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION) + svn_error_clear(err); + else + SVN_ERR(err); + + *modified = modcheck_baton.found_mod; + *all_edits_are_deletes = (modcheck_baton.found_mod + && !modcheck_baton.found_not_delete); + + return SVN_NO_ERROR; +} + +/* Indicates an unset svn_wc_conflict_reason_t. */ +#define SVN_WC_CONFLICT_REASON_NONE (svn_wc_conflict_reason_t)(-1) + +/* Check whether the incoming change ACTION on FULL_PATH would conflict with + * LOCAL_ABSPATH's scheduled change. If so, then raise a tree conflict with + * LOCAL_ABSPATH as the victim. + * + * The edit baton EB gives information including whether the operation is + * an update or a switch. + * + * WORKING_STATUS is the current node status of LOCAL_ABSPATH + * and EXISTS_IN_REPOS specifies whether a BASE_NODE representation for exists + * for this node. In that case the on disk type is compared to EXPECTED_KIND. + * + * If a tree conflict reason was found for the incoming action, the resulting + * tree conflict info is returned in *PCONFLICT. PCONFLICT must be non-NULL, + * while *PCONFLICT is always overwritten. + * + * The tree conflict is allocated in RESULT_POOL. Temporary allocations use + * SCRATCH_POOL. + */ +static svn_error_t * +check_tree_conflict(svn_skel_t **pconflict, + struct edit_baton *eb, + const char *local_abspath, + svn_wc__db_status_t working_status, + svn_boolean_t exists_in_repos, + svn_node_kind_t expected_kind, + svn_wc_conflict_action_t action, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_conflict_reason_t reason = SVN_WC_CONFLICT_REASON_NONE; + svn_boolean_t modified = FALSE; + svn_boolean_t all_mods_are_deletes = FALSE; + const char *move_src_op_root_abspath = NULL; + + *pconflict = NULL; + + /* Find out if there are any local changes to this node that may + * be the "reason" of a tree-conflict with the incoming "action". */ + switch (working_status) + { + case svn_wc__db_status_added: + case svn_wc__db_status_moved_here: + case svn_wc__db_status_copied: + if (!exists_in_repos) + { + /* The node is locally added, and it did not exist before. This + * is an 'update', so the local add can only conflict with an + * incoming 'add'. In fact, if we receive anything else than an + * svn_wc_conflict_action_add (which includes 'added', + * 'copied-here' and 'moved-here') during update on a node that + * did not exist before, then something is very wrong. + * Note that if there was no action on the node, this code + * would not have been called in the first place. */ + SVN_ERR_ASSERT(action == svn_wc_conflict_action_add); + + /* Scan the addition in case our caller didn't. */ + if (working_status == svn_wc__db_status_added) + SVN_ERR(svn_wc__db_scan_addition(&working_status, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, + eb->db, local_abspath, + scratch_pool, scratch_pool)); + + if (working_status == svn_wc__db_status_moved_here) + reason = svn_wc_conflict_reason_moved_here; + else + reason = svn_wc_conflict_reason_added; + } + else + { + /* The node is locally replaced but could also be moved-away. */ + SVN_ERR(svn_wc__db_base_moved_to(NULL, NULL, NULL, + &move_src_op_root_abspath, + eb->db, local_abspath, + scratch_pool, scratch_pool)); + if (move_src_op_root_abspath) + reason = svn_wc_conflict_reason_moved_away; + else + reason = svn_wc_conflict_reason_replaced; + } + break; + + + case svn_wc__db_status_deleted: + { + SVN_ERR(svn_wc__db_base_moved_to(NULL, NULL, NULL, + &move_src_op_root_abspath, + eb->db, local_abspath, + scratch_pool, scratch_pool)); + if (move_src_op_root_abspath) + reason = svn_wc_conflict_reason_moved_away; + else + reason = svn_wc_conflict_reason_deleted; + } + break; + + case svn_wc__db_status_incomplete: + /* We used svn_wc__db_read_info(), so 'incomplete' means + * - there is no node in the WORKING tree + * - a BASE node is known to exist + * So the node exists and is essentially 'normal'. We still need to + * check prop and text mods, and those checks will retrieve the + * missing information (hopefully). */ + case svn_wc__db_status_normal: + if (action == svn_wc_conflict_action_edit) + { + /* An edit onto a local edit or onto *no* local changes is no + * tree-conflict. (It's possibly a text- or prop-conflict, + * but we don't handle those here.) + * + * Except when there is a local obstruction + */ + if (exists_in_repos) + { + svn_node_kind_t disk_kind; + + SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, + scratch_pool)); + + if (disk_kind != expected_kind && disk_kind != svn_node_none) + { + reason = svn_wc_conflict_reason_obstructed; + break; + } + + } + return SVN_NO_ERROR; + } + + /* Replace is handled as delete and then specifically in + add_directory() and add_file(), so we only expect deletes here */ + SVN_ERR_ASSERT(action == svn_wc_conflict_action_delete); + + /* Check if the update wants to delete or replace a locally + * modified node. */ + + + /* Do a deep tree detection of local changes. The update editor will + * not visit the subdirectories of a directory that it wants to delete. + * Therefore, we need to start a separate crawl here. */ + + SVN_ERR(svn_wc__node_has_local_mods(&modified, &all_mods_are_deletes, + eb->db, local_abspath, + eb->cancel_func, eb->cancel_baton, + scratch_pool)); + + if (modified) + { + if (all_mods_are_deletes) + reason = svn_wc_conflict_reason_deleted; + else + reason = svn_wc_conflict_reason_edited; + } + break; + + case svn_wc__db_status_server_excluded: + /* Not allowed to view the node. Not allowed to report tree + * conflicts. */ + case svn_wc__db_status_excluded: + /* Locally marked as excluded. No conflicts wanted. */ + case svn_wc__db_status_not_present: + /* A committed delete (but parent not updated). The delete is + committed, so no conflict possible during update. */ + return SVN_NO_ERROR; + + case svn_wc__db_status_base_deleted: + /* An internal status. Should never show up here. */ + SVN_ERR_MALFUNCTION(); + break; + + } + + if (reason == SVN_WC_CONFLICT_REASON_NONE) + /* No conflict with the current action. */ + return SVN_NO_ERROR; + + + /* Sanity checks. Note that if there was no action on the node, this function + * would not have been called in the first place.*/ + if (reason == svn_wc_conflict_reason_edited + || reason == svn_wc_conflict_reason_obstructed + || reason == svn_wc_conflict_reason_deleted + || reason == svn_wc_conflict_reason_moved_away + || reason == svn_wc_conflict_reason_replaced) + { + /* When the node existed before (it was locally deleted, replaced or + * edited), then 'update' cannot add it "again". So it can only send + * _action_edit, _delete or _replace. */ + if (action != svn_wc_conflict_action_edit + && action != svn_wc_conflict_action_delete + && action != svn_wc_conflict_action_replace) + return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL, + _("Unexpected attempt to add a node at path '%s'"), + svn_dirent_local_style(local_abspath, scratch_pool)); + } + else if (reason == svn_wc_conflict_reason_added || + reason == svn_wc_conflict_reason_moved_here) + { + /* When the node did not exist before (it was locally added), + * then 'update' cannot want to modify it in any way. + * It can only send _action_add. */ + if (action != svn_wc_conflict_action_add) + return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL, + _("Unexpected attempt to edit, delete, or replace " + "a node at path '%s'"), + svn_dirent_local_style(local_abspath, scratch_pool)); + + } + + + /* A conflict was detected. Create a conflict skel to record it. */ + *pconflict = svn_wc__conflict_skel_create(result_pool); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(*pconflict, + eb->db, local_abspath, + reason, + action, + move_src_op_root_abspath, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* If LOCAL_ABSPATH is inside a conflicted tree and the conflict is + * not a moved-away-edit conflict, set *CONFLICTED to TRUE. Otherwise + * set *CONFLICTED to FALSE. + */ +static svn_error_t * +already_in_a_tree_conflict(svn_boolean_t *conflicted, + svn_boolean_t *ignored, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + const char *ancestor_abspath = local_abspath; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + *conflicted = *ignored = FALSE; + + while (TRUE) + { + svn_boolean_t is_wc_root; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_wc__conflicted_for_update_p(conflicted, ignored, db, + ancestor_abspath, TRUE, + scratch_pool)); + if (*conflicted || *ignored) + break; + + SVN_ERR(svn_wc__db_is_wcroot(&is_wc_root, db, ancestor_abspath, + iterpool)); + if (is_wc_root) + break; + + ancestor_abspath = svn_dirent_dirname(ancestor_abspath, scratch_pool); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Temporary helper until the new conflict handling is in place */ +static svn_error_t * +node_already_conflicted(svn_boolean_t *conflicted, + svn_boolean_t *conflict_ignored, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_wc__conflicted_for_update_p(conflicted, conflict_ignored, db, + local_abspath, FALSE, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t revision, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + const char *base = svn_relpath_basename(path, NULL); + const char *local_abspath; + const char *repos_relpath; + svn_node_kind_t kind, base_kind; + svn_revnum_t old_revision; + svn_boolean_t conflicted; + svn_boolean_t have_work; + svn_skel_t *tree_conflict = NULL; + svn_wc__db_status_t status; + svn_wc__db_status_t base_status; + apr_pool_t *scratch_pool; + svn_boolean_t deleting_target; + svn_boolean_t deleting_switched; + svn_boolean_t keep_as_working = FALSE; + svn_boolean_t queue_deletes = TRUE; + + if (pb->skip_this) + return SVN_NO_ERROR; + + scratch_pool = svn_pool_create(pb->pool); + + SVN_ERR(mark_directory_edited(pb, scratch_pool)); + + SVN_ERR(path_join_under_root(&local_abspath, pb->local_abspath, base, + scratch_pool)); + + deleting_target = (strcmp(local_abspath, eb->target_abspath) == 0); + + /* Detect obstructing working copies */ + { + svn_boolean_t is_root; + + SVN_ERR(svn_wc__db_is_wcroot(&is_root, eb->db, local_abspath, + scratch_pool)); + + if (is_root) + { + /* Just skip this node; a future update will handle it */ + SVN_ERR(remember_skipped_tree(eb, local_abspath, pool)); + do_notification(eb, local_abspath, svn_node_unknown, + svn_wc_notify_update_skip_obstruction, scratch_pool); + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; + } + } + + SVN_ERR(svn_wc__db_read_info(&status, &kind, &old_revision, &repos_relpath, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &conflicted, NULL, NULL, NULL, + NULL, NULL, &have_work, + eb->db, local_abspath, + scratch_pool, scratch_pool)); + + if (!have_work) + { + base_status = status; + base_kind = kind; + } + else + SVN_ERR(svn_wc__db_base_get_info(&base_status, &base_kind, &old_revision, + &repos_relpath, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + eb->db, local_abspath, + scratch_pool, scratch_pool)); + + if (pb->old_repos_relpath && repos_relpath) + { + const char *expected_name; + + expected_name = svn_relpath_skip_ancestor(pb->old_repos_relpath, + repos_relpath); + + deleting_switched = (!expected_name || strcmp(expected_name, base) != 0); + } + else + deleting_switched = FALSE; + + /* Is this path a conflict victim? */ + if (pb->shadowed) + conflicted = FALSE; /* Conflict applies to WORKING */ + else if (conflicted) + SVN_ERR(node_already_conflicted(&conflicted, NULL, + eb->db, local_abspath, scratch_pool)); + if (conflicted) + { + SVN_ERR(remember_skipped_tree(eb, local_abspath, scratch_pool)); + + do_notification(eb, local_abspath, svn_node_unknown, + svn_wc_notify_skip_conflicted, + scratch_pool); + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; + } + + + /* Receive the remote removal of excluded/server-excluded/not present node. + Do not notify, but perform the change even when the node is shadowed */ + if (base_status == svn_wc__db_status_not_present + || base_status == svn_wc__db_status_excluded + || base_status == svn_wc__db_status_server_excluded) + { + SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath, + FALSE /* keep_as_working */, + FALSE /* queue_deletes */, + SVN_INVALID_REVNUM /* not_present_rev */, + NULL, NULL, + scratch_pool)); + + if (deleting_target) + eb->target_deleted = TRUE; + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; + } + + /* Is this path the victim of a newly-discovered tree conflict? If so, + * remember it and notify the client. Then (if it was existing and + * modified), re-schedule the node to be added back again, as a (modified) + * copy of the previous base version. */ + + /* Check for conflicts only when we haven't already recorded + * a tree-conflict on a parent node. */ + if (!pb->shadowed && !pb->edit_obstructed) + { + SVN_ERR(check_tree_conflict(&tree_conflict, eb, local_abspath, + status, TRUE, + (kind == svn_node_dir) + ? svn_node_dir + : svn_node_file, + svn_wc_conflict_action_delete, + pb->pool, scratch_pool)); + } + else + queue_deletes = FALSE; /* There is no in-wc representation */ + + if (tree_conflict != NULL) + { + svn_wc_conflict_reason_t reason; + /* When we raise a tree conflict on a node, we don't want to mark the + * node as skipped, to allow a replacement to continue doing at least + * a bit of its work (possibly adding a not present node, for the + * next update) */ + if (!pb->deletion_conflicts) + pb->deletion_conflicts = apr_hash_make(pb->pool); + + svn_hash_sets(pb->deletion_conflicts, apr_pstrdup(pb->pool, base), + tree_conflict); + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL, + eb->db, local_abspath, + tree_conflict, + scratch_pool, scratch_pool)); + + if (reason == svn_wc_conflict_reason_edited + || reason == svn_wc_conflict_reason_obstructed) + { + /* The item exists locally and has some sort of local mod. + * It no longer exists in the repository at its target URL@REV. + * + * To prepare the "accept mine" resolution for the tree conflict, + * we must schedule the existing content for re-addition as a copy + * of what it was, but with its local modifications preserved. */ + keep_as_working = TRUE; + + /* Fall through to remove the BASE_NODEs properly, with potentially + keeping a not-present marker */ + } + else if (reason == svn_wc_conflict_reason_deleted + || reason == svn_wc_conflict_reason_moved_away + || reason == svn_wc_conflict_reason_replaced) + { + /* The item does not exist locally because it was already shadowed. + * We must complete the deletion, leaving the tree conflict info + * as the only difference from a normal deletion. */ + + /* Fall through to the normal "delete" code path. */ + } + else + SVN_ERR_MALFUNCTION(); /* other reasons are not expected here */ + } + + SVN_ERR(complete_conflict(tree_conflict, eb, local_abspath, repos_relpath, + old_revision, NULL, + (kind == svn_node_dir) + ? svn_node_dir + : svn_node_file, + svn_node_none, + pb->pool, scratch_pool)); + + /* Issue a wq operation to delete the BASE_NODE data and to delete actual + nodes based on that from disk, but leave any WORKING_NODEs on disk. + + Local modifications are already turned into copies at this point. + + If the thing being deleted is the *target* of this update, then + we need to recreate a 'deleted' entry, so that the parent can give + accurate reports about itself in the future. */ + if (! deleting_target && ! deleting_switched) + { + /* Delete, and do not leave a not-present node. */ + SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath, + keep_as_working, queue_deletes, + SVN_INVALID_REVNUM /* not_present_rev */, + tree_conflict, NULL, + scratch_pool)); + } + else + { + /* Delete, leaving a not-present node. */ + SVN_ERR(svn_wc__db_base_remove(eb->db, local_abspath, + keep_as_working, queue_deletes, + *eb->target_revision, + tree_conflict, NULL, + scratch_pool)); + if (deleting_target) + eb->target_deleted = TRUE; + else + { + /* Don't remove the not-present marker at the final bump */ + SVN_ERR(remember_skipped_tree(eb, local_abspath, pool)); + } + } + + SVN_ERR(svn_wc__wq_run(eb->db, pb->local_abspath, + eb->cancel_func, eb->cancel_baton, + scratch_pool)); + + /* Notify. */ + if (tree_conflict) + do_notification(eb, local_abspath, svn_node_unknown, + svn_wc_notify_tree_conflict, scratch_pool); + else + { + svn_wc_notify_action_t action = svn_wc_notify_update_delete; + svn_node_kind_t node_kind; + + if (pb->shadowed || pb->edit_obstructed) + action = svn_wc_notify_update_shadowed_delete; + + if (kind == svn_node_dir) + node_kind = svn_node_dir; + else + node_kind = svn_node_file; + + do_notification(eb, local_abspath, node_kind, action, scratch_pool); + } + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +add_directory(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_rev, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct dir_baton *db; + svn_node_kind_t kind; + svn_wc__db_status_t status; + svn_node_kind_t wc_kind; + svn_boolean_t conflicted; + svn_boolean_t conflict_ignored = FALSE; + svn_boolean_t versioned_locally_and_present; + svn_skel_t *tree_conflict = NULL; + svn_error_t *err; + + SVN_ERR_ASSERT(! (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_rev))); + + SVN_ERR(make_dir_baton(&db, path, eb, pb, TRUE, pool)); + *child_baton = db; + + if (db->skip_this) + return SVN_NO_ERROR; + + SVN_ERR(mark_directory_edited(db, pool)); + + if (strcmp(eb->target_abspath, db->local_abspath) == 0) + { + /* The target of the edit is being added, give it the requested + depth of the edit (but convert svn_depth_unknown to + svn_depth_infinity). */ + db->ambient_depth = (eb->requested_depth == svn_depth_unknown) + ? svn_depth_infinity : eb->requested_depth; + } + else if (eb->requested_depth == svn_depth_immediates + || (eb->requested_depth == svn_depth_unknown + && pb->ambient_depth == svn_depth_immediates)) + { + db->ambient_depth = svn_depth_empty; + } + else + { + db->ambient_depth = svn_depth_infinity; + } + + /* It may not be named the same as the administrative directory. */ + if (svn_wc_is_adm_dir(db->name, pool)) + return svn_error_createf( + SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Failed to add directory '%s': object of the same name as the " + "administrative directory"), + svn_dirent_local_style(db->local_abspath, pool)); + + SVN_ERR(svn_io_check_path(db->local_abspath, &kind, db->pool)); + + err = svn_wc__db_read_info(&status, &wc_kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + &conflicted, NULL, NULL, NULL, NULL, NULL, NULL, + eb->db, db->local_abspath, db->pool, db->pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + wc_kind = svn_node_unknown; + status = svn_wc__db_status_normal; + conflicted = FALSE; + + versioned_locally_and_present = FALSE; + } + else if (wc_kind == svn_node_dir + && status == svn_wc__db_status_normal) + { + /* !! We found the root of a separate working copy obstructing the wc !! + + If the directory would be part of our own working copy then + we wouldn't have been called as an add_directory(). + + The only thing we can do is add a not-present node, to allow + a future update to bring in the new files when the problem is + resolved. Note that svn_wc__db_base_add_not_present_node() + explicitly adds the node into the parent's node database. */ + + SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db, db->local_abspath, + db->new_relpath, + eb->repos_root, + eb->repos_uuid, + *eb->target_revision, + svn_node_file, + NULL, NULL, + pool)); + + SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool)); + db->skip_this = TRUE; + db->already_notified = TRUE; + + do_notification(eb, db->local_abspath, svn_node_dir, + svn_wc_notify_update_skip_obstruction, pool); + + return SVN_NO_ERROR; + } + else if (status == svn_wc__db_status_normal + && (wc_kind == svn_node_file + || wc_kind == svn_node_symlink)) + { + /* We found a file external occupating the place we need in BASE. + + We can't add a not-present node in this case as that would overwrite + the file external. Luckily the file external itself stops us from + forgetting a child of this parent directory like an obstructing + working copy would. + + The reason we get here is that the adm crawler doesn't report + file externals. + */ + + SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool)); + db->skip_this = TRUE; + db->already_notified = TRUE; + + do_notification(eb, db->local_abspath, svn_node_file, + svn_wc_notify_update_skip_obstruction, pool); + + return SVN_NO_ERROR; + } + else if (wc_kind == svn_node_unknown) + versioned_locally_and_present = FALSE; /* Tree conflict ACTUAL-only node */ + else + versioned_locally_and_present = IS_NODE_PRESENT(status); + + /* Is this path a conflict victim? */ + if (conflicted) + { + if (pb->deletion_conflicts) + tree_conflict = svn_hash_gets(pb->deletion_conflicts, db->name); + + if (tree_conflict) + { + svn_wc_conflict_reason_t reason; + /* So this deletion wasn't just a deletion, it is actually a + replacement. Let's install a better tree conflict. */ + + /* ### Should store the conflict in DB to allow reinstalling + ### with theoretically more data in close_directory() */ + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL, + eb->db, + db->local_abspath, + tree_conflict, + db->pool, db->pool)); + + tree_conflict = svn_wc__conflict_skel_create(db->pool); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict( + tree_conflict, + eb->db, db->local_abspath, + reason, svn_wc_conflict_action_replace, + NULL, + db->pool, db->pool)); + + /* And now stop checking for conflicts here and just perform + a shadowed update */ + db->edit_conflict = tree_conflict; /* Cache for close_directory */ + tree_conflict = NULL; /* No direct notification */ + db->shadowed = TRUE; /* Just continue */ + conflicted = FALSE; /* No skip */ + } + else + SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored, + eb->db, db->local_abspath, pool)); + } + + /* Now the "usual" behaviour if already conflicted. Skip it. */ + if (conflicted) + { + /* Record this conflict so that its descendants are skipped silently. */ + SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool)); + + db->skip_this = TRUE; + db->already_notified = TRUE; + + /* We skip this node, but once the update completes the parent node will + be updated to the new revision. So a future recursive update of the + parent will not bring in this new node as the revision of the parent + describes to the repository that all children are available. + + To resolve this problem, we add a not-present node to allow bringing + the node in once this conflict is resolved. + + Note that we can safely assume that no present base node exists, + because then we would not have received an add_directory. + */ + SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db, db->local_abspath, + db->new_relpath, + eb->repos_root, + eb->repos_uuid, + *eb->target_revision, + svn_node_dir, + NULL, NULL, + pool)); + + /* ### TODO: Also print victim_path in the skip msg. */ + do_notification(eb, db->local_abspath, svn_node_dir, + svn_wc_notify_skip_conflicted, pool); + return SVN_NO_ERROR; + } + else if (conflict_ignored) + { + db->shadowed = TRUE; + } + + if (db->shadowed) + { + /* Nothing to check; does not and will not exist in working copy */ + } + else if (versioned_locally_and_present) + { + /* What to do with a versioned or schedule-add dir: + + A dir already added without history is OK. Set add_existed + so that user notification is delayed until after any prop + conflicts have been found. + + An existing versioned dir is an error. In the future we may + relax this restriction and simply update such dirs. + + A dir added with history is a tree conflict. */ + + svn_boolean_t local_is_non_dir; + svn_wc__db_status_t add_status = svn_wc__db_status_normal; + + /* Is the local add a copy? */ + if (status == svn_wc__db_status_added) + SVN_ERR(svn_wc__db_scan_addition(&add_status, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + eb->db, db->local_abspath, + pool, pool)); + + + /* Is there *something* that is not a dir? */ + local_is_non_dir = (wc_kind != svn_node_dir + && status != svn_wc__db_status_deleted); + + /* Do tree conflict checking if + * - if there is a local copy. + * - if this is a switch operation + * - the node kinds mismatch + * + * During switch, local adds at the same path as incoming adds get + * "lost" in that switching back to the original will no longer have the + * local add. So switch always alerts the user with a tree conflict. */ + if (!eb->adds_as_modification + || local_is_non_dir + || add_status != svn_wc__db_status_added) + { + SVN_ERR(check_tree_conflict(&tree_conflict, eb, + db->local_abspath, + status, FALSE, svn_node_none, + svn_wc_conflict_action_add, + pool, pool)); + } + + if (tree_conflict == NULL) + db->add_existed = TRUE; /* Take over WORKING */ + else + db->shadowed = TRUE; /* Only update BASE */ + } + else if (kind != svn_node_none) + { + /* There's an unversioned node at this path. */ + db->obstruction_found = TRUE; + + /* Unversioned, obstructing dirs are handled by prop merge/conflict, + * if unversioned obstructions are allowed. */ + if (! (kind == svn_node_dir && eb->allow_unver_obstructions)) + { + /* Bring in the node as deleted */ /* ### Obstructed Conflict */ + db->shadowed = TRUE; + + /* Mark a conflict */ + tree_conflict = svn_wc__conflict_skel_create(db->pool); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict( + tree_conflict, + eb->db, db->local_abspath, + svn_wc_conflict_reason_unversioned, + svn_wc_conflict_action_add, NULL, + db->pool, pool)); + db->edit_conflict = tree_conflict; + } + } + + if (tree_conflict) + SVN_ERR(complete_conflict(tree_conflict, eb, db->local_abspath, + db->old_repos_relpath, db->old_revision, + db->new_relpath, + wc_kind, + svn_node_dir, + db->pool, pool)); + + SVN_ERR(svn_wc__db_base_add_incomplete_directory( + eb->db, db->local_abspath, + db->new_relpath, + eb->repos_root, + eb->repos_uuid, + *eb->target_revision, + db->ambient_depth, + (db->shadowed && db->obstruction_found), + (! db->shadowed + && status == svn_wc__db_status_added), + tree_conflict, NULL, + pool)); + + /* Make sure there is a real directory at LOCAL_ABSPATH, unless we are just + updating the DB */ + if (!db->shadowed) + SVN_ERR(svn_wc__ensure_directory(db->local_abspath, pool)); + + if (tree_conflict != NULL) + { + db->already_notified = TRUE; + do_notification(eb, db->local_abspath, svn_node_dir, + svn_wc_notify_tree_conflict, pool); + } + + + /* If this add was obstructed by dir scheduled for addition without + history let close_directory() handle the notification because there + might be properties to deal with. If PATH was added inside a locally + deleted tree, then suppress notification, a tree conflict was already + issued. */ + if (eb->notify_func && !db->already_notified && !db->add_existed) + { + svn_wc_notify_action_t action; + + if (db->shadowed) + action = svn_wc_notify_update_shadowed_add; + else if (db->obstruction_found || db->add_existed) + action = svn_wc_notify_exists; + else + action = svn_wc_notify_update_add; + + db->already_notified = TRUE; + + do_notification(eb, db->local_abspath, svn_node_dir, action, pool); + } + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +open_directory(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *db, *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + svn_boolean_t have_work; + svn_boolean_t conflicted; + svn_boolean_t conflict_ignored = FALSE; + svn_skel_t *tree_conflict = NULL; + svn_wc__db_status_t status, base_status; + svn_node_kind_t wc_kind; + + SVN_ERR(make_dir_baton(&db, path, eb, pb, FALSE, pool)); + *child_baton = db; + + if (db->skip_this) + return SVN_NO_ERROR; + + /* Detect obstructing working copies */ + { + svn_boolean_t is_root; + + SVN_ERR(svn_wc__db_is_wcroot(&is_root, eb->db, db->local_abspath, + pool)); + + if (is_root) + { + /* Just skip this node; a future update will handle it */ + SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool)); + db->skip_this = TRUE; + db->already_notified = TRUE; + + do_notification(eb, db->local_abspath, svn_node_dir, + svn_wc_notify_update_skip_obstruction, pool); + + return SVN_NO_ERROR; + } + } + + /* We should have a write lock on every directory touched. */ + SVN_ERR(svn_wc__write_check(eb->db, db->local_abspath, pool)); + + SVN_ERR(svn_wc__db_read_info(&status, &wc_kind, &db->old_revision, + &db->old_repos_relpath, NULL, NULL, + &db->changed_rev, &db->changed_date, + &db->changed_author, &db->ambient_depth, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + &conflicted, NULL, NULL, NULL, + NULL, NULL, &have_work, + eb->db, db->local_abspath, + db->pool, pool)); + + if (!have_work) + base_status = status; + else + SVN_ERR(svn_wc__db_base_get_info(&base_status, NULL, &db->old_revision, + &db->old_repos_relpath, NULL, NULL, + &db->changed_rev, &db->changed_date, + &db->changed_author, &db->ambient_depth, + NULL, NULL, NULL, NULL, NULL, NULL, + eb->db, db->local_abspath, + db->pool, pool)); + + db->was_incomplete = (base_status == svn_wc__db_status_incomplete); + + /* Is this path a conflict victim? */ + if (db->shadowed) + conflicted = FALSE; /* Conflict applies to WORKING */ + else if (conflicted) + SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored, + eb->db, db->local_abspath, pool)); + if (conflicted) + { + SVN_ERR(remember_skipped_tree(eb, db->local_abspath, pool)); + + db->skip_this = TRUE; + db->already_notified = TRUE; + + do_notification(eb, db->local_abspath, svn_node_unknown, + svn_wc_notify_skip_conflicted, pool); + + return SVN_NO_ERROR; + } + else if (conflict_ignored) + { + db->shadowed = TRUE; + } + + /* Is this path a fresh tree conflict victim? If so, skip the tree + with one notification. */ + + /* Check for conflicts only when we haven't already recorded + * a tree-conflict on a parent node. */ + if (!db->shadowed) + SVN_ERR(check_tree_conflict(&tree_conflict, eb, db->local_abspath, + status, TRUE, svn_node_dir, + svn_wc_conflict_action_edit, + db->pool, pool)); + + /* Remember the roots of any locally deleted trees. */ + if (tree_conflict != NULL) + { + svn_wc_conflict_reason_t reason; + db->edit_conflict = tree_conflict; + /* Other modifications wouldn't be a tree conflict */ + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL, + eb->db, db->local_abspath, + tree_conflict, + db->pool, db->pool)); + SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_deleted + || reason == svn_wc_conflict_reason_moved_away + || reason == svn_wc_conflict_reason_replaced + || reason == svn_wc_conflict_reason_obstructed); + + /* Continue updating BASE */ + if (reason == svn_wc_conflict_reason_obstructed) + db->edit_obstructed = TRUE; + else + db->shadowed = TRUE; + } + + /* Mark directory as being at target_revision and URL, but incomplete. */ + SVN_ERR(svn_wc__db_temp_op_start_directory_update(eb->db, db->local_abspath, + db->new_relpath, + *eb->target_revision, + pool)); + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +change_dir_prop(void *dir_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + svn_prop_t *propchange; + struct dir_baton *db = dir_baton; + + if (db->skip_this) + return SVN_NO_ERROR; + + propchange = apr_array_push(db->propchanges); + propchange->name = apr_pstrdup(db->pool, name); + propchange->value = value ? svn_string_dup(value, db->pool) : NULL; + + if (!db->edited && svn_property_kind2(name) == svn_prop_regular_kind) + SVN_ERR(mark_directory_edited(db, pool)); + + return SVN_NO_ERROR; +} + +/* If any of the svn_prop_t objects in PROPCHANGES represents a change + to the SVN_PROP_EXTERNALS property, return that change, else return + null. If PROPCHANGES contains more than one such change, return + the first. */ +static const svn_prop_t * +externals_prop_changed(const apr_array_header_t *propchanges) +{ + int i; + + for (i = 0; i < propchanges->nelts; i++) + { + const svn_prop_t *p = &(APR_ARRAY_IDX(propchanges, i, svn_prop_t)); + if (strcmp(p->name, SVN_PROP_EXTERNALS) == 0) + return p; + } + + return NULL; +} + + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +close_directory(void *dir_baton, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + struct edit_baton *eb = db->edit_baton; + svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown; + apr_array_header_t *entry_prop_changes; + apr_array_header_t *dav_prop_changes; + apr_array_header_t *regular_prop_changes; + apr_hash_t *base_props; + apr_hash_t *actual_props; + apr_hash_t *new_base_props = NULL; + apr_hash_t *new_actual_props = NULL; + svn_revnum_t new_changed_rev = SVN_INVALID_REVNUM; + apr_time_t new_changed_date = 0; + const char *new_changed_author = NULL; + apr_pool_t *scratch_pool = db->pool; + svn_skel_t *all_work_items = NULL; + svn_skel_t *conflict_skel = NULL; + + /* Skip if we're in a conflicted tree. */ + if (db->skip_this) + { + /* Allow the parent to complete its update. */ + SVN_ERR(maybe_release_dir_info(db)); + + return SVN_NO_ERROR; + } + + if (db->edited) + conflict_skel = db->edit_conflict; + + SVN_ERR(svn_categorize_props(db->propchanges, &entry_prop_changes, + &dav_prop_changes, ®ular_prop_changes, pool)); + + /* Fetch the existing properties. */ + if ((!db->adding_dir || db->add_existed) + && !db->shadowed) + { + SVN_ERR(svn_wc__get_actual_props(&actual_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + } + else + actual_props = apr_hash_make(pool); + + if (db->add_existed) + { + /* This node already exists. Grab the current pristine properties. */ + SVN_ERR(svn_wc__db_read_pristine_props(&base_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + } + else if (!db->adding_dir) + { + /* Get the BASE properties for proper merging. */ + SVN_ERR(svn_wc__db_base_get_props(&base_props, + eb->db, db->local_abspath, + scratch_pool, scratch_pool)); + } + else + base_props = apr_hash_make(pool); + + /* An incomplete directory might have props which were supposed to be + deleted but weren't. Because the server sent us all the props we're + supposed to have, any previous base props not in this list must be + deleted (issue #1672). */ + if (db->was_incomplete) + { + int i; + apr_hash_t *props_to_delete; + apr_hash_index_t *hi; + + /* In a copy of the BASE props, remove every property that we see an + incoming change for. The remaining unmentioned properties are those + which need to be deleted. */ + props_to_delete = apr_hash_copy(pool, base_props); + for (i = 0; i < regular_prop_changes->nelts; i++) + { + const svn_prop_t *prop; + prop = &APR_ARRAY_IDX(regular_prop_changes, i, svn_prop_t); + svn_hash_sets(props_to_delete, prop->name, NULL); + } + + /* Add these props to the incoming propchanges (in + * regular_prop_changes). */ + for (hi = apr_hash_first(pool, props_to_delete); + hi != NULL; + hi = apr_hash_next(hi)) + { + const char *propname = svn__apr_hash_index_key(hi); + svn_prop_t *prop = apr_array_push(regular_prop_changes); + + /* Record a deletion for PROPNAME. */ + prop->name = propname; + prop->value = NULL; + } + } + + /* If this directory has property changes stored up, now is the time + to deal with them. */ + if (regular_prop_changes->nelts) + { + /* If recording traversal info, then see if the + SVN_PROP_EXTERNALS property on this directory changed, + and record before and after for the change. */ + if (eb->external_func) + { + const svn_prop_t *change + = externals_prop_changed(regular_prop_changes); + + if (change) + { + const svn_string_t *new_val_s = change->value; + const svn_string_t *old_val_s; + + old_val_s = svn_hash_gets(base_props, SVN_PROP_EXTERNALS); + + if ((new_val_s == NULL) && (old_val_s == NULL)) + ; /* No value before, no value after... so do nothing. */ + else if (new_val_s && old_val_s + && (svn_string_compare(old_val_s, new_val_s))) + ; /* Value did not change... so do nothing. */ + else if (old_val_s || new_val_s) + /* something changed, record the change */ + { + SVN_ERR((eb->external_func)( + eb->external_baton, + db->local_abspath, + old_val_s, + new_val_s, + db->ambient_depth, + db->pool)); + } + } + } + + if (db->shadowed) + { + /* We don't have a relevant actual row, but we need actual properties + to allow property merging without conflicts. */ + if (db->adding_dir) + actual_props = apr_hash_make(scratch_pool); + else + actual_props = base_props; + } + + /* Merge pending properties. */ + new_base_props = svn_prop__patch(base_props, regular_prop_changes, + db->pool); + SVN_ERR_W(svn_wc__merge_props(&conflict_skel, + &prop_state, + &new_actual_props, + eb->db, + db->local_abspath, + NULL /* use baseprops */, + base_props, + actual_props, + regular_prop_changes, + db->pool, + scratch_pool), + _("Couldn't do property merge")); + /* After a (not-dry-run) merge, we ALWAYS have props to save. */ + SVN_ERR_ASSERT(new_base_props != NULL && new_actual_props != NULL); + } + + SVN_ERR(accumulate_last_change(&new_changed_rev, &new_changed_date, + &new_changed_author, entry_prop_changes, + scratch_pool, scratch_pool)); + + /* Check if we should add some not-present markers before marking the + directory complete (Issue #3569) */ + { + apr_hash_t *new_children = svn_hash_gets(eb->dir_dirents, db->new_relpath); + + if (new_children != NULL) + { + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + for (hi = apr_hash_first(scratch_pool, new_children); + hi; + hi = apr_hash_next(hi)) + { + const char *child_name; + const char *child_abspath; + const char *child_relpath; + const svn_dirent_t *dirent; + svn_wc__db_status_t status; + svn_node_kind_t child_kind; + svn_error_t *err; + + svn_pool_clear(iterpool); + + child_name = svn__apr_hash_index_key(hi); + child_abspath = svn_dirent_join(db->local_abspath, child_name, + iterpool); + + dirent = svn__apr_hash_index_val(hi); + child_kind = (dirent->kind == svn_node_dir) + ? svn_node_dir + : svn_node_file; + + if (db->ambient_depth < svn_depth_immediates + && child_kind == svn_node_dir) + continue; /* We don't need the subdirs */ + + /* ### We just check if there is some node in BASE at this path */ + err = svn_wc__db_base_get_info(&status, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + eb->db, child_abspath, + iterpool, iterpool); + + if (!err) + { + svn_boolean_t is_wcroot; + SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, eb->db, child_abspath, + iterpool)); + + if (!is_wcroot) + continue; /* Everything ok... Nothing to do here */ + /* Fall through to allow recovering later */ + } + else if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + + child_relpath = svn_relpath_join(db->new_relpath, child_name, + iterpool); + + SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db, + child_abspath, + child_relpath, + eb->repos_root, + eb->repos_uuid, + *eb->target_revision, + child_kind, + NULL, NULL, + iterpool)); + } + + svn_pool_destroy(iterpool); + } + } + + if (apr_hash_count(db->not_present_files)) + { + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + /* This should call some new function (which could also be used + for new_children above) to add all the names in single + transaction, but I can't even trigger it. I've tried + ra_local, ra_svn, ra_neon, ra_serf and they all call + close_file before close_dir. */ + for (hi = apr_hash_first(scratch_pool, db->not_present_files); + hi; + hi = apr_hash_next(hi)) + { + const char *child = svn__apr_hash_index_key(hi); + const char *child_abspath, *child_relpath; + + svn_pool_clear(iterpool); + + child_abspath = svn_dirent_join(db->local_abspath, child, iterpool); + child_relpath = svn_dirent_join(db->new_relpath, child, iterpool); + + SVN_ERR(svn_wc__db_base_add_not_present_node(eb->db, + child_abspath, + child_relpath, + eb->repos_root, + eb->repos_uuid, + *eb->target_revision, + svn_node_file, + NULL, NULL, + iterpool)); + } + svn_pool_destroy(iterpool); + } + + /* If this directory is merely an anchor for a targeted child, then we + should not be updating the node at all. */ + if (db->parent_baton == NULL + && *eb->target_basename != '\0') + { + /* And we should not have received any changes! */ + SVN_ERR_ASSERT(db->propchanges->nelts == 0); + /* ... which also implies NEW_CHANGED_* are not set, + and NEW_BASE_PROPS == NULL. */ + } + else + { + apr_hash_t *props; + apr_array_header_t *iprops = NULL; + + /* ### we know a base node already exists. it was created in + ### open_directory or add_directory. let's just preserve the + ### existing DEPTH value, and possibly CHANGED_*. */ + /* If we received any changed_* values, then use them. */ + if (SVN_IS_VALID_REVNUM(new_changed_rev)) + db->changed_rev = new_changed_rev; + if (new_changed_date != 0) + db->changed_date = new_changed_date; + if (new_changed_author != NULL) + db->changed_author = new_changed_author; + + /* If no depth is set yet, set to infinity. */ + if (db->ambient_depth == svn_depth_unknown) + db->ambient_depth = svn_depth_infinity; + + if (eb->depth_is_sticky + && db->ambient_depth != eb->requested_depth) + { + /* After a depth upgrade the entry must reflect the new depth. + Upgrading to infinity changes the depth of *all* directories, + upgrading to something else only changes the target. */ + + if (eb->requested_depth == svn_depth_infinity + || (strcmp(db->local_abspath, eb->target_abspath) == 0 + && eb->requested_depth > db->ambient_depth)) + { + db->ambient_depth = eb->requested_depth; + } + } + + /* Do we have new properties to install? Or shall we simply retain + the prior set of properties? If we're installing new properties, + then we also want to write them to an old-style props file. */ + props = new_base_props; + if (props == NULL) + props = base_props; + + if (conflict_skel) + { + svn_skel_t *work_item; + + SVN_ERR(complete_conflict(conflict_skel, + db->edit_baton, + db->local_abspath, + db->old_repos_relpath, + db->old_revision, + db->new_relpath, + svn_node_dir, svn_node_dir, + db->pool, scratch_pool)); + + SVN_ERR(svn_wc__conflict_create_markers(&work_item, + eb->db, db->local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + scratch_pool); + } + + /* Any inherited props to be set set for this base node? */ + if (eb->wcroot_iprops) + { + iprops = svn_hash_gets(eb->wcroot_iprops, db->local_abspath); + + /* close_edit may also update iprops for switched nodes, catching + those for which close_directory is never called (e.g. a switch + with no changes). So as a minor optimization we remove any + iprops from the hash so as not to set them again in + close_edit. */ + if (iprops) + svn_hash_sets(eb->wcroot_iprops, db->local_abspath, NULL); + } + + /* Update the BASE data for the directory and mark the directory + complete */ + SVN_ERR(svn_wc__db_base_add_directory( + eb->db, db->local_abspath, + eb->wcroot_abspath, + db->new_relpath, + eb->repos_root, eb->repos_uuid, + *eb->target_revision, + props, + db->changed_rev, db->changed_date, db->changed_author, + NULL /* children */, + db->ambient_depth, + (dav_prop_changes->nelts > 0) + ? svn_prop_array_to_hash(dav_prop_changes, pool) + : NULL, + conflict_skel, + (! db->shadowed) && new_base_props != NULL, + new_actual_props, + iprops, all_work_items, + scratch_pool)); + } + + /* Process all of the queued work items for this directory. */ + SVN_ERR(svn_wc__wq_run(eb->db, db->local_abspath, + eb->cancel_func, eb->cancel_baton, + scratch_pool)); + + if (conflict_skel && eb->conflict_func) + SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, db->local_abspath, + conflict_skel, + NULL /* merge_options */, + eb->conflict_func, + eb->conflict_baton, + eb->cancel_func, + eb->conflict_baton, + scratch_pool)); + + /* Notify of any prop changes on this directory -- but do nothing if + it's an added or skipped directory, because notification has already + happened in that case - unless the add was obstructed by a dir + scheduled for addition without history, in which case we handle + notification here). */ + if (!db->already_notified && eb->notify_func && db->edited) + { + svn_wc_notify_t *notify; + svn_wc_notify_action_t action; + + if (db->shadowed || db->edit_obstructed) + action = svn_wc_notify_update_shadowed_update; + else if (db->obstruction_found || db->add_existed) + action = svn_wc_notify_exists; + else + action = svn_wc_notify_update_update; + + notify = svn_wc_create_notify(db->local_abspath, action, pool); + notify->kind = svn_node_dir; + notify->prop_state = prop_state; + notify->revision = *eb->target_revision; + notify->old_revision = db->old_revision; + + eb->notify_func(eb->notify_baton, notify, scratch_pool); + } + + /* We're done with this directory, so remove one reference from the + bump information. */ + SVN_ERR(maybe_release_dir_info(db)); + + return SVN_NO_ERROR; +} + + +/* Common code for 'absent_file' and 'absent_directory'. */ +static svn_error_t * +absent_node(const char *path, + svn_node_kind_t absent_kind, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + apr_pool_t *scratch_pool = svn_pool_create(pool); + const char *name = svn_dirent_basename(path, NULL); + const char *local_abspath; + svn_error_t *err; + svn_wc__db_status_t status; + svn_node_kind_t kind; + + if (pb->skip_this) + return SVN_NO_ERROR; + + SVN_ERR(mark_directory_edited(pb, scratch_pool)); + + local_abspath = svn_dirent_join(pb->local_abspath, name, scratch_pool); + + /* If an item by this name is scheduled for addition that's a + genuine tree-conflict. */ + err = svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + eb->db, local_abspath, + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + status = svn_wc__db_status_not_present; + kind = svn_node_unknown; + } + + if (status == svn_wc__db_status_normal + && kind == svn_node_dir) + { + /* We found an obstructing working copy! + + We can do two things now: + 1) notify the user, record a skip, etc. + 2) Just record the absent node in BASE in the parent + working copy. + + As option 2 happens to be exactly what we do anyway, lets do that. + */ + } + else if (status == svn_wc__db_status_not_present + || status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded) + { + /* The BASE node is not actually there, so we can safely turn it into + an absent node */ + } + else + { + /* We have a local addition. If this would be a BASE node it would have + been deleted before we get here. (Which might have turned it into + a copy). + + ### This should be recorded as a tree conflict and the update + ### can just continue, as we can just record the absent status + ### in BASE. + */ + SVN_ERR_ASSERT(status != svn_wc__db_status_normal); + + return svn_error_createf( + SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Failed to mark '%s' absent: item of the same name is already " + "scheduled for addition"), + svn_dirent_local_style(local_abspath, pool)); + } + + { + const char *repos_relpath; + repos_relpath = svn_relpath_join(pb->new_relpath, name, scratch_pool); + + /* Insert an excluded node below the parent node to note that this child + is absent. (This puts it in the parent db if the child is obstructed) */ + SVN_ERR(svn_wc__db_base_add_excluded_node(eb->db, local_abspath, + repos_relpath, eb->repos_root, + eb->repos_uuid, + *(eb->target_revision), + absent_kind, + svn_wc__db_status_server_excluded, + NULL, NULL, + scratch_pool)); + } + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +absent_file(const char *path, + void *parent_baton, + apr_pool_t *pool) +{ + return absent_node(path, svn_node_file, parent_baton, pool); +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +absent_directory(const char *path, + void *parent_baton, + apr_pool_t *pool) +{ + return absent_node(path, svn_node_dir, parent_baton, pool); +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +add_file(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_rev, + apr_pool_t *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct file_baton *fb; + svn_node_kind_t kind = svn_node_none; + svn_node_kind_t wc_kind = svn_node_unknown; + svn_wc__db_status_t status = svn_wc__db_status_normal; + apr_pool_t *scratch_pool; + svn_boolean_t conflicted = FALSE; + svn_boolean_t conflict_ignored = FALSE; + svn_boolean_t versioned_locally_and_present = FALSE; + svn_skel_t *tree_conflict = NULL; + svn_error_t *err = SVN_NO_ERROR; + + SVN_ERR_ASSERT(! (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_rev))); + + SVN_ERR(make_file_baton(&fb, pb, path, TRUE, pool)); + *file_baton = fb; + + if (fb->skip_this) + return SVN_NO_ERROR; + + SVN_ERR(mark_file_edited(fb, pool)); + + /* The file_pool can stick around for a *long* time, so we want to + use a subpool for any temporary allocations. */ + scratch_pool = svn_pool_create(pool); + + + /* It may not be named the same as the administrative directory. */ + if (svn_wc_is_adm_dir(fb->name, pool)) + return svn_error_createf( + SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Failed to add file '%s': object of the same name as the " + "administrative directory"), + svn_dirent_local_style(fb->local_abspath, pool)); + + if (!eb->clean_checkout) + { + SVN_ERR(svn_io_check_path(fb->local_abspath, &kind, scratch_pool)); + + err = svn_wc__db_read_info(&status, &wc_kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + &conflicted, NULL, NULL, NULL, NULL, NULL, NULL, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool); + } + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + wc_kind = svn_node_unknown; + conflicted = FALSE; + + versioned_locally_and_present = FALSE; + } + else if (wc_kind == svn_node_dir + && status == svn_wc__db_status_normal) + { + /* !! We found the root of a separate working copy obstructing the wc !! + + If the directory would be part of our own working copy then + we wouldn't have been called as an add_file(). + + The only thing we can do is add a not-present node, to allow + a future update to bring in the new files when the problem is + resolved. */ + svn_hash_sets(pb->not_present_files, apr_pstrdup(pb->pool, fb->name), + (void *)1); + + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool)); + fb->skip_this = TRUE; + fb->already_notified = TRUE; + + do_notification(eb, fb->local_abspath, svn_node_file, + svn_wc_notify_update_skip_obstruction, scratch_pool); + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; + } + else if (status == svn_wc__db_status_normal + && (wc_kind == svn_node_file + || wc_kind == svn_node_symlink)) + { + /* We found a file external occupating the place we need in BASE. + + We can't add a not-present node in this case as that would overwrite + the file external. Luckily the file external itself stops us from + forgetting a child of this parent directory like an obstructing + working copy would. + + The reason we get here is that the adm crawler doesn't report + file externals. + */ + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool)); + fb->skip_this = TRUE; + fb->already_notified = TRUE; + + do_notification(eb, fb->local_abspath, svn_node_file, + svn_wc_notify_update_skip_obstruction, scratch_pool); + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; + } + else if (wc_kind == svn_node_unknown) + versioned_locally_and_present = FALSE; /* Tree conflict ACTUAL-only node */ + else + versioned_locally_and_present = IS_NODE_PRESENT(status); + + + /* Is this path a conflict victim? */ + if (fb->shadowed) + conflicted = FALSE; /* Conflict applies to WORKING */ + else if (conflicted) + { + if (pb->deletion_conflicts) + tree_conflict = svn_hash_gets(pb->deletion_conflicts, fb->name); + + if (tree_conflict) + { + svn_wc_conflict_reason_t reason; + /* So this deletion wasn't just a deletion, it is actually a + replacement. Let's install a better tree conflict. */ + + /* ### Should store the conflict in DB to allow reinstalling + ### with theoretically more data in close_directory() */ + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL, + eb->db, + fb->local_abspath, + tree_conflict, + fb->pool, fb->pool)); + + tree_conflict = svn_wc__conflict_skel_create(fb->pool); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict( + tree_conflict, + eb->db, fb->local_abspath, + reason, svn_wc_conflict_action_replace, + NULL, + fb->pool, fb->pool)); + + /* And now stop checking for conflicts here and just perform + a shadowed update */ + fb->edit_conflict = tree_conflict; /* Cache for close_file */ + tree_conflict = NULL; /* No direct notification */ + fb->shadowed = TRUE; /* Just continue */ + conflicted = FALSE; /* No skip */ + } + else + SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored, + eb->db, fb->local_abspath, pool)); + } + + /* Now the usual conflict handling: skip. */ + if (conflicted) + { + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool)); + + fb->skip_this = TRUE; + fb->already_notified = TRUE; + + /* We skip this node, but once the update completes the parent node will + be updated to the new revision. So a future recursive update of the + parent will not bring in this new node as the revision of the parent + describes to the repository that all children are available. + + To resolve this problem, we add a not-present node to allow bringing + the node in once this conflict is resolved. + + Note that we can safely assume that no present base node exists, + because then we would not have received an add_file. + */ + svn_hash_sets(pb->not_present_files, apr_pstrdup(pb->pool, fb->name), + (void *)1); + + do_notification(eb, fb->local_abspath, svn_node_unknown, + svn_wc_notify_skip_conflicted, scratch_pool); + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; + } + else if (conflict_ignored) + { + fb->shadowed = TRUE; + } + + if (fb->shadowed) + { + /* Nothing to check; does not and will not exist in working copy */ + } + else if (versioned_locally_and_present) + { + /* What to do with a versioned or schedule-add file: + + If the UUID doesn't match the parent's, or the URL isn't a child of + the parent dir's URL, it's an error. + + Set add_existed so that user notification is delayed until after any + text or prop conflicts have been found. + + Whether the incoming add is a symlink or a file will only be known in + close_file(), when the props are known. So with a locally added file + or symlink, let close_file() check for a tree conflict. + + We will never see missing files here, because these would be + re-added during the crawler phase. */ + svn_boolean_t local_is_file; + + /* Is the local node a copy or move */ + if (status == svn_wc__db_status_added) + SVN_ERR(svn_wc__db_scan_addition(&status, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool)); + + /* Is there something that is a file? */ + local_is_file = (wc_kind == svn_node_file + || wc_kind == svn_node_symlink); + + /* Do tree conflict checking if + * - if there is a local copy. + * - if this is a switch operation + * - the node kinds mismatch + * + * During switch, local adds at the same path as incoming adds get + * "lost" in that switching back to the original will no longer have the + * local add. So switch always alerts the user with a tree conflict. */ + if (!eb->adds_as_modification + || !local_is_file + || status != svn_wc__db_status_added) + { + SVN_ERR(check_tree_conflict(&tree_conflict, eb, + fb->local_abspath, + status, FALSE, svn_node_none, + svn_wc_conflict_action_add, + scratch_pool, scratch_pool)); + } + + if (tree_conflict == NULL) + fb->add_existed = TRUE; /* Take over WORKING */ + else + fb->shadowed = TRUE; /* Only update BASE */ + + } + else if (kind != svn_node_none) + { + /* There's an unversioned node at this path. */ + fb->obstruction_found = TRUE; + + /* Unversioned, obstructing files are handled by text merge/conflict, + * if unversioned obstructions are allowed. */ + if (! (kind == svn_node_file && eb->allow_unver_obstructions)) + { + /* Bring in the node as deleted */ /* ### Obstructed Conflict */ + fb->shadowed = TRUE; + + /* Mark a conflict */ + tree_conflict = svn_wc__conflict_skel_create(fb->pool); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict( + tree_conflict, + eb->db, fb->local_abspath, + svn_wc_conflict_reason_unversioned, + svn_wc_conflict_action_add, + NULL, + fb->pool, scratch_pool)); + } + } + + /* When this is not the update target add a not-present BASE node now, + to allow marking the parent directory complete in its close_edit() call. + This resolves issues when that occurs before the close_file(). */ + if (pb->parent_baton + || *eb->target_basename == '\0' + || (strcmp(fb->local_abspath, eb->target_abspath) != 0)) + { + svn_hash_sets(pb->not_present_files, apr_pstrdup(pb->pool, fb->name), + (void *)1); + } + + if (tree_conflict != NULL) + { + SVN_ERR(complete_conflict(tree_conflict, + fb->edit_baton, + fb->local_abspath, + fb->old_repos_relpath, + fb->old_revision, + fb->new_relpath, + wc_kind, + svn_node_file, + fb->pool, scratch_pool)); + + SVN_ERR(svn_wc__db_op_mark_conflict(eb->db, + fb->local_abspath, + tree_conflict, NULL, + scratch_pool)); + + fb->already_notified = TRUE; + do_notification(eb, fb->local_abspath, svn_node_file, + svn_wc_notify_tree_conflict, scratch_pool); + } + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +open_file(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct file_baton *fb; + svn_boolean_t conflicted; + svn_boolean_t conflict_ignored = FALSE; + svn_boolean_t have_work; + svn_wc__db_status_t status; + svn_node_kind_t wc_kind; + svn_skel_t *tree_conflict = NULL; + + /* the file_pool can stick around for a *long* time, so we want to use + a subpool for any temporary allocations. */ + apr_pool_t *scratch_pool = svn_pool_create(pool); + + SVN_ERR(make_file_baton(&fb, pb, path, FALSE, pool)); + *file_baton = fb; + + if (fb->skip_this) + return SVN_NO_ERROR; + + /* Detect obstructing working copies */ + { + svn_boolean_t is_root; + + SVN_ERR(svn_wc__db_is_wcroot(&is_root, eb->db, fb->local_abspath, + pool)); + + if (is_root) + { + /* Just skip this node; a future update will handle it */ + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool)); + fb->skip_this = TRUE; + fb->already_notified = TRUE; + + do_notification(eb, fb->local_abspath, svn_node_file, + svn_wc_notify_update_skip_obstruction, pool); + + return SVN_NO_ERROR; + } + } + + /* Sanity check. */ + + /* If replacing, make sure the .svn entry already exists. */ + SVN_ERR(svn_wc__db_read_info(&status, &wc_kind, &fb->old_revision, + &fb->old_repos_relpath, NULL, NULL, + &fb->changed_rev, &fb->changed_date, + &fb->changed_author, NULL, + &fb->original_checksum, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + &conflicted, NULL, NULL, &fb->local_prop_mods, + NULL, NULL, &have_work, + eb->db, fb->local_abspath, + fb->pool, scratch_pool)); + + if (have_work) + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, &fb->old_revision, + &fb->old_repos_relpath, NULL, NULL, + &fb->changed_rev, &fb->changed_date, + &fb->changed_author, NULL, + &fb->original_checksum, NULL, NULL, + NULL, NULL, NULL, + eb->db, fb->local_abspath, + fb->pool, scratch_pool)); + + /* Is this path a conflict victim? */ + if (fb->shadowed) + conflicted = FALSE; /* Conflict applies to WORKING */ + else if (conflicted) + SVN_ERR(node_already_conflicted(&conflicted, &conflict_ignored, + eb->db, fb->local_abspath, pool)); + if (conflicted) + { + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, pool)); + + fb->skip_this = TRUE; + fb->already_notified = TRUE; + + do_notification(eb, fb->local_abspath, svn_node_unknown, + svn_wc_notify_skip_conflicted, scratch_pool); + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; + } + else if (conflict_ignored) + { + fb->shadowed = TRUE; + } + + /* Check for conflicts only when we haven't already recorded + * a tree-conflict on a parent node. */ + if (!fb->shadowed) + SVN_ERR(check_tree_conflict(&tree_conflict, eb, fb->local_abspath, + status, TRUE, svn_node_file, + svn_wc_conflict_action_edit, + fb->pool, scratch_pool)); + + /* Is this path the victim of a newly-discovered tree conflict? */ + if (tree_conflict != NULL) + { + svn_wc_conflict_reason_t reason; + fb->edit_conflict = tree_conflict; + /* Other modifications wouldn't be a tree conflict */ + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, NULL, NULL, + eb->db, fb->local_abspath, + tree_conflict, + scratch_pool, scratch_pool)); + SVN_ERR_ASSERT(reason == svn_wc_conflict_reason_deleted + || reason == svn_wc_conflict_reason_moved_away + || reason == svn_wc_conflict_reason_replaced + || reason == svn_wc_conflict_reason_obstructed); + + /* Continue updating BASE */ + if (reason == svn_wc_conflict_reason_obstructed) + fb->edit_obstructed = TRUE; + else + fb->shadowed = TRUE; + } + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; +} + +/* Implements svn_stream_lazyopen_func_t. */ +static svn_error_t * +lazy_open_source(svn_stream_t **stream, + void *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct file_baton *fb = baton; + + SVN_ERR(svn_wc__db_pristine_read(stream, NULL, fb->edit_baton->db, + fb->local_abspath, + fb->original_checksum, + result_pool, scratch_pool)); + + + return SVN_NO_ERROR; +} + +struct lazy_target_baton { + struct file_baton *fb; + struct handler_baton *hb; + struct edit_baton *eb; +}; + +/* Implements svn_stream_lazyopen_func_t. */ +static svn_error_t * +lazy_open_target(svn_stream_t **stream, + void *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct lazy_target_baton *tb = baton; + + SVN_ERR(svn_wc__open_writable_base(stream, &tb->hb->new_text_base_tmp_abspath, + NULL, &tb->hb->new_text_base_sha1_checksum, + tb->fb->edit_baton->db, + tb->eb->wcroot_abspath, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +apply_textdelta(void *file_baton, + const char *expected_checksum, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct file_baton *fb = file_baton; + apr_pool_t *handler_pool = svn_pool_create(fb->pool); + struct handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb)); + struct edit_baton *eb = fb->edit_baton; + const svn_checksum_t *recorded_base_checksum; + svn_checksum_t *expected_base_checksum; + svn_stream_t *source; + struct lazy_target_baton *tb; + svn_stream_t *target; + + if (fb->skip_this) + { + *handler = svn_delta_noop_window_handler; + *handler_baton = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(mark_file_edited(fb, pool)); + + /* Parse checksum or sets expected_base_checksum to NULL */ + SVN_ERR(svn_checksum_parse_hex(&expected_base_checksum, svn_checksum_md5, + expected_checksum, pool)); + + /* Before applying incoming svndiff data to text base, make sure + text base hasn't been corrupted, and that its checksum + matches the expected base checksum. */ + + /* The incoming delta is targeted against EXPECTED_BASE_CHECKSUM. Find and + check our RECORDED_BASE_CHECKSUM. (In WC-1, we could not do this test + for replaced nodes because we didn't store the checksum of the "revert + base". In WC-NG, we do and we can.) */ + recorded_base_checksum = fb->original_checksum; + + /* If we have a checksum that we want to compare to a MD5 checksum, + ensure that it is a MD5 checksum */ + if (recorded_base_checksum + && expected_base_checksum + && recorded_base_checksum->kind != svn_checksum_md5) + SVN_ERR(svn_wc__db_pristine_get_md5(&recorded_base_checksum, + eb->db, eb->wcroot_abspath, + recorded_base_checksum, pool, pool)); + + + if (!svn_checksum_match(expected_base_checksum, recorded_base_checksum)) + return svn_error_createf(SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL, + _("Checksum mismatch for '%s':\n" + " expected: %s\n" + " recorded: %s\n"), + svn_dirent_local_style(fb->local_abspath, pool), + svn_checksum_to_cstring_display(expected_base_checksum, + pool), + svn_checksum_to_cstring_display(recorded_base_checksum, + pool)); + + /* Open the text base for reading, unless this is an added file. */ + + /* + kff todo: what we really need to do here is: + + 1. See if there's a file or dir by this name already here. + 2. See if it's under revision control. + 3. If both are true, open text-base. + 4. If only 1 is true, bail, because we can't go destroying user's + files (or as an alternative to bailing, move it to some tmp + name and somehow tell the user, but communicating with the + user without erroring is a whole callback system we haven't + finished inventing yet.) + */ + + if (! fb->adding_file) + { + SVN_ERR_ASSERT(!fb->original_checksum + || fb->original_checksum->kind == svn_checksum_sha1); + + source = svn_stream_lazyopen_create(lazy_open_source, fb, FALSE, + handler_pool); + } + else + { + source = svn_stream_empty(handler_pool); + } + + /* If we don't have a recorded checksum, use the ra provided checksum */ + if (!recorded_base_checksum) + recorded_base_checksum = expected_base_checksum; + + /* Checksum the text base while applying deltas */ + if (recorded_base_checksum) + { + hb->expected_source_checksum = svn_checksum_dup(recorded_base_checksum, + handler_pool); + + /* Wrap stream and store reference to allow calculating the + checksum. */ + source = svn_stream_checksummed2(source, + &hb->actual_source_checksum, + NULL, recorded_base_checksum->kind, + TRUE, handler_pool); + hb->source_checksum_stream = source; + } + + tb = apr_palloc(handler_pool, sizeof(struct lazy_target_baton)); + tb->hb = hb; + tb->fb = fb; + tb->eb = eb; + target = svn_stream_lazyopen_create(lazy_open_target, tb, TRUE, handler_pool); + + /* Prepare to apply the delta. */ + svn_txdelta_apply(source, target, + hb->new_text_base_md5_digest, + hb->new_text_base_tmp_abspath /* error_info */, + handler_pool, + &hb->apply_handler, &hb->apply_baton); + + hb->pool = handler_pool; + hb->fb = fb; + + /* We're all set. */ + *handler_baton = hb; + *handler = window_handler; + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +change_file_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool) +{ + struct file_baton *fb = file_baton; + svn_prop_t *propchange; + + if (fb->skip_this) + return SVN_NO_ERROR; + + /* Push a new propchange to the file baton's array of propchanges */ + propchange = apr_array_push(fb->propchanges); + propchange->name = apr_pstrdup(fb->pool, name); + propchange->value = value ? svn_string_dup(value, fb->pool) : NULL; + + if (!fb->edited && svn_property_kind2(name) == svn_prop_regular_kind) + SVN_ERR(mark_file_edited(fb, scratch_pool)); + + if (! fb->shadowed + && strcmp(name, SVN_PROP_SPECIAL) == 0) + { + struct edit_baton *eb = fb->edit_baton; + svn_boolean_t modified = FALSE; + svn_boolean_t becomes_symlink; + svn_boolean_t was_symlink; + + /* Let's see if we have a change as in some scenarios servers report + non-changes of properties. */ + becomes_symlink = (value != NULL); + + if (fb->adding_file) + was_symlink = becomes_symlink; /* No change */ + else + { + apr_hash_t *props; + + /* We read the server-props, not the ACTUAL props here as we just + want to see if this is really an incoming prop change. */ + SVN_ERR(svn_wc__db_base_get_props(&props, eb->db, + fb->local_abspath, + scratch_pool, scratch_pool)); + + was_symlink = ((props + && svn_hash_gets(props, SVN_PROP_SPECIAL) != NULL) + ? svn_tristate_true + : svn_tristate_false); + } + + if (was_symlink != becomes_symlink) + { + /* If the local node was not modified, we continue as usual, if + modified we want a tree conflict just like how we would handle + it when receiving a delete + add (aka "replace") */ + if (fb->local_prop_mods) + modified = TRUE; + else + SVN_ERR(svn_wc__internal_file_modified_p(&modified, eb->db, + fb->local_abspath, + FALSE, scratch_pool)); + } + + if (modified) + { + if (!fb->edit_conflict) + fb->edit_conflict = svn_wc__conflict_skel_create(fb->pool); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict( + fb->edit_conflict, + eb->db, fb->local_abspath, + svn_wc_conflict_reason_edited, + svn_wc_conflict_action_replace, + NULL, + fb->pool, scratch_pool)); + + SVN_ERR(complete_conflict(fb->edit_conflict, fb->edit_baton, + fb->local_abspath, fb->old_repos_relpath, + fb->old_revision, fb->new_relpath, + svn_node_file, svn_node_file, + fb->pool, scratch_pool)); + + /* Create a copy of the existing (pre update) BASE node in WORKING, + mark a tree conflict and handle the rest of the update as + shadowed */ + SVN_ERR(svn_wc__db_op_make_copy(eb->db, fb->local_abspath, + fb->edit_conflict, NULL, + scratch_pool)); + + do_notification(eb, fb->local_abspath, svn_node_file, + svn_wc_notify_tree_conflict, scratch_pool); + + /* Ok, we introduced a replacement, so we can now handle the rest + as a normal shadowed update */ + fb->shadowed = TRUE; + fb->add_existed = FALSE; + fb->already_notified = TRUE; + } + } + + return SVN_NO_ERROR; +} + +/* Perform the actual merge of file changes between an original file, + identified by ORIGINAL_CHECKSUM (an empty file if NULL) to a new file + identified by NEW_CHECKSUM. + + Merge the result into LOCAL_ABSPATH, which is part of the working copy + identified by WRI_ABSPATH. Use OLD_REVISION and TARGET_REVISION for naming + the intermediate files. + + The rest of the arguments are passed to svn_wc__internal_merge(). + */ +svn_error_t * +svn_wc__perform_file_merge(svn_skel_t **work_items, + svn_skel_t **conflict_skel, + svn_boolean_t *found_conflict, + svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const svn_checksum_t *new_checksum, + const svn_checksum_t *original_checksum, + apr_hash_t *old_actual_props, + const apr_array_header_t *ext_patterns, + svn_revnum_t old_revision, + svn_revnum_t target_revision, + const apr_array_header_t *propchanges, + const char *diff3_cmd, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + /* Actual file exists and has local mods: + Now we need to let loose svn_wc__internal_merge() to merge + the textual changes into the working file. */ + const char *oldrev_str, *newrev_str, *mine_str; + const char *merge_left; + svn_boolean_t delete_left = FALSE; + const char *path_ext = ""; + const char *new_text_base_tmp_abspath; + enum svn_wc_merge_outcome_t merge_outcome = svn_wc_merge_unchanged; + svn_skel_t *work_item; + + *work_items = NULL; + + SVN_ERR(svn_wc__db_pristine_get_path(&new_text_base_tmp_abspath, + db, wri_abspath, new_checksum, + scratch_pool, scratch_pool)); + + /* If we have any file extensions we're supposed to + preserve in generated conflict file names, then find + this path's extension. But then, if it isn't one of + the ones we want to keep in conflict filenames, + pretend it doesn't have an extension at all. */ + if (ext_patterns && ext_patterns->nelts) + { + svn_path_splitext(NULL, &path_ext, local_abspath, scratch_pool); + if (! (*path_ext && svn_cstring_match_glob_list(path_ext, ext_patterns))) + path_ext = ""; + } + + /* old_revision can be invalid when the conflict is against a + local addition */ + if (!SVN_IS_VALID_REVNUM(old_revision)) + old_revision = 0; + + oldrev_str = apr_psprintf(scratch_pool, ".r%ld%s%s", + old_revision, + *path_ext ? "." : "", + *path_ext ? path_ext : ""); + + newrev_str = apr_psprintf(scratch_pool, ".r%ld%s%s", + target_revision, + *path_ext ? "." : "", + *path_ext ? path_ext : ""); + mine_str = apr_psprintf(scratch_pool, ".mine%s%s", + *path_ext ? "." : "", + *path_ext ? path_ext : ""); + + if (! original_checksum) + { + SVN_ERR(get_empty_tmp_file(&merge_left, db, wri_abspath, + result_pool, scratch_pool)); + delete_left = TRUE; + } + else + SVN_ERR(svn_wc__db_pristine_get_path(&merge_left, db, wri_abspath, + original_checksum, + result_pool, scratch_pool)); + + /* Merge the changes from the old textbase to the new + textbase into the file we're updating. + Remember that this function wants full paths! */ + SVN_ERR(svn_wc__internal_merge(&work_item, + conflict_skel, + &merge_outcome, + db, + merge_left, + new_text_base_tmp_abspath, + local_abspath, + wri_abspath, + oldrev_str, newrev_str, mine_str, + old_actual_props, + FALSE /* dry_run */, + diff3_cmd, NULL, propchanges, + cancel_func, cancel_baton, + result_pool, scratch_pool)); + + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + *found_conflict = (merge_outcome == svn_wc_merge_conflict); + + /* If we created a temporary left merge file, get rid of it. */ + if (delete_left) + { + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db, wri_abspath, + merge_left, + result_pool, scratch_pool)); + *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool); + } + + return SVN_NO_ERROR; +} + +/* This is the small planet. It has the complex responsibility of + * "integrating" a new revision of a file into a working copy. + * + * Given a file_baton FB for a file either already under version control, or + * prepared (see below) to join version control, fully install a + * new revision of the file. + * + * ### transitional: installation of the working file will be handled + * ### by the *INSTALL_PRISTINE flag. + * + * By "install", we mean: create a new text-base and prop-base, merge + * any textual and property changes into the working file, and finally + * update all metadata so that the working copy believes it has a new + * working revision of the file. All of this work includes being + * sensitive to eol translation, keyword substitution, and performing + * all actions accumulated the parent directory's work queue. + * + * Set *CONTENT_STATE to the state of the contents after the + * installation. + * + * Return values are allocated in RESULT_POOL and temporary allocations + * are performed in SCRATCH_POOL. + */ +static svn_error_t * +merge_file(svn_skel_t **work_items, + svn_skel_t **conflict_skel, + svn_boolean_t *install_pristine, + const char **install_from, + svn_wc_notify_state_t *content_state, + struct file_baton *fb, + apr_hash_t *actual_props, + apr_time_t last_changed_date, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb = fb->edit_baton; + struct dir_baton *pb = fb->dir_baton; + svn_boolean_t is_locally_modified; + svn_boolean_t found_text_conflict = FALSE; + + SVN_ERR_ASSERT(! fb->shadowed + && ! fb->obstruction_found + && ! fb->edit_obstructed); + + /* + When this function is called on file F, we assume the following + things are true: + + - The new pristine text of F is present in the pristine store + iff FB->NEW_TEXT_BASE_SHA1_CHECKSUM is not NULL. + + - The WC metadata still reflects the old version of F. + (We can still access the old pristine base text of F.) + + The goal is to update the local working copy of F to reflect + the changes received from the repository, preserving any local + modifications. + */ + + *work_items = NULL; + *install_pristine = FALSE; + *install_from = NULL; + + /* Start by splitting the file path, getting an access baton for the parent, + and an entry for the file if any. */ + + /* Has the user made local mods to the working file? + Note that this compares to the current pristine file, which is + different from fb->old_text_base_path if we have a replaced-with-history + file. However, in the case we had an obstruction, we check against the + new text base. + */ + if (fb->adding_file && !fb->add_existed) + { + is_locally_modified = FALSE; /* There is no file: Don't check */ + } + else + { + /* The working file is not an obstruction. + So: is the file modified, relative to its ORIGINAL pristine? + + This function sets is_locally_modified to FALSE for + files that do not exist and for directories. */ + + SVN_ERR(svn_wc__internal_file_modified_p(&is_locally_modified, + eb->db, fb->local_abspath, + FALSE /* exact_comparison */, + scratch_pool)); + } + + /* For 'textual' merging, we use the following system: + + When a file is modified and we have a new BASE: + - For text files + * svn_wc_merge uses diff3 + * possibly makes backups and marks files as conflicted. + + - For binary files + * svn_wc_merge makes backups and marks files as conflicted. + + If a file is not modified and we have a new BASE: + * Install from pristine. + + If we have property changes related to magic properties or if the + svn:keywords property is set: + * Retranslate from the working file. + */ + if (! is_locally_modified + && fb->new_text_base_sha1_checksum) + { + /* If there are no local mods, who cares whether it's a text + or binary file! Just write a log command to overwrite + any working file with the new text-base. If newline + conversion or keyword substitution is activated, this + will happen as well during the copy. + For replaced files, though, we want to merge in the changes + even if the file is not modified compared to the (non-revert) + text-base. */ + + *install_pristine = TRUE; + } + else if (fb->new_text_base_sha1_checksum) + { + /* Actual file exists and has local mods: + Now we need to let loose svn_wc__merge_internal() to merge + the textual changes into the working file. */ + SVN_ERR(svn_wc__perform_file_merge(work_items, + conflict_skel, + &found_text_conflict, + eb->db, + fb->local_abspath, + pb->local_abspath, + fb->new_text_base_sha1_checksum, + fb->add_existed + ? NULL + : fb->original_checksum, + actual_props, + eb->ext_patterns, + fb->old_revision, + *eb->target_revision, + fb->propchanges, + eb->diff3_cmd, + eb->cancel_func, eb->cancel_baton, + result_pool, scratch_pool)); + } /* end: working file exists and has mods */ + else + { + /* There is no new text base, but let's see if the working file needs + to be updated for any other reason. */ + + apr_hash_t *keywords; + + /* Determine if any of the propchanges are the "magic" ones that + might require changing the working file. */ + svn_boolean_t magic_props_changed; + + magic_props_changed = svn_wc__has_magic_property(fb->propchanges); + + SVN_ERR(svn_wc__get_translate_info(NULL, NULL, + &keywords, + NULL, + eb->db, fb->local_abspath, + actual_props, TRUE, + scratch_pool, scratch_pool)); + if (magic_props_changed || keywords) + { + /* Special edge-case: it's possible that this file installation + only involves propchanges, but that some of those props still + require a retranslation of the working file. + + OR that the file doesn't involve propchanges which by themselves + require retranslation, but receiving a change bumps the revision + number which requires re-expansion of keywords... */ + + if (is_locally_modified) + { + const char *tmptext; + + /* Copy and DEtranslate the working file to a temp text-base. + Note that detranslation is done according to the old props. */ + SVN_ERR(svn_wc__internal_translated_file( + &tmptext, fb->local_abspath, eb->db, fb->local_abspath, + SVN_WC_TRANSLATE_TO_NF + | SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP, + eb->cancel_func, eb->cancel_baton, + result_pool, scratch_pool)); + + /* We always want to reinstall the working file if the magic + properties have changed, or there are any keywords present. + Note that TMPTEXT might actually refer to the working file + itself (the above function skips a detranslate when not + required). This is acceptable, as we will (re)translate + according to the new properties into a temporary file (from + the working file), and then rename the temp into place. Magic! + */ + *install_pristine = TRUE; + *install_from = tmptext; + } + else + { + /* Use our existing 'copy' from the pristine store instead + of making a new copy. This way we can use the standard code + to update the recorded size and modification time. + (Issue #3842) */ + *install_pristine = TRUE; + } + } + } + + /* Set the returned content state. */ + + if (found_text_conflict) + *content_state = svn_wc_notify_state_conflicted; + else if (fb->new_text_base_sha1_checksum) + { + if (is_locally_modified) + *content_state = svn_wc_notify_state_merged; + else + *content_state = svn_wc_notify_state_changed; + } + else + *content_state = svn_wc_notify_state_unchanged; + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +/* Mostly a wrapper around merge_file. */ +static svn_error_t * +close_file(void *file_baton, + const char *expected_md5_digest, + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + struct dir_baton *pdb = fb->dir_baton; + struct edit_baton *eb = fb->edit_baton; + svn_wc_notify_state_t content_state, prop_state; + svn_wc_notify_lock_state_t lock_state; + svn_checksum_t *expected_md5_checksum = NULL; + apr_hash_t *new_base_props = NULL; + apr_hash_t *new_actual_props = NULL; + apr_array_header_t *entry_prop_changes; + apr_array_header_t *dav_prop_changes; + apr_array_header_t *regular_prop_changes; + apr_hash_t *current_base_props = NULL; + apr_hash_t *current_actual_props = NULL; + apr_hash_t *local_actual_props = NULL; + svn_skel_t *all_work_items = NULL; + svn_skel_t *conflict_skel = NULL; + svn_skel_t *work_item; + apr_pool_t *scratch_pool = fb->pool; /* Destroyed at function exit */ + svn_boolean_t keep_recorded_info = FALSE; + const svn_checksum_t *new_checksum; + apr_array_header_t *iprops = NULL; + + if (fb->skip_this) + { + svn_pool_destroy(fb->pool); + SVN_ERR(maybe_release_dir_info(pdb)); + return SVN_NO_ERROR; + } + + if (fb->edited) + conflict_skel = fb->edit_conflict; + + if (expected_md5_digest) + SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5, + expected_md5_digest, scratch_pool)); + + if (fb->new_text_base_md5_checksum && expected_md5_checksum + && !svn_checksum_match(expected_md5_checksum, + fb->new_text_base_md5_checksum)) + return svn_error_trace( + svn_checksum_mismatch_err(expected_md5_checksum, + fb->new_text_base_md5_checksum, + scratch_pool, + _("Checksum mismatch for '%s'"), + svn_dirent_local_style( + fb->local_abspath, pool))); + + /* Gather the changes for each kind of property. */ + SVN_ERR(svn_categorize_props(fb->propchanges, &entry_prop_changes, + &dav_prop_changes, ®ular_prop_changes, + scratch_pool)); + + /* Extract the changed_* and lock state information. */ + { + svn_revnum_t new_changed_rev; + apr_time_t new_changed_date; + const char *new_changed_author; + + SVN_ERR(accumulate_last_change(&new_changed_rev, + &new_changed_date, + &new_changed_author, + entry_prop_changes, + scratch_pool, scratch_pool)); + + if (SVN_IS_VALID_REVNUM(new_changed_rev)) + fb->changed_rev = new_changed_rev; + if (new_changed_date != 0) + fb->changed_date = new_changed_date; + if (new_changed_author != NULL) + fb->changed_author = new_changed_author; + } + + /* Determine whether the file has become unlocked. */ + { + int i; + + lock_state = svn_wc_notify_lock_state_unchanged; + + for (i = 0; i < entry_prop_changes->nelts; ++i) + { + const svn_prop_t *prop + = &APR_ARRAY_IDX(entry_prop_changes, i, svn_prop_t); + + /* If we see a change to the LOCK_TOKEN entry prop, then the only + possible change is its REMOVAL. Thus, the lock has been removed, + and we should likewise remove our cached copy of it. */ + if (! strcmp(prop->name, SVN_PROP_ENTRY_LOCK_TOKEN)) + { + /* If we lose the lock, but not because we are switching to + another url, remove the state lock from the wc */ + if (! eb->switch_relpath + || strcmp(fb->new_relpath, fb->old_repos_relpath) == 0) + { + SVN_ERR_ASSERT(prop->value == NULL); + SVN_ERR(svn_wc__db_lock_remove(eb->db, fb->local_abspath, + scratch_pool)); + + lock_state = svn_wc_notify_lock_state_unlocked; + } + break; + } + } + } + + /* Install all kinds of properties. It is important to do this before + any file content merging, since that process might expand keywords, in + which case we want the new entryprops to be in place. */ + + /* Write log commands to merge REGULAR_PROPS into the existing + properties of FB->LOCAL_ABSPATH. Update *PROP_STATE to reflect + the result of the regular prop merge. + + BASE_PROPS and WORKING_PROPS are hashes of the base and + working props of the file; if NULL they are read from the wc. */ + + /* ### some of this feels like voodoo... */ + + if ((!fb->adding_file || fb->add_existed) + && !fb->shadowed) + SVN_ERR(svn_wc__get_actual_props(&local_actual_props, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool)); + if (local_actual_props == NULL) + local_actual_props = apr_hash_make(scratch_pool); + + if (fb->add_existed) + { + /* This node already exists. Grab the current pristine properties. */ + SVN_ERR(svn_wc__db_read_pristine_props(¤t_base_props, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool)); + current_actual_props = local_actual_props; + } + else if (!fb->adding_file) + { + /* Get the BASE properties for proper merging. */ + SVN_ERR(svn_wc__db_base_get_props(¤t_base_props, + eb->db, fb->local_abspath, + scratch_pool, scratch_pool)); + current_actual_props = local_actual_props; + } + + /* Note: even if the node existed before, it may not have + pristine props (e.g a local-add) */ + if (current_base_props == NULL) + current_base_props = apr_hash_make(scratch_pool); + + /* And new nodes need an empty set of ACTUAL props. */ + if (current_actual_props == NULL) + current_actual_props = apr_hash_make(scratch_pool); + + prop_state = svn_wc_notify_state_unknown; + + if (! fb->shadowed) + { + svn_boolean_t install_pristine; + const char *install_from = NULL; + + /* Merge the 'regular' props into the existing working proplist. */ + /* This will merge the old and new props into a new prop db, and + write <cp> commands to the logfile to install the merged + props. */ + new_base_props = svn_prop__patch(current_base_props, regular_prop_changes, + scratch_pool); + SVN_ERR(svn_wc__merge_props(&conflict_skel, + &prop_state, + &new_actual_props, + eb->db, + fb->local_abspath, + NULL /* server_baseprops (update, not merge) */, + current_base_props, + current_actual_props, + regular_prop_changes, /* propchanges */ + scratch_pool, + scratch_pool)); + /* We will ALWAYS have properties to save (after a not-dry-run merge). */ + SVN_ERR_ASSERT(new_base_props != NULL && new_actual_props != NULL); + + /* Merge the text. This will queue some additional work. */ + if (!fb->obstruction_found && !fb->edit_obstructed) + { + svn_error_t *err; + err = merge_file(&work_item, &conflict_skel, + &install_pristine, &install_from, + &content_state, fb, current_actual_props, + fb->changed_date, scratch_pool, scratch_pool); + + if (err && err->apr_err == SVN_ERR_WC_PATH_ACCESS_DENIED) + { + if (eb->notify_func) + { + svn_wc_notify_t *notify =svn_wc_create_notify( + fb->local_abspath, + svn_wc_notify_update_skip_access_denied, + scratch_pool); + + notify->kind = svn_node_file; + notify->err = err; + + eb->notify_func(eb->notify_baton, notify, scratch_pool); + } + svn_error_clear(err); + + SVN_ERR(remember_skipped_tree(eb, fb->local_abspath, + scratch_pool)); + fb->skip_this = TRUE; + + svn_pool_destroy(fb->pool); + SVN_ERR(maybe_release_dir_info(pdb)); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + scratch_pool); + } + else + { + install_pristine = FALSE; + if (fb->new_text_base_sha1_checksum) + content_state = svn_wc_notify_state_changed; + else + content_state = svn_wc_notify_state_unchanged; + } + + if (install_pristine) + { + svn_boolean_t record_fileinfo; + + /* If we are installing from the pristine contents, then go ahead and + record the fileinfo. That will be the "proper" values. Installing + from some random file means the fileinfo does NOT correspond to + the pristine (in which case, the fileinfo will be cleared for + safety's sake). */ + record_fileinfo = (install_from == NULL); + + SVN_ERR(svn_wc__wq_build_file_install(&work_item, + eb->db, + fb->local_abspath, + install_from, + eb->use_commit_times, + record_fileinfo, + scratch_pool, scratch_pool)); + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + scratch_pool); + } + else if (lock_state == svn_wc_notify_lock_state_unlocked + && !fb->obstruction_found) + { + /* If a lock was removed and we didn't update the text contents, we + might need to set the file read-only. + + Note: this will also update the executable flag, but ... meh. */ + SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, eb->db, + fb->local_abspath, + scratch_pool, scratch_pool)); + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + scratch_pool); + } + + if (! install_pristine + && (content_state == svn_wc_notify_state_unchanged)) + { + /* It is safe to keep the current recorded timestamp and size */ + keep_recorded_info = TRUE; + } + + /* Clean up any temporary files. */ + + /* Remove the INSTALL_FROM file, as long as it doesn't refer to the + working file. */ + if (install_from != NULL + && strcmp(install_from, fb->local_abspath) != 0) + { + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, eb->db, + fb->local_abspath, install_from, + scratch_pool, scratch_pool)); + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + scratch_pool); + } + } + else + { + /* Adding or updating a BASE node under a locally added node. */ + apr_hash_t *fake_actual_props; + + if (fb->adding_file) + fake_actual_props = apr_hash_make(scratch_pool); + else + fake_actual_props = current_base_props; + + /* Store the incoming props (sent as propchanges) in new_base_props + and create a set of new actual props to use for notifications */ + new_base_props = svn_prop__patch(current_base_props, regular_prop_changes, + scratch_pool); + SVN_ERR(svn_wc__merge_props(&conflict_skel, + &prop_state, + &new_actual_props, + eb->db, + fb->local_abspath, + NULL /* server_baseprops (not merging) */, + current_base_props /* pristine_props */, + fake_actual_props /* actual_props */, + regular_prop_changes, /* propchanges */ + scratch_pool, + scratch_pool)); + + if (fb->new_text_base_sha1_checksum) + content_state = svn_wc_notify_state_changed; + else + content_state = svn_wc_notify_state_unchanged; + } + + /* Insert/replace the BASE node with all of the new metadata. */ + + /* Set the 'checksum' column of the file's BASE_NODE row to + * NEW_TEXT_BASE_SHA1_CHECKSUM. The pristine text identified by that + * checksum is already in the pristine store. */ + new_checksum = fb->new_text_base_sha1_checksum; + + /* If we don't have a NEW checksum, then the base must not have changed. + Just carry over the old checksum. */ + if (new_checksum == NULL) + new_checksum = fb->original_checksum; + + if (conflict_skel) + { + SVN_ERR(complete_conflict(conflict_skel, + fb->edit_baton, + fb->local_abspath, + fb->old_repos_relpath, + fb->old_revision, + fb->new_relpath, + svn_node_file, svn_node_file, + fb->pool, scratch_pool)); + + SVN_ERR(svn_wc__conflict_create_markers(&work_item, + eb->db, fb->local_abspath, + conflict_skel, + scratch_pool, scratch_pool)); + + all_work_items = svn_wc__wq_merge(all_work_items, work_item, + scratch_pool); + } + + /* Any inherited props to be set set for this base node? */ + if (eb->wcroot_iprops) + { + iprops = svn_hash_gets(eb->wcroot_iprops, fb->local_abspath); + + /* close_edit may also update iprops for switched nodes, catching + those for which close_directory is never called (e.g. a switch + with no changes). So as a minor optimization we remove any + iprops from the hash so as not to set them again in + close_edit. */ + if (iprops) + svn_hash_sets(eb->wcroot_iprops, fb->local_abspath, NULL); + } + + SVN_ERR(svn_wc__db_base_add_file(eb->db, fb->local_abspath, + eb->wcroot_abspath, + fb->new_relpath, + eb->repos_root, eb->repos_uuid, + *eb->target_revision, + new_base_props, + fb->changed_rev, + fb->changed_date, + fb->changed_author, + new_checksum, + (dav_prop_changes->nelts > 0) + ? svn_prop_array_to_hash( + dav_prop_changes, + scratch_pool) + : NULL, + (fb->add_existed && fb->adding_file), + (! fb->shadowed) && new_base_props, + new_actual_props, + iprops, + keep_recorded_info, + (fb->shadowed && fb->obstruction_found), + conflict_skel, + all_work_items, + scratch_pool)); + + if (conflict_skel && eb->conflict_func) + SVN_ERR(svn_wc__conflict_invoke_resolver(eb->db, fb->local_abspath, + conflict_skel, + NULL /* merge_options */, + eb->conflict_func, + eb->conflict_baton, + eb->cancel_func, + eb->cancel_baton, + scratch_pool)); + + /* Deal with the WORKING tree, based on updates to the BASE tree. */ + + svn_hash_sets(fb->dir_baton->not_present_files, fb->name, NULL); + + /* Send a notification to the callback function. (Skip notifications + about files which were already notified for another reason.) */ + if (eb->notify_func && !fb->already_notified + && (fb->edited || lock_state == svn_wc_notify_lock_state_unlocked)) + { + svn_wc_notify_t *notify; + svn_wc_notify_action_t action = svn_wc_notify_update_update; + + if (fb->edited) + { + if (fb->shadowed || fb->edit_obstructed) + action = fb->adding_file + ? svn_wc_notify_update_shadowed_add + : svn_wc_notify_update_shadowed_update; + else if (fb->obstruction_found || fb->add_existed) + { + if (content_state != svn_wc_notify_state_conflicted) + action = svn_wc_notify_exists; + } + else if (fb->adding_file) + { + action = svn_wc_notify_update_add; + } + } + else + { + SVN_ERR_ASSERT(lock_state == svn_wc_notify_lock_state_unlocked); + action = svn_wc_notify_update_broken_lock; + } + + /* If the file was moved-away, notify for the moved-away node. + * The original location only had its BASE info changed and + * we don't usually notify about such changes. */ + notify = svn_wc_create_notify(fb->local_abspath, action, scratch_pool); + notify->kind = svn_node_file; + notify->content_state = content_state; + notify->prop_state = prop_state; + notify->lock_state = lock_state; + notify->revision = *eb->target_revision; + notify->old_revision = fb->old_revision; + + /* Fetch the mimetype from the actual properties */ + notify->mime_type = svn_prop_get_value(new_actual_props, + SVN_PROP_MIME_TYPE); + + eb->notify_func(eb->notify_baton, notify, scratch_pool); + } + + svn_pool_destroy(fb->pool); /* Destroy scratch_pool */ + + /* We have one less referrer to the directory */ + SVN_ERR(maybe_release_dir_info(pdb)); + + return SVN_NO_ERROR; +} + + +/* An svn_delta_editor_t function. */ +static svn_error_t * +close_edit(void *edit_baton, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + apr_pool_t *scratch_pool = eb->pool; + + /* The editor didn't even open the root; we have to take care of + some cleanup stuffs. */ + if (! eb->root_opened + && *eb->target_basename == '\0') + { + /* We need to "un-incomplete" the root directory. */ + SVN_ERR(svn_wc__db_temp_op_end_directory_update(eb->db, + eb->anchor_abspath, + scratch_pool)); + } + + /* By definition, anybody "driving" this editor for update or switch + purposes at a *minimum* must have called set_target_revision() at + the outset, and close_edit() at the end -- even if it turned out + that no changes ever had to be made, and open_root() was never + called. That's fine. But regardless, when the edit is over, + this editor needs to make sure that *all* paths have had their + revisions bumped to the new target revision. */ + + /* Make sure our update target now has the new working revision. + Also, if this was an 'svn switch', then rewrite the target's + url. All of this tweaking might happen recursively! Note + that if eb->target is NULL, that's okay (albeit "sneaky", + some might say). */ + + /* Extra check: if the update did nothing but make its target + 'deleted', then do *not* run cleanup on the target, as it + will only remove the deleted entry! */ + if (! eb->target_deleted) + { + SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db, + eb->target_abspath, + eb->requested_depth, + eb->switch_relpath, + eb->repos_root, + eb->repos_uuid, + *(eb->target_revision), + eb->skipped_trees, + eb->wcroot_iprops, + eb->notify_func, + eb->notify_baton, + eb->pool)); + + if (*eb->target_basename != '\0') + { + svn_wc__db_status_t status; + svn_error_t *err; + + /* Note: we are fetching information about the *target*, not anchor. + There is no guarantee that the target has a BASE node. + For example: + + The node was not present in BASE, but locally-added, and the + update did not create a new BASE node "under" the local-add. + + If there is no BASE node for the target, then we certainly don't + have to worry about removing it. */ + err = svn_wc__db_base_get_info(&status, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + eb->db, eb->target_abspath, + scratch_pool, scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + } + else if (status == svn_wc__db_status_excluded) + { + /* There is a small chance that the explicit target of an update/ + switch is gone in the repository, in that specific case the + node hasn't been re-added to the BASE tree by this update. + + If so, we should get rid of this excluded node now. */ + + SVN_ERR(svn_wc__db_base_remove(eb->db, eb->target_abspath, + FALSE /* keep_as_working */, + FALSE /* queue_deletes */, + SVN_INVALID_REVNUM, + NULL, NULL, scratch_pool)); + } + } + } + + /* The edit is over: run the wq with proper cancel support, + but first kill the handler that would run it on the pool + cleanup at the end of this function. */ + apr_pool_cleanup_kill(eb->pool, eb, cleanup_edit_baton); + + SVN_ERR(svn_wc__wq_run(eb->db, eb->wcroot_abspath, + eb->cancel_func, eb->cancel_baton, + eb->pool)); + + /* The edit is over, free its pool. + ### No, this is wrong. Who says this editor/baton won't be used + again? But the change is not merely to remove this call. We + should also make eb->pool not be a subpool (see make_editor), + and change callers of svn_client_{checkout,update,switch} to do + better pool management. ### */ + + svn_pool_destroy(eb->pool); + + return SVN_NO_ERROR; +} + + +/*** Returning editors. ***/ + +/* Helper for the three public editor-supplying functions. */ +static svn_error_t * +make_editor(svn_revnum_t *target_revision, + svn_wc__db_t *db, + const char *anchor_abspath, + const char *target_basename, + apr_hash_t *wcroot_iprops, + svn_boolean_t use_commit_times, + const char *switch_url, + svn_depth_t depth, + svn_boolean_t depth_is_sticky, + svn_boolean_t allow_unver_obstructions, + svn_boolean_t adds_as_modification, + svn_boolean_t server_performs_filtering, + svn_boolean_t clean_checkout, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_dirents_func_t fetch_dirents_func, + void *fetch_dirents_baton, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_wc_external_update_t external_func, + void *external_baton, + const char *diff3_cmd, + const apr_array_header_t *preserved_exts, + const svn_delta_editor_t **editor, + void **edit_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb; + void *inner_baton; + apr_pool_t *edit_pool = svn_pool_create(result_pool); + svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool); + const svn_delta_editor_t *inner_editor; + const char *repos_root, *repos_uuid; + struct svn_wc__shim_fetch_baton_t *sfb; + svn_delta_shim_callbacks_t *shim_callbacks = + svn_delta_shim_callbacks_default(edit_pool); + + /* An unknown depth can't be sticky. */ + if (depth == svn_depth_unknown) + depth_is_sticky = FALSE; + + /* Get the anchor's repository root and uuid. The anchor must already exist + in BASE. */ + SVN_ERR(svn_wc__db_scan_base_repos(NULL, &repos_root, &repos_uuid, + db, anchor_abspath, + result_pool, scratch_pool)); + + /* With WC-NG we need a valid repository root */ + SVN_ERR_ASSERT(repos_root != NULL && repos_uuid != NULL); + + /* Disallow a switch operation to change the repository root of the target, + if that is known. */ + if (switch_url && !svn_uri__is_ancestor(repos_root, switch_url)) + return svn_error_createf(SVN_ERR_WC_INVALID_SWITCH, NULL, + _("'%s'\nis not the same repository as\n'%s'"), + switch_url, repos_root); + + /* Construct an edit baton. */ + eb = apr_pcalloc(edit_pool, sizeof(*eb)); + eb->pool = edit_pool; + eb->use_commit_times = use_commit_times; + eb->target_revision = target_revision; + eb->repos_root = repos_root; + eb->repos_uuid = repos_uuid; + eb->db = db; + eb->target_basename = target_basename; + eb->anchor_abspath = anchor_abspath; + eb->wcroot_iprops = wcroot_iprops; + + SVN_ERR(svn_wc__db_get_wcroot(&eb->wcroot_abspath, db, anchor_abspath, + edit_pool, scratch_pool)); + + if (switch_url) + eb->switch_relpath = + svn_uri_skip_ancestor(repos_root, switch_url, scratch_pool); + else + eb->switch_relpath = NULL; + + if (svn_path_is_empty(target_basename)) + eb->target_abspath = eb->anchor_abspath; + else + eb->target_abspath = svn_dirent_join(eb->anchor_abspath, target_basename, + edit_pool); + + eb->requested_depth = depth; + eb->depth_is_sticky = depth_is_sticky; + eb->notify_func = notify_func; + eb->notify_baton = notify_baton; + eb->external_func = external_func; + eb->external_baton = external_baton; + eb->diff3_cmd = diff3_cmd; + eb->cancel_func = cancel_func; + eb->cancel_baton = cancel_baton; + eb->conflict_func = conflict_func; + eb->conflict_baton = conflict_baton; + eb->allow_unver_obstructions = allow_unver_obstructions; + eb->adds_as_modification = adds_as_modification; + eb->clean_checkout = clean_checkout; + eb->skipped_trees = apr_hash_make(edit_pool); + eb->dir_dirents = apr_hash_make(edit_pool); + eb->ext_patterns = preserved_exts; + + apr_pool_cleanup_register(edit_pool, eb, cleanup_edit_baton, + apr_pool_cleanup_null); + + /* Construct an editor. */ + tree_editor->set_target_revision = set_target_revision; + tree_editor->open_root = open_root; + tree_editor->delete_entry = delete_entry; + tree_editor->add_directory = add_directory; + tree_editor->open_directory = open_directory; + tree_editor->change_dir_prop = change_dir_prop; + tree_editor->close_directory = close_directory; + tree_editor->absent_directory = absent_directory; + tree_editor->add_file = add_file; + tree_editor->open_file = open_file; + tree_editor->apply_textdelta = apply_textdelta; + tree_editor->change_file_prop = change_file_prop; + tree_editor->close_file = close_file; + tree_editor->absent_file = absent_file; + tree_editor->close_edit = close_edit; + + /* Fiddle with the type system. */ + inner_editor = tree_editor; + inner_baton = eb; + + if (!depth_is_sticky + && depth != svn_depth_unknown + && svn_depth_empty <= depth && depth < svn_depth_infinity + && fetch_dirents_func) + { + /* We are asked to perform an update at a depth less than the ambient + depth. In this case the update won't describe additions that would + have been reported if we updated at the ambient depth. */ + svn_error_t *err; + svn_node_kind_t dir_kind; + svn_wc__db_status_t dir_status; + const char *dir_repos_relpath; + svn_depth_t dir_depth; + + /* we have to do this on the target of the update, not the anchor */ + err = svn_wc__db_base_get_info(&dir_status, &dir_kind, NULL, + &dir_repos_relpath, NULL, NULL, NULL, + NULL, NULL, &dir_depth, NULL, NULL, NULL, + NULL, NULL, NULL, + db, eb->target_abspath, + scratch_pool, scratch_pool); + + if (!err + && dir_kind == svn_node_dir + && dir_status == svn_wc__db_status_normal) + { + if (dir_depth > depth) + { + apr_hash_t *dirents; + + /* If we switch, we should look at the new relpath */ + if (eb->switch_relpath) + dir_repos_relpath = eb->switch_relpath; + + SVN_ERR(fetch_dirents_func(fetch_dirents_baton, &dirents, + repos_root, dir_repos_relpath, + edit_pool, scratch_pool)); + + if (dirents != NULL && apr_hash_count(dirents)) + svn_hash_sets(eb->dir_dirents, + apr_pstrdup(edit_pool, dir_repos_relpath), + dirents); + } + + if (depth == svn_depth_immediates) + { + /* Worst case scenario of issue #3569 fix: We have to do the + same for all existing subdirs, but then we check for + svn_depth_empty. */ + const apr_array_header_t *children; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + SVN_ERR(svn_wc__db_base_get_children(&children, db, + eb->target_abspath, + scratch_pool, + iterpool)); + + for (i = 0; i < children->nelts; i++) + { + const char *child_abspath; + const char *child_name; + + svn_pool_clear(iterpool); + + child_name = APR_ARRAY_IDX(children, i, const char *); + + child_abspath = svn_dirent_join(eb->target_abspath, + child_name, iterpool); + + SVN_ERR(svn_wc__db_base_get_info(&dir_status, &dir_kind, + NULL, &dir_repos_relpath, + NULL, NULL, NULL, NULL, + NULL, &dir_depth, NULL, + NULL, NULL, NULL, NULL, + NULL, + db, child_abspath, + iterpool, iterpool)); + + if (dir_kind == svn_node_dir + && dir_status == svn_wc__db_status_normal + && dir_depth > svn_depth_empty) + { + apr_hash_t *dirents; + + /* If we switch, we should look at the new relpath */ + if (eb->switch_relpath) + dir_repos_relpath = svn_relpath_join( + eb->switch_relpath, + child_name, iterpool); + + SVN_ERR(fetch_dirents_func(fetch_dirents_baton, &dirents, + repos_root, dir_repos_relpath, + edit_pool, iterpool)); + + if (dirents != NULL && apr_hash_count(dirents)) + svn_hash_sets(eb->dir_dirents, + apr_pstrdup(edit_pool, + dir_repos_relpath), + dirents); + } + } + } + } + else if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + svn_error_clear(err); + else + SVN_ERR(err); + } + + /* We need to limit the scope of our operation to the ambient depths + present in the working copy already, but only if the requested + depth is not sticky. If a depth was explicitly requested, + libsvn_delta/depth_filter_editor.c will ensure that we never see + editor calls that extend beyond the scope of the requested depth. + But even what we do so might extend beyond the scope of our + ambient depth. So we use another filtering editor to avoid + modifying the ambient working copy depth when not asked to do so. + (This can also be skipped if the server understands depth.) */ + if (!server_performs_filtering + && !depth_is_sticky) + SVN_ERR(svn_wc__ambient_depth_filter_editor(&inner_editor, + &inner_baton, + db, + anchor_abspath, + target_basename, + inner_editor, + inner_baton, + result_pool)); + + SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, + cancel_baton, + inner_editor, + inner_baton, + editor, + edit_baton, + result_pool)); + + sfb = apr_palloc(result_pool, sizeof(*sfb)); + sfb->db = db; + sfb->base_abspath = eb->anchor_abspath; + sfb->fetch_base = TRUE; + + shim_callbacks->fetch_kind_func = svn_wc__fetch_kind_func; + shim_callbacks->fetch_props_func = svn_wc__fetch_props_func; + shim_callbacks->fetch_base_func = svn_wc__fetch_base_func; + shim_callbacks->fetch_baton = sfb; + + SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, + NULL, NULL, shim_callbacks, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__get_update_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_revnum_t *target_revision, + svn_wc_context_t *wc_ctx, + const char *anchor_abspath, + const char *target_basename, + apr_hash_t *wcroot_iprops, + svn_boolean_t use_commit_times, + svn_depth_t depth, + svn_boolean_t depth_is_sticky, + svn_boolean_t allow_unver_obstructions, + svn_boolean_t adds_as_modification, + svn_boolean_t server_performs_filtering, + svn_boolean_t clean_checkout, + const char *diff3_cmd, + const apr_array_header_t *preserved_exts, + svn_wc_dirents_func_t fetch_dirents_func, + void *fetch_dirents_baton, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_wc_external_update_t external_func, + void *external_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return make_editor(target_revision, wc_ctx->db, anchor_abspath, + target_basename, wcroot_iprops, use_commit_times, + NULL, depth, depth_is_sticky, allow_unver_obstructions, + adds_as_modification, server_performs_filtering, + clean_checkout, + notify_func, notify_baton, + cancel_func, cancel_baton, + fetch_dirents_func, fetch_dirents_baton, + conflict_func, conflict_baton, + external_func, external_baton, + diff3_cmd, preserved_exts, editor, edit_baton, + result_pool, scratch_pool); +} + +svn_error_t * +svn_wc__get_switch_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_revnum_t *target_revision, + svn_wc_context_t *wc_ctx, + const char *anchor_abspath, + const char *target_basename, + const char *switch_url, + apr_hash_t *wcroot_iprops, + svn_boolean_t use_commit_times, + svn_depth_t depth, + svn_boolean_t depth_is_sticky, + svn_boolean_t allow_unver_obstructions, + svn_boolean_t server_performs_filtering, + const char *diff3_cmd, + const apr_array_header_t *preserved_exts, + svn_wc_dirents_func_t fetch_dirents_func, + void *fetch_dirents_baton, + svn_wc_conflict_resolver_func2_t conflict_func, + void *conflict_baton, + svn_wc_external_update_t external_func, + void *external_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(switch_url && svn_uri_is_canonical(switch_url, scratch_pool)); + + return make_editor(target_revision, wc_ctx->db, anchor_abspath, + target_basename, wcroot_iprops, use_commit_times, + switch_url, + depth, depth_is_sticky, allow_unver_obstructions, + FALSE /* adds_as_modification */, + server_performs_filtering, + FALSE /* clean_checkout */, + notify_func, notify_baton, + cancel_func, cancel_baton, + fetch_dirents_func, fetch_dirents_baton, + conflict_func, conflict_baton, + external_func, external_baton, + diff3_cmd, preserved_exts, + editor, edit_baton, + result_pool, scratch_pool); +} + + + +/* ### Note that this function is completely different from the rest of the + update editor in what it updates. The update editor changes only BASE + and ACTUAL and this function just changes WORKING and ACTUAL. + + In the entries world this function shared a lot of code with the + update editor but in the wonderful new WC-NG world it will probably + do more and more by itself and would be more logically grouped with + the add/copy functionality in adm_ops.c and copy.c. */ +svn_error_t * +svn_wc_add_repos_file4(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_stream_t *new_base_contents, + svn_stream_t *new_contents, + apr_hash_t *new_base_props, + apr_hash_t *new_props, + const char *copyfrom_url, + svn_revnum_t copyfrom_rev, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = wc_ctx->db; + const char *dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + svn_wc__db_status_t status; + svn_node_kind_t kind; + const char *tmp_text_base_abspath; + svn_checksum_t *new_text_base_md5_checksum; + svn_checksum_t *new_text_base_sha1_checksum; + const char *source_abspath = NULL; + svn_skel_t *all_work_items = NULL; + svn_skel_t *work_item; + const char *repos_root_url; + const char *repos_uuid; + const char *original_repos_relpath; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + svn_error_t *err; + apr_pool_t *pool = scratch_pool; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(new_base_contents != NULL); + SVN_ERR_ASSERT(new_base_props != NULL); + + /* We should have a write lock on this file's parent directory. */ + SVN_ERR(svn_wc__write_check(db, dir_abspath, pool)); + + err = svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, scratch_pool, scratch_pool); + + if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + else if(err) + svn_error_clear(err); + else + switch (status) + { + case svn_wc__db_status_not_present: + case svn_wc__db_status_deleted: + break; + default: + return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL, + _("Node '%s' exists."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, &repos_root_url, + &repos_uuid, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + db, dir_abspath, scratch_pool, scratch_pool)); + + switch (status) + { + case svn_wc__db_status_normal: + case svn_wc__db_status_added: + break; + case svn_wc__db_status_deleted: + return + svn_error_createf(SVN_ERR_WC_SCHEDULE_CONFLICT, NULL, + _("Can't add '%s' to a parent directory" + " scheduled for deletion"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + default: + return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, err, + _("Can't find parent directory's node while" + " trying to add '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + if (kind != svn_node_dir) + return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, + _("Can't schedule an addition of '%s'" + " below a not-directory node"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + /* Fabricate the anticipated new URL of the target and check the + copyfrom URL to be in the same repository. */ + if (copyfrom_url != NULL) + { + /* Find the repository_root via the parent directory, which + is always versioned before this function is called */ + + if (!repos_root_url) + { + /* The parent is an addition, scan upwards to find the right info */ + SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, + &repos_root_url, &repos_uuid, + NULL, NULL, NULL, NULL, + wc_ctx->db, dir_abspath, + scratch_pool, scratch_pool)); + } + SVN_ERR_ASSERT(repos_root_url); + + original_repos_relpath = + svn_uri_skip_ancestor(repos_root_url, copyfrom_url, scratch_pool); + + if (!original_repos_relpath) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Copyfrom-url '%s' has different repository" + " root than '%s'"), + copyfrom_url, repos_root_url); + } + else + { + original_repos_relpath = NULL; + copyfrom_rev = SVN_INVALID_REVNUM; /* Just to be sure. */ + } + + /* Set CHANGED_* to reflect the entry props in NEW_BASE_PROPS, and + filter NEW_BASE_PROPS so it contains only regular props. */ + { + apr_array_header_t *regular_props; + apr_array_header_t *entry_props; + + SVN_ERR(svn_categorize_props(svn_prop_hash_to_array(new_base_props, pool), + &entry_props, NULL, ®ular_props, + pool)); + + /* Put regular props back into a hash table. */ + new_base_props = svn_prop_array_to_hash(regular_props, pool); + + /* Get the change_* info from the entry props. */ + SVN_ERR(accumulate_last_change(&changed_rev, + &changed_date, + &changed_author, + entry_props, pool, pool)); + } + + /* Copy NEW_BASE_CONTENTS into a temporary file so our log can refer to + it, and set TMP_TEXT_BASE_ABSPATH to its path. Compute its + NEW_TEXT_BASE_MD5_CHECKSUM and NEW_TEXT_BASE_SHA1_CHECKSUM as we copy. */ + { + svn_stream_t *tmp_base_contents; + + SVN_ERR(svn_wc__open_writable_base(&tmp_base_contents, + &tmp_text_base_abspath, + &new_text_base_md5_checksum, + &new_text_base_sha1_checksum, + wc_ctx->db, local_abspath, + pool, pool)); + SVN_ERR(svn_stream_copy3(new_base_contents, tmp_base_contents, + cancel_func, cancel_baton, pool)); + } + + /* If the caller gave us a new working file, copy it to a safe (temporary) + location and set SOURCE_ABSPATH to that path. We'll then translate/copy + that into place after the node's state has been created. */ + if (new_contents) + { + const char *temp_dir_abspath; + svn_stream_t *tmp_contents; + + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, db, + local_abspath, pool, pool)); + SVN_ERR(svn_stream_open_unique(&tmp_contents, &source_abspath, + temp_dir_abspath, svn_io_file_del_none, + pool, pool)); + SVN_ERR(svn_stream_copy3(new_contents, tmp_contents, + cancel_func, cancel_baton, pool)); + } + + /* Install new text base for copied files. Added files do NOT have a + text base. */ + if (copyfrom_url != NULL) + { + SVN_ERR(svn_wc__db_pristine_install(db, tmp_text_base_abspath, + new_text_base_sha1_checksum, + new_text_base_md5_checksum, pool)); + } + else + { + /* ### There's something wrong around here. Sometimes (merge from a + foreign repository, at least) we are called with copyfrom_url = + NULL and an empty new_base_contents (and an empty set of + new_base_props). Why an empty "new base"? + + That happens in merge_tests.py 54,87,88,89,143. + + In that case, having been given this supposed "new base" file, we + copy it and calculate its checksum but do not install it. Why? + That must be wrong. + + To crudely work around one issue with this, that we shouldn't + record a checksum in the database if we haven't installed the + corresponding pristine text, for now we'll just set the checksum + to NULL. + + The proper solution is probably more like: the caller should pass + NULL for the missing information, and this function should learn to + handle that. */ + + new_text_base_sha1_checksum = NULL; + new_text_base_md5_checksum = NULL; + } + + /* For added files without NEW_CONTENTS, then generate the working file + from the provided "pristine" contents. */ + if (new_contents == NULL && copyfrom_url == NULL) + source_abspath = tmp_text_base_abspath; + + { + svn_boolean_t record_fileinfo; + + /* If new contents were provided, then we do NOT want to record the + file information. We assume the new contents do not match the + "proper" values for RECORDED_SIZE and RECORDED_TIME. */ + record_fileinfo = (new_contents == NULL); + + /* Install the working copy file (with appropriate translation) from + the appropriate source. SOURCE_ABSPATH will be NULL, indicating an + installation from the pristine (available for copied/moved files), + or it will specify a temporary file where we placed a "pristine" + (for an added file) or a detranslated local-mods file. */ + SVN_ERR(svn_wc__wq_build_file_install(&work_item, + db, local_abspath, + source_abspath, + FALSE /* use_commit_times */, + record_fileinfo, + pool, pool)); + all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool); + + /* If we installed from somewhere besides the official pristine, then + it is a temporary file, which needs to be removed. */ + if (source_abspath != NULL) + { + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db, local_abspath, + source_abspath, + pool, pool)); + all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool); + } + } + + /* ### ideally, we would have a single DB operation, and queue the work + ### items on that. for now, we'll queue them with the second call. */ + + SVN_ERR(svn_wc__db_op_copy_file(db, local_abspath, + new_base_props, + changed_rev, + changed_date, + changed_author, + original_repos_relpath, + original_repos_relpath ? repos_root_url + : NULL, + original_repos_relpath ? repos_uuid : NULL, + copyfrom_rev, + new_text_base_sha1_checksum, + TRUE, + new_props, + FALSE /* is_move */, + NULL /* conflict */, + all_work_items, + pool)); + + return svn_error_trace(svn_wc__wq_run(db, dir_abspath, + cancel_func, cancel_baton, + pool)); +} + +svn_error_t * +svn_wc__complete_directory_add(svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_hash_t *new_original_props, + const char *copyfrom_url, + svn_revnum_t copyfrom_rev, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + const char *original_repos_relpath; + const char *original_root_url; + const char *original_uuid; + svn_boolean_t had_props; + svn_boolean_t props_mod; + + svn_revnum_t original_revision; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + + SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + &original_repos_relpath, &original_root_url, + &original_uuid, &original_revision, NULL, NULL, + NULL, NULL, NULL, NULL, &had_props, &props_mod, + NULL, NULL, NULL, + wc_ctx->db, local_abspath, + scratch_pool, scratch_pool)); + + if (status != svn_wc__db_status_added + || kind != svn_node_dir + || had_props + || props_mod + || !original_repos_relpath) + { + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("'%s' is not an unmodified copied directory"), + svn_dirent_local_style(local_abspath, scratch_pool)); + } + if (original_revision != copyfrom_rev + || strcmp(copyfrom_url, + svn_path_url_add_component2(original_root_url, + original_repos_relpath, + scratch_pool))) + { + return svn_error_createf( + SVN_ERR_WC_COPYFROM_PATH_NOT_FOUND, NULL, + _("Copyfrom '%s' doesn't match original location of '%s'"), + copyfrom_url, + svn_dirent_local_style(local_abspath, scratch_pool)); + } + + { + apr_array_header_t *regular_props; + apr_array_header_t *entry_props; + + SVN_ERR(svn_categorize_props(svn_prop_hash_to_array(new_original_props, + scratch_pool), + &entry_props, NULL, ®ular_props, + scratch_pool)); + + /* Put regular props back into a hash table. */ + new_original_props = svn_prop_array_to_hash(regular_props, scratch_pool); + + /* Get the change_* info from the entry props. */ + SVN_ERR(accumulate_last_change(&changed_rev, + &changed_date, + &changed_author, + entry_props, scratch_pool, scratch_pool)); + } + + return svn_error_trace( + svn_wc__db_op_copy_dir(wc_ctx->db, local_abspath, + new_original_props, + changed_rev, changed_date, changed_author, + original_repos_relpath, original_root_url, + original_uuid, original_revision, + NULL /* children */, + FALSE /* is_move */, + svn_depth_infinity, + NULL /* conflict */, + NULL /* work_items */, + scratch_pool)); +} diff --git a/subversion/libsvn_wc/upgrade.c b/subversion/libsvn_wc/upgrade.c new file mode 100644 index 000000000000..983892cc35b0 --- /dev/null +++ b/subversion/libsvn_wc/upgrade.c @@ -0,0 +1,2376 @@ +/* + * upgrade.c: routines for upgrading a working copy + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <apr_pools.h> + +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" + +#include "wc.h" +#include "adm_files.h" +#include "conflicts.h" +#include "entries.h" +#include "wc_db.h" +#include "tree_conflicts.h" +#include "wc-queries.h" /* for STMT_* */ +#include "workqueue.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" +#include "private/svn_sqlite.h" +#include "private/svn_token.h" + +/* WC-1.0 administrative area extensions */ +#define SVN_WC__BASE_EXT ".svn-base" /* for text and prop bases */ +#define SVN_WC__WORK_EXT ".svn-work" /* for working propfiles */ +#define SVN_WC__REVERT_EXT ".svn-revert" /* for reverting a replaced + file */ + +/* Old locations for storing "wcprops" (aka "dav cache"). */ +#define WCPROPS_SUBDIR_FOR_FILES "wcprops" +#define WCPROPS_FNAME_FOR_DIR "dir-wcprops" +#define WCPROPS_ALL_DATA "all-wcprops" + +/* Old property locations. */ +#define PROPS_SUBDIR "props" +#define PROP_BASE_SUBDIR "prop-base" +#define PROP_BASE_FOR_DIR "dir-prop-base" +#define PROP_REVERT_FOR_DIR "dir-prop-revert" +#define PROP_WORKING_FOR_DIR "dir-props" + +/* Old textbase location. */ +#define TEXT_BASE_SUBDIR "text-base" + +#define TEMP_DIR "tmp" + +/* Old data files that we no longer need/use. */ +#define ADM_README "README.txt" +#define ADM_EMPTY_FILE "empty-file" +#define ADM_LOG "log" +#define ADM_LOCK "lock" + +/* New pristine location */ +#define PRISTINE_STORAGE_RELPATH "pristine" +#define PRISTINE_STORAGE_EXT ".svn-base" +/* Number of characters in a pristine file basename, in WC format <= 28. */ +#define PRISTINE_BASENAME_OLD_LEN 40 +#define SDB_FILE "wc.db" + + +/* Read the properties from the file at PROPFILE_ABSPATH, returning them + as a hash in *PROPS. If the propfile is NOT present, then NULL will + be returned in *PROPS. */ +static svn_error_t * +read_propfile(apr_hash_t **props, + const char *propfile_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_stream_t *stream; + apr_finfo_t finfo; + + err = svn_io_stat(&finfo, propfile_abspath, APR_FINFO_SIZE, scratch_pool); + + if (err + && (APR_STATUS_IS_ENOENT(err->apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) + { + svn_error_clear(err); + + /* The propfile was not there. Signal with a NULL. */ + *props = NULL; + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + /* A 0-bytes file signals an empty property list. + (mostly used for revert-props) */ + if (finfo.size == 0) + { + *props = apr_hash_make(result_pool); + return SVN_NO_ERROR; + } + + SVN_ERR(svn_stream_open_readonly(&stream, propfile_abspath, + scratch_pool, scratch_pool)); + + /* ### does this function need to be smarter? will we see zero-length + ### files? see props.c::load_props(). there may be more work here. + ### need a historic analysis of 1.x property storage. what will we + ### actually run into? */ + + /* ### loggy_write_properties() and immediate_install_props() write + ### zero-length files for "no props", so we should be a bit smarter + ### in here. */ + + /* ### should we be forgiving in here? I say "no". if we can't be sure, + ### then we could effectively corrupt the local working copy. */ + + *props = apr_hash_make(result_pool); + SVN_ERR(svn_hash_read2(*props, stream, SVN_HASH_TERMINATOR, result_pool)); + + return svn_error_trace(svn_stream_close(stream)); +} + + +/* Read one proplist (allocated from RESULT_POOL) from STREAM, and place it + into ALL_WCPROPS at NAME. */ +static svn_error_t * +read_one_proplist(apr_hash_t *all_wcprops, + const char *name, + svn_stream_t *stream, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *proplist; + + proplist = apr_hash_make(result_pool); + SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, result_pool)); + svn_hash_sets(all_wcprops, name, proplist); + + return SVN_NO_ERROR; +} + + +/* Read the wcprops from all the files in the admin area of DIR_ABSPATH, + returning them in *ALL_WCPROPS. Results are allocated in RESULT_POOL, + and temporary allocations are performed in SCRATCH_POOL. */ +static svn_error_t * +read_many_wcprops(apr_hash_t **all_wcprops, + const char *dir_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *propfile_abspath; + apr_hash_t *wcprops; + apr_hash_t *dirents; + const char *props_dir_abspath; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_index_t *hi; + + *all_wcprops = apr_hash_make(result_pool); + + /* First, look at dir-wcprops. */ + propfile_abspath = svn_wc__adm_child(dir_abspath, WCPROPS_FNAME_FOR_DIR, + scratch_pool); + SVN_ERR(read_propfile(&wcprops, propfile_abspath, result_pool, iterpool)); + if (wcprops != NULL) + svn_hash_sets(*all_wcprops, SVN_WC_ENTRY_THIS_DIR, wcprops); + + props_dir_abspath = svn_wc__adm_child(dir_abspath, WCPROPS_SUBDIR_FOR_FILES, + scratch_pool); + + /* Now walk the wcprops directory. */ + SVN_ERR(svn_io_get_dirents3(&dirents, props_dir_abspath, TRUE, + scratch_pool, scratch_pool)); + + for (hi = apr_hash_first(scratch_pool, dirents); + hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + + svn_pool_clear(iterpool); + + propfile_abspath = svn_dirent_join(props_dir_abspath, name, iterpool); + + SVN_ERR(read_propfile(&wcprops, propfile_abspath, + result_pool, iterpool)); + SVN_ERR_ASSERT(wcprops != NULL); + svn_hash_sets(*all_wcprops, apr_pstrdup(result_pool, name), wcprops); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + + +/* For wcprops stored in a single file in this working copy, read that + file and return it in *ALL_WCPROPS, allocated in RESULT_POOL. Use + SCRATCH_POOL for temporary allocations. */ +static svn_error_t * +read_wcprops(apr_hash_t **all_wcprops, + const char *dir_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_stream_t *stream; + svn_error_t *err; + + *all_wcprops = apr_hash_make(result_pool); + + err = svn_wc__open_adm_stream(&stream, dir_abspath, + WCPROPS_ALL_DATA, + scratch_pool, scratch_pool); + + /* A non-existent file means there are no props. */ + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + SVN_ERR(err); + + /* Read the proplist for THIS_DIR. */ + SVN_ERR(read_one_proplist(*all_wcprops, SVN_WC_ENTRY_THIS_DIR, stream, + result_pool, scratch_pool)); + + /* And now, the children. */ + while (1729) + { + svn_stringbuf_t *line; + svn_boolean_t eof; + + SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool)); + if (eof) + { + if (line->len > 0) + return svn_error_createf + (SVN_ERR_WC_CORRUPT, NULL, + _("Missing end of line in wcprops file for '%s'"), + svn_dirent_local_style(dir_abspath, scratch_pool)); + break; + } + SVN_ERR(read_one_proplist(*all_wcprops, line->data, stream, + result_pool, scratch_pool)); + } + + return svn_error_trace(svn_stream_close(stream)); +} + +/* Return in CHILDREN, the list of all 1.6 versioned subdirectories + which also exist on disk as directories. + + If DELETE_DIR is not NULL set *DELETE_DIR to TRUE if the directory + should be deleted after migrating to WC-NG, otherwise to FALSE. + + If SKIP_MISSING is TRUE, don't add missing or obstructed subdirectories + to the list of children. + */ +static svn_error_t * +get_versioned_subdirs(apr_array_header_t **children, + svn_boolean_t *delete_dir, + const char *dir_abspath, + svn_boolean_t skip_missing, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_t *entries; + apr_hash_index_t *hi; + svn_wc_entry_t *this_dir = NULL; + + *children = apr_array_make(result_pool, 10, sizeof(const char *)); + + SVN_ERR(svn_wc__read_entries_old(&entries, dir_abspath, + scratch_pool, iterpool)); + for (hi = apr_hash_first(scratch_pool, entries); + hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + const svn_wc_entry_t *entry = svn__apr_hash_index_val(hi); + const char *child_abspath; + svn_boolean_t hidden; + + /* skip "this dir" */ + if (*name == '\0') + { + this_dir = svn__apr_hash_index_val(hi); + continue; + } + else if (entry->kind != svn_node_dir) + continue; + + svn_pool_clear(iterpool); + + /* If a directory is 'hidden' skip it as subdir */ + SVN_ERR(svn_wc__entry_is_hidden(&hidden, entry)); + if (hidden) + continue; + + child_abspath = svn_dirent_join(dir_abspath, name, scratch_pool); + + if (skip_missing) + { + svn_node_kind_t kind; + SVN_ERR(svn_io_check_path(child_abspath, &kind, scratch_pool)); + + if (kind != svn_node_dir) + continue; + } + + APR_ARRAY_PUSH(*children, const char *) = apr_pstrdup(result_pool, + child_abspath); + } + + svn_pool_destroy(iterpool); + + if (delete_dir != NULL) + { + *delete_dir = (this_dir != NULL) + && (this_dir->schedule == svn_wc_schedule_delete) + && ! this_dir->keep_local; + } + + return SVN_NO_ERROR; +} + + +/* Return in CHILDREN the names of all versioned *files* in SDB that + are children of PARENT_RELPATH. These files' existence on disk is + not tested. + + This set of children is intended for property upgrades. + Subdirectory's properties exist in the subdirs. + + Note that this uses just the SDB to locate children, which means + that the children must have been upgraded to wc-ng format. */ +static svn_error_t * +get_versioned_files(const apr_array_header_t **children, + const char *parent_relpath, + svn_sqlite__db_t *sdb, + apr_int64_t wc_id, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + apr_array_header_t *child_names; + svn_boolean_t have_row; + + /* ### just select 'file' children. do we need 'symlink' in the future? */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_ALL_FILES)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, parent_relpath)); + + /* ### 10 is based on Subversion's average of 8.5 files per versioned + ### directory in its repository. maybe use a different value? or + ### count rows first? */ + child_names = apr_array_make(result_pool, 10, sizeof(const char *)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *local_relpath = svn_sqlite__column_text(stmt, 0, + result_pool); + + APR_ARRAY_PUSH(child_names, const char *) + = svn_relpath_basename(local_relpath, result_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + *children = child_names; + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + + +/* Return the path of the old-school administrative lock file + associated with LOCAL_DIR_ABSPATH, allocated from RESULT_POOL. */ +static const char * +build_lockfile_path(const char *local_dir_abspath, + apr_pool_t *result_pool) +{ + return svn_dirent_join_many(result_pool, + local_dir_abspath, + svn_wc_get_adm_dir(result_pool), + ADM_LOCK, + NULL); +} + + +/* Create a physical lock file in the admin directory for ABSPATH. */ +static svn_error_t * +create_physical_lock(const char *abspath, apr_pool_t *scratch_pool) +{ + const char *lock_abspath = build_lockfile_path(abspath, scratch_pool); + svn_error_t *err; + apr_file_t *file; + + err = svn_io_file_open(&file, lock_abspath, + APR_WRITE | APR_CREATE | APR_EXCL, + APR_OS_DEFAULT, + scratch_pool); + + if (err && APR_STATUS_IS_EEXIST(err->apr_err)) + { + /* Congratulations, we just stole a physical lock from somebody */ + svn_error_clear(err); + return SVN_NO_ERROR; + } + + return svn_error_trace(err); +} + + +/* Wipe out all the obsolete files/dirs from the administrative area. */ +static void +wipe_obsolete_files(const char *wcroot_abspath, apr_pool_t *scratch_pool) +{ + /* Zap unused files. */ + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + SVN_WC__ADM_FORMAT, + scratch_pool), + TRUE, scratch_pool)); + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + SVN_WC__ADM_ENTRIES, + scratch_pool), + TRUE, scratch_pool)); + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + ADM_EMPTY_FILE, + scratch_pool), + TRUE, scratch_pool)); + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + ADM_README, + scratch_pool), + TRUE, scratch_pool)); + + /* For formats <= SVN_WC__WCPROPS_MANY_FILES_VERSION, we toss the wcprops + for the directory itself, and then all the wcprops for the files. */ + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + WCPROPS_FNAME_FOR_DIR, + scratch_pool), + TRUE, scratch_pool)); + svn_error_clear(svn_io_remove_dir2( + svn_wc__adm_child(wcroot_abspath, + WCPROPS_SUBDIR_FOR_FILES, + scratch_pool), + FALSE, NULL, NULL, scratch_pool)); + + /* And for later formats, they are aggregated into one file. */ + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + WCPROPS_ALL_DATA, + scratch_pool), + TRUE, scratch_pool)); + + /* Remove the old text-base directory and the old text-base files. */ + svn_error_clear(svn_io_remove_dir2( + svn_wc__adm_child(wcroot_abspath, + TEXT_BASE_SUBDIR, + scratch_pool), + FALSE, NULL, NULL, scratch_pool)); + + /* Remove the old properties files... whole directories at a time. */ + svn_error_clear(svn_io_remove_dir2( + svn_wc__adm_child(wcroot_abspath, + PROPS_SUBDIR, + scratch_pool), + FALSE, NULL, NULL, scratch_pool)); + svn_error_clear(svn_io_remove_dir2( + svn_wc__adm_child(wcroot_abspath, + PROP_BASE_SUBDIR, + scratch_pool), + FALSE, NULL, NULL, scratch_pool)); + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + PROP_WORKING_FOR_DIR, + scratch_pool), + TRUE, scratch_pool)); + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + PROP_BASE_FOR_DIR, + scratch_pool), + TRUE, scratch_pool)); + svn_error_clear(svn_io_remove_file2( + svn_wc__adm_child(wcroot_abspath, + PROP_REVERT_FOR_DIR, + scratch_pool), + TRUE, scratch_pool)); + +#if 0 + /* ### this checks for a write-lock, and we are not (always) taking out + ### a write lock in all callers. */ + SVN_ERR(svn_wc__adm_cleanup_tmp_area(db, wcroot_abspath, iterpool)); +#endif + + /* Remove the old-style lock file LAST. */ + svn_error_clear(svn_io_remove_file2( + build_lockfile_path(wcroot_abspath, scratch_pool), + TRUE, scratch_pool)); +} + +svn_error_t * +svn_wc__wipe_postupgrade(const char *dir_abspath, + svn_boolean_t whole_admin, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_array_header_t *subdirs; + svn_error_t *err; + svn_boolean_t delete_dir; + int i; + + if (cancel_func) + SVN_ERR((*cancel_func)(cancel_baton)); + + err = get_versioned_subdirs(&subdirs, &delete_dir, dir_abspath, TRUE, + scratch_pool, iterpool); + if (err) + { + if (APR_STATUS_IS_ENOENT(err->apr_err)) + { + /* An unversioned dir is obstructing a versioned dir */ + svn_error_clear(err); + err = NULL; + } + svn_pool_destroy(iterpool); + return svn_error_trace(err); + } + for (i = 0; i < subdirs->nelts; ++i) + { + const char *child_abspath = APR_ARRAY_IDX(subdirs, i, const char *); + + svn_pool_clear(iterpool); + SVN_ERR(svn_wc__wipe_postupgrade(child_abspath, TRUE, + cancel_func, cancel_baton, iterpool)); + } + + /* ### Should we really be ignoring errors here? */ + if (whole_admin) + svn_error_clear(svn_io_remove_dir2(svn_wc__adm_child(dir_abspath, "", + iterpool), + TRUE, NULL, NULL, iterpool)); + else + wipe_obsolete_files(dir_abspath, scratch_pool); + + if (delete_dir) + { + /* If this was a WC-NG single database copy, this directory wouldn't + be here (unless it was deleted with --keep-local) + + If the directory is empty, we can just delete it; if not we + keep it. + */ + svn_error_clear(svn_io_dir_remove_nonrecursive(dir_abspath, iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Ensure that ENTRY has its REPOS and UUID fields set. These will be + used to establish the REPOSITORY row in the new database, and then + used within the upgraded entries as they are written into the database. + + If one or both are not available, then it attempts to retrieve this + information from REPOS_CACHE. And if that fails from REPOS_INFO_FUNC, + passing REPOS_INFO_BATON. + Returns a user understandable error using LOCAL_ABSPATH if the + information cannot be obtained. */ +static svn_error_t * +ensure_repos_info(svn_wc_entry_t *entry, + const char *local_abspath, + svn_wc_upgrade_get_repos_info_t repos_info_func, + void *repos_info_baton, + apr_hash_t *repos_cache, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + /* Easy exit. */ + if (entry->repos != NULL && entry->uuid != NULL) + return SVN_NO_ERROR; + + if ((entry->repos == NULL || entry->uuid == NULL) + && entry->url) + { + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, repos_cache); + hi; hi = apr_hash_next(hi)) + { + if (svn_uri__is_ancestor(svn__apr_hash_index_key(hi), entry->url)) + { + if (!entry->repos) + entry->repos = svn__apr_hash_index_key(hi); + + if (!entry->uuid) + entry->uuid = svn__apr_hash_index_val(hi); + + return SVN_NO_ERROR; + } + } + } + + if (entry->repos == NULL && repos_info_func == NULL) + return svn_error_createf( + SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL, + _("Working copy '%s' can't be upgraded because the repository root is " + "not available and can't be retrieved"), + svn_dirent_local_style(local_abspath, scratch_pool)); + + if (entry->uuid == NULL && repos_info_func == NULL) + return svn_error_createf( + SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL, + _("Working copy '%s' can't be upgraded because the repository uuid is " + "not available and can't be retrieved"), + svn_dirent_local_style(local_abspath, scratch_pool)); + + if (entry->url == NULL) + return svn_error_createf( + SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL, + _("Working copy '%s' can't be upgraded because it doesn't have a url"), + svn_dirent_local_style(local_abspath, scratch_pool)); + + return svn_error_trace((*repos_info_func)(&entry->repos, &entry->uuid, + repos_info_baton, + entry->url, + result_pool, scratch_pool)); +} + + +/* + * Read tree conflict descriptions from @a conflict_data. Set @a *conflicts + * to a hash of pointers to svn_wc_conflict_description2_t objects indexed by + * svn_wc_conflict_description2_t.local_abspath, all newly allocated in @a + * pool. @a dir_path is the path to the working copy directory whose conflicts + * are being read. The conflicts read are the tree conflicts on the immediate + * child nodes of @a dir_path. Do all allocations in @a pool. + * + * Note: There were some concerns about this function: + * + * ### this is BAD. the CONFLICTS structure should not be dependent upon + * ### DIR_PATH. each conflict should be labeled with an entry name, not + * ### a whole path. (and a path which happens to vary based upon invocation + * ### of the user client and these APIs) + * + * those assumptions were baked into former versions of the data model, so + * they have to stick around here. But they have been removed from the + * New Way. */ +static svn_error_t * +read_tree_conflicts(apr_hash_t **conflicts, + const char *conflict_data, + const char *dir_path, + apr_pool_t *pool) +{ + const svn_skel_t *skel; + apr_pool_t *iterpool; + + *conflicts = apr_hash_make(pool); + + if (conflict_data == NULL) + return SVN_NO_ERROR; + + skel = svn_skel__parse(conflict_data, strlen(conflict_data), pool); + if (skel == NULL) + return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, + _("Error parsing tree conflict skel")); + + iterpool = svn_pool_create(pool); + for (skel = skel->children; skel != NULL; skel = skel->next) + { + const svn_wc_conflict_description2_t *conflict; + + svn_pool_clear(iterpool); + SVN_ERR(svn_wc__deserialize_conflict(&conflict, skel, dir_path, + pool, iterpool)); + if (conflict != NULL) + svn_hash_sets(*conflicts, + svn_dirent_basename(conflict->local_abspath, pool), + conflict); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +migrate_single_tree_conflict_data(svn_sqlite__db_t *sdb, + const char *tree_conflict_data, + apr_int64_t wc_id, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + apr_hash_t *conflicts; + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + SVN_ERR(read_tree_conflicts(&conflicts, tree_conflict_data, local_relpath, + scratch_pool)); + + iterpool = svn_pool_create(scratch_pool); + for (hi = apr_hash_first(scratch_pool, conflicts); + hi; + hi = apr_hash_next(hi)) + { + const svn_wc_conflict_description2_t *conflict = + svn__apr_hash_index_val(hi); + const char *conflict_relpath; + const char *conflict_data; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_skel_t *skel; + + svn_pool_clear(iterpool); + + conflict_relpath = svn_dirent_join(local_relpath, + svn_dirent_basename( + conflict->local_abspath, iterpool), + iterpool); + + SVN_ERR(svn_wc__serialize_conflict(&skel, conflict, iterpool, iterpool)); + conflict_data = svn_skel__unparse(skel, iterpool)->data; + + /* See if we need to update or insert an ACTUAL node. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, conflict_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (have_row) + { + /* There is an existing ACTUAL row, so just update it. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPDATE_ACTUAL_CONFLICT_DATA)); + } + else + { + /* We need to insert an ACTUAL row with the tree conflict data. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_INSERT_ACTUAL_CONFLICT_DATA)); + } + + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wc_id, conflict_relpath, + conflict_data)); + if (!have_row) + SVN_ERR(svn_sqlite__bind_text(stmt, 4, local_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* */ +static svn_error_t * +migrate_tree_conflict_data(svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + /* Iterate over each node which has a set of tree conflicts, then insert + all of them into the new schema. */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT)); + + /* Get all the existing tree conflict data. */ + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + apr_int64_t wc_id; + const char *local_relpath; + const char *tree_conflict_data; + + svn_pool_clear(iterpool); + + wc_id = svn_sqlite__column_int64(stmt, 0); + local_relpath = svn_sqlite__column_text(stmt, 1, iterpool); + tree_conflict_data = svn_sqlite__column_text(stmt, 2, iterpool); + + SVN_ERR(migrate_single_tree_conflict_data(sdb, tree_conflict_data, + wc_id, local_relpath, + iterpool)); + + /* We don't need to do anything but step over the previously + prepared statement. */ + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Erase all the old tree conflict data. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPGRADE_21_ERASE_OLD_CONFLICTS)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + + +struct bump_baton { + const char *wcroot_abspath; +}; + +/* Migrate the properties for one node (LOCAL_ABSPATH). */ +static svn_error_t * +migrate_node_props(const char *dir_abspath, + const char *new_wcroot_abspath, + const char *name, + svn_sqlite__db_t *sdb, + int original_format, + apr_int64_t wc_id, + apr_pool_t *scratch_pool) +{ + const char *base_abspath; /* old name. nowadays: "pristine" */ + const char *revert_abspath; /* old name. nowadays: "BASE" */ + const char *working_abspath; /* old name. nowadays: "ACTUAL" */ + apr_hash_t *base_props; + apr_hash_t *revert_props; + apr_hash_t *working_props; + const char *old_wcroot_abspath + = svn_dirent_get_longest_ancestor(dir_abspath, new_wcroot_abspath, + scratch_pool); + const char *dir_relpath = svn_dirent_skip_ancestor(old_wcroot_abspath, + dir_abspath); + + if (*name == '\0') + { + base_abspath = svn_wc__adm_child(dir_abspath, + PROP_BASE_FOR_DIR, scratch_pool); + revert_abspath = svn_wc__adm_child(dir_abspath, + PROP_REVERT_FOR_DIR, scratch_pool); + working_abspath = svn_wc__adm_child(dir_abspath, + PROP_WORKING_FOR_DIR, scratch_pool); + } + else + { + const char *basedir_abspath; + const char *propsdir_abspath; + + propsdir_abspath = svn_wc__adm_child(dir_abspath, PROPS_SUBDIR, + scratch_pool); + basedir_abspath = svn_wc__adm_child(dir_abspath, PROP_BASE_SUBDIR, + scratch_pool); + + base_abspath = svn_dirent_join(basedir_abspath, + apr_pstrcat(scratch_pool, + name, + SVN_WC__BASE_EXT, + (char *)NULL), + scratch_pool); + + revert_abspath = svn_dirent_join(basedir_abspath, + apr_pstrcat(scratch_pool, + name, + SVN_WC__REVERT_EXT, + (char *)NULL), + scratch_pool); + + working_abspath = svn_dirent_join(propsdir_abspath, + apr_pstrcat(scratch_pool, + name, + SVN_WC__WORK_EXT, + (char *)NULL), + scratch_pool); + } + + SVN_ERR(read_propfile(&base_props, base_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(read_propfile(&revert_props, revert_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(read_propfile(&working_props, working_abspath, + scratch_pool, scratch_pool)); + + return svn_error_trace(svn_wc__db_upgrade_apply_props( + sdb, new_wcroot_abspath, + svn_relpath_join(dir_relpath, name, scratch_pool), + base_props, revert_props, working_props, + original_format, wc_id, + scratch_pool)); +} + + +/* */ +static svn_error_t * +migrate_props(const char *dir_abspath, + const char *new_wcroot_abspath, + svn_sqlite__db_t *sdb, + int original_format, + apr_int64_t wc_id, + apr_pool_t *scratch_pool) +{ + /* General logic here: iterate over all the immediate children of the root + (since we aren't yet in a centralized system), and for any properties that + exist, map them as follows: + + if (revert props exist): + revert -> BASE + base -> WORKING + working -> ACTUAL + else if (prop pristine is working [as defined in props.c] ): + base -> WORKING + working -> ACTUAL + else: + base -> BASE + working -> ACTUAL + + ### the middle "test" should simply look for a WORKING_NODE row + + Note that it is legal for "working" props to be missing. That implies + no local changes to the properties. + */ + const apr_array_header_t *children; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + const char *old_wcroot_abspath + = svn_dirent_get_longest_ancestor(dir_abspath, new_wcroot_abspath, + scratch_pool); + const char *dir_relpath = svn_dirent_skip_ancestor(old_wcroot_abspath, + dir_abspath); + int i; + + /* Migrate the props for "this dir". */ + SVN_ERR(migrate_node_props(dir_abspath, new_wcroot_abspath, "", sdb, + original_format, wc_id, iterpool)); + + /* Iterate over all the files in this SDB. */ + SVN_ERR(get_versioned_files(&children, dir_relpath, sdb, wc_id, scratch_pool, + iterpool)); + for (i = 0; i < children->nelts; i++) + { + const char *name = APR_ARRAY_IDX(children, i, const char *); + + svn_pool_clear(iterpool); + + SVN_ERR(migrate_node_props(dir_abspath, new_wcroot_abspath, + name, sdb, original_format, wc_id, iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* If STR ends with SUFFIX and is longer than SUFFIX, return the part of + * STR that comes before SUFFIX; else return NULL. */ +static char * +remove_suffix(const char *str, const char *suffix, apr_pool_t *result_pool) +{ + size_t str_len = strlen(str); + size_t suffix_len = strlen(suffix); + + if (str_len > suffix_len + && strcmp(str + str_len - suffix_len, suffix) == 0) + { + return apr_pstrmemdup(result_pool, str, str_len - suffix_len); + } + + return NULL; +} + +/* Copy all the text-base files from the administrative area of WC directory + DIR_ABSPATH into the pristine store of SDB which is located in directory + NEW_WCROOT_ABSPATH. + + Set *TEXT_BASES_INFO to a new hash, allocated in RESULT_POOL, that maps + (const char *) name of the versioned file to (svn_wc__text_base_info_t *) + information about the pristine text. */ +static svn_error_t * +migrate_text_bases(apr_hash_t **text_bases_info, + const char *dir_abspath, + const char *new_wcroot_abspath, + svn_sqlite__db_t *sdb, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *dirents; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_index_t *hi; + const char *text_base_dir = svn_wc__adm_child(dir_abspath, + TEXT_BASE_SUBDIR, + scratch_pool); + + *text_bases_info = apr_hash_make(result_pool); + + /* Iterate over the text-base files */ + SVN_ERR(svn_io_get_dirents3(&dirents, text_base_dir, TRUE, + scratch_pool, scratch_pool)); + for (hi = apr_hash_first(scratch_pool, dirents); hi; + hi = apr_hash_next(hi)) + { + const char *text_base_basename = svn__apr_hash_index_key(hi); + svn_checksum_t *md5_checksum; + svn_checksum_t *sha1_checksum; + + svn_pool_clear(iterpool); + + /* Calculate its checksums and copy it to the pristine store */ + { + const char *pristine_path; + const char *text_base_path; + const char *temp_path; + svn_sqlite__stmt_t *stmt; + apr_finfo_t finfo; + svn_stream_t *read_stream; + svn_stream_t *result_stream; + + text_base_path = svn_dirent_join(text_base_dir, text_base_basename, + iterpool); + + /* Create a copy and calculate a checksum in one step */ + SVN_ERR(svn_stream_open_unique(&result_stream, &temp_path, + new_wcroot_abspath, + svn_io_file_del_none, + iterpool, iterpool)); + + SVN_ERR(svn_stream_open_readonly(&read_stream, text_base_path, + iterpool, iterpool)); + + read_stream = svn_stream_checksummed2(read_stream, &md5_checksum, + NULL, svn_checksum_md5, + TRUE, iterpool); + + read_stream = svn_stream_checksummed2(read_stream, &sha1_checksum, + NULL, svn_checksum_sha1, + TRUE, iterpool); + + /* This calculates the hash, creates a copy and closes the stream */ + SVN_ERR(svn_stream_copy3(read_stream, result_stream, + NULL, NULL, iterpool)); + + SVN_ERR(svn_io_stat(&finfo, text_base_path, APR_FINFO_SIZE, iterpool)); + + /* Insert a row into the pristine table. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_INSERT_OR_IGNORE_PRISTINE)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, iterpool)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, iterpool)); + SVN_ERR(svn_sqlite__bind_int64(stmt, 3, finfo.size)); + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + + SVN_ERR(svn_wc__db_pristine_get_future_path(&pristine_path, + new_wcroot_abspath, + sha1_checksum, + iterpool, iterpool)); + + /* Ensure any sharding directories exist. */ + SVN_ERR(svn_wc__ensure_directory(svn_dirent_dirname(pristine_path, + iterpool), + iterpool)); + + /* Now move the file into the pristine store, overwriting + existing files with the same checksum. */ + SVN_ERR(svn_io_file_move(temp_path, pristine_path, iterpool)); + } + + /* Add the checksums for this text-base to *TEXT_BASES_INFO. */ + { + const char *versioned_file_name; + svn_boolean_t is_revert_base; + svn_wc__text_base_info_t *info; + svn_wc__text_base_file_info_t *file_info; + + /* Determine the versioned file name and whether this is a normal base + * or a revert base. */ + versioned_file_name = remove_suffix(text_base_basename, + SVN_WC__REVERT_EXT, result_pool); + if (versioned_file_name) + { + is_revert_base = TRUE; + } + else + { + versioned_file_name = remove_suffix(text_base_basename, + SVN_WC__BASE_EXT, result_pool); + is_revert_base = FALSE; + } + + if (! versioned_file_name) + { + /* Some file that doesn't end with .svn-base or .svn-revert. + No idea why that would be in our administrative area, but + we shouldn't segfault on this case. + + Note that we already copied this file in the pristine store, + but the next cleanup will take care of that. + */ + continue; + } + + /* Create a new info struct for this versioned file, or fill in the + * existing one if this is the second text-base we've found for it. */ + info = svn_hash_gets(*text_bases_info, versioned_file_name); + if (info == NULL) + info = apr_pcalloc(result_pool, sizeof (*info)); + file_info = (is_revert_base ? &info->revert_base : &info->normal_base); + + file_info->sha1_checksum = svn_checksum_dup(sha1_checksum, result_pool); + file_info->md5_checksum = svn_checksum_dup(md5_checksum, result_pool); + svn_hash_sets(*text_bases_info, versioned_file_name, info); + } + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_20(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_NODES)); + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_20)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_21(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_21)); + SVN_ERR(migrate_tree_conflict_data(sdb, scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_22(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_22)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_23(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + const char *wcroot_abspath = ((struct bump_baton *)baton)->wcroot_abspath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPGRADE_23_HAS_WORKING_NODES)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("The working copy at '%s' is format 22 with " + "WORKING nodes; use a format 22 client to " + "diff/revert before using this client"), + wcroot_abspath); + + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_23)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_24(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_24)); + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_NODES_TRIGGERS)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_25(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_25)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_26(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_26)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_27(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + const char *wcroot_abspath = ((struct bump_baton *)baton)->wcroot_abspath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("The working copy at '%s' is format 26 with " + "conflicts; use a format 26 client to resolve " + "before using this client"), + wcroot_abspath); + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_27)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_28(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_28)); + return SVN_NO_ERROR; +} + +/* If FINFO indicates that ABSPATH names a file, rename it to + * '<ABSPATH>.svn-base'. + * + * Ignore any file whose name is not the expected length, in order to make + * life easier for any developer who runs this code twice or has some + * non-standard files in the pristine directory. + * + * A callback for bump_to_29(), implementing #svn_io_walk_func_t. */ +static svn_error_t * +rename_pristine_file(void *baton, + const char *abspath, + const apr_finfo_t *finfo, + apr_pool_t *pool) +{ + if (finfo->filetype == APR_REG + && (strlen(svn_dirent_basename(abspath, pool)) + == PRISTINE_BASENAME_OLD_LEN)) + { + const char *new_abspath + = apr_pstrcat(pool, abspath, PRISTINE_STORAGE_EXT, (char *)NULL); + + SVN_ERR(svn_io_file_rename(abspath, new_abspath, pool)); + } + return SVN_NO_ERROR; +} + +static svn_error_t * +upgrade_externals(struct bump_baton *bb, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_sqlite__stmt_t *stmt_add; + svn_boolean_t have_row; + apr_pool_t *iterpool; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_SELECT_EXTERNAL_PROPERTIES)); + + SVN_ERR(svn_sqlite__get_statement(&stmt_add, sdb, + STMT_INSERT_EXTERNAL)); + + /* ### For this intermediate upgrade we just assume WC_ID = 1. + ### Before this bump we lost track of externals all the time, + ### so lets keep this easy. */ + SVN_ERR(svn_sqlite__bindf(stmt, "is", (apr_int64_t)1, "")); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + apr_hash_t *props; + const char *externals; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__column_properties(&props, stmt, 0, + iterpool, iterpool)); + + externals = svn_prop_get_value(props, SVN_PROP_EXTERNALS); + + if (externals) + { + apr_array_header_t *ext; + const char *local_relpath; + const char *local_abspath; + int i; + + local_relpath = svn_sqlite__column_text(stmt, 1, NULL); + local_abspath = svn_dirent_join(bb->wcroot_abspath, local_relpath, + iterpool); + + SVN_ERR(svn_wc_parse_externals_description3(&ext, local_abspath, + externals, FALSE, + iterpool)); + + for (i = 0; i < ext->nelts; i++) + { + const svn_wc_external_item2_t *item; + const char *item_relpath; + + item = APR_ARRAY_IDX(ext, i, const svn_wc_external_item2_t *); + item_relpath = svn_relpath_join(local_relpath, item->target_dir, + iterpool); + + /* Insert dummy externals definitions: Insert an unknown + external, to make sure it will be cleaned up when it is not + updated on the next update. */ + SVN_ERR(svn_sqlite__bindf(stmt_add, "isssssis", + (apr_int64_t)1, /* wc_id */ + item_relpath, + svn_relpath_dirname(item_relpath, + iterpool), + "normal", + "unknown", + local_relpath, + (apr_int64_t)1, /* repos_id */ + "" /* repos_relpath */)); + SVN_ERR(svn_sqlite__insert(NULL, stmt_add)); + } + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + svn_pool_destroy(iterpool); + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +static svn_error_t * +bump_to_29(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + struct bump_baton *bb = baton; + const char *wcroot_abspath = bb->wcroot_abspath; + const char *pristine_dir_abspath; + + /* Rename all pristine files, adding a ".svn-base" suffix. */ + pristine_dir_abspath = svn_dirent_join_many(scratch_pool, wcroot_abspath, + svn_wc_get_adm_dir(scratch_pool), + PRISTINE_STORAGE_RELPATH, NULL); + SVN_ERR(svn_io_dir_walk2(pristine_dir_abspath, APR_FINFO_MIN, + rename_pristine_file, NULL, scratch_pool)); + + /* Externals */ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_EXTERNALS)); + + SVN_ERR(upgrade_externals(bb, sdb, scratch_pool)); + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_29)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__upgrade_conflict_skel_from_raw(svn_skel_t **conflicts, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_relpath, + const char *conflict_old, + const char *conflict_wrk, + const char *conflict_new, + const char *prej_file, + const char *tree_conflict_data, + apr_size_t tree_conflict_len, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_skel_t *conflict_data = NULL; + const char *wcroot_abspath; + + SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, db, wri_abspath, + scratch_pool, scratch_pool)); + + if (conflict_old || conflict_new || conflict_wrk) + { + const char *old_abspath = NULL; + const char *new_abspath = NULL; + const char *wrk_abspath = NULL; + + conflict_data = svn_wc__conflict_skel_create(result_pool); + + if (conflict_old) + old_abspath = svn_dirent_join(wcroot_abspath, conflict_old, + scratch_pool); + + if (conflict_new) + new_abspath = svn_dirent_join(wcroot_abspath, conflict_new, + scratch_pool); + + if (conflict_wrk) + wrk_abspath = svn_dirent_join(wcroot_abspath, conflict_wrk, + scratch_pool); + + SVN_ERR(svn_wc__conflict_skel_add_text_conflict(conflict_data, + db, wri_abspath, + wrk_abspath, + old_abspath, + new_abspath, + scratch_pool, + scratch_pool)); + } + + if (prej_file) + { + const char *prej_abspath; + + if (!conflict_data) + conflict_data = svn_wc__conflict_skel_create(result_pool); + + prej_abspath = svn_dirent_join(wcroot_abspath, prej_file, scratch_pool); + + SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(conflict_data, + db, wri_abspath, + prej_abspath, + NULL, NULL, NULL, + apr_hash_make(scratch_pool), + scratch_pool, + scratch_pool)); + } + + if (tree_conflict_data) + { + svn_skel_t *tc_skel; + const svn_wc_conflict_description2_t *tc; + const char *local_abspath; + + if (!conflict_data) + conflict_data = svn_wc__conflict_skel_create(scratch_pool); + + tc_skel = svn_skel__parse(tree_conflict_data, tree_conflict_len, + scratch_pool); + + local_abspath = svn_dirent_join(wcroot_abspath, local_relpath, + scratch_pool); + + SVN_ERR(svn_wc__deserialize_conflict(&tc, tc_skel, + svn_dirent_dirname(local_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(conflict_data, + db, wri_abspath, + tc->reason, + tc->action, + NULL, + scratch_pool, + scratch_pool)); + + switch (tc->operation) + { + case svn_wc_operation_update: + default: + SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_data, + tc->src_left_version, + tc->src_right_version, + scratch_pool, + scratch_pool)); + break; + case svn_wc_operation_switch: + SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict_data, + tc->src_left_version, + tc->src_right_version, + scratch_pool, + scratch_pool)); + break; + case svn_wc_operation_merge: + SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_data, + tc->src_left_version, + tc->src_right_version, + scratch_pool, + scratch_pool)); + break; + } + } + else if (conflict_data) + { + SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_data, NULL, NULL, + scratch_pool, + scratch_pool)); + } + + *conflicts = conflict_data; + return SVN_NO_ERROR; +} + +/* Helper function to upgrade a single conflict from bump_to_30 */ +static svn_error_t * +bump_30_upgrade_one_conflict(svn_wc__db_t *wc_db, + const char *wcroot_abspath, + svn_sqlite__stmt_t *stmt, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt_store; + svn_stringbuf_t *skel_data; + svn_skel_t *conflict_data; + apr_int64_t wc_id = svn_sqlite__column_int64(stmt, 0); + const char *local_relpath = svn_sqlite__column_text(stmt, 1, NULL); + const char *conflict_old = svn_sqlite__column_text(stmt, 2, NULL); + const char *conflict_wrk = svn_sqlite__column_text(stmt, 3, NULL); + const char *conflict_new = svn_sqlite__column_text(stmt, 4, NULL); + const char *prop_reject = svn_sqlite__column_text(stmt, 5, NULL); + apr_size_t tree_conflict_size; + const char *tree_conflict_data = svn_sqlite__column_blob(stmt, 6, + &tree_conflict_size, NULL); + + SVN_ERR(svn_wc__upgrade_conflict_skel_from_raw(&conflict_data, + wc_db, wcroot_abspath, + local_relpath, + conflict_old, + conflict_wrk, + conflict_new, + prop_reject, + tree_conflict_data, + tree_conflict_size, + scratch_pool, scratch_pool)); + + SVN_ERR_ASSERT(conflict_data != NULL); + + skel_data = svn_skel__unparse(conflict_data, scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt_store, sdb, + STMT_UPGRADE_30_SET_CONFLICT)); + SVN_ERR(svn_sqlite__bindf(stmt_store, "isb", wc_id, local_relpath, + skel_data->data, skel_data->len)); + SVN_ERR(svn_sqlite__step_done(stmt_store)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_30(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool) +{ + struct bump_baton *bb = baton; + svn_boolean_t have_row; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + svn_sqlite__stmt_t *stmt; + svn_wc__db_t *db; /* Read only temp db */ + + SVN_ERR(svn_wc__db_open(&db, NULL, TRUE /* open_without_upgrade */, FALSE, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + svn_error_t *err; + svn_pool_clear(iterpool); + + err = bump_30_upgrade_one_conflict(db, bb->wcroot_abspath, stmt, sdb, + iterpool); + + if (err) + { + return svn_error_trace( + svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_30)); + SVN_ERR(svn_wc__db_close(db)); + return SVN_NO_ERROR; +} + +static svn_error_t * +bump_to_31(void *baton, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt, *stmt_mark_switch_roots; + svn_boolean_t have_row; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_array_header_t *empty_iprops = apr_array_make( + scratch_pool, 0, sizeof(svn_prop_inherited_item_t *)); + svn_boolean_t iprops_column_exists = FALSE; + svn_error_t *err; + + /* Add the inherited_props column to NODES if it does not yet exist. + * + * When using a format >= 31 client to upgrade from old formats which + * did not yet have a NODES table, the inherited_props column has + * already been created as part of the NODES table. Attemping to add + * the inherited_props column will raise an error in this case, so check + * if the column exists first. + * + * Checking for the existence of a column before ALTER TABLE is not + * possible within SQLite. We need to run a separate query and evaluate + * its result in C first. + */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_PRAGMA_TABLE_INFO_NODES)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *column_name = svn_sqlite__column_text(stmt, 1, NULL); + + if (strcmp(column_name, "inherited_props") == 0) + { + iprops_column_exists = TRUE; + break; + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + if (!iprops_column_exists) + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_31_ALTER_TABLE)); + + /* Run additional statements to finalize the upgrade to format 31. */ + SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_UPGRADE_TO_31_FINALIZE)); + + /* Set inherited_props to an empty array for the roots of all + switched subtrees in the WC. This allows subsequent updates + to recognize these roots as needing an iprops cache. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPGRADE_31_SELECT_WCROOT_NODES)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + err = svn_sqlite__get_statement(&stmt_mark_switch_roots, sdb, + STMT_UPDATE_IPROP); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + while (have_row) + { + const char *switched_relpath = svn_sqlite__column_text(stmt, 1, NULL); + apr_int64_t wc_id = svn_sqlite__column_int64(stmt, 0); + + err = svn_sqlite__bindf(stmt_mark_switch_roots, "is", wc_id, + switched_relpath); + if (!err) + err = svn_sqlite__bind_iprops(stmt_mark_switch_roots, 3, + empty_iprops, iterpool); + if (!err) + err = svn_sqlite__step_done(stmt_mark_switch_roots); + if (!err) + err = svn_sqlite__step(&have_row, stmt); + + if (err) + return svn_error_compose_create( + err, + svn_error_compose_create( + /* Reset in either order is OK. */ + svn_sqlite__reset(stmt), + svn_sqlite__reset(stmt_mark_switch_roots))); + } + + err = svn_sqlite__reset(stmt_mark_switch_roots); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +struct upgrade_data_t { + svn_sqlite__db_t *sdb; + const char *root_abspath; + apr_int64_t repos_id; + apr_int64_t wc_id; +}; + +/* Upgrade the working copy directory represented by DB/DIR_ABSPATH + from OLD_FORMAT to the wc-ng format (SVN_WC__WC_NG_VERSION)'. + + Pass REPOS_INFO_FUNC, REPOS_INFO_BATON and REPOS_CACHE to + ensure_repos_info. Add the found repository root and UUID to + REPOS_CACHE if it doesn't have a cached entry for this + repository. + + *DATA refers to the single root db. + + Uses SCRATCH_POOL for all temporary allocation. */ +static svn_error_t * +upgrade_to_wcng(void **dir_baton, + void *parent_baton, + svn_wc__db_t *db, + const char *dir_abspath, + int old_format, + apr_int64_t wc_id, + svn_wc_upgrade_get_repos_info_t repos_info_func, + void *repos_info_baton, + apr_hash_t *repos_cache, + const struct upgrade_data_t *data, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *logfile_path = svn_wc__adm_child(dir_abspath, ADM_LOG, + scratch_pool); + svn_node_kind_t logfile_on_disk_kind; + apr_hash_t *entries; + svn_wc_entry_t *this_dir; + const char *old_wcroot_abspath, *dir_relpath; + apr_hash_t *text_bases_info; + svn_error_t *err; + + /* Don't try to mess with the WC if there are old log files left. */ + + /* Is the (first) log file present? */ + SVN_ERR(svn_io_check_path(logfile_path, &logfile_on_disk_kind, + scratch_pool)); + if (logfile_on_disk_kind == svn_node_file) + return svn_error_create(SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL, + _("Cannot upgrade with existing logs; run a " + "cleanup operation on this working copy using " + "a client version which is compatible with this " + "working copy's format (such as the version " + "you are upgrading from), then retry the " + "upgrade with the current version")); + + /* Lock this working copy directory, or steal an existing lock. Do this + BEFORE we read the entries. We don't want another process to modify the + entries after we've read them into memory. */ + SVN_ERR(create_physical_lock(dir_abspath, scratch_pool)); + + /* What's going on here? + * + * We're attempting to upgrade an older working copy to the new wc-ng format. + * The semantics and storage mechanisms between the two are vastly different, + * so it's going to be a bit painful. Here's a plan for the operation: + * + * 1) Read the old 'entries' using the old-format reader. + * + * 2) Create the new DB if it hasn't already been created. + * + * 3) Use our compatibility code for writing entries to fill out the (new) + * DB state. Use the remembered checksums, since an entry has only the + * MD5 not the SHA1 checksum, and in the case of a revert-base doesn't + * even have that. + * + * 4) Convert wcprop to the wc-ng format + * + * 5) Migrate regular properties to the WC-NG DB. + */ + + /***** ENTRIES - READ *****/ + SVN_ERR(svn_wc__read_entries_old(&entries, dir_abspath, + scratch_pool, scratch_pool)); + + this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR); + SVN_ERR(ensure_repos_info(this_dir, dir_abspath, + repos_info_func, repos_info_baton, + repos_cache, + scratch_pool, scratch_pool)); + + /* Cache repos UUID pairs for when a subdir doesn't have this information */ + if (!svn_hash_gets(repos_cache, this_dir->repos)) + { + apr_pool_t *hash_pool = apr_hash_pool_get(repos_cache); + + svn_hash_sets(repos_cache, + apr_pstrdup(hash_pool, this_dir->repos), + apr_pstrdup(hash_pool, this_dir->uuid)); + } + + old_wcroot_abspath = svn_dirent_get_longest_ancestor(dir_abspath, + data->root_abspath, + scratch_pool); + dir_relpath = svn_dirent_skip_ancestor(old_wcroot_abspath, dir_abspath); + + /***** TEXT BASES *****/ + SVN_ERR(migrate_text_bases(&text_bases_info, dir_abspath, data->root_abspath, + data->sdb, scratch_pool, scratch_pool)); + + /***** ENTRIES - WRITE *****/ + err = svn_wc__write_upgraded_entries(dir_baton, parent_baton, db, data->sdb, + data->repos_id, data->wc_id, + dir_abspath, data->root_abspath, + entries, text_bases_info, + result_pool, scratch_pool); + if (err && err->apr_err == SVN_ERR_WC_CORRUPT) + return svn_error_quick_wrap(err, + _("This working copy is corrupt and " + "cannot be upgraded. Please check out " + "a new working copy.")); + else + SVN_ERR(err); + + /***** WC PROPS *****/ + /* If we don't know precisely where the wcprops are, ignore them. */ + if (old_format != SVN_WC__WCPROPS_LOST) + { + apr_hash_t *all_wcprops; + + if (old_format <= SVN_WC__WCPROPS_MANY_FILES_VERSION) + SVN_ERR(read_many_wcprops(&all_wcprops, dir_abspath, + scratch_pool, scratch_pool)); + else + SVN_ERR(read_wcprops(&all_wcprops, dir_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_upgrade_apply_dav_cache(data->sdb, dir_relpath, + all_wcprops, scratch_pool)); + } + + /* Upgrade all the properties (including "this dir"). + + Note: this must come AFTER the entries have been migrated into the + database. The upgrade process needs the children in BASE_NODE and + WORKING_NODE, and to examine the resultant WORKING state. */ + SVN_ERR(migrate_props(dir_abspath, data->root_abspath, data->sdb, old_format, + wc_id, scratch_pool)); + + return SVN_NO_ERROR; +} + +const char * +svn_wc__version_string_from_format(int wc_format) +{ + switch (wc_format) + { + case 4: return "<=1.3"; + case 8: return "1.4"; + case 9: return "1.5"; + case 10: return "1.6"; + case SVN_WC__WC_NG_VERSION: return "1.7"; + } + return _("(unreleased development version)"); +} + +svn_error_t * +svn_wc__upgrade_sdb(int *result_format, + const char *wcroot_abspath, + svn_sqlite__db_t *sdb, + int start_format, + apr_pool_t *scratch_pool) +{ + struct bump_baton bb; + + bb.wcroot_abspath = wcroot_abspath; + + if (start_format < SVN_WC__WC_NG_VERSION /* 12 */) + return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL, + _("Working copy '%s' is too old (format %d, " + "created by Subversion %s)"), + svn_dirent_local_style(wcroot_abspath, + scratch_pool), + start_format, + svn_wc__version_string_from_format(start_format)); + + /* Early WCNG formats no longer supported. */ + if (start_format < 19) + return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL, + _("Working copy '%s' is an old development " + "version (format %d); to upgrade it, " + "use a format 18 client, then " + "use 'tools/dev/wc-ng/bump-to-19.py', then " + "use the current client"), + svn_dirent_local_style(wcroot_abspath, + scratch_pool), + start_format); + + /* ### need lock-out. only one upgrade at a time. note that other code + ### cannot use this un-upgraded database until we finish the upgrade. */ + + /* Note: none of these have "break" statements; the fall-through is + intentional. */ + switch (start_format) + { + case 19: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_20, &bb, + scratch_pool)); + *result_format = 20; + /* FALLTHROUGH */ + + case 20: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_21, &bb, + scratch_pool)); + *result_format = 21; + /* FALLTHROUGH */ + + case 21: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_22, &bb, + scratch_pool)); + *result_format = 22; + /* FALLTHROUGH */ + + case 22: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_23, &bb, + scratch_pool)); + *result_format = 23; + /* FALLTHROUGH */ + + case 23: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_24, &bb, + scratch_pool)); + *result_format = 24; + /* FALLTHROUGH */ + + case 24: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_25, &bb, + scratch_pool)); + *result_format = 25; + /* FALLTHROUGH */ + + case 25: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_26, &bb, + scratch_pool)); + *result_format = 26; + /* FALLTHROUGH */ + + case 26: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_27, &bb, + scratch_pool)); + *result_format = 27; + /* FALLTHROUGH */ + + case 27: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_28, &bb, + scratch_pool)); + *result_format = 28; + /* FALLTHROUGH */ + + case 28: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_29, &bb, + scratch_pool)); + *result_format = 29; + /* FALLTHROUGH */ + + case 29: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_30, &bb, + scratch_pool)); + *result_format = 30; + + case 30: + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_31, &bb, + scratch_pool)); + *result_format = 31; + /* FALLTHROUGH */ + /* ### future bumps go here. */ +#if 0 + case XXX-1: + /* Revamp the recording of tree conflicts. */ + SVN_ERR(svn_sqlite__with_transaction(sdb, bump_to_XXX, &bb, + scratch_pool)); + *result_format = XXX; + /* FALLTHROUGH */ +#endif + case SVN_WC__VERSION: + /* already upgraded */ + *result_format = SVN_WC__VERSION; + } + +#ifdef SVN_DEBUG + if (*result_format != start_format) + { + int schema_version; + SVN_ERR(svn_sqlite__read_schema_version(&schema_version, sdb, scratch_pool)); + + /* If this assertion fails the schema isn't updated correctly */ + SVN_ERR_ASSERT(schema_version == *result_format); + } +#endif + + /* Zap anything that might be remaining or escaped our notice. */ + wipe_obsolete_files(wcroot_abspath, scratch_pool); + + return SVN_NO_ERROR; +} + + +/* */ +static svn_error_t * +upgrade_working_copy(void *parent_baton, + svn_wc__db_t *db, + const char *dir_abspath, + svn_wc_upgrade_get_repos_info_t repos_info_func, + void *repos_info_baton, + apr_hash_t *repos_cache, + const struct upgrade_data_t *data, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + void *dir_baton; + int old_format; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_array_header_t *subdirs; + svn_error_t *err; + int i; + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + SVN_ERR(svn_wc__db_temp_get_format(&old_format, db, dir_abspath, + iterpool)); + + if (old_format >= SVN_WC__WC_NG_VERSION) + { + if (notify_func) + notify_func(notify_baton, + svn_wc_create_notify(dir_abspath, svn_wc_notify_skip, + iterpool), + iterpool); + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; + } + + err = get_versioned_subdirs(&subdirs, NULL, dir_abspath, FALSE, + scratch_pool, iterpool); + if (err) + { + if (APR_STATUS_IS_ENOENT(err->apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)) + { + /* An unversioned dir is obstructing a versioned dir */ + svn_error_clear(err); + err = NULL; + if (notify_func) + notify_func(notify_baton, + svn_wc_create_notify(dir_abspath, svn_wc_notify_skip, + iterpool), + iterpool); + } + svn_pool_destroy(iterpool); + return err; + } + + + SVN_ERR(upgrade_to_wcng(&dir_baton, parent_baton, db, dir_abspath, + old_format, data->wc_id, + repos_info_func, repos_info_baton, + repos_cache, data, scratch_pool, iterpool)); + + if (notify_func) + notify_func(notify_baton, + svn_wc_create_notify(dir_abspath, svn_wc_notify_upgraded_path, + iterpool), + iterpool); + + for (i = 0; i < subdirs->nelts; ++i) + { + const char *child_abspath = APR_ARRAY_IDX(subdirs, i, const char *); + + svn_pool_clear(iterpool); + + SVN_ERR(upgrade_working_copy(dir_baton, db, child_abspath, + repos_info_func, repos_info_baton, + repos_cache, data, + cancel_func, cancel_baton, + notify_func, notify_baton, + iterpool, iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Return a verbose error if LOCAL_ABSPATH is a not a pre-1.7 working + copy root */ +static svn_error_t * +is_old_wcroot(const char *local_abspath, + apr_pool_t *scratch_pool) +{ + apr_hash_t *entries; + const char *parent_abspath, *name; + svn_wc_entry_t *entry; + svn_error_t *err = svn_wc__read_entries_old(&entries, local_abspath, + scratch_pool, scratch_pool); + if (err) + { + return svn_error_createf( + SVN_ERR_WC_INVALID_OP_ON_CWD, err, + _("Can't upgrade '%s' as it is not a working copy"), + svn_dirent_local_style(local_abspath, scratch_pool)); + } + else if (svn_dirent_is_root(local_abspath, strlen(local_abspath))) + return SVN_NO_ERROR; + + svn_dirent_split(&parent_abspath, &name, local_abspath, scratch_pool); + + err = svn_wc__read_entries_old(&entries, parent_abspath, + scratch_pool, scratch_pool); + if (err) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + + entry = svn_hash_gets(entries, name); + if (!entry + || entry->absent + || (entry->deleted && entry->schedule != svn_wc_schedule_add) + || entry->depth == svn_depth_exclude) + { + return SVN_NO_ERROR; + } + + while (!svn_dirent_is_root(parent_abspath, strlen(parent_abspath))) + { + svn_dirent_split(&parent_abspath, &name, parent_abspath, scratch_pool); + err = svn_wc__read_entries_old(&entries, parent_abspath, + scratch_pool, scratch_pool); + if (err) + { + svn_error_clear(err); + parent_abspath = svn_dirent_join(parent_abspath, name, scratch_pool); + break; + } + entry = svn_hash_gets(entries, name); + if (!entry + || entry->absent + || (entry->deleted && entry->schedule != svn_wc_schedule_add) + || entry->depth == svn_depth_exclude) + { + parent_abspath = svn_dirent_join(parent_abspath, name, scratch_pool); + break; + } + } + + return svn_error_createf( + SVN_ERR_WC_INVALID_OP_ON_CWD, NULL, + _("Can't upgrade '%s' as it is not a working copy root," + " the root is '%s'"), + svn_dirent_local_style(local_abspath, scratch_pool), + svn_dirent_local_style(parent_abspath, scratch_pool)); +} + +/* Data for upgrade_working_copy_txn(). */ +typedef struct upgrade_working_copy_baton_t +{ + svn_wc__db_t *db; + const char *dir_abspath; + svn_wc_upgrade_get_repos_info_t repos_info_func; + void *repos_info_baton; + apr_hash_t *repos_cache; + const struct upgrade_data_t *data; + svn_cancel_func_t cancel_func; + void *cancel_baton; + svn_wc_notify_func2_t notify_func; + void *notify_baton; + apr_pool_t *result_pool; +} upgrade_working_copy_baton_t; + + +/* Helper for svn_wc_upgrade. Implements svn_sqlite__transaction_callback_t */ +static svn_error_t * +upgrade_working_copy_txn(void *baton, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + upgrade_working_copy_baton_t *b = baton; + + /* Upgrade the pre-wcng into a wcng in a temporary location. */ + return(upgrade_working_copy(NULL, b->db, b->dir_abspath, + b->repos_info_func, b->repos_info_baton, + b->repos_cache, b->data, + b->cancel_func, b->cancel_baton, + b->notify_func, b->notify_baton, + b->result_pool, scratch_pool)); +} + +svn_error_t * +svn_wc_upgrade(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_wc_upgrade_get_repos_info_t repos_info_func, + void *repos_info_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db; + struct upgrade_data_t data = { NULL }; + svn_skel_t *work_item, *work_items = NULL; + const char *pristine_from, *pristine_to, *db_from, *db_to; + apr_hash_t *repos_cache = apr_hash_make(scratch_pool); + svn_wc_entry_t *this_dir; + apr_hash_t *entries; + const char *root_adm_abspath; + upgrade_working_copy_baton_t cb_baton; + svn_error_t *err; + int result_format; + + /* Try upgrading a wc-ng-style working copy. */ + SVN_ERR(svn_wc__db_open(&db, NULL /* ### config */, TRUE, FALSE, + scratch_pool, scratch_pool)); + + + err = svn_wc__db_bump_format(&result_format, local_abspath, db, + scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED) + { + return svn_error_trace( + svn_error_compose_create( + err, + svn_wc__db_close(db))); + } + + svn_error_clear(err); + /* Pre 1.7: Fall through */ + } + else + { + /* Auto-upgrade worked! */ + SVN_ERR(svn_wc__db_close(db)); + + SVN_ERR_ASSERT(result_format == SVN_WC__VERSION); + + return SVN_NO_ERROR; + } + + SVN_ERR(is_old_wcroot(local_abspath, scratch_pool)); + + /* Given a pre-wcng root some/wc we create a temporary wcng in + some/wc/.svn/tmp/wcng/wc.db and copy the metadata from one to the + other, then the temporary wc.db file gets moved into the original + root. Until the wc.db file is moved the original working copy + remains a pre-wcng and 'cleanup' with an old client will remove + the partial upgrade. Moving the wc.db file creates a wcng, and + 'cleanup' with a new client will complete any outstanding + upgrade. */ + + SVN_ERR(svn_wc__read_entries_old(&entries, local_abspath, + scratch_pool, scratch_pool)); + + this_dir = svn_hash_gets(entries, SVN_WC_ENTRY_THIS_DIR); + SVN_ERR(ensure_repos_info(this_dir, local_abspath, repos_info_func, + repos_info_baton, repos_cache, + scratch_pool, scratch_pool)); + + /* Cache repos UUID pairs for when a subdir doesn't have this information */ + if (!svn_hash_gets(repos_cache, this_dir->repos)) + svn_hash_sets(repos_cache, + apr_pstrdup(scratch_pool, this_dir->repos), + apr_pstrdup(scratch_pool, this_dir->uuid)); + + /* Create the new DB in the temporary root wc/.svn/tmp/wcng/.svn */ + data.root_abspath = svn_dirent_join(svn_wc__adm_child(local_abspath, "tmp", + scratch_pool), + "wcng", scratch_pool); + root_adm_abspath = svn_wc__adm_child(data.root_abspath, "", + scratch_pool); + SVN_ERR(svn_io_remove_dir2(root_adm_abspath, TRUE, NULL, NULL, + scratch_pool)); + SVN_ERR(svn_wc__ensure_directory(root_adm_abspath, scratch_pool)); + + /* Create an empty sqlite database for this directory and store it in DB. */ + SVN_ERR(svn_wc__db_upgrade_begin(&data.sdb, + &data.repos_id, &data.wc_id, + db, data.root_abspath, + this_dir->repos, this_dir->uuid, + scratch_pool)); + + /* Migrate the entries over to the new database. + ### We need to think about atomicity here. + + entries_write_new() writes in current format rather than + f12. Thus, this function bumps a working copy all the way to + current. */ + SVN_ERR(svn_wc__db_wclock_obtain(db, data.root_abspath, 0, FALSE, + scratch_pool)); + + cb_baton.db = db; + cb_baton.dir_abspath = local_abspath; + cb_baton.repos_info_func = repos_info_func; + cb_baton.repos_info_baton = repos_info_baton; + cb_baton.repos_cache = repos_cache; + cb_baton.data = &data; + cb_baton.cancel_func = cancel_func; + cb_baton.cancel_baton = cancel_baton; + cb_baton.notify_func = notify_func; + cb_baton.notify_baton = notify_baton; + cb_baton.result_pool = scratch_pool; + + SVN_ERR(svn_sqlite__with_lock(data.sdb, + upgrade_working_copy_txn, + &cb_baton, + scratch_pool)); + + /* A workqueue item to move the pristine dir into place */ + pristine_from = svn_wc__adm_child(data.root_abspath, PRISTINE_STORAGE_RELPATH, + scratch_pool); + pristine_to = svn_wc__adm_child(local_abspath, PRISTINE_STORAGE_RELPATH, + scratch_pool); + SVN_ERR(svn_wc__ensure_directory(pristine_from, scratch_pool)); + SVN_ERR(svn_wc__wq_build_file_move(&work_item, db, local_abspath, + pristine_from, pristine_to, + scratch_pool, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + + /* A workqueue item to remove pre-wcng metadata */ + SVN_ERR(svn_wc__wq_build_postupgrade(&work_item, scratch_pool)); + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + SVN_ERR(svn_wc__db_wq_add(db, data.root_abspath, work_items, scratch_pool)); + + SVN_ERR(svn_wc__db_wclock_release(db, data.root_abspath, scratch_pool)); + SVN_ERR(svn_wc__db_close(db)); + + /* Renaming the db file is what makes the pre-wcng into a wcng */ + db_from = svn_wc__adm_child(data.root_abspath, SDB_FILE, scratch_pool); + db_to = svn_wc__adm_child(local_abspath, SDB_FILE, scratch_pool); + SVN_ERR(svn_io_file_rename(db_from, db_to, scratch_pool)); + + /* Now we have a working wcng, tidy up the droppings */ + SVN_ERR(svn_wc__db_open(&db, NULL /* ### config */, FALSE, FALSE, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, + scratch_pool)); + SVN_ERR(svn_wc__db_close(db)); + + /* Should we have the workqueue remove this empty dir? */ + SVN_ERR(svn_io_remove_dir2(data.root_abspath, FALSE, NULL, NULL, + scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__upgrade_add_external_info(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_node_kind_t kind, + const char *def_local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t def_peg_revision, + svn_revnum_t def_revision, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t db_kind; + switch (kind) + { + case svn_node_dir: + db_kind = svn_node_dir; + break; + + case svn_node_file: + db_kind = svn_node_file; + break; + + case svn_node_unknown: + db_kind = svn_node_unknown; + break; + + default: + SVN_ERR_MALFUNCTION(); + } + + SVN_ERR(svn_wc__db_upgrade_insert_external(wc_ctx->db, local_abspath, + db_kind, + svn_dirent_dirname(local_abspath, + scratch_pool), + def_local_abspath, repos_relpath, + repos_root_url, repos_uuid, + def_peg_revision, def_revision, + scratch_pool)); + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/util.c b/subversion/libsvn_wc/util.c new file mode 100644 index 000000000000..a527eda51b93 --- /dev/null +++ b/subversion/libsvn_wc/util.c @@ -0,0 +1,636 @@ +/* + * util.c: general routines defying categorization; eventually I + * suspect they'll end up in libsvn_subr, but don't want to + * pollute that right now. Note that nothing in here is + * specific to working copies. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <apr_pools.h> +#include <apr_file_io.h> + +#include "svn_io.h" +#include "svn_types.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_props.h" +#include "svn_version.h" + +#include "wc.h" /* just for prototypes of things in this .c file */ +#include "entries.h" +#include "private/svn_wc_private.h" + +#include "svn_private_config.h" + + +svn_error_t * +svn_wc__ensure_directory(const char *path, + apr_pool_t *pool) +{ + svn_node_kind_t kind; + + SVN_ERR(svn_io_check_path(path, &kind, pool)); + + if (kind != svn_node_none && kind != svn_node_dir) + { + /* If got an error other than dir non-existence, then we can't + ensure this directory's existence, so just return the error. + Might happen if there's a file in the way, for example. */ + return svn_error_createf(APR_ENOTDIR, NULL, + _("'%s' is not a directory"), + svn_dirent_local_style(path, pool)); + } + else if (kind == svn_node_none) + { + /* The dir doesn't exist, and it's our job to change that. */ + SVN_ERR(svn_io_make_dir_recursively(path, pool)); + } + else /* No problem, the dir already existed, so just leave. */ + SVN_ERR_ASSERT(kind == svn_node_dir); + + return SVN_NO_ERROR; +} + +/* Return the library version number. */ +const svn_version_t * +svn_wc_version(void) +{ + SVN_VERSION_BODY; +} + +svn_wc_notify_t * +svn_wc_create_notify(const char *path, + svn_wc_notify_action_t action, + apr_pool_t *pool) +{ + svn_wc_notify_t *ret = apr_pcalloc(pool, sizeof(*ret)); + ret->path = path; + ret->action = action; + ret->kind = svn_node_unknown; + ret->content_state = ret->prop_state = svn_wc_notify_state_unknown; + ret->lock_state = svn_wc_notify_lock_state_unknown; + ret->revision = SVN_INVALID_REVNUM; + ret->old_revision = SVN_INVALID_REVNUM; + + return ret; +} + +svn_wc_notify_t * +svn_wc_create_notify_url(const char *url, + svn_wc_notify_action_t action, + apr_pool_t *pool) +{ + svn_wc_notify_t *ret = svn_wc_create_notify(".", action, pool); + ret->url = url; + + return ret; +} + +/* Pool cleanup function to clear an svn_error_t *. */ +static apr_status_t err_cleanup(void *data) +{ + svn_error_clear(data); + + return APR_SUCCESS; +} + +svn_wc_notify_t * +svn_wc_dup_notify(const svn_wc_notify_t *notify, + apr_pool_t *pool) +{ + svn_wc_notify_t *ret = apr_palloc(pool, sizeof(*ret)); + + *ret = *notify; + + if (ret->path) + ret->path = apr_pstrdup(pool, ret->path); + if (ret->mime_type) + ret->mime_type = apr_pstrdup(pool, ret->mime_type); + if (ret->lock) + ret->lock = svn_lock_dup(ret->lock, pool); + if (ret->err) + { + ret->err = svn_error_dup(ret->err); + apr_pool_cleanup_register(pool, ret->err, err_cleanup, + apr_pool_cleanup_null); + } + if (ret->changelist_name) + ret->changelist_name = apr_pstrdup(pool, ret->changelist_name); + if (ret->merge_range) + ret->merge_range = svn_merge_range_dup(ret->merge_range, pool); + if (ret->url) + ret->url = apr_pstrdup(pool, ret->url); + if (ret->path_prefix) + ret->path_prefix = apr_pstrdup(pool, ret->path_prefix); + if (ret->prop_name) + ret->prop_name = apr_pstrdup(pool, ret->prop_name); + if (ret->rev_props) + ret->rev_props = svn_prop_hash_dup(ret->rev_props, pool); + + return ret; +} + +svn_error_t * +svn_wc_external_item2_create(svn_wc_external_item2_t **item, + apr_pool_t *pool) +{ + *item = apr_pcalloc(pool, sizeof(svn_wc_external_item2_t)); + return SVN_NO_ERROR; +} + + +svn_wc_external_item2_t * +svn_wc_external_item2_dup(const svn_wc_external_item2_t *item, + apr_pool_t *pool) +{ + svn_wc_external_item2_t *new_item = apr_palloc(pool, sizeof(*new_item)); + + *new_item = *item; + + if (new_item->target_dir) + new_item->target_dir = apr_pstrdup(pool, new_item->target_dir); + + if (new_item->url) + new_item->url = apr_pstrdup(pool, new_item->url); + + return new_item; +} + + +svn_boolean_t +svn_wc_match_ignore_list(const char *str, const apr_array_header_t *list, + apr_pool_t *pool) +{ + /* For now, we simply forward to svn_cstring_match_glob_list. In the + future, if we support more complex ignore patterns, we would iterate + over 'list' ourselves, and decide for each pattern how to handle + it. */ + + return svn_cstring_match_glob_list(str, list); +} + +svn_wc_conflict_description2_t * +svn_wc_conflict_description_create_text2(const char *local_abspath, + apr_pool_t *result_pool) +{ + svn_wc_conflict_description2_t *conflict; + + SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_abspath)); + + conflict = apr_pcalloc(result_pool, sizeof(*conflict)); + conflict->local_abspath = apr_pstrdup(result_pool, local_abspath); + conflict->node_kind = svn_node_file; + conflict->kind = svn_wc_conflict_kind_text; + conflict->action = svn_wc_conflict_action_edit; + conflict->reason = svn_wc_conflict_reason_edited; + return conflict; +} + +svn_wc_conflict_description2_t * +svn_wc_conflict_description_create_prop2(const char *local_abspath, + svn_node_kind_t node_kind, + const char *property_name, + apr_pool_t *result_pool) +{ + svn_wc_conflict_description2_t *conflict; + + SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_abspath)); + + conflict = apr_pcalloc(result_pool, sizeof(*conflict)); + conflict->local_abspath = apr_pstrdup(result_pool, local_abspath); + conflict->node_kind = node_kind; + conflict->kind = svn_wc_conflict_kind_property; + conflict->property_name = apr_pstrdup(result_pool, property_name); + return conflict; +} + +svn_wc_conflict_description2_t * +svn_wc_conflict_description_create_tree2( + const char *local_abspath, + svn_node_kind_t node_kind, + svn_wc_operation_t operation, + const svn_wc_conflict_version_t *src_left_version, + const svn_wc_conflict_version_t *src_right_version, + apr_pool_t *result_pool) +{ + svn_wc_conflict_description2_t *conflict; + + SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_abspath)); + + conflict = apr_pcalloc(result_pool, sizeof(*conflict)); + conflict->local_abspath = apr_pstrdup(result_pool, local_abspath); + conflict->node_kind = node_kind; + conflict->kind = svn_wc_conflict_kind_tree; + conflict->operation = operation; + conflict->src_left_version = svn_wc_conflict_version_dup(src_left_version, + result_pool); + conflict->src_right_version = svn_wc_conflict_version_dup(src_right_version, + result_pool); + return conflict; +} + + +svn_wc_conflict_description2_t * +svn_wc__conflict_description2_dup(const svn_wc_conflict_description2_t *conflict, + apr_pool_t *pool) +{ + svn_wc_conflict_description2_t *new_conflict; + + new_conflict = apr_pcalloc(pool, sizeof(*new_conflict)); + + /* Shallow copy all members. */ + *new_conflict = *conflict; + + if (conflict->local_abspath) + new_conflict->local_abspath = apr_pstrdup(pool, conflict->local_abspath); + if (conflict->property_name) + new_conflict->property_name = apr_pstrdup(pool, conflict->property_name); + if (conflict->mime_type) + new_conflict->mime_type = apr_pstrdup(pool, conflict->mime_type); + if (conflict->base_abspath) + new_conflict->base_abspath = apr_pstrdup(pool, conflict->base_abspath); + if (conflict->their_abspath) + new_conflict->their_abspath = apr_pstrdup(pool, conflict->their_abspath); + if (conflict->my_abspath) + new_conflict->my_abspath = apr_pstrdup(pool, conflict->my_abspath); + if (conflict->merged_file) + new_conflict->merged_file = apr_pstrdup(pool, conflict->merged_file); + if (conflict->src_left_version) + new_conflict->src_left_version = + svn_wc_conflict_version_dup(conflict->src_left_version, pool); + if (conflict->src_right_version) + new_conflict->src_right_version = + svn_wc_conflict_version_dup(conflict->src_right_version, pool); + + return new_conflict; +} + +svn_wc_conflict_version_t * +svn_wc_conflict_version_create2(const char *repos_url, + const char *repos_uuid, + const char *repos_relpath, + svn_revnum_t revision, + svn_node_kind_t kind, + apr_pool_t *result_pool) +{ + svn_wc_conflict_version_t *version; + + version = apr_pcalloc(result_pool, sizeof(*version)); + + SVN_ERR_ASSERT_NO_RETURN(svn_uri_is_canonical(repos_url, result_pool) + && svn_relpath_is_canonical(repos_relpath) + && SVN_IS_VALID_REVNUM(revision) + /* ### repos_uuid can be NULL :( */); + + version->repos_url = repos_url; + version->peg_rev = revision; + version->path_in_repos = repos_relpath; + version->node_kind = kind; + version->repos_uuid = repos_uuid; + + return version; +} + + +svn_wc_conflict_version_t * +svn_wc_conflict_version_dup(const svn_wc_conflict_version_t *version, + apr_pool_t *result_pool) +{ + + svn_wc_conflict_version_t *new_version; + + if (version == NULL) + return NULL; + + new_version = apr_pcalloc(result_pool, sizeof(*new_version)); + + /* Shallow copy all members. */ + *new_version = *version; + + if (version->repos_url) + new_version->repos_url = apr_pstrdup(result_pool, version->repos_url); + + if (version->path_in_repos) + new_version->path_in_repos = apr_pstrdup(result_pool, + version->path_in_repos); + + if (version->repos_uuid) + new_version->repos_uuid = apr_pstrdup(result_pool, version->repos_uuid); + + return new_version; +} + + +svn_wc_conflict_description_t * +svn_wc__cd2_to_cd(const svn_wc_conflict_description2_t *conflict, + apr_pool_t *result_pool) +{ + svn_wc_conflict_description_t *new_conflict; + + if (conflict == NULL) + return NULL; + + new_conflict = apr_pcalloc(result_pool, sizeof(*new_conflict)); + + new_conflict->path = apr_pstrdup(result_pool, conflict->local_abspath); + new_conflict->node_kind = conflict->node_kind; + new_conflict->kind = conflict->kind; + new_conflict->action = conflict->action; + new_conflict->reason = conflict->reason; + if (conflict->src_left_version) + new_conflict->src_left_version = + svn_wc_conflict_version_dup(conflict->src_left_version, result_pool); + if (conflict->src_right_version) + new_conflict->src_right_version = + svn_wc_conflict_version_dup(conflict->src_right_version, result_pool); + + switch (conflict->kind) + { + + case svn_wc_conflict_kind_property: + new_conflict->property_name = apr_pstrdup(result_pool, + conflict->property_name); + /* Falling through. */ + + case svn_wc_conflict_kind_text: + new_conflict->is_binary = conflict->is_binary; + if (conflict->mime_type) + new_conflict->mime_type = apr_pstrdup(result_pool, + conflict->mime_type); + if (conflict->base_abspath) + new_conflict->base_file = apr_pstrdup(result_pool, + conflict->base_abspath); + if (conflict->their_abspath) + new_conflict->their_file = apr_pstrdup(result_pool, + conflict->their_abspath); + if (conflict->my_abspath) + new_conflict->my_file = apr_pstrdup(result_pool, + conflict->my_abspath); + if (conflict->merged_file) + new_conflict->merged_file = apr_pstrdup(result_pool, + conflict->merged_file); + break; + + case svn_wc_conflict_kind_tree: + new_conflict->operation = conflict->operation; + break; + } + + /* A NULL access baton is allowable by the API. */ + new_conflict->access = NULL; + + return new_conflict; +} + + +svn_error_t * +svn_wc__status2_from_3(svn_wc_status2_t **status, + const svn_wc_status3_t *old_status, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const svn_wc_entry_t *entry = NULL; + + if (old_status == NULL) + { + *status = NULL; + return SVN_NO_ERROR; + } + + *status = apr_pcalloc(result_pool, sizeof(**status)); + + if (old_status->versioned) + { + svn_error_t *err; + err= svn_wc__get_entry(&entry, wc_ctx->db, local_abspath, FALSE, + svn_node_unknown, result_pool, scratch_pool); + + if (err && err->apr_err == SVN_ERR_NODE_UNEXPECTED_KIND) + svn_error_clear(err); + else + SVN_ERR(err); + } + + (*status)->entry = entry; + (*status)->copied = old_status->copied; + (*status)->repos_lock = svn_lock_dup(old_status->repos_lock, result_pool); + + if (old_status->repos_relpath) + (*status)->url = svn_path_url_add_component2(old_status->repos_root_url, + old_status->repos_relpath, + result_pool); + (*status)->ood_last_cmt_rev = old_status->ood_changed_rev; + (*status)->ood_last_cmt_date = old_status->ood_changed_date; + (*status)->ood_kind = old_status->ood_kind; + (*status)->ood_last_cmt_author = old_status->ood_changed_author; + + if (old_status->conflicted) + { + const svn_wc_conflict_description2_t *tree_conflict; + SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + (*status)->tree_conflict = svn_wc__cd2_to_cd(tree_conflict, result_pool); + } + + (*status)->switched = old_status->switched; + + (*status)->text_status = old_status->node_status; + (*status)->prop_status = old_status->prop_status; + + (*status)->repos_text_status = old_status->repos_node_status; + (*status)->repos_prop_status = old_status->repos_prop_status; + + /* Some values might be inherited from properties */ + if (old_status->node_status == svn_wc_status_modified + || old_status->node_status == svn_wc_status_conflicted) + (*status)->text_status = old_status->text_status; + + /* (Currently a no-op, but just make sure it is ok) */ + if (old_status->repos_node_status == svn_wc_status_modified + || old_status->repos_node_status == svn_wc_status_conflicted) + (*status)->repos_text_status = old_status->repos_text_status; + + if (old_status->node_status == svn_wc_status_added) + (*status)->prop_status = svn_wc_status_none; /* No separate info */ + + /* Find pristine_text_status value */ + switch (old_status->text_status) + { + case svn_wc_status_none: + case svn_wc_status_normal: + case svn_wc_status_modified: + (*status)->pristine_text_status = old_status->text_status; + break; + case svn_wc_status_conflicted: + default: + /* ### Fetch compare data, or fall back to the documented + not retrieved behavior? */ + (*status)->pristine_text_status = svn_wc_status_none; + break; + } + + /* Find pristine_prop_status value */ + switch (old_status->prop_status) + { + case svn_wc_status_none: + case svn_wc_status_normal: + case svn_wc_status_modified: + if (old_status->node_status != svn_wc_status_added + && old_status->node_status != svn_wc_status_deleted + && old_status->node_status != svn_wc_status_replaced) + { + (*status)->pristine_prop_status = old_status->prop_status; + } + else + (*status)->pristine_prop_status = svn_wc_status_none; + break; + case svn_wc_status_conflicted: + default: + /* ### Fetch compare data, or fall back to the documented + not retrieved behavior? */ + (*status)->pristine_prop_status = svn_wc_status_none; + break; + } + + if (old_status->versioned + && old_status->conflicted + && old_status->node_status != svn_wc_status_obstructed + && (old_status->kind == svn_node_file + || old_status->node_status != svn_wc_status_missing)) + { + svn_boolean_t text_conflict_p, prop_conflict_p; + + /* The entry says there was a conflict, but the user might have + marked it as resolved by deleting the artifact files, so check + for that. */ + SVN_ERR(svn_wc__internal_conflicted_p(&text_conflict_p, + &prop_conflict_p, + NULL, + wc_ctx->db, local_abspath, + scratch_pool)); + + if (text_conflict_p) + (*status)->text_status = svn_wc_status_conflicted; + + if (prop_conflict_p) + (*status)->prop_status = svn_wc_status_conflicted; + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__fetch_kind_func(svn_node_kind_t *kind, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *scratch_pool) +{ + struct svn_wc__shim_fetch_baton_t *sfb = baton; + const char *local_abspath = svn_dirent_join(sfb->base_abspath, path, + scratch_pool); + + SVN_ERR(svn_wc__db_read_kind(kind, sfb->db, local_abspath, + FALSE /* allow_missing */, + TRUE /* show_deleted */, + FALSE /* show_hidden */, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__fetch_props_func(apr_hash_t **props, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct svn_wc__shim_fetch_baton_t *sfb = baton; + const char *local_abspath = svn_dirent_join(sfb->base_abspath, path, + scratch_pool); + svn_error_t *err; + + if (sfb->fetch_base) + err = svn_wc__db_base_get_props(props, sfb->db, local_abspath, result_pool, + scratch_pool); + else + err = svn_wc__db_read_props(props, sfb->db, local_abspath, + result_pool, scratch_pool); + + /* If the path doesn't exist, just return an empty set of props. */ + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + *props = apr_hash_make(result_pool); + } + else if (err) + return svn_error_trace(err); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__fetch_base_func(const char **filename, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct svn_wc__shim_fetch_baton_t *sfb = baton; + const svn_checksum_t *checksum; + svn_error_t *err; + const char *local_abspath = svn_dirent_join(sfb->base_abspath, path, + scratch_pool); + + err = svn_wc__db_base_get_info(NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &checksum, + NULL, NULL, NULL, NULL, NULL, + sfb->db, local_abspath, + scratch_pool, scratch_pool); + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + *filename = NULL; + return SVN_NO_ERROR; + } + else if (err) + return svn_error_trace(err); + + if (checksum == NULL) + { + *filename = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_wc__db_pristine_get_path(filename, sfb->db, local_abspath, + checksum, scratch_pool, scratch_pool)); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/wc-checks.h b/subversion/libsvn_wc/wc-checks.h new file mode 100644 index 000000000000..470460e4f188 --- /dev/null +++ b/subversion/libsvn_wc/wc-checks.h @@ -0,0 +1,55 @@ +/* This file is automatically generated from wc-checks.sql and .dist_sandbox/subversion-1.8.0-rc3/subversion/libsvn_wc/token-map.h. + * Do not edit this file -- edit the source and rerun gen-make.py */ + +#define STMT_VERIFICATION_TRIGGERS 0 +#define STMT_0_INFO {"STMT_VERIFICATION_TRIGGERS", NULL} +#define STMT_0 \ + "CREATE TEMPORARY TRIGGER no_repository_updates BEFORE UPDATE ON repository " \ + "BEGIN " \ + " SELECT RAISE(FAIL, 'Updates to REPOSITORY are not allowed.'); " \ + "END; " \ + "CREATE TEMPORARY TRIGGER validation_01 BEFORE INSERT ON nodes " \ + "WHEN NOT ((new.local_relpath = '' AND new.parent_relpath IS NULL) " \ + " OR (relpath_depth(new.local_relpath) " \ + " = relpath_depth(new.parent_relpath) + 1)) " \ + "BEGIN " \ + " SELECT RAISE(FAIL, 'WC DB validity check 01 failed'); " \ + "END; " \ + "CREATE TEMPORARY TRIGGER validation_02 BEFORE INSERT ON nodes " \ + "WHEN NOT new.op_depth <= relpath_depth(new.local_relpath) " \ + "BEGIN " \ + " SELECT RAISE(FAIL, 'WC DB validity check 02 failed'); " \ + "END; " \ + "CREATE TEMPORARY TRIGGER validation_03 BEFORE INSERT ON nodes " \ + "WHEN NOT ( " \ + " (new.op_depth = relpath_depth(new.local_relpath)) " \ + " OR " \ + " (EXISTS (SELECT 1 FROM nodes " \ + " WHERE wc_id = new.wc_id AND op_depth = new.op_depth " \ + " AND local_relpath = new.parent_relpath)) " \ + " ) " \ + " AND NOT (new.file_external IS NOT NULL AND new.op_depth = 0) " \ + "BEGIN " \ + " SELECT RAISE(FAIL, 'WC DB validity check 03 failed'); " \ + "END; " \ + "CREATE TEMPORARY TRIGGER validation_04 BEFORE INSERT ON actual_node " \ + "WHEN NOT (new.local_relpath = '' " \ + " OR EXISTS (SELECT 1 FROM nodes " \ + " WHERE wc_id = new.wc_id " \ + " AND local_relpath = new.parent_relpath)) " \ + "BEGIN " \ + " SELECT RAISE(FAIL, 'WC DB validity check 04 failed'); " \ + "END; " \ + "" + +#define WC_CHECKS_SQL_DECLARE_STATEMENTS(varname) \ + static const char * const varname[] = { \ + STMT_0, \ + NULL \ + } + +#define WC_CHECKS_SQL_DECLARE_STATEMENT_INFO(varname) \ + static const char * const varname[][2] = { \ + STMT_0_INFO, \ + {NULL, NULL} \ + } diff --git a/subversion/libsvn_wc/wc-checks.sql b/subversion/libsvn_wc/wc-checks.sql new file mode 100644 index 000000000000..a677270f3600 --- /dev/null +++ b/subversion/libsvn_wc/wc-checks.sql @@ -0,0 +1,77 @@ +/* wc-checks.sql -- trigger-based checks for the wc-metadata database. + * This is intended for use with SQLite 3 + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +-- STMT_VERIFICATION_TRIGGERS + +/* ------------------------------------------------------------------------- */ + +CREATE TEMPORARY TRIGGER no_repository_updates BEFORE UPDATE ON repository +BEGIN + SELECT RAISE(FAIL, 'Updates to REPOSITORY are not allowed.'); +END; + +/* ------------------------------------------------------------------------- */ + +/* Verify: on every NODES row: parent_relpath is parent of local_relpath */ +CREATE TEMPORARY TRIGGER validation_01 BEFORE INSERT ON nodes +WHEN NOT ((new.local_relpath = '' AND new.parent_relpath IS NULL) + OR (relpath_depth(new.local_relpath) + = relpath_depth(new.parent_relpath) + 1)) +BEGIN + SELECT RAISE(FAIL, 'WC DB validity check 01 failed'); +END; + +/* Verify: on every NODES row: its op-depth <= its own depth */ +CREATE TEMPORARY TRIGGER validation_02 BEFORE INSERT ON nodes +WHEN NOT new.op_depth <= relpath_depth(new.local_relpath) +BEGIN + SELECT RAISE(FAIL, 'WC DB validity check 02 failed'); +END; + +/* Verify: on every NODES row: it is an op-root or it has a parent with the + sames op-depth. (Except when the node is a file external) */ +CREATE TEMPORARY TRIGGER validation_03 BEFORE INSERT ON nodes +WHEN NOT ( + (new.op_depth = relpath_depth(new.local_relpath)) + OR + (EXISTS (SELECT 1 FROM nodes + WHERE wc_id = new.wc_id AND op_depth = new.op_depth + AND local_relpath = new.parent_relpath)) + ) + AND NOT (new.file_external IS NOT NULL AND new.op_depth = 0) +BEGIN + SELECT RAISE(FAIL, 'WC DB validity check 03 failed'); +END; + +/* Verify: on every ACTUAL row (except root): a NODES row exists at its + * parent path. */ +CREATE TEMPORARY TRIGGER validation_04 BEFORE INSERT ON actual_node +WHEN NOT (new.local_relpath = '' + OR EXISTS (SELECT 1 FROM nodes + WHERE wc_id = new.wc_id + AND local_relpath = new.parent_relpath)) +BEGIN + SELECT RAISE(FAIL, 'WC DB validity check 04 failed'); +END; + diff --git a/subversion/libsvn_wc/wc-metadata.h b/subversion/libsvn_wc/wc-metadata.h new file mode 100644 index 000000000000..a0d69653e9c5 --- /dev/null +++ b/subversion/libsvn_wc/wc-metadata.h @@ -0,0 +1,516 @@ +/* This file is automatically generated from wc-metadata.sql and .dist_sandbox/subversion-1.8.0-rc3/subversion/libsvn_wc/token-map.h. + * Do not edit this file -- edit the source and rerun gen-make.py */ + +#define STMT_CREATE_SCHEMA 0 +#define STMT_0_INFO {"STMT_CREATE_SCHEMA", NULL} +#define STMT_0 \ + "CREATE TABLE REPOSITORY ( " \ + " id INTEGER PRIMARY KEY AUTOINCREMENT, " \ + " root TEXT UNIQUE NOT NULL, " \ + " uuid TEXT NOT NULL " \ + " ); " \ + "CREATE INDEX I_UUID ON REPOSITORY (uuid); " \ + "CREATE INDEX I_ROOT ON REPOSITORY (root); " \ + "CREATE TABLE WCROOT ( " \ + " id INTEGER PRIMARY KEY AUTOINCREMENT, " \ + " local_abspath TEXT UNIQUE " \ + " ); " \ + "CREATE UNIQUE INDEX I_LOCAL_ABSPATH ON WCROOT (local_abspath); " \ + "CREATE TABLE PRISTINE ( " \ + " checksum TEXT NOT NULL PRIMARY KEY, " \ + " compression INTEGER, " \ + " size INTEGER NOT NULL, " \ + " refcount INTEGER NOT NULL, " \ + " md5_checksum TEXT NOT NULL " \ + " ); " \ + "CREATE INDEX I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \ + "CREATE TABLE ACTUAL_NODE ( " \ + " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ + " local_relpath TEXT NOT NULL, " \ + " parent_relpath TEXT, " \ + " properties BLOB, " \ + " conflict_old TEXT, " \ + " conflict_new TEXT, " \ + " conflict_working TEXT, " \ + " prop_reject TEXT, " \ + " changelist TEXT, " \ + " text_mod TEXT, " \ + " tree_conflict_data TEXT, " \ + " conflict_data BLOB, " \ + " older_checksum TEXT REFERENCES PRISTINE (checksum), " \ + " left_checksum TEXT REFERENCES PRISTINE (checksum), " \ + " right_checksum TEXT REFERENCES PRISTINE (checksum), " \ + " PRIMARY KEY (wc_id, local_relpath) " \ + " ); " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ + "CREATE TABLE LOCK ( " \ + " repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), " \ + " repos_relpath TEXT NOT NULL, " \ + " lock_token TEXT NOT NULL, " \ + " lock_owner TEXT, " \ + " lock_comment TEXT, " \ + " lock_date INTEGER, " \ + " PRIMARY KEY (repos_id, repos_relpath) " \ + " ); " \ + "CREATE TABLE WORK_QUEUE ( " \ + " id INTEGER PRIMARY KEY AUTOINCREMENT, " \ + " work BLOB NOT NULL " \ + " ); " \ + "CREATE TABLE WC_LOCK ( " \ + " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ + " local_dir_relpath TEXT NOT NULL, " \ + " locked_levels INTEGER NOT NULL DEFAULT -1, " \ + " PRIMARY KEY (wc_id, local_dir_relpath) " \ + " ); " \ + "PRAGMA user_version = " \ + APR_STRINGIFY(SVN_WC__VERSION) \ + "; " \ + "" + +#define STMT_CREATE_NODES 1 +#define STMT_1_INFO {"STMT_CREATE_NODES", NULL} +#define STMT_1 \ + "CREATE TABLE NODES ( " \ + " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ + " local_relpath TEXT NOT NULL, " \ + " op_depth INTEGER NOT NULL, " \ + " parent_relpath TEXT, " \ + " repos_id INTEGER REFERENCES REPOSITORY (id), " \ + " repos_path TEXT, " \ + " revision INTEGER, " \ + " presence TEXT NOT NULL, " \ + " moved_here INTEGER, " \ + " moved_to TEXT, " \ + " kind TEXT NOT NULL, " \ + " properties BLOB, " \ + " depth TEXT, " \ + " checksum TEXT REFERENCES PRISTINE (checksum), " \ + " symlink_target TEXT, " \ + " changed_revision INTEGER, " \ + " changed_date INTEGER, " \ + " changed_author TEXT, " \ + " translated_size INTEGER, " \ + " last_mod_time INTEGER, " \ + " dav_cache BLOB, " \ + " file_external INTEGER, " \ + " inherited_props BLOB, " \ + " PRIMARY KEY (wc_id, local_relpath, op_depth) " \ + " ); " \ + "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \ + " local_relpath, op_depth); " \ + "CREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth); " \ + "CREATE VIEW NODES_CURRENT AS " \ + " SELECT * FROM nodes AS n " \ + " WHERE op_depth = (SELECT MAX(op_depth) FROM nodes AS n2 " \ + " WHERE n2.wc_id = n.wc_id " \ + " AND n2.local_relpath = n.local_relpath); " \ + "CREATE VIEW NODES_BASE AS " \ + " SELECT * FROM nodes " \ + " WHERE op_depth = 0; " \ + "" + +#define STMT_CREATE_NODES_TRIGGERS 2 +#define STMT_2_INFO {"STMT_CREATE_NODES_TRIGGERS", NULL} +#define STMT_2 \ + "CREATE TRIGGER nodes_insert_trigger " \ + "AFTER INSERT ON nodes " \ + "WHEN NEW.checksum IS NOT NULL " \ + "BEGIN " \ + " UPDATE pristine SET refcount = refcount + 1 " \ + " WHERE checksum = NEW.checksum; " \ + "END; " \ + "CREATE TRIGGER nodes_delete_trigger " \ + "AFTER DELETE ON nodes " \ + "WHEN OLD.checksum IS NOT NULL " \ + "BEGIN " \ + " UPDATE pristine SET refcount = refcount - 1 " \ + " WHERE checksum = OLD.checksum; " \ + "END; " \ + "CREATE TRIGGER nodes_update_checksum_trigger " \ + "AFTER UPDATE OF checksum ON nodes " \ + "WHEN NEW.checksum IS NOT OLD.checksum " \ + "BEGIN " \ + " UPDATE pristine SET refcount = refcount + 1 " \ + " WHERE checksum = NEW.checksum; " \ + " UPDATE pristine SET refcount = refcount - 1 " \ + " WHERE checksum = OLD.checksum; " \ + "END; " \ + "" + +#define STMT_CREATE_EXTERNALS 3 +#define STMT_3_INFO {"STMT_CREATE_EXTERNALS", NULL} +#define STMT_3 \ + "CREATE TABLE EXTERNALS ( " \ + " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ + " local_relpath TEXT NOT NULL, " \ + " parent_relpath TEXT NOT NULL, " \ + " repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), " \ + " presence TEXT NOT NULL, " \ + " kind TEXT NOT NULL, " \ + " def_local_relpath TEXT NOT NULL, " \ + " def_repos_relpath TEXT NOT NULL, " \ + " def_operational_revision TEXT, " \ + " def_revision TEXT, " \ + " PRIMARY KEY (wc_id, local_relpath) " \ + "); " \ + "CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS (wc_id, " \ + " def_local_relpath, " \ + " local_relpath); " \ + "" + +#define STMT_UPGRADE_TO_20 4 +#define STMT_4_INFO {"STMT_UPGRADE_TO_20", NULL} +#define STMT_4 \ + "UPDATE BASE_NODE SET checksum = (SELECT checksum FROM pristine " \ + " WHERE md5_checksum = BASE_NODE.checksum) " \ + "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = BASE_NODE.checksum); " \ + "UPDATE WORKING_NODE SET checksum = (SELECT checksum FROM pristine " \ + " WHERE md5_checksum = WORKING_NODE.checksum) " \ + "WHERE EXISTS (SELECT 1 FROM pristine " \ + " WHERE md5_checksum = WORKING_NODE.checksum); " \ + "INSERT INTO NODES ( " \ + " wc_id, local_relpath, op_depth, parent_relpath, " \ + " repos_id, repos_path, revision, " \ + " presence, depth, moved_here, moved_to, kind, " \ + " changed_revision, changed_date, changed_author, " \ + " checksum, properties, translated_size, last_mod_time, " \ + " dav_cache, symlink_target, file_external ) " \ + "SELECT wc_id, local_relpath, 0 , parent_relpath, " \ + " repos_id, repos_relpath, revnum, " \ + " presence, depth, NULL , NULL , kind, " \ + " changed_rev, changed_date, changed_author, " \ + " checksum, properties, translated_size, last_mod_time, " \ + " dav_cache, symlink_target, file_external " \ + "FROM BASE_NODE; " \ + "INSERT INTO NODES ( " \ + " wc_id, local_relpath, op_depth, parent_relpath, " \ + " repos_id, repos_path, revision, " \ + " presence, depth, moved_here, moved_to, kind, " \ + " changed_revision, changed_date, changed_author, " \ + " checksum, properties, translated_size, last_mod_time, " \ + " dav_cache, symlink_target, file_external ) " \ + "SELECT wc_id, local_relpath, 2 , parent_relpath, " \ + " copyfrom_repos_id, copyfrom_repos_path, copyfrom_revnum, " \ + " presence, depth, NULL , NULL , kind, " \ + " changed_rev, changed_date, changed_author, " \ + " checksum, properties, translated_size, last_mod_time, " \ + " NULL , symlink_target, NULL " \ + "FROM WORKING_NODE; " \ + "DROP TABLE BASE_NODE; " \ + "DROP TABLE WORKING_NODE; " \ + "PRAGMA user_version = 20; " \ + "" + +#define STMT_UPGRADE_TO_21 5 +#define STMT_5_INFO {"STMT_UPGRADE_TO_21", NULL} +#define STMT_5 \ + "PRAGMA user_version = 21; " \ + "" + +#define STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT 6 +#define STMT_6_INFO {"STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT", NULL} +#define STMT_6 \ + "SELECT wc_id, local_relpath, tree_conflict_data " \ + "FROM actual_node " \ + "WHERE tree_conflict_data IS NOT NULL " \ + "" + +#define STMT_UPGRADE_21_ERASE_OLD_CONFLICTS 7 +#define STMT_7_INFO {"STMT_UPGRADE_21_ERASE_OLD_CONFLICTS", NULL} +#define STMT_7 \ + "UPDATE actual_node SET tree_conflict_data = NULL " \ + "" + +#define STMT_UPGRADE_TO_22 8 +#define STMT_8_INFO {"STMT_UPGRADE_TO_22", NULL} +#define STMT_8 \ + "UPDATE actual_node SET tree_conflict_data = conflict_data; " \ + "UPDATE actual_node SET conflict_data = NULL; " \ + "PRAGMA user_version = 22; " \ + "" + +#define STMT_UPGRADE_TO_23 9 +#define STMT_9_INFO {"STMT_UPGRADE_TO_23", NULL} +#define STMT_9 \ + "PRAGMA user_version = 23; " \ + "" + +#define STMT_UPGRADE_23_HAS_WORKING_NODES 10 +#define STMT_10_INFO {"STMT_UPGRADE_23_HAS_WORKING_NODES", NULL} +#define STMT_10 \ + "SELECT 1 FROM nodes WHERE op_depth > 0 " \ + "LIMIT 1 " \ + "" + +#define STMT_UPGRADE_TO_24 11 +#define STMT_11_INFO {"STMT_UPGRADE_TO_24", NULL} +#define STMT_11 \ + "UPDATE pristine SET refcount = " \ + " (SELECT COUNT(*) FROM nodes " \ + " WHERE checksum = pristine.checksum ); " \ + "PRAGMA user_version = 24; " \ + "" + +#define STMT_UPGRADE_TO_25 12 +#define STMT_12_INFO {"STMT_UPGRADE_TO_25", NULL} +#define STMT_12 \ + "DROP VIEW IF EXISTS NODES_CURRENT; " \ + "CREATE VIEW NODES_CURRENT AS " \ + " SELECT * FROM nodes " \ + " JOIN (SELECT wc_id, local_relpath, MAX(op_depth) AS op_depth FROM nodes " \ + " GROUP BY wc_id, local_relpath) AS filter " \ + " ON nodes.wc_id = filter.wc_id " \ + " AND nodes.local_relpath = filter.local_relpath " \ + " AND nodes.op_depth = filter.op_depth; " \ + "PRAGMA user_version = 25; " \ + "" + +#define STMT_UPGRADE_TO_26 13 +#define STMT_13_INFO {"STMT_UPGRADE_TO_26", NULL} +#define STMT_13 \ + "DROP VIEW IF EXISTS NODES_BASE; " \ + "CREATE VIEW NODES_BASE AS " \ + " SELECT * FROM nodes " \ + " WHERE op_depth = 0; " \ + "PRAGMA user_version = 26; " \ + "" + +#define STMT_UPGRADE_TO_27 14 +#define STMT_14_INFO {"STMT_UPGRADE_TO_27", NULL} +#define STMT_14 \ + "PRAGMA user_version = 27; " \ + "" + +#define STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS 15 +#define STMT_15_INFO {"STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS", NULL} +#define STMT_15 \ + "SELECT 1 FROM actual_node " \ + "WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL) " \ + " AND (conflict_new IS NULL) AND (conflict_working IS NULL) " \ + " AND (tree_conflict_data IS NULL)) " \ + "LIMIT 1 " \ + "" + +#define STMT_UPGRADE_TO_28 16 +#define STMT_16_INFO {"STMT_UPGRADE_TO_28", NULL} +#define STMT_16 \ + "UPDATE NODES SET checksum = (SELECT checksum FROM pristine " \ + " WHERE md5_checksum = nodes.checksum) " \ + "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = nodes.checksum); " \ + "PRAGMA user_version = 28; " \ + "" + +#define STMT_UPGRADE_TO_29 17 +#define STMT_17_INFO {"STMT_UPGRADE_TO_29", NULL} +#define STMT_17 \ + "DROP TRIGGER IF EXISTS nodes_update_checksum_trigger; " \ + "DROP TRIGGER IF EXISTS nodes_insert_trigger; " \ + "DROP TRIGGER IF EXISTS nodes_delete_trigger; " \ + "CREATE TRIGGER nodes_update_checksum_trigger " \ + "AFTER UPDATE OF checksum ON nodes " \ + "WHEN NEW.checksum IS NOT OLD.checksum " \ + "BEGIN " \ + " UPDATE pristine SET refcount = refcount + 1 " \ + " WHERE checksum = NEW.checksum; " \ + " UPDATE pristine SET refcount = refcount - 1 " \ + " WHERE checksum = OLD.checksum; " \ + "END; " \ + "CREATE TRIGGER nodes_insert_trigger " \ + "AFTER INSERT ON nodes " \ + "WHEN NEW.checksum IS NOT NULL " \ + "BEGIN " \ + " UPDATE pristine SET refcount = refcount + 1 " \ + " WHERE checksum = NEW.checksum; " \ + "END; " \ + "CREATE TRIGGER nodes_delete_trigger " \ + "AFTER DELETE ON nodes " \ + "WHEN OLD.checksum IS NOT NULL " \ + "BEGIN " \ + " UPDATE pristine SET refcount = refcount - 1 " \ + " WHERE checksum = OLD.checksum; " \ + "END; " \ + "PRAGMA user_version = 29; " \ + "" + +#define STMT_UPGRADE_TO_30 18 +#define STMT_18_INFO {"STMT_UPGRADE_TO_30", NULL} +#define STMT_18 \ + "CREATE UNIQUE INDEX IF NOT EXISTS I_NODES_MOVED " \ + "ON NODES (wc_id, moved_to, op_depth); " \ + "CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \ + "UPDATE nodes SET presence = \"server-excluded\" WHERE presence = \"absent\"; " \ + "UPDATE nodes SET file_external=1 WHERE file_external IS NOT NULL; " \ + "" + +#define STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE 19 +#define STMT_19_INFO {"STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE", NULL} +#define STMT_19 \ + "SELECT wc_id, local_relpath, " \ + " conflict_old, conflict_working, conflict_new, prop_reject, tree_conflict_data " \ + "FROM actual_node " \ + "WHERE conflict_old IS NOT NULL " \ + " OR conflict_working IS NOT NULL " \ + " OR conflict_new IS NOT NULL " \ + " OR prop_reject IS NOT NULL " \ + " OR tree_conflict_data IS NOT NULL " \ + "ORDER by wc_id, local_relpath " \ + "" + +#define STMT_UPGRADE_30_SET_CONFLICT 20 +#define STMT_20_INFO {"STMT_UPGRADE_30_SET_CONFLICT", NULL} +#define STMT_20 \ + "UPDATE actual_node SET conflict_data = ?3, conflict_old = NULL, " \ + " conflict_working = NULL, conflict_new = NULL, prop_reject = NULL, " \ + " tree_conflict_data = NULL " \ + "WHERE wc_id = ?1 and local_relpath = ?2 " \ + "" + +#define STMT_UPGRADE_TO_31_ALTER_TABLE 21 +#define STMT_21_INFO {"STMT_UPGRADE_TO_31_ALTER_TABLE", NULL} +#define STMT_21 \ + "ALTER TABLE NODES ADD COLUMN inherited_props BLOB; " \ + "" + +#define STMT_UPGRADE_TO_31_FINALIZE 22 +#define STMT_22_INFO {"STMT_UPGRADE_TO_31_FINALIZE", NULL} +#define STMT_22 \ + "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \ + "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \ + "DROP INDEX I_NODES_PARENT; " \ + "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \ + " local_relpath, op_depth); " \ + "DROP INDEX I_ACTUAL_PARENT; " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ + "PRAGMA user_version = 31; " \ + "" + +#define STMT_UPGRADE_31_SELECT_WCROOT_NODES 23 +#define STMT_23_INFO {"STMT_UPGRADE_31_SELECT_WCROOT_NODES", NULL} +#define STMT_23 \ + "SELECT l.wc_id, l.local_relpath FROM nodes as l " \ + "LEFT OUTER JOIN nodes as r " \ + "ON l.wc_id = r.wc_id " \ + " AND r.local_relpath = l.parent_relpath " \ + " AND r.op_depth = 0 " \ + "WHERE l.op_depth = 0 " \ + " AND l.repos_path != '' " \ + " AND ((l.repos_id IS NOT r.repos_id) " \ + " OR (l.repos_path IS NOT (CASE WHEN (r.local_relpath) = '' THEN (CASE WHEN (r.repos_path) = '' THEN (l.local_relpath) WHEN (l.local_relpath) = '' THEN (r.repos_path) ELSE (r.repos_path) || '/' || (l.local_relpath) END) WHEN (r.repos_path) = '' THEN (CASE WHEN (r.local_relpath) = '' THEN (l.local_relpath) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN '' WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+2) END END) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN (r.repos_path) WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN (r.repos_path) || SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1) END END))) " \ + "" + +#define STMT_UPGRADE_TO_32 24 +#define STMT_24_INFO {"STMT_UPGRADE_TO_32", NULL} +#define STMT_24 \ + "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \ + "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \ + "CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath); " \ + "DROP INDEX I_NODES_PARENT; " \ + "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \ + " local_relpath, op_depth); " \ + "DROP INDEX I_ACTUAL_PARENT; " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ + "-- format: YYY " \ + "" + +#define WC_METADATA_SQL_99 \ + "CREATE TABLE ACTUAL_NODE_BACKUP ( " \ + " wc_id INTEGER NOT NULL, " \ + " local_relpath TEXT NOT NULL, " \ + " parent_relpath TEXT, " \ + " properties BLOB, " \ + " conflict_old TEXT, " \ + " conflict_new TEXT, " \ + " conflict_working TEXT, " \ + " prop_reject TEXT, " \ + " changelist TEXT, " \ + " text_mod TEXT " \ + " ); " \ + "INSERT INTO ACTUAL_NODE_BACKUP SELECT " \ + " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \ + " conflict_new, conflict_working, prop_reject, changelist, text_mod " \ + "FROM ACTUAL_NODE; " \ + "DROP TABLE ACTUAL_NODE; " \ + "CREATE TABLE ACTUAL_NODE ( " \ + " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ + " local_relpath TEXT NOT NULL, " \ + " parent_relpath TEXT, " \ + " properties BLOB, " \ + " conflict_old TEXT, " \ + " conflict_new TEXT, " \ + " conflict_working TEXT, " \ + " prop_reject TEXT, " \ + " changelist TEXT, " \ + " text_mod TEXT, " \ + " PRIMARY KEY (wc_id, local_relpath) " \ + " ); " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ + "INSERT INTO ACTUAL_NODE SELECT " \ + " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \ + " conflict_new, conflict_working, prop_reject, changelist, text_mod " \ + "FROM ACTUAL_NODE_BACKUP; " \ + "DROP TABLE ACTUAL_NODE_BACKUP; " \ + "" + +#define WC_METADATA_SQL_DECLARE_STATEMENTS(varname) \ + static const char * const varname[] = { \ + STMT_0, \ + STMT_1, \ + STMT_2, \ + STMT_3, \ + STMT_4, \ + STMT_5, \ + STMT_6, \ + STMT_7, \ + STMT_8, \ + STMT_9, \ + STMT_10, \ + STMT_11, \ + STMT_12, \ + STMT_13, \ + STMT_14, \ + STMT_15, \ + STMT_16, \ + STMT_17, \ + STMT_18, \ + STMT_19, \ + STMT_20, \ + STMT_21, \ + STMT_22, \ + STMT_23, \ + STMT_24, \ + NULL \ + } + +#define WC_METADATA_SQL_DECLARE_STATEMENT_INFO(varname) \ + static const char * const varname[][2] = { \ + STMT_0_INFO, \ + STMT_1_INFO, \ + STMT_2_INFO, \ + STMT_3_INFO, \ + STMT_4_INFO, \ + STMT_5_INFO, \ + STMT_6_INFO, \ + STMT_7_INFO, \ + STMT_8_INFO, \ + STMT_9_INFO, \ + STMT_10_INFO, \ + STMT_11_INFO, \ + STMT_12_INFO, \ + STMT_13_INFO, \ + STMT_14_INFO, \ + STMT_15_INFO, \ + STMT_16_INFO, \ + STMT_17_INFO, \ + STMT_18_INFO, \ + STMT_19_INFO, \ + STMT_20_INFO, \ + STMT_21_INFO, \ + STMT_22_INFO, \ + STMT_23_INFO, \ + STMT_24_INFO, \ + {NULL, NULL} \ + } diff --git a/subversion/libsvn_wc/wc-metadata.sql b/subversion/libsvn_wc/wc-metadata.sql new file mode 100644 index 000000000000..d2a61613ac62 --- /dev/null +++ b/subversion/libsvn_wc/wc-metadata.sql @@ -0,0 +1,951 @@ +/* wc-metadata.sql -- schema used in the wc-metadata SQLite database + * This is intended for use with SQLite 3 + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* + * the KIND column in these tables has one of the following values + * (documented in the corresponding C type #svn_kind_t): + * "file" + * "dir" + * "symlink" + * "unknown" + * + * the PRESENCE column in these tables has one of the following values + * (see also the C type #svn_wc__db_status_t): + * "normal" + * "server-excluded" -- server has declared it excluded (ie. authz failure) + * "excluded" -- administratively excluded (ie. sparse WC) + * "not-present" -- node not present at this REV + * "incomplete" -- state hasn't been filled in + * "base-deleted" -- node represents a delete of a BASE node + */ + +/* One big list of statements to create our (current) schema. */ +-- STMT_CREATE_SCHEMA + +/* ------------------------------------------------------------------------- */ + +CREATE TABLE REPOSITORY ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + + /* The root URL of the repository. This value is URI-encoded. */ + root TEXT UNIQUE NOT NULL, + + /* the UUID of the repository */ + uuid TEXT NOT NULL + ); + +/* Note: a repository (identified by its UUID) may appear at multiple URLs. + For example, http://example.com/repos/ and https://example.com/repos/. */ +CREATE INDEX I_UUID ON REPOSITORY (uuid); +CREATE INDEX I_ROOT ON REPOSITORY (root); + + +/* ------------------------------------------------------------------------- */ + +CREATE TABLE WCROOT ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + + /* absolute path in the local filesystem. NULL if storing metadata in + the wcroot itself. */ + local_abspath TEXT UNIQUE + ); + +CREATE UNIQUE INDEX I_LOCAL_ABSPATH ON WCROOT (local_abspath); + + +/* ------------------------------------------------------------------------- */ + +/* The PRISTINE table keeps track of pristine texts. Each row describes a + single pristine text. The text itself is stored in a file whose name is + derived from the 'checksum' column. Each pristine text is referenced by + any number of rows in the NODES and ACTUAL_NODE tables. + + In future, the pristine text file may be compressed. + */ +CREATE TABLE PRISTINE ( + /* The SHA-1 checksum of the pristine text. This is a unique key. The + SHA-1 checksum of a pristine text is assumed to be unique among all + pristine texts referenced from this database. */ + checksum TEXT NOT NULL PRIMARY KEY, + + /* Enumerated values specifying type of compression. The only value + supported so far is NULL, meaning that no compression has been applied + and the pristine text is stored verbatim in the file. */ + compression INTEGER, + + /* The size in bytes of the file in which the pristine text is stored. + Used to verify the pristine file is "proper". */ + size INTEGER NOT NULL, + + /* The number of rows in the NODES table that have a 'checksum' column + value that refers to this row. (References in other places, such as + in the ACTUAL_NODE table, are not counted.) */ + refcount INTEGER NOT NULL, + + /* Alternative MD5 checksum used for communicating with older + repositories. Not strictly guaranteed to be unique among table rows. */ + md5_checksum TEXT NOT NULL + ); + +CREATE INDEX I_PRISTINE_MD5 ON PRISTINE (md5_checksum); + +/* ------------------------------------------------------------------------- */ + +/* The ACTUAL_NODE table describes text changes and property changes + on each node in the WC, relative to the NODES table row for the + same path. (A NODES row must exist if this node exists, but an + ACTUAL_NODE row can exist on its own if it is just recording info + on a non-present node - a tree conflict or a changelist, for + example.) + + The ACTUAL_NODE table row for a given path exists if the node at that + path is known to have text or property changes relative to its + NODES row. ("Is known" because a text change on disk may not yet + have been discovered and recorded here.) + + The ACTUAL_NODE table row for a given path may also exist in other cases, + including if the "changelist" or any of the conflict columns have a + non-null value. + */ +CREATE TABLE ACTUAL_NODE ( + /* specifies the location of this node in the local filesystem */ + wc_id INTEGER NOT NULL REFERENCES WCROOT (id), + local_relpath TEXT NOT NULL, + + /* parent's local_relpath for aggregating children of a given parent. + this will be "" if the parent is the wcroot. NULL if this is the + wcroot node. */ + parent_relpath TEXT, + + /* serialized skel of this node's properties. NULL implies no change to + the properties, relative to WORKING/BASE as appropriate. */ + properties BLOB, + + /* relpaths of the conflict files. */ + /* ### These columns will eventually be merged into conflict_data below. */ + conflict_old TEXT, + conflict_new TEXT, + conflict_working TEXT, + prop_reject TEXT, + + /* if not NULL, this node is part of a changelist. */ + changelist TEXT, + + /* ### need to determine values. "unknown" (no info), "admin" (they + ### used something like 'svn edit'), "noticed" (saw a mod while + ### scanning the filesystem). */ + text_mod TEXT, + + /* if a directory, serialized data for all of tree conflicts therein. + ### This column will eventually be merged into the conflict_data column, + ### but within the ACTUAL node of the tree conflict victim itself, rather + ### than the node of the tree conflict victim's parent directory. */ + tree_conflict_data TEXT, + + /* A skel containing the conflict details. */ + conflict_data BLOB, + + /* Three columns containing the checksums of older, left and right conflict + texts. Stored in a column to allow storing them in the pristine store */ + /* stsp: This is meant for text conflicts, right? What about property + conflicts? Why do we need these in a column to refer to the + pristine store? Can't we just parse the checksums from + conflict_data as well? + rhuijben: Because that won't allow triggers to handle refcounts. + We would have to scan all conflict skels before cleaning up the + a single file from the pristine stor */ + older_checksum TEXT REFERENCES PRISTINE (checksum), + left_checksum TEXT REFERENCES PRISTINE (checksum), + right_checksum TEXT REFERENCES PRISTINE (checksum), + + PRIMARY KEY (wc_id, local_relpath) + ); + +CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, + local_relpath); + + +/* ------------------------------------------------------------------------- */ + +/* This table is a cache of information about repository locks. */ +CREATE TABLE LOCK ( + /* what repository location is locked */ + repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), + repos_relpath TEXT NOT NULL, + + /* Information about the lock. Note: these values are just caches from + the server, and are not authoritative. */ + lock_token TEXT NOT NULL, + /* ### make the following fields NOT NULL ? */ + lock_owner TEXT, + lock_comment TEXT, + lock_date INTEGER, /* an APR date/time (usec since 1970) */ + + PRIMARY KEY (repos_id, repos_relpath) + ); + + +/* ------------------------------------------------------------------------- */ + +CREATE TABLE WORK_QUEUE ( + /* Work items are identified by this value. */ + id INTEGER PRIMARY KEY AUTOINCREMENT, + + /* A serialized skel specifying the work item. */ + work BLOB NOT NULL + ); + + +/* ------------------------------------------------------------------------- */ + +CREATE TABLE WC_LOCK ( + /* specifies the location of this node in the local filesystem */ + wc_id INTEGER NOT NULL REFERENCES WCROOT (id), + local_dir_relpath TEXT NOT NULL, + + locked_levels INTEGER NOT NULL DEFAULT -1, + + PRIMARY KEY (wc_id, local_dir_relpath) + ); + + +PRAGMA user_version = +-- define: SVN_WC__VERSION +; + + +/* ------------------------------------------------------------------------- */ + +/* The NODES table describes the way WORKING nodes are layered on top of + BASE nodes and on top of other WORKING nodes, due to nested tree structure + changes. The layers are modelled using the "op_depth" column. + + An 'operation depth' refers to the number of directory levels down from + the WC root at which a tree-change operation (delete, add?, copy, move) + was performed. A row's 'op_depth' does NOT refer to the depth of its own + 'local_relpath', but rather to the depth of the nearest tree change that + affects that node. + + The row with op_depth=0 for any given local relpath represents the "base" + node that is created and updated by checkout, update, switch and commit + post-processing. The row with the highest op_depth for a particular + local_relpath represents the working version. Any rows with intermediate + op_depth values are not normally visible to the user but may become + visible after reverting local changes. + + This table contains full node descriptions for nodes in either the BASE + or WORKING trees as described in notes/wc-ng/design. Fields relate + both to BASE and WORKING trees, unless documented otherwise. + + For illustration, with a scenario like this: + + # (0) + svn rm foo + svn cp ^/moo foo # (1) + svn rm foo/bar + touch foo/bar + svn add foo/bar # (2) + + , these are the NODES table rows for the path foo/bar: + + (0) "BASE" ---> NODES (op_depth == 0) + (1) NODES (op_depth == 1) + (2) NODES (op_depth == 2) + + 0 is the original data for foo/bar before 'svn rm foo' (if it existed). + 1 is the data for foo/bar copied in from ^/moo/bar. + 2 is the to-be-committed data for foo/bar, created by 'svn add foo/bar'. + + An 'svn revert foo/bar' would remove the NODES of (2). + + */ +-- STMT_CREATE_NODES +CREATE TABLE NODES ( + /* Working copy location related fields */ + + wc_id INTEGER NOT NULL REFERENCES WCROOT (id), + local_relpath TEXT NOT NULL, + + /* Contains the depth (= number of path segments) of the operation + modifying the working copy tree structure. All nodes below the root + of the operation (aka operation root, aka oproot) affected by the + operation will be assigned the same op_depth. + + op_depth == 0 designates the initial checkout; the BASE tree. + + */ + op_depth INTEGER NOT NULL, + + /* parent's local_relpath for aggregating children of a given parent. + this will be "" if the parent is the wcroot. Since a wcroot will + never have a WORKING node the parent_relpath will never be null, + except when op_depth == 0 and the node is a wcroot. */ + parent_relpath TEXT, + + + /* Repository location fields */ + + /* When op_depth == 0, these fields refer to the repository location of the + BASE node, the location of the initial checkout. + + When op_depth != 0, they indicate where this node was copied/moved from. + In this case, the fields are set for the root of the operation and for all + children. */ + repos_id INTEGER REFERENCES REPOSITORY (id), + repos_path TEXT, + revision INTEGER, + + + /* WC state fields */ + + /* The tree state of the node. + + In case 'op_depth' is equal to 0, this node is part of the 'BASE' + tree. The 'BASE' represents pristine nodes that are in the + repository; it is obtained and modified by commands such as + checkout/update/switch. + + In case 'op_depth' is greater than 0, this node is part of a + layer of working nodes. The 'WORKING' tree is obtained and + modified by commands like delete/copy/revert. + + The 'BASE' and 'WORKING' trees use the same literal values for + the 'presence' but the meaning of each value can vary depending + on the tree. + + normal: in the 'BASE' tree this is an ordinary node for which we + have full information. In the 'WORKING' tree it's an added or + copied node for which we have full information. + + not-present: in the 'BASE' tree this is a node that is implied to + exist by the parent node, but is not present in the working + copy. Typically obtained by delete/commit, or by update to + revision in which the node does not exist. In the 'WORKING' + tree this is a copy of a 'not-present' node from the 'BASE' + tree, and it will be deleted on commit. Such a node cannot be + copied directly, but can be copied as a descendant. + + incomplete: in the 'BASE' tree this is an ordinary node for which + we do not have full information. Only the name is guaranteed; + we may not have all its children, we may not have its checksum, + etc. In the 'WORKING' tree this is a copied node for which we + do not have the full information. This state is generally + obtained when an operation was interrupted. + + base-deleted: not valid in 'BASE' tree. In the 'WORKING' tree + this represents a node that is deleted from the tree below the + current 'op_depth'. This state is badly named, it should be + something like 'deleted'. + + server-excluded: in the 'BASE' tree this is a node that is excluded by + authz. The name of the node is known from the parent, but no + other information is available. Not valid in the 'WORKING' + tree as there is no way to commit such a node. + + excluded: in the 'BASE' tree this node is administratively + excluded by the user (sparse WC). In the 'WORKING' tree this + is a copy of an excluded node from the 'BASE' tree. Such a + node cannot be copied directly but can be copied as a + descendant. */ + + presence TEXT NOT NULL, + + /* ### JF: For an old-style move, "copyfrom" info stores its source, but a + new WC-NG "move" is intended to be a "true rename" so its copyfrom + revision is implicit, being in effect (new head - 1) at commit time. + For a (new) move, we need to store or deduce the copyfrom local-relpath; + perhaps add a column called "moved_from". */ + + /* Boolean value, specifying if this node was moved here (rather than just + copied). This is set on all the nodes in the moved tree. The source of + the move is implied by a different node with a moved_to column pointing + at the root node of the moved tree. */ + moved_here INTEGER, + + /* If the underlying node was moved away (rather than just deleted), this + specifies the local_relpath of where the node was moved to. + This is set only on the root of a move, and is NULL for all children. + + The op-depth of the moved-to node is not recorded. A moved_to path + always points at a node within the highest op-depth layer at the + destination. This invariant must be maintained by operations which + change existing move information. */ + moved_to TEXT, + + + /* Content fields */ + + /* the kind of the new node. may be "unknown" if the node is not present. */ + kind TEXT NOT NULL, + + /* serialized skel of this node's properties (when presence is 'normal' or + 'incomplete'); an empty skel or NULL indicates no properties. NULL if + we have no information about the properties (any other presence). + TODO: Choose & require a single representation for 'no properties'. + */ + properties BLOB, + + /* NULL depth means "default" (typically svn_depth_infinity) */ + /* ### depth on WORKING? seems this is a BASE-only concept. how do + ### you do "files" on an added-directory? can't really ignore + ### the subdirs! */ + /* ### maybe a WC-to-WC copy can retain a depth? */ + depth TEXT, + + /* The SHA-1 checksum of the pristine text, if this node is a file and was + moved here or copied here, else NULL. */ + checksum TEXT REFERENCES PRISTINE (checksum), + + /* for kind==symlink, this specifies the target. */ + symlink_target TEXT, + + + /* Last-Change fields */ + + /* If this node was moved here or copied here, then the following fields may + have information about their source node. changed_rev must be not-null + if this node has presence=="normal". changed_date and changed_author may + be null if the corresponding revprops are missing. + + For an added or not-present node, these are null. */ + changed_revision INTEGER, + changed_date INTEGER, /* an APR date/time (usec since 1970) */ + changed_author TEXT, + + + /* Various cache fields */ + + /* The size in bytes of the working file when it had no local text + modifications. This means the size of the text when translated from + repository-normal format to working copy format with EOL style + translated and keywords expanded according to the properties in the + "properties" column of this row. + + NULL if this node is not a file or if the size has not (yet) been + computed. */ + translated_size INTEGER, + + /* The mod-time of the working file when it was last determined to be + logically unmodified relative to its base, taking account of keywords + and EOL style. This value is used in the change detection heuristic + used by the status command. + + NULL if this node is not a file or if this info has not yet been + determined. + */ + last_mod_time INTEGER, /* an APR date/time (usec since 1970) */ + + /* serialized skel of this node's dav-cache. could be NULL if the + node does not have any dav-cache. */ + dav_cache BLOB, + + /* Is there a file external in this location. NULL if there + is no file external, otherwise '1' */ + /* ### Originally we had a wc-1.0 like skel in this place, so we + ### check for NULL. + ### In Subversion 1.7 we defined this column as TEXT, but Sqlite + ### only uses this information for deciding how to optimize + ### anyway. */ + file_external INTEGER, + + /* serialized skel of this node's inherited properties. NULL if this + is not the BASE of a WC root node. */ + inherited_props BLOB, + + PRIMARY KEY (wc_id, local_relpath, op_depth) + + ); + +CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, + local_relpath, op_depth); +/* I_NODES_MOVED is introduced in format 30 */ +CREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth); + +/* Many queries have to filter the nodes table to pick only that version + of each node with the highest (most "current") op_depth. This view + does the heavy lifting for such queries. + + Note that this view includes a row for each and every path that is known + in the WC, including, for example, paths that were children of a base- or + lower-op-depth directory that has been replaced by something else in the + current view. + */ +CREATE VIEW NODES_CURRENT AS + SELECT * FROM nodes AS n + WHERE op_depth = (SELECT MAX(op_depth) FROM nodes AS n2 + WHERE n2.wc_id = n.wc_id + AND n2.local_relpath = n.local_relpath); + +/* Many queries have to filter the nodes table to pick only that version + of each node with the BASE ("as checked out") op_depth. This view + does the heavy lifting for such queries. */ +CREATE VIEW NODES_BASE AS + SELECT * FROM nodes + WHERE op_depth = 0; + +-- STMT_CREATE_NODES_TRIGGERS + +CREATE TRIGGER nodes_insert_trigger +AFTER INSERT ON nodes +WHEN NEW.checksum IS NOT NULL +BEGIN + UPDATE pristine SET refcount = refcount + 1 + WHERE checksum = NEW.checksum; +END; + +CREATE TRIGGER nodes_delete_trigger +AFTER DELETE ON nodes +WHEN OLD.checksum IS NOT NULL +BEGIN + UPDATE pristine SET refcount = refcount - 1 + WHERE checksum = OLD.checksum; +END; + +CREATE TRIGGER nodes_update_checksum_trigger +AFTER UPDATE OF checksum ON nodes +WHEN NEW.checksum IS NOT OLD.checksum + /* AND (NEW.checksum IS NOT NULL OR OLD.checksum IS NOT NULL) */ +BEGIN + UPDATE pristine SET refcount = refcount + 1 + WHERE checksum = NEW.checksum; + UPDATE pristine SET refcount = refcount - 1 + WHERE checksum = OLD.checksum; +END; + +-- STMT_CREATE_EXTERNALS + +CREATE TABLE EXTERNALS ( + /* Working copy location related fields (like NODES)*/ + + wc_id INTEGER NOT NULL REFERENCES WCROOT (id), + local_relpath TEXT NOT NULL, + + /* The working copy root can't be recorded as an external in itself + so this will never be NULL. ### ATM only inserted, never queried */ + parent_relpath TEXT NOT NULL, + + /* Repository location fields */ + repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), + + /* Either MAP_NORMAL or MAP_EXCLUDED */ + presence TEXT NOT NULL, + + /* the kind of the external. */ + kind TEXT NOT NULL, + + /* The local relpath of the directory NODE defining this external + (Defaults to the parent directory of the file external after upgrade) */ + def_local_relpath TEXT NOT NULL, + + /* The url of the external as used in the definition */ + def_repos_relpath TEXT NOT NULL, + + /* The operational (peg) and node revision if this is a revision fixed + external; otherwise NULL. (Usually these will both have the same value) */ + def_operational_revision TEXT, + def_revision TEXT, + + PRIMARY KEY (wc_id, local_relpath) +); + +CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS (wc_id, + def_local_relpath, + local_relpath); + +/* ------------------------------------------------------------------------- */ + +/* Format 20 introduces NODES and removes BASE_NODE and WORKING_NODE */ + +-- STMT_UPGRADE_TO_20 + +UPDATE BASE_NODE SET checksum = (SELECT checksum FROM pristine + WHERE md5_checksum = BASE_NODE.checksum) +WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = BASE_NODE.checksum); + +UPDATE WORKING_NODE SET checksum = (SELECT checksum FROM pristine + WHERE md5_checksum = WORKING_NODE.checksum) +WHERE EXISTS (SELECT 1 FROM pristine + WHERE md5_checksum = WORKING_NODE.checksum); + +INSERT INTO NODES ( + wc_id, local_relpath, op_depth, parent_relpath, + repos_id, repos_path, revision, + presence, depth, moved_here, moved_to, kind, + changed_revision, changed_date, changed_author, + checksum, properties, translated_size, last_mod_time, + dav_cache, symlink_target, file_external ) +SELECT wc_id, local_relpath, 0 /*op_depth*/, parent_relpath, + repos_id, repos_relpath, revnum, + presence, depth, NULL /*moved_here*/, NULL /*moved_to*/, kind, + changed_rev, changed_date, changed_author, + checksum, properties, translated_size, last_mod_time, + dav_cache, symlink_target, file_external +FROM BASE_NODE; +INSERT INTO NODES ( + wc_id, local_relpath, op_depth, parent_relpath, + repos_id, repos_path, revision, + presence, depth, moved_here, moved_to, kind, + changed_revision, changed_date, changed_author, + checksum, properties, translated_size, last_mod_time, + dav_cache, symlink_target, file_external ) +SELECT wc_id, local_relpath, 2 /*op_depth*/, parent_relpath, + copyfrom_repos_id, copyfrom_repos_path, copyfrom_revnum, + presence, depth, NULL /*moved_here*/, NULL /*moved_to*/, kind, + changed_rev, changed_date, changed_author, + checksum, properties, translated_size, last_mod_time, + NULL /*dav_cache*/, symlink_target, NULL /*file_external*/ +FROM WORKING_NODE; + +DROP TABLE BASE_NODE; +DROP TABLE WORKING_NODE; + +PRAGMA user_version = 20; + + +/* ------------------------------------------------------------------------- */ + +/* Format 21 involves no schema changes, it moves the tree conflict victim + information to victime nodes, rather than parents. */ + +-- STMT_UPGRADE_TO_21 +PRAGMA user_version = 21; + +/* For format 21 bump code */ +-- STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT +SELECT wc_id, local_relpath, tree_conflict_data +FROM actual_node +WHERE tree_conflict_data IS NOT NULL + +/* For format 21 bump code */ +-- STMT_UPGRADE_21_ERASE_OLD_CONFLICTS +UPDATE actual_node SET tree_conflict_data = NULL + +/* ------------------------------------------------------------------------- */ + +/* Format 22 simply moves the tree conflict information from the conflict_data + column to the tree_conflict_data column. */ + +-- STMT_UPGRADE_TO_22 +UPDATE actual_node SET tree_conflict_data = conflict_data; +UPDATE actual_node SET conflict_data = NULL; + +PRAGMA user_version = 22; + + +/* ------------------------------------------------------------------------- */ + +/* Format 23 involves no schema changes, it introduces multi-layer + op-depth processing for NODES. */ + +-- STMT_UPGRADE_TO_23 +PRAGMA user_version = 23; + +-- STMT_UPGRADE_23_HAS_WORKING_NODES +SELECT 1 FROM nodes WHERE op_depth > 0 +LIMIT 1 + +/* ------------------------------------------------------------------------- */ + +/* Format 24 involves no schema changes; it starts using the pristine + table's refcount column correctly. */ + +-- STMT_UPGRADE_TO_24 +UPDATE pristine SET refcount = + (SELECT COUNT(*) FROM nodes + WHERE checksum = pristine.checksum /*OR checksum = pristine.md5_checksum*/); + +PRAGMA user_version = 24; + +/* ------------------------------------------------------------------------- */ + +/* Format 25 introduces the NODES_CURRENT view. */ + +-- STMT_UPGRADE_TO_25 +DROP VIEW IF EXISTS NODES_CURRENT; +CREATE VIEW NODES_CURRENT AS + SELECT * FROM nodes + JOIN (SELECT wc_id, local_relpath, MAX(op_depth) AS op_depth FROM nodes + GROUP BY wc_id, local_relpath) AS filter + ON nodes.wc_id = filter.wc_id + AND nodes.local_relpath = filter.local_relpath + AND nodes.op_depth = filter.op_depth; + +PRAGMA user_version = 25; + +/* ------------------------------------------------------------------------- */ + +/* Format 26 introduces the NODES_BASE view. */ + +-- STMT_UPGRADE_TO_26 +DROP VIEW IF EXISTS NODES_BASE; +CREATE VIEW NODES_BASE AS + SELECT * FROM nodes + WHERE op_depth = 0; + +PRAGMA user_version = 26; + +/* ------------------------------------------------------------------------- */ + +/* Format 27 involves no schema changes, it introduces stores + conflict files as relpaths rather than names in ACTUAL_NODE. */ + +-- STMT_UPGRADE_TO_27 +PRAGMA user_version = 27; + +/* For format 27 bump code */ +-- STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS +SELECT 1 FROM actual_node +WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL) + AND (conflict_new IS NULL) AND (conflict_working IS NULL) + AND (tree_conflict_data IS NULL)) +LIMIT 1 + + +/* ------------------------------------------------------------------------- */ + +/* Format 28 involves no schema changes, it only converts MD5 pristine + references to SHA1. */ + +-- STMT_UPGRADE_TO_28 + +UPDATE NODES SET checksum = (SELECT checksum FROM pristine + WHERE md5_checksum = nodes.checksum) +WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = nodes.checksum); + +PRAGMA user_version = 28; + +/* ------------------------------------------------------------------------- */ + +/* Format 29 introduces the EXTERNALS table (See STMT_CREATE_TRIGGERS) and + optimizes a few trigger definitions. ... */ + +-- STMT_UPGRADE_TO_29 + +DROP TRIGGER IF EXISTS nodes_update_checksum_trigger; +DROP TRIGGER IF EXISTS nodes_insert_trigger; +DROP TRIGGER IF EXISTS nodes_delete_trigger; + +CREATE TRIGGER nodes_update_checksum_trigger +AFTER UPDATE OF checksum ON nodes +WHEN NEW.checksum IS NOT OLD.checksum + /* AND (NEW.checksum IS NOT NULL OR OLD.checksum IS NOT NULL) */ +BEGIN + UPDATE pristine SET refcount = refcount + 1 + WHERE checksum = NEW.checksum; + UPDATE pristine SET refcount = refcount - 1 + WHERE checksum = OLD.checksum; +END; + +CREATE TRIGGER nodes_insert_trigger +AFTER INSERT ON nodes +WHEN NEW.checksum IS NOT NULL +BEGIN + UPDATE pristine SET refcount = refcount + 1 + WHERE checksum = NEW.checksum; +END; + +CREATE TRIGGER nodes_delete_trigger +AFTER DELETE ON nodes +WHEN OLD.checksum IS NOT NULL +BEGIN + UPDATE pristine SET refcount = refcount - 1 + WHERE checksum = OLD.checksum; +END; + +PRAGMA user_version = 29; + +/* ------------------------------------------------------------------------- */ + +/* Format 30 creates a new NODES index for move information, and a new + PRISTINE index for the md5_checksum column. It also activates use of + skel-based conflict storage -- see notes/wc-ng/conflict-storage-2.0. + It also renames the "absent" presence to "server-excluded". */ +-- STMT_UPGRADE_TO_30 +CREATE UNIQUE INDEX IF NOT EXISTS I_NODES_MOVED +ON NODES (wc_id, moved_to, op_depth); + +CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum); + +UPDATE nodes SET presence = "server-excluded" WHERE presence = "absent"; + +/* Just to be sure clear out file external skels from pre 1.7.0 development + working copies that were never updated by 1.7.0+ style clients */ +UPDATE nodes SET file_external=1 WHERE file_external IS NOT NULL; + +-- STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE +SELECT wc_id, local_relpath, + conflict_old, conflict_working, conflict_new, prop_reject, tree_conflict_data +FROM actual_node +WHERE conflict_old IS NOT NULL + OR conflict_working IS NOT NULL + OR conflict_new IS NOT NULL + OR prop_reject IS NOT NULL + OR tree_conflict_data IS NOT NULL +ORDER by wc_id, local_relpath + +-- STMT_UPGRADE_30_SET_CONFLICT +UPDATE actual_node SET conflict_data = ?3, conflict_old = NULL, + conflict_working = NULL, conflict_new = NULL, prop_reject = NULL, + tree_conflict_data = NULL +WHERE wc_id = ?1 and local_relpath = ?2 + +/* ------------------------------------------------------------------------- */ + +/* Format 31 adds the inherited_props column to the NODES table. C code then + initializes the update/switch roots to make sure future updates fetch the + inherited properties */ +-- STMT_UPGRADE_TO_31_ALTER_TABLE +ALTER TABLE NODES ADD COLUMN inherited_props BLOB; +-- STMT_UPGRADE_TO_31_FINALIZE +DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; +DROP INDEX IF EXISTS I_EXTERNALS_PARENT; + +DROP INDEX I_NODES_PARENT; +CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, + local_relpath, op_depth); + +DROP INDEX I_ACTUAL_PARENT; +CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, + local_relpath); + +PRAGMA user_version = 31; + +-- STMT_UPGRADE_31_SELECT_WCROOT_NODES +/* Select all base nodes which are the root of a WC, including + switched subtrees, but excluding those which map to the root + of the repos. + + ### IPROPS: Is this query horribly inefficient? Quite likely, + ### but it only runs during an upgrade, so do we care? */ +SELECT l.wc_id, l.local_relpath FROM nodes as l +LEFT OUTER JOIN nodes as r +ON l.wc_id = r.wc_id + AND r.local_relpath = l.parent_relpath + AND r.op_depth = 0 +WHERE l.op_depth = 0 + AND l.repos_path != '' + AND ((l.repos_id IS NOT r.repos_id) + OR (l.repos_path IS NOT RELPATH_SKIP_JOIN(r.local_relpath, r.repos_path, l.local_relpath))) + + +/* ------------------------------------------------------------------------- */ +/* Format 32 .... */ +-- STMT_UPGRADE_TO_32 + +/* Drop old index. ### Remove this part from the upgrade to 31 once bumped */ +DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; +DROP INDEX IF EXISTS I_EXTERNALS_PARENT; +CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath); + +DROP INDEX I_NODES_PARENT; +CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, + local_relpath, op_depth); + +DROP INDEX I_ACTUAL_PARENT; +CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, + local_relpath); + +/* ------------------------------------------------------------------------- */ + +/* Format YYY introduces new handling for conflict information. */ +-- format: YYY + + +/* ------------------------------------------------------------------------- */ + +/* Format 99 drops all columns not needed due to previous format upgrades. + Before we release 1.7, these statements will be pulled into a format bump + and all the tables will be cleaned up. We don't know what that format + number will be, however, so we're just marking it as 99 for now. */ +-- format: 99 + +/* TODO: Un-confuse *_revision column names in the EXTERNALS table to + "-r<operative> foo@<peg>", as suggested by the patch attached to + http://svn.haxx.se/dev/archive-2011-09/0478.shtml */ +/* TODO: Remove column parent_relpath from EXTERNALS. We're not using it and + never will. It's not interesting like in the NODES table: the external's + parent path may be *anything*: unversioned, "behind" a another WC... */ + +/* Now "drop" the tree_conflict_data column from actual_node. */ +CREATE TABLE ACTUAL_NODE_BACKUP ( + wc_id INTEGER NOT NULL, + local_relpath TEXT NOT NULL, + parent_relpath TEXT, + properties BLOB, + conflict_old TEXT, + conflict_new TEXT, + conflict_working TEXT, + prop_reject TEXT, + changelist TEXT, + text_mod TEXT + ); + +INSERT INTO ACTUAL_NODE_BACKUP SELECT + wc_id, local_relpath, parent_relpath, properties, conflict_old, + conflict_new, conflict_working, prop_reject, changelist, text_mod +FROM ACTUAL_NODE; + +DROP TABLE ACTUAL_NODE; + +CREATE TABLE ACTUAL_NODE ( + wc_id INTEGER NOT NULL REFERENCES WCROOT (id), + local_relpath TEXT NOT NULL, + parent_relpath TEXT, + properties BLOB, + conflict_old TEXT, + conflict_new TEXT, + conflict_working TEXT, + prop_reject TEXT, + changelist TEXT, + text_mod TEXT, + + PRIMARY KEY (wc_id, local_relpath) + ); + +CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, + local_relpath); + +INSERT INTO ACTUAL_NODE SELECT + wc_id, local_relpath, parent_relpath, properties, conflict_old, + conflict_new, conflict_working, prop_reject, changelist, text_mod +FROM ACTUAL_NODE_BACKUP; + +DROP TABLE ACTUAL_NODE_BACKUP; + +/* Note: Other differences between the schemas of an upgraded and a + * fresh WC. + * + * While format 22 was current, "NOT NULL" was added to the + * columns PRISTINE.size and PRISTINE.md5_checksum. The format was not + * bumped because it is a forward- and backward-compatible change. + * + * While format 23 was current, "REFERENCES PRISTINE" was added to the + * columns ACTUAL_NODE.older_checksum, ACTUAL_NODE.left_checksum, + * ACTUAL_NODE.right_checksum, NODES.checksum. + * + * The "NODES_BASE" view was originally implemented with a more complex (but + * functionally equivalent) statement using a 'JOIN'. WCs that were created + * at or upgraded to format 26 before it was changed will still have the old + * version. + */ + diff --git a/subversion/libsvn_wc/wc-queries.h b/subversion/libsvn_wc/wc-queries.h new file mode 100644 index 000000000000..19c709ebe2e7 --- /dev/null +++ b/subversion/libsvn_wc/wc-queries.h @@ -0,0 +1,3100 @@ +/* This file is automatically generated from wc-queries.sql and .dist_sandbox/subversion-1.8.0-rc3/subversion/libsvn_wc/token-map.h. + * Do not edit this file -- edit the source and rerun gen-make.py */ + +#define STMT_SELECT_NODE_INFO 0 +#define STMT_0_INFO {"STMT_SELECT_NODE_INFO", NULL} +#define STMT_0 \ + "SELECT op_depth, repos_id, repos_path, presence, kind, revision, checksum, " \ + " translated_size, changed_revision, changed_date, changed_author, depth, " \ + " symlink_target, last_mod_time, properties, moved_here, inherited_props, " \ + " moved_to " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "ORDER BY op_depth DESC " \ + "" + +#define STMT_SELECT_NODE_INFO_WITH_LOCK 1 +#define STMT_1_INFO {"STMT_SELECT_NODE_INFO_WITH_LOCK", NULL} +#define STMT_1 \ + "SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision, " \ + " checksum, translated_size, changed_revision, changed_date, changed_author, " \ + " depth, symlink_target, last_mod_time, properties, moved_here, " \ + " inherited_props, " \ + " lock_token, lock_owner, lock_comment, lock_date " \ + "FROM nodes " \ + "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \ + " AND nodes.repos_path = lock.repos_relpath " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "ORDER BY op_depth DESC " \ + "" + +#define STMT_SELECT_BASE_NODE 2 +#define STMT_2_INFO {"STMT_SELECT_BASE_NODE", NULL} +#define STMT_2 \ + "SELECT repos_id, repos_path, presence, kind, revision, checksum, " \ + " translated_size, changed_revision, changed_date, changed_author, depth, " \ + " symlink_target, last_mod_time, properties, file_external " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ + "" + +#define STMT_SELECT_BASE_NODE_WITH_LOCK 3 +#define STMT_3_INFO {"STMT_SELECT_BASE_NODE_WITH_LOCK", NULL} +#define STMT_3 \ + "SELECT nodes.repos_id, nodes.repos_path, presence, kind, revision, " \ + " checksum, translated_size, changed_revision, changed_date, changed_author, " \ + " depth, symlink_target, last_mod_time, properties, file_external, " \ + " lock_token, lock_owner, lock_comment, lock_date " \ + "FROM nodes " \ + "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \ + " AND nodes.repos_path = lock.repos_relpath " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ + "" + +#define STMT_SELECT_BASE_CHILDREN_INFO 4 +#define STMT_4_INFO {"STMT_SELECT_BASE_CHILDREN_INFO", NULL} +#define STMT_4 \ + "SELECT local_relpath, nodes.repos_id, nodes.repos_path, presence, kind, " \ + " revision, depth, file_external, " \ + " lock_token, lock_owner, lock_comment, lock_date " \ + "FROM nodes " \ + "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \ + " AND nodes.repos_path = lock.repos_relpath " \ + "WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = 0 " \ + "" + +#define STMT_SELECT_WORKING_NODE 5 +#define STMT_5_INFO {"STMT_SELECT_WORKING_NODE", NULL} +#define STMT_5 \ + "SELECT op_depth, presence, kind, checksum, translated_size, " \ + " changed_revision, changed_date, changed_author, depth, symlink_target, " \ + " repos_id, repos_path, revision, " \ + " moved_here, moved_to, last_mod_time, properties " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0 " \ + "ORDER BY op_depth DESC " \ + "LIMIT 1 " \ + "" + +#define STMT_SELECT_DEPTH_NODE 6 +#define STMT_6_INFO {"STMT_SELECT_DEPTH_NODE", NULL} +#define STMT_6 \ + "SELECT repos_id, repos_path, presence, kind, revision, checksum, " \ + " translated_size, changed_revision, changed_date, changed_author, depth, " \ + " symlink_target, last_mod_time, properties " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \ + "" + +#define STMT_SELECT_LOWEST_WORKING_NODE 7 +#define STMT_7_INFO {"STMT_SELECT_LOWEST_WORKING_NODE", NULL} +#define STMT_7 \ + "SELECT op_depth, presence, kind, moved_to " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 " \ + "ORDER BY op_depth " \ + "LIMIT 1 " \ + "" + +#define STMT_SELECT_HIGHEST_WORKING_NODE 8 +#define STMT_8_INFO {"STMT_SELECT_HIGHEST_WORKING_NODE", NULL} +#define STMT_8 \ + "SELECT op_depth " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth < ?3 " \ + "ORDER BY op_depth DESC " \ + "LIMIT 1 " \ + "" + +#define STMT_SELECT_ACTUAL_NODE 9 +#define STMT_9_INFO {"STMT_SELECT_ACTUAL_NODE", NULL} +#define STMT_9 \ + "SELECT changelist, properties, conflict_data " \ + "FROM actual_node " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "" + +#define STMT_SELECT_NODE_CHILDREN_INFO 10 +#define STMT_10_INFO {"STMT_SELECT_NODE_CHILDREN_INFO", NULL} +#define STMT_10 \ + "SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision, " \ + " checksum, translated_size, changed_revision, changed_date, changed_author, " \ + " depth, symlink_target, last_mod_time, properties, lock_token, lock_owner, " \ + " lock_comment, lock_date, local_relpath, moved_here, moved_to, file_external " \ + "FROM nodes " \ + "LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id " \ + " AND nodes.repos_path = lock.repos_relpath AND op_depth = 0 " \ + "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ + "" + +#define STMT_SELECT_NODE_CHILDREN_WALKER_INFO 11 +#define STMT_11_INFO {"STMT_SELECT_NODE_CHILDREN_WALKER_INFO", NULL} +#define STMT_11 \ + "SELECT local_relpath, op_depth, presence, kind " \ + "FROM nodes_current " \ + "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ + "" + +#define STMT_SELECT_ACTUAL_CHILDREN_INFO 12 +#define STMT_12_INFO {"STMT_SELECT_ACTUAL_CHILDREN_INFO", NULL} +#define STMT_12 \ + "SELECT local_relpath, changelist, properties, conflict_data " \ + "FROM actual_node " \ + "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ + "" + +#define STMT_SELECT_REPOSITORY_BY_ID 13 +#define STMT_13_INFO {"STMT_SELECT_REPOSITORY_BY_ID", NULL} +#define STMT_13 \ + "SELECT root, uuid FROM repository WHERE id = ?1 " \ + "" + +#define STMT_SELECT_WCROOT_NULL 14 +#define STMT_14_INFO {"STMT_SELECT_WCROOT_NULL", NULL} +#define STMT_14 \ + "SELECT id FROM wcroot WHERE local_abspath IS NULL " \ + "" + +#define STMT_SELECT_REPOSITORY 15 +#define STMT_15_INFO {"STMT_SELECT_REPOSITORY", NULL} +#define STMT_15 \ + "SELECT id FROM repository WHERE root = ?1 " \ + "" + +#define STMT_INSERT_REPOSITORY 16 +#define STMT_16_INFO {"STMT_INSERT_REPOSITORY", NULL} +#define STMT_16 \ + "INSERT INTO repository (root, uuid) VALUES (?1, ?2) " \ + "" + +#define STMT_INSERT_NODE 17 +#define STMT_17_INFO {"STMT_INSERT_NODE", NULL} +#define STMT_17 \ + "INSERT OR REPLACE INTO nodes ( " \ + " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \ + " revision, presence, depth, kind, changed_revision, changed_date, " \ + " changed_author, checksum, properties, translated_size, last_mod_time, " \ + " dav_cache, symlink_target, file_external, moved_to, moved_here, " \ + " inherited_props) " \ + "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, " \ + " ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23) " \ + "" + +#define STMT_SELECT_BASE_PRESENT 18 +#define STMT_18_INFO {"STMT_SELECT_BASE_PRESENT", NULL} +#define STMT_18 \ + "SELECT local_relpath, kind FROM nodes n " \ + "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 " \ + " AND presence in ('normal', 'incomplete') " \ + " AND NOT EXISTS(SELECT 1 FROM NODES w " \ + " WHERE w.wc_id = ?1 AND w.local_relpath = n.local_relpath " \ + " AND op_depth > 0) " \ + "ORDER BY local_relpath DESC " \ + "" + +#define STMT_SELECT_WORKING_PRESENT 19 +#define STMT_19_INFO {"STMT_SELECT_WORKING_PRESENT", NULL} +#define STMT_19 \ + "SELECT local_relpath, kind, checksum, translated_size, last_mod_time " \ + "FROM nodes n " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND presence in ('normal', 'incomplete') " \ + " AND op_depth = (SELECT MAX(op_depth) " \ + " FROM NODES w " \ + " WHERE w.wc_id = ?1 " \ + " AND w.local_relpath = n.local_relpath) " \ + "ORDER BY local_relpath DESC " \ + "" + +#define STMT_DELETE_NODE_RECURSIVE 20 +#define STMT_20_INFO {"STMT_DELETE_NODE_RECURSIVE", NULL} +#define STMT_20 \ + "DELETE FROM NODES " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + "" + +#define STMT_DELETE_NODE 21 +#define STMT_21_INFO {"STMT_DELETE_NODE", NULL} +#define STMT_21 \ + "DELETE " \ + "FROM NODES " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "" + +#define STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE 22 +#define STMT_22_INFO {"STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE", NULL} +#define STMT_22 \ + "DELETE FROM actual_node " \ + "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND EXISTS(SELECT 1 FROM NODES b " \ + " WHERE b.wc_id = ?1 " \ + " AND b.local_relpath = actual_node.local_relpath " \ + " AND op_depth = 0) " \ + " AND NOT EXISTS(SELECT 1 FROM NODES w " \ + " WHERE w.wc_id = ?1 " \ + " AND w.local_relpath = actual_node.local_relpath " \ + " AND op_depth > 0 " \ + " AND presence in ('normal', 'incomplete', 'not-present')) " \ + "" + +#define STMT_DELETE_WORKING_BASE_DELETE 23 +#define STMT_23_INFO {"STMT_DELETE_WORKING_BASE_DELETE", NULL} +#define STMT_23 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND presence = 'base-deleted' " \ + " AND op_depth > 0 " \ + " AND op_depth = (SELECT MIN(op_depth) FROM nodes n " \ + " WHERE n.wc_id = ?1 " \ + " AND n.local_relpath = nodes.local_relpath " \ + " AND op_depth > 0) " \ + "" + +#define STMT_DELETE_WORKING_RECURSIVE 24 +#define STMT_24_INFO {"STMT_DELETE_WORKING_RECURSIVE", NULL} +#define STMT_24 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth > 0 " \ + "" + +#define STMT_DELETE_BASE_RECURSIVE 25 +#define STMT_25_INFO {"STMT_DELETE_BASE_RECURSIVE", NULL} +#define STMT_25 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 " \ + "" + +#define STMT_DELETE_WORKING_OP_DEPTH 26 +#define STMT_26_INFO {"STMT_DELETE_WORKING_OP_DEPTH", NULL} +#define STMT_26 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_DELETE_WORKING_OP_DEPTH_ABOVE 27 +#define STMT_27_INFO {"STMT_DELETE_WORKING_OP_DEPTH_ABOVE", NULL} +#define STMT_27 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth > ?3 " \ + "" + +#define STMT_SELECT_LOCAL_RELPATH_OP_DEPTH 28 +#define STMT_28_INFO {"STMT_SELECT_LOCAL_RELPATH_OP_DEPTH", NULL} +#define STMT_28 \ + "SELECT local_relpath " \ + "FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_SELECT_CHILDREN_OP_DEPTH 29 +#define STMT_29_INFO {"STMT_SELECT_CHILDREN_OP_DEPTH", NULL} +#define STMT_29 \ + "SELECT local_relpath, kind " \ + "FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = ?3 " \ + "ORDER BY local_relpath DESC " \ + "" + +#define STMT_COPY_NODE_MOVE 30 +#define STMT_30_INFO {"STMT_COPY_NODE_MOVE", NULL} +#define STMT_30 \ + "INSERT OR REPLACE INTO nodes ( " \ + " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \ + " revision, presence, depth, kind, changed_revision, changed_date, " \ + " changed_author, checksum, properties, translated_size, last_mod_time, " \ + " symlink_target, moved_here, moved_to ) " \ + "SELECT " \ + " wc_id, ?4 , ?5 , ?6 , " \ + " repos_id, " \ + " repos_path, revision, presence, depth, kind, changed_revision, " \ + " changed_date, changed_author, checksum, properties, translated_size, " \ + " last_mod_time, symlink_target, 1, " \ + " (SELECT dst.moved_to FROM nodes AS dst " \ + " WHERE dst.wc_id = ?1 " \ + " AND dst.local_relpath = ?4 " \ + " AND dst.op_depth = ?5) " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \ + "" + +#define STMT_SELECT_OP_DEPTH_CHILDREN 31 +#define STMT_31_INFO {"STMT_SELECT_OP_DEPTH_CHILDREN", NULL} +#define STMT_31 \ + "SELECT local_relpath, kind FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND parent_relpath = ?2 " \ + " AND op_depth = ?3 " \ + " AND presence != 'base-deleted' " \ + " AND file_external is NULL " \ + "" + +#define STMT_SELECT_GE_OP_DEPTH_CHILDREN 32 +#define STMT_32_INFO {"STMT_SELECT_GE_OP_DEPTH_CHILDREN", NULL} +#define STMT_32 \ + "SELECT 1 FROM nodes " \ + "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ + " AND (op_depth > ?3 OR (op_depth = ?3 AND presence != 'base-deleted')) " \ + "UNION ALL " \ + "SELECT 1 FROM ACTUAL_NODE a " \ + "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ + " AND NOT EXISTS (SELECT 1 FROM nodes n " \ + " WHERE wc_id = ?1 AND n.local_relpath = a.local_relpath) " \ + "" + +#define STMT_DELETE_SHADOWED_RECURSIVE 33 +#define STMT_33_INFO {"STMT_DELETE_SHADOWED_RECURSIVE", NULL} +#define STMT_33 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND (op_depth < ?3 " \ + " OR (op_depth = ?3 AND presence = 'base-deleted')) " \ + "" + +#define STMT_CLEAR_MOVED_TO_FROM_DEST 34 +#define STMT_34_INFO {"STMT_CLEAR_MOVED_TO_FROM_DEST", NULL} +#define STMT_34 \ + "UPDATE NODES SET moved_to = NULL " \ + "WHERE wc_id = ?1 " \ + " AND moved_to = ?2 " \ + "" + +#define STMT_SELECT_NOT_PRESENT_DESCENDANTS 35 +#define STMT_35_INFO {"STMT_SELECT_NOT_PRESENT_DESCENDANTS", NULL} +#define STMT_35 \ + "SELECT local_relpath FROM nodes " \ + "WHERE wc_id = ?1 AND op_depth = ?3 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND presence = 'not-present' " \ + "" + +#define STMT_COMMIT_DESCENDANTS_TO_BASE 36 +#define STMT_36_INFO {"STMT_COMMIT_DESCENDANTS_TO_BASE", NULL} +#define STMT_36 \ + "UPDATE NODES SET op_depth = 0, " \ + " repos_id = ?4, " \ + " repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1), " \ + " revision = ?6, " \ + " dav_cache = NULL, " \ + " moved_here = NULL, " \ + " presence = CASE presence " \ + " WHEN 'normal' THEN 'normal' " \ + " WHEN 'excluded' THEN 'excluded' " \ + " ELSE 'not-present' " \ + " END " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_SELECT_NODE_CHILDREN 37 +#define STMT_37_INFO {"STMT_SELECT_NODE_CHILDREN", NULL} +#define STMT_37 \ + "SELECT local_relpath FROM nodes " \ + "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ + "" + +#define STMT_SELECT_WORKING_CHILDREN 38 +#define STMT_38_INFO {"STMT_SELECT_WORKING_CHILDREN", NULL} +#define STMT_38 \ + "SELECT local_relpath FROM nodes " \ + "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ + " AND (op_depth > (SELECT MAX(op_depth) FROM nodes " \ + " WHERE wc_id = ?1 AND local_relpath = ?2) " \ + " OR " \ + " (op_depth = (SELECT MAX(op_depth) FROM nodes " \ + " WHERE wc_id = ?1 AND local_relpath = ?2) " \ + " AND presence != 'base-deleted')) " \ + "" + +#define STMT_SELECT_NODE_PROPS 39 +#define STMT_39_INFO {"STMT_SELECT_NODE_PROPS", NULL} +#define STMT_39 \ + "SELECT properties, presence FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "ORDER BY op_depth DESC " \ + "" + +#define STMT_SELECT_ACTUAL_PROPS 40 +#define STMT_40_INFO {"STMT_SELECT_ACTUAL_PROPS", NULL} +#define STMT_40 \ + "SELECT properties FROM actual_node " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "" + +#define STMT_UPDATE_ACTUAL_PROPS 41 +#define STMT_41_INFO {"STMT_UPDATE_ACTUAL_PROPS", NULL} +#define STMT_41 \ + "UPDATE actual_node SET properties = ?3 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "" + +#define STMT_INSERT_ACTUAL_PROPS 42 +#define STMT_42_INFO {"STMT_INSERT_ACTUAL_PROPS", NULL} +#define STMT_42 \ + "INSERT INTO actual_node (wc_id, local_relpath, parent_relpath, properties) " \ + "VALUES (?1, ?2, ?3, ?4) " \ + "" + +#define STMT_INSERT_LOCK 43 +#define STMT_43_INFO {"STMT_INSERT_LOCK", NULL} +#define STMT_43 \ + "INSERT OR REPLACE INTO lock " \ + "(repos_id, repos_relpath, lock_token, lock_owner, lock_comment, " \ + " lock_date) " \ + "VALUES (?1, ?2, ?3, ?4, ?5, ?6) " \ + "" + +#define STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE 44 +#define STMT_44_INFO {"STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE", NULL} +#define STMT_44 \ + "SELECT nodes.repos_id, nodes.repos_path, lock_token " \ + "FROM nodes " \ + "LEFT JOIN lock ON nodes.repos_id = lock.repos_id " \ + " AND nodes.repos_path = lock.repos_relpath " \ + "WHERE wc_id = ?1 AND op_depth = 0 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + "" + +#define STMT_INSERT_WCROOT 45 +#define STMT_45_INFO {"STMT_INSERT_WCROOT", NULL} +#define STMT_45 \ + "INSERT INTO wcroot (local_abspath) " \ + "VALUES (?1) " \ + "" + +#define STMT_UPDATE_BASE_NODE_DAV_CACHE 46 +#define STMT_46_INFO {"STMT_UPDATE_BASE_NODE_DAV_CACHE", NULL} +#define STMT_46 \ + "UPDATE nodes SET dav_cache = ?3 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ + "" + +#define STMT_SELECT_BASE_DAV_CACHE 47 +#define STMT_47_INFO {"STMT_SELECT_BASE_DAV_CACHE", NULL} +#define STMT_47 \ + "SELECT dav_cache FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ + "" + +#define STMT_SELECT_DELETION_INFO 48 +#define STMT_48_INFO {"STMT_SELECT_DELETION_INFO", NULL} +#define STMT_48 \ + "SELECT (SELECT b.presence FROM nodes AS b " \ + " WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0), " \ + " work.presence, work.op_depth " \ + "FROM nodes_current AS work " \ + "WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0 " \ + "LIMIT 1 " \ + "" + +#define STMT_SELECT_DELETION_INFO_SCAN 49 +#define STMT_49_INFO {"STMT_SELECT_DELETION_INFO_SCAN", NULL} +#define STMT_49 \ + "SELECT (SELECT b.presence FROM nodes AS b " \ + " WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0), " \ + " work.presence, work.op_depth, moved.moved_to " \ + "FROM nodes_current AS work " \ + "LEFT OUTER JOIN nodes AS moved " \ + " ON moved.wc_id = work.wc_id " \ + " AND moved.local_relpath = work.local_relpath " \ + " AND moved.moved_to IS NOT NULL " \ + "WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0 " \ + "LIMIT 1 " \ + "" + +#define STMT_SELECT_OP_DEPTH_MOVED_TO 50 +#define STMT_50_INFO {"STMT_SELECT_OP_DEPTH_MOVED_TO", NULL} +#define STMT_50 \ + "SELECT op_depth, moved_to, repos_path, revision " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + " AND op_depth <= (SELECT MIN(op_depth) FROM nodes " \ + " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3) " \ + "ORDER BY op_depth DESC " \ + "" + +#define STMT_SELECT_MOVED_TO 51 +#define STMT_51_INFO {"STMT_SELECT_MOVED_TO", NULL} +#define STMT_51 \ + "SELECT moved_to " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \ + "" + +#define STMT_SELECT_MOVED_HERE 52 +#define STMT_52_INFO {"STMT_SELECT_MOVED_HERE", NULL} +#define STMT_52 \ + "SELECT moved_here, presence, repos_path, revision " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth >= ?3 " \ + "ORDER BY op_depth " \ + "" + +#define STMT_SELECT_MOVED_BACK 53 +#define STMT_53_INFO {"STMT_SELECT_MOVED_BACK", NULL} +#define STMT_53 \ + "SELECT u.local_relpath, " \ + " u.presence, u.repos_id, u.repos_path, u.revision, " \ + " l.presence, l.repos_id, l.repos_path, l.revision, " \ + " u.moved_here, u.moved_to " \ + "FROM nodes u " \ + "LEFT OUTER JOIN nodes l ON l.wc_id = ?1 " \ + " AND l.local_relpath = u.local_relpath " \ + " AND l.op_depth = ?3 " \ + "WHERE u.wc_id = ?1 " \ + " AND u.local_relpath = ?2 " \ + " AND u.op_depth = ?4 " \ + "UNION ALL " \ + "SELECT u.local_relpath, " \ + " u.presence, u.repos_id, u.repos_path, u.revision, " \ + " l.presence, l.repos_id, l.repos_path, l.revision, " \ + " u.moved_here, NULL " \ + "FROM nodes u " \ + "LEFT OUTER JOIN nodes l ON l.wc_id=?1 " \ + " AND l.local_relpath=u.local_relpath " \ + " AND l.op_depth=?3 " \ + "WHERE u.wc_id = ?1 " \ + " AND (((u.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((u.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND u.op_depth = ?4 " \ + "" + +#define STMT_DELETE_MOVED_BACK 54 +#define STMT_54_INFO {"STMT_DELETE_MOVED_BACK", NULL} +#define STMT_54 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_DELETE_LOCK 55 +#define STMT_55_INFO {"STMT_DELETE_LOCK", NULL} +#define STMT_55 \ + "DELETE FROM lock " \ + "WHERE repos_id = ?1 AND repos_relpath = ?2 " \ + "" + +#define STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE 56 +#define STMT_56_INFO {"STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE", NULL} +#define STMT_56 \ + "UPDATE nodes SET dav_cache = NULL " \ + "WHERE dav_cache IS NOT NULL AND wc_id = ?1 AND op_depth = 0 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + "" + +#define STMT_RECURSIVE_UPDATE_NODE_REPO 57 +#define STMT_57_INFO {"STMT_RECURSIVE_UPDATE_NODE_REPO", NULL} +#define STMT_57 \ + "UPDATE nodes SET repos_id = ?4, dav_cache = NULL " \ + "WHERE (wc_id = ?1 AND local_relpath = ?2 AND repos_id = ?3) " \ + " OR (wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND repos_id = ?3) " \ + "" + +#define STMT_UPDATE_LOCK_REPOS_ID 58 +#define STMT_58_INFO {"STMT_UPDATE_LOCK_REPOS_ID", NULL} +#define STMT_58 \ + "UPDATE lock SET repos_id = ?2 " \ + "WHERE repos_id = ?1 " \ + "" + +#define STMT_UPDATE_NODE_FILEINFO 59 +#define STMT_59_INFO {"STMT_UPDATE_NODE_FILEINFO", NULL} +#define STMT_59 \ + "UPDATE nodes SET translated_size = ?3, last_mod_time = ?4 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + " AND op_depth = (SELECT MAX(op_depth) FROM nodes " \ + " WHERE wc_id = ?1 AND local_relpath = ?2) " \ + "" + +#define STMT_INSERT_ACTUAL_CONFLICT 60 +#define STMT_60_INFO {"STMT_INSERT_ACTUAL_CONFLICT", NULL} +#define STMT_60 \ + "INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath) " \ + "VALUES (?1, ?2, ?3, ?4) " \ + "" + +#define STMT_UPDATE_ACTUAL_CONFLICT 61 +#define STMT_61_INFO {"STMT_UPDATE_ACTUAL_CONFLICT", NULL} +#define STMT_61 \ + "UPDATE actual_node SET conflict_data = ?3 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "" + +#define STMT_UPDATE_ACTUAL_CHANGELISTS 62 +#define STMT_62_INFO {"STMT_UPDATE_ACTUAL_CHANGELISTS", NULL} +#define STMT_62 \ + "UPDATE actual_node SET changelist = ?3 " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND local_relpath = (SELECT local_relpath FROM targets_list AS t " \ + " WHERE wc_id = ?1 " \ + " AND t.local_relpath = actual_node.local_relpath " \ + " AND kind = 'file') " \ + "" + +#define STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST 63 +#define STMT_63_INFO {"STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST", NULL} +#define STMT_63 \ + "UPDATE actual_node SET changelist = NULL " \ + " WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "" + +#define STMT_MARK_SKIPPED_CHANGELIST_DIRS 64 +#define STMT_64_INFO {"STMT_MARK_SKIPPED_CHANGELIST_DIRS", NULL} +#define STMT_64 \ + "INSERT INTO changelist_list (wc_id, local_relpath, notify, changelist) " \ + "SELECT wc_id, local_relpath, 7, ?3 " \ + "FROM targets_list " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND kind = 'dir' " \ + "" + +#define STMT_RESET_ACTUAL_WITH_CHANGELIST 65 +#define STMT_65_INFO {"STMT_RESET_ACTUAL_WITH_CHANGELIST", NULL} +#define STMT_65 \ + "REPLACE INTO actual_node ( " \ + " wc_id, local_relpath, parent_relpath, changelist) " \ + "VALUES (?1, ?2, ?3, ?4) " \ + "" + +#define STMT_CREATE_CHANGELIST_LIST 66 +#define STMT_66_INFO {"STMT_CREATE_CHANGELIST_LIST", NULL} +#define STMT_66 \ + "DROP TABLE IF EXISTS changelist_list; " \ + "CREATE TEMPORARY TABLE changelist_list ( " \ + " wc_id INTEGER NOT NULL, " \ + " local_relpath TEXT NOT NULL, " \ + " notify INTEGER NOT NULL, " \ + " changelist TEXT NOT NULL, " \ + " PRIMARY KEY (wc_id, local_relpath, notify DESC) " \ + ") " \ + "" + +#define STMT_CREATE_CHANGELIST_TRIGGER 67 +#define STMT_67_INFO {"STMT_CREATE_CHANGELIST_TRIGGER", NULL} +#define STMT_67 \ + "DROP TRIGGER IF EXISTS trigger_changelist_list_change; " \ + "CREATE TEMPORARY TRIGGER trigger_changelist_list_change " \ + "BEFORE UPDATE ON actual_node " \ + "WHEN old.changelist IS NOT new.changelist " \ + "BEGIN " \ + " INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) " \ + " SELECT old.wc_id, old.local_relpath, 27, old.changelist " \ + " WHERE old.changelist is NOT NULL; " \ + " INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) " \ + " SELECT new.wc_id, new.local_relpath, 26, new.changelist " \ + " WHERE new.changelist IS NOT NULL; " \ + "END " \ + "" + +#define STMT_FINALIZE_CHANGELIST 68 +#define STMT_68_INFO {"STMT_FINALIZE_CHANGELIST", NULL} +#define STMT_68 \ + "DROP TRIGGER trigger_changelist_list_change; " \ + "DROP TABLE changelist_list; " \ + "DROP TABLE targets_list " \ + "" + +#define STMT_SELECT_CHANGELIST_LIST 69 +#define STMT_69_INFO {"STMT_SELECT_CHANGELIST_LIST", NULL} +#define STMT_69 \ + "SELECT wc_id, local_relpath, notify, changelist " \ + "FROM changelist_list " \ + "ORDER BY wc_id, local_relpath ASC, notify DESC " \ + "" + +#define STMT_CREATE_TARGETS_LIST 70 +#define STMT_70_INFO {"STMT_CREATE_TARGETS_LIST", NULL} +#define STMT_70 \ + "DROP TABLE IF EXISTS targets_list; " \ + "CREATE TEMPORARY TABLE targets_list ( " \ + " wc_id INTEGER NOT NULL, " \ + " local_relpath TEXT NOT NULL, " \ + " parent_relpath TEXT, " \ + " kind TEXT NOT NULL, " \ + " PRIMARY KEY (wc_id, local_relpath) " \ + " ); " \ + "" + +#define STMT_DROP_TARGETS_LIST 71 +#define STMT_71_INFO {"STMT_DROP_TARGETS_LIST", NULL} +#define STMT_71 \ + "DROP TABLE targets_list " \ + "" + +#define STMT_INSERT_TARGET 72 +#define STMT_72_INFO {"STMT_INSERT_TARGET", NULL} +#define STMT_72 \ + "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ + "SELECT wc_id, local_relpath, parent_relpath, kind " \ + "FROM nodes_current " \ + "WHERE wc_id = ?1 " \ + " AND local_relpath = ?2 " \ + "" + +#define STMT_INSERT_TARGET_DEPTH_FILES 73 +#define STMT_73_INFO {"STMT_INSERT_TARGET_DEPTH_FILES", NULL} +#define STMT_73 \ + "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ + "SELECT wc_id, local_relpath, parent_relpath, kind " \ + "FROM nodes_current " \ + "WHERE wc_id = ?1 " \ + " AND parent_relpath = ?2 " \ + " AND kind = 'file' " \ + "" + +#define STMT_INSERT_TARGET_DEPTH_IMMEDIATES 74 +#define STMT_74_INFO {"STMT_INSERT_TARGET_DEPTH_IMMEDIATES", NULL} +#define STMT_74 \ + "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ + "SELECT wc_id, local_relpath, parent_relpath, kind " \ + "FROM nodes_current " \ + "WHERE wc_id = ?1 " \ + " AND parent_relpath = ?2 " \ + "" + +#define STMT_INSERT_TARGET_DEPTH_INFINITY 75 +#define STMT_75_INFO {"STMT_INSERT_TARGET_DEPTH_INFINITY", NULL} +#define STMT_75 \ + "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ + "SELECT wc_id, local_relpath, parent_relpath, kind " \ + "FROM nodes_current " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + "" + +#define STMT_INSERT_TARGET_WITH_CHANGELIST 76 +#define STMT_76_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST", NULL} +#define STMT_76 \ + "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ + "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \ + " FROM actual_node AS A JOIN nodes_current AS N " \ + " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \ + " WHERE N.wc_id = ?1 " \ + " AND N.local_relpath = ?2 " \ + " AND A.changelist = ?3 " \ + "" + +#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES 77 +#define STMT_77_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES", NULL} +#define STMT_77 \ + "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ + "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \ + " FROM actual_node AS A JOIN nodes_current AS N " \ + " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \ + " WHERE N.wc_id = ?1 " \ + " AND N.parent_relpath = ?2 " \ + " AND kind = 'file' " \ + " AND A.changelist = ?3 " \ + "" + +#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES 78 +#define STMT_78_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES", NULL} +#define STMT_78 \ + "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ + "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \ + " FROM actual_node AS A JOIN nodes_current AS N " \ + " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \ + " WHERE N.wc_id = ?1 " \ + " AND N.parent_relpath = ?2 " \ + " AND A.changelist = ?3 " \ + "" + +#define STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY 79 +#define STMT_79_INFO {"STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY", NULL} +#define STMT_79 \ + "INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) " \ + "SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind " \ + " FROM actual_node AS A JOIN nodes_current AS N " \ + " ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath " \ + " WHERE N.wc_id = ?1 " \ + " AND (((N.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((N.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND A.changelist = ?3 " \ + "" + +#define STMT_INSERT_ACTUAL_EMPTIES 80 +#define STMT_80_INFO {"STMT_INSERT_ACTUAL_EMPTIES", NULL} +#define STMT_80 \ + "INSERT OR IGNORE INTO actual_node ( " \ + " wc_id, local_relpath, parent_relpath) " \ + "SELECT wc_id, local_relpath, parent_relpath " \ + "FROM targets_list " \ + "" + +#define STMT_DELETE_ACTUAL_EMPTY 81 +#define STMT_81_INFO {"STMT_DELETE_ACTUAL_EMPTY", NULL} +#define STMT_81 \ + "DELETE FROM actual_node " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + " AND properties IS NULL " \ + " AND conflict_data IS NULL " \ + " AND changelist IS NULL " \ + " AND text_mod IS NULL " \ + " AND older_checksum IS NULL " \ + " AND right_checksum IS NULL " \ + " AND left_checksum IS NULL " \ + "" + +#define STMT_DELETE_ACTUAL_EMPTIES 82 +#define STMT_82_INFO {"STMT_DELETE_ACTUAL_EMPTIES", NULL} +#define STMT_82 \ + "DELETE FROM actual_node " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND properties IS NULL " \ + " AND conflict_data IS NULL " \ + " AND changelist IS NULL " \ + " AND text_mod IS NULL " \ + " AND older_checksum IS NULL " \ + " AND right_checksum IS NULL " \ + " AND left_checksum IS NULL " \ + "" + +#define STMT_DELETE_BASE_NODE 83 +#define STMT_83_INFO {"STMT_DELETE_BASE_NODE", NULL} +#define STMT_83 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ + "" + +#define STMT_DELETE_WORKING_NODE 84 +#define STMT_84_INFO {"STMT_DELETE_WORKING_NODE", NULL} +#define STMT_84 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + " AND op_depth = (SELECT MAX(op_depth) FROM nodes " \ + " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0) " \ + "" + +#define STMT_DELETE_LOWEST_WORKING_NODE 85 +#define STMT_85_INFO {"STMT_DELETE_LOWEST_WORKING_NODE", NULL} +#define STMT_85 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + " AND op_depth = (SELECT MIN(op_depth) FROM nodes " \ + " WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3) " \ + " AND presence = 'base-deleted' " \ + "" + +#define STMT_DELETE_ALL_LAYERS 86 +#define STMT_86_INFO {"STMT_DELETE_ALL_LAYERS", NULL} +#define STMT_86 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "" + +#define STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE 87 +#define STMT_87_INFO {"STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE", NULL} +#define STMT_87 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth >= ?3 " \ + "" + +#define STMT_DELETE_ACTUAL_NODE 88 +#define STMT_88_INFO {"STMT_DELETE_ACTUAL_NODE", NULL} +#define STMT_88 \ + "DELETE FROM actual_node " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "" + +#define STMT_DELETE_ACTUAL_NODE_RECURSIVE 89 +#define STMT_89_INFO {"STMT_DELETE_ACTUAL_NODE_RECURSIVE", NULL} +#define STMT_89 \ + "DELETE FROM actual_node " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + "" + +#define STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST 90 +#define STMT_90_INFO {"STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST", NULL} +#define STMT_90 \ + "DELETE FROM actual_node " \ + "WHERE wc_id = ?1 " \ + " AND local_relpath = ?2 " \ + " AND (changelist IS NULL " \ + " OR NOT EXISTS (SELECT 1 FROM nodes_current c " \ + " WHERE c.wc_id = ?1 AND c.local_relpath = ?2 " \ + " AND c.kind = 'file')) " \ + "" + +#define STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE 91 +#define STMT_91_INFO {"STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE", NULL} +#define STMT_91 \ + "DELETE FROM actual_node " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND (changelist IS NULL " \ + " OR NOT EXISTS (SELECT 1 FROM nodes_current c " \ + " WHERE c.wc_id = ?1 " \ + " AND c.local_relpath = actual_node.local_relpath " \ + " AND c.kind = 'file')) " \ + "" + +#define STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST 92 +#define STMT_92_INFO {"STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST", NULL} +#define STMT_92 \ + "UPDATE actual_node " \ + "SET properties = NULL, " \ + " text_mod = NULL, " \ + " conflict_data = NULL, " \ + " tree_conflict_data = NULL, " \ + " older_checksum = NULL, " \ + " left_checksum = NULL, " \ + " right_checksum = NULL " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "" + +#define STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE 93 +#define STMT_93_INFO {"STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE", NULL} +#define STMT_93 \ + "UPDATE actual_node " \ + "SET properties = NULL, " \ + " text_mod = NULL, " \ + " conflict_data = NULL, " \ + " tree_conflict_data = NULL, " \ + " older_checksum = NULL, " \ + " left_checksum = NULL, " \ + " right_checksum = NULL " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + "" + +#define STMT_UPDATE_NODE_BASE_DEPTH 94 +#define STMT_94_INFO {"STMT_UPDATE_NODE_BASE_DEPTH", NULL} +#define STMT_94 \ + "UPDATE nodes SET depth = ?3 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ + " AND kind='dir' " \ + "" + +#define STMT_UPDATE_NODE_BASE_PRESENCE 95 +#define STMT_95_INFO {"STMT_UPDATE_NODE_BASE_PRESENCE", NULL} +#define STMT_95 \ + "UPDATE nodes SET presence = ?3 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ + "" + +#define STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH 96 +#define STMT_96_INFO {"STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH", NULL} +#define STMT_96 \ + "UPDATE nodes SET presence = ?3, revision = ?4, repos_path = ?5 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ + "" + +#define STMT_LOOK_FOR_WORK 97 +#define STMT_97_INFO {"STMT_LOOK_FOR_WORK", NULL} +#define STMT_97 \ + "SELECT id FROM work_queue LIMIT 1 " \ + "" + +#define STMT_INSERT_WORK_ITEM 98 +#define STMT_98_INFO {"STMT_INSERT_WORK_ITEM", NULL} +#define STMT_98 \ + "INSERT INTO work_queue (work) VALUES (?1) " \ + "" + +#define STMT_SELECT_WORK_ITEM 99 +#define STMT_99_INFO {"STMT_SELECT_WORK_ITEM", NULL} +#define STMT_99 \ + "SELECT id, work FROM work_queue ORDER BY id LIMIT 1 " \ + "" + +#define STMT_DELETE_WORK_ITEM 100 +#define STMT_100_INFO {"STMT_DELETE_WORK_ITEM", NULL} +#define STMT_100 \ + "DELETE FROM work_queue WHERE id = ?1 " \ + "" + +#define STMT_INSERT_OR_IGNORE_PRISTINE 101 +#define STMT_101_INFO {"STMT_INSERT_OR_IGNORE_PRISTINE", NULL} +#define STMT_101 \ + "INSERT OR IGNORE INTO pristine (checksum, md5_checksum, size, refcount) " \ + "VALUES (?1, ?2, ?3, 0) " \ + "" + +#define STMT_INSERT_PRISTINE 102 +#define STMT_102_INFO {"STMT_INSERT_PRISTINE", NULL} +#define STMT_102 \ + "INSERT INTO pristine (checksum, md5_checksum, size, refcount) " \ + "VALUES (?1, ?2, ?3, 0) " \ + "" + +#define STMT_SELECT_PRISTINE 103 +#define STMT_103_INFO {"STMT_SELECT_PRISTINE", NULL} +#define STMT_103 \ + "SELECT md5_checksum " \ + "FROM pristine " \ + "WHERE checksum = ?1 " \ + "" + +#define STMT_SELECT_PRISTINE_SIZE 104 +#define STMT_104_INFO {"STMT_SELECT_PRISTINE_SIZE", NULL} +#define STMT_104 \ + "SELECT size " \ + "FROM pristine " \ + "WHERE checksum = ?1 LIMIT 1 " \ + "" + +#define STMT_SELECT_PRISTINE_BY_MD5 105 +#define STMT_105_INFO {"STMT_SELECT_PRISTINE_BY_MD5", NULL} +#define STMT_105 \ + "SELECT checksum " \ + "FROM pristine " \ + "WHERE md5_checksum = ?1 " \ + "" + +#define STMT_SELECT_UNREFERENCED_PRISTINES 106 +#define STMT_106_INFO {"STMT_SELECT_UNREFERENCED_PRISTINES", NULL} +#define STMT_106 \ + "SELECT checksum " \ + "FROM pristine " \ + "WHERE refcount = 0 " \ + "" + +#define STMT_DELETE_PRISTINE_IF_UNREFERENCED 107 +#define STMT_107_INFO {"STMT_DELETE_PRISTINE_IF_UNREFERENCED", NULL} +#define STMT_107 \ + "DELETE FROM pristine " \ + "WHERE checksum = ?1 AND refcount = 0 " \ + "" + +#define STMT_SELECT_COPY_PRISTINES 108 +#define STMT_108_INFO {"STMT_SELECT_COPY_PRISTINES", NULL} +#define STMT_108 \ + "SELECT n.checksum, md5_checksum, size " \ + "FROM nodes_current n " \ + "LEFT JOIN pristine p ON n.checksum = p.checksum " \ + "WHERE wc_id = ?1 " \ + " AND n.local_relpath = ?2 " \ + " AND n.checksum IS NOT NULL " \ + "UNION ALL " \ + "SELECT n.checksum, md5_checksum, size " \ + "FROM nodes n " \ + "LEFT JOIN pristine p ON n.checksum = p.checksum " \ + "WHERE wc_id = ?1 " \ + " AND (((n.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((n.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth >= " \ + " (SELECT MAX(op_depth) FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2) " \ + " AND n.checksum IS NOT NULL " \ + "" + +#define STMT_VACUUM 109 +#define STMT_109_INFO {"STMT_VACUUM", NULL} +#define STMT_109 \ + "VACUUM " \ + "" + +#define STMT_SELECT_CONFLICT_VICTIMS 110 +#define STMT_110_INFO {"STMT_SELECT_CONFLICT_VICTIMS", NULL} +#define STMT_110 \ + "SELECT local_relpath, conflict_data " \ + "FROM actual_node " \ + "WHERE wc_id = ?1 AND parent_relpath = ?2 AND " \ + " NOT (conflict_data IS NULL) " \ + "" + +#define STMT_INSERT_WC_LOCK 111 +#define STMT_111_INFO {"STMT_INSERT_WC_LOCK", NULL} +#define STMT_111 \ + "INSERT INTO wc_lock (wc_id, local_dir_relpath, locked_levels) " \ + "VALUES (?1, ?2, ?3) " \ + "" + +#define STMT_SELECT_WC_LOCK 112 +#define STMT_112_INFO {"STMT_SELECT_WC_LOCK", NULL} +#define STMT_112 \ + "SELECT locked_levels FROM wc_lock " \ + "WHERE wc_id = ?1 AND local_dir_relpath = ?2 " \ + "" + +#define STMT_SELECT_ANCESTOR_WCLOCKS 113 +#define STMT_113_INFO {"STMT_SELECT_ANCESTOR_WCLOCKS", NULL} +#define STMT_113 \ + "SELECT local_dir_relpath, locked_levels FROM wc_lock " \ + "WHERE wc_id = ?1 " \ + " AND ((local_dir_relpath >= ?3 AND local_dir_relpath <= ?2) " \ + " OR local_dir_relpath = '') " \ + "" + +#define STMT_DELETE_WC_LOCK 114 +#define STMT_114_INFO {"STMT_DELETE_WC_LOCK", NULL} +#define STMT_114 \ + "DELETE FROM wc_lock " \ + "WHERE wc_id = ?1 AND local_dir_relpath = ?2 " \ + "" + +#define STMT_FIND_WC_LOCK 115 +#define STMT_115_INFO {"STMT_FIND_WC_LOCK", NULL} +#define STMT_115 \ + "SELECT local_dir_relpath FROM wc_lock " \ + "WHERE wc_id = ?1 " \ + " AND (((local_dir_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_dir_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + "" + +#define STMT_DELETE_WC_LOCK_ORPHAN 116 +#define STMT_116_INFO {"STMT_DELETE_WC_LOCK_ORPHAN", NULL} +#define STMT_116 \ + "DELETE FROM wc_lock " \ + "WHERE wc_id = ?1 AND local_dir_relpath = ?2 " \ + "AND NOT EXISTS (SELECT 1 FROM nodes " \ + " WHERE nodes.wc_id = ?1 " \ + " AND nodes.local_relpath = wc_lock.local_dir_relpath) " \ + "" + +#define STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE 117 +#define STMT_117_INFO {"STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE", NULL} +#define STMT_117 \ + "DELETE FROM wc_lock " \ + "WHERE wc_id = ?1 " \ + " AND (local_dir_relpath = ?2 " \ + " OR (((local_dir_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_dir_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND NOT EXISTS (SELECT 1 FROM nodes " \ + " WHERE nodes.wc_id = ?1 " \ + " AND nodes.local_relpath = wc_lock.local_dir_relpath) " \ + "" + +#define STMT_APPLY_CHANGES_TO_BASE_NODE 118 +#define STMT_118_INFO {"STMT_APPLY_CHANGES_TO_BASE_NODE", NULL} +#define STMT_118 \ + "INSERT OR REPLACE INTO nodes ( " \ + " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \ + " revision, presence, depth, kind, changed_revision, changed_date, " \ + " changed_author, checksum, properties, dav_cache, symlink_target, " \ + " inherited_props, file_external ) " \ + "VALUES (?1, ?2, 0, " \ + " ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, " \ + " (SELECT file_external FROM nodes " \ + " WHERE wc_id = ?1 " \ + " AND local_relpath = ?2 " \ + " AND op_depth = 0)) " \ + "" + +#define STMT_INSTALL_WORKING_NODE_FOR_DELETE 119 +#define STMT_119_INFO {"STMT_INSTALL_WORKING_NODE_FOR_DELETE", NULL} +#define STMT_119 \ + "INSERT OR REPLACE INTO nodes ( " \ + " wc_id, local_relpath, op_depth, " \ + " parent_relpath, presence, kind) " \ + "VALUES(?1, ?2, ?3, ?4, 'base-deleted', ?5) " \ + "" + +#define STMT_DELETE_NO_LOWER_LAYER 120 +#define STMT_120_INFO {"STMT_DELETE_NO_LOWER_LAYER", NULL} +#define STMT_120 \ + "DELETE FROM nodes " \ + " WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + " AND NOT EXISTS (SELECT 1 FROM nodes n " \ + " WHERE n.wc_id = ?1 " \ + " AND n.local_relpath = nodes.local_relpath " \ + " AND n.op_depth = ?4 " \ + " AND n.presence IN ('normal', 'incomplete')) " \ + "" + +#define STMT_REPLACE_WITH_BASE_DELETED 121 +#define STMT_121_INFO {"STMT_REPLACE_WITH_BASE_DELETED", NULL} +#define STMT_121 \ + "INSERT OR REPLACE INTO nodes (wc_id, local_relpath, op_depth, parent_relpath, " \ + " kind, moved_to, presence) " \ + "SELECT wc_id, local_relpath, op_depth, parent_relpath, " \ + " kind, moved_to, 'base-deleted' " \ + " FROM nodes " \ + " WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_INSERT_DELETE_FROM_NODE_RECURSIVE 122 +#define STMT_122_INFO {"STMT_INSERT_DELETE_FROM_NODE_RECURSIVE", NULL} +#define STMT_122 \ + "INSERT INTO nodes ( " \ + " wc_id, local_relpath, op_depth, parent_relpath, presence, kind) " \ + "SELECT wc_id, local_relpath, ?4 , parent_relpath, 'base-deleted', " \ + " kind " \ + "FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + " AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'server-excluded') " \ + " AND file_external IS NULL " \ + "" + +#define STMT_INSERT_WORKING_NODE_FROM_BASE_COPY 123 +#define STMT_123_INFO {"STMT_INSERT_WORKING_NODE_FROM_BASE_COPY", NULL} +#define STMT_123 \ + "INSERT INTO nodes ( " \ + " wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, " \ + " revision, presence, depth, kind, changed_revision, changed_date, " \ + " changed_author, checksum, properties, translated_size, last_mod_time, " \ + " symlink_target ) " \ + "SELECT wc_id, local_relpath, ?3 , parent_relpath, repos_id, " \ + " repos_path, revision, presence, depth, kind, changed_revision, " \ + " changed_date, changed_author, checksum, properties, translated_size, " \ + " last_mod_time, symlink_target " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ + "" + +#define STMT_INSERT_DELETE_FROM_BASE 124 +#define STMT_124_INFO {"STMT_INSERT_DELETE_FROM_BASE", NULL} +#define STMT_124 \ + "INSERT INTO nodes ( " \ + " wc_id, local_relpath, op_depth, parent_relpath, presence, kind) " \ + "SELECT wc_id, local_relpath, ?3 , parent_relpath, " \ + " 'base-deleted', kind " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ + "" + +#define STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE 125 +#define STMT_125_INFO {"STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE", NULL} +#define STMT_125 \ + "UPDATE nodes SET op_depth = ?3 + 1 " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_UPDATE_OP_DEPTH_RECURSIVE 126 +#define STMT_126_INFO {"STMT_UPDATE_OP_DEPTH_RECURSIVE", NULL} +#define STMT_126 \ + "UPDATE nodes SET op_depth = ?4, moved_here = NULL " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_DOES_NODE_EXIST 127 +#define STMT_127_INFO {"STMT_DOES_NODE_EXIST", NULL} +#define STMT_127 \ + "SELECT 1 FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "LIMIT 1 " \ + "" + +#define STMT_HAS_SERVER_EXCLUDED_DESCENDANTS 128 +#define STMT_128_INFO {"STMT_HAS_SERVER_EXCLUDED_DESCENDANTS", NULL} +#define STMT_128 \ + "SELECT local_relpath FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 AND presence = 'server-excluded' " \ + "LIMIT 1 " \ + "" + +#define STMT_SELECT_ALL_EXCLUDED_DESCENDANTS 129 +#define STMT_129_INFO {"STMT_SELECT_ALL_EXCLUDED_DESCENDANTS", NULL} +#define STMT_129 \ + "SELECT local_relpath FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 " \ + " AND (presence = 'server-excluded' OR presence = 'excluded') " \ + "" + +#define STMT_INSERT_WORKING_NODE_COPY_FROM 130 +#define STMT_130_INFO {"STMT_INSERT_WORKING_NODE_COPY_FROM", NULL} +#define STMT_130 \ + "INSERT OR REPLACE INTO nodes ( " \ + " wc_id, local_relpath, op_depth, parent_relpath, repos_id, " \ + " repos_path, revision, presence, depth, moved_here, kind, changed_revision, " \ + " changed_date, changed_author, checksum, properties, translated_size, " \ + " last_mod_time, symlink_target, moved_to ) " \ + "SELECT wc_id, ?3 , ?4 , ?5 , " \ + " repos_id, repos_path, revision, ?6 , depth, " \ + " ?7, kind, changed_revision, changed_date, " \ + " changed_author, checksum, properties, translated_size, " \ + " last_mod_time, symlink_target, " \ + " (SELECT dst.moved_to FROM nodes AS dst " \ + " WHERE dst.wc_id = ?1 " \ + " AND dst.local_relpath = ?3 " \ + " AND dst.op_depth = ?4) " \ + "FROM nodes_current " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "" + +#define STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH 131 +#define STMT_131_INFO {"STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH", NULL} +#define STMT_131 \ + "INSERT OR REPLACE INTO nodes ( " \ + " wc_id, local_relpath, op_depth, parent_relpath, repos_id, " \ + " repos_path, revision, presence, depth, moved_here, kind, changed_revision, " \ + " changed_date, changed_author, checksum, properties, translated_size, " \ + " last_mod_time, symlink_target, moved_to ) " \ + "SELECT wc_id, ?3 , ?4 , ?5 , " \ + " repos_id, repos_path, revision, ?6 , depth, " \ + " ?8 , kind, changed_revision, changed_date, " \ + " changed_author, checksum, properties, translated_size, " \ + " last_mod_time, symlink_target, " \ + " (SELECT dst.moved_to FROM nodes AS dst " \ + " WHERE dst.wc_id = ?1 " \ + " AND dst.local_relpath = ?3 " \ + " AND dst.op_depth = ?4) " \ + "FROM nodes " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?7 " \ + "" + +#define STMT_UPDATE_BASE_REVISION 132 +#define STMT_132_INFO {"STMT_UPDATE_BASE_REVISION", NULL} +#define STMT_132 \ + "UPDATE nodes SET revision = ?3 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ + "" + +#define STMT_UPDATE_BASE_REPOS 133 +#define STMT_133_INFO {"STMT_UPDATE_BASE_REPOS", NULL} +#define STMT_133 \ + "UPDATE nodes SET repos_id = ?3, repos_path = ?4 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 " \ + "" + +#define STMT_ACTUAL_HAS_CHILDREN 134 +#define STMT_134_INFO {"STMT_ACTUAL_HAS_CHILDREN", NULL} +#define STMT_134 \ + "SELECT 1 FROM actual_node " \ + "WHERE wc_id = ?1 AND parent_relpath = ?2 " \ + "LIMIT 1 " \ + "" + +#define STMT_INSERT_EXTERNAL 135 +#define STMT_135_INFO {"STMT_INSERT_EXTERNAL", NULL} +#define STMT_135 \ + "INSERT OR REPLACE INTO externals ( " \ + " wc_id, local_relpath, parent_relpath, presence, kind, def_local_relpath, " \ + " repos_id, def_repos_relpath, def_operational_revision, def_revision) " \ + "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10) " \ + "" + +#define STMT_SELECT_EXTERNAL_INFO 136 +#define STMT_136_INFO {"STMT_SELECT_EXTERNAL_INFO", NULL} +#define STMT_136 \ + "SELECT presence, kind, def_local_relpath, repos_id, " \ + " def_repos_relpath, def_operational_revision, def_revision " \ + "FROM externals WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "LIMIT 1 " \ + "" + +#define STMT_DELETE_FILE_EXTERNALS 137 +#define STMT_137_INFO {"STMT_DELETE_FILE_EXTERNALS", NULL} +#define STMT_137 \ + "DELETE FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 " \ + " AND file_external IS NOT NULL " \ + "" + +#define STMT_DELETE_FILE_EXTERNAL_REGISTATIONS 138 +#define STMT_138_INFO {"STMT_DELETE_FILE_EXTERNAL_REGISTATIONS", NULL} +#define STMT_138 \ + "DELETE FROM externals " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND kind != 'dir' " \ + "" + +#define STMT_DELETE_EXTERNAL_REGISTATIONS 139 +#define STMT_139_INFO {"STMT_DELETE_EXTERNAL_REGISTATIONS", NULL} +#define STMT_139 \ + "DELETE FROM externals " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + "" + +#define STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW 140 +#define STMT_140_INFO {"STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW", NULL} +#define STMT_140 \ + "SELECT local_relpath, kind, def_repos_relpath, " \ + " (SELECT root FROM repository AS r WHERE r.id = e.repos_id) " \ + "FROM externals e " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND def_revision IS NULL " \ + " AND repos_id = (SELECT repos_id " \ + " FROM nodes AS n " \ + " WHERE n.wc_id = ?1 " \ + " AND n.local_relpath = '' " \ + " AND n.op_depth = 0) " \ + " AND ((kind='dir') " \ + " OR EXISTS (SELECT 1 FROM nodes " \ + " WHERE nodes.wc_id = e.wc_id " \ + " AND nodes.local_relpath = e.parent_relpath)) " \ + "" + +#define STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW 141 +#define STMT_141_INFO {"STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW", NULL} +#define STMT_141 \ + "SELECT local_relpath, kind, def_repos_relpath, " \ + " (SELECT root FROM repository AS r WHERE r.id = e.repos_id) " \ + "FROM externals e " \ + "WHERE wc_id = ?1 " \ + " AND (((e.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((e.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND parent_relpath = ?2 " \ + " AND def_revision IS NULL " \ + " AND repos_id = (SELECT repos_id " \ + " FROM nodes AS n " \ + " WHERE n.wc_id = ?1 " \ + " AND n.local_relpath = '' " \ + " AND n.op_depth = 0) " \ + " AND ((kind='dir') " \ + " OR EXISTS (SELECT 1 FROM nodes " \ + " WHERE nodes.wc_id = e.wc_id " \ + " AND nodes.local_relpath = e.parent_relpath)) " \ + "" + +#define STMT_SELECT_EXTERNALS_DEFINED 142 +#define STMT_142_INFO {"STMT_SELECT_EXTERNALS_DEFINED", NULL} +#define STMT_142 \ + "SELECT local_relpath, def_local_relpath " \ + "FROM externals " \ + "WHERE (wc_id = ?1 AND def_local_relpath = ?2) " \ + " OR (wc_id = ?1 AND (((def_local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((def_local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + "" + +#define STMT_DELETE_EXTERNAL 143 +#define STMT_143_INFO {"STMT_DELETE_EXTERNAL", NULL} +#define STMT_143 \ + "DELETE FROM externals " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "" + +#define STMT_SELECT_EXTERNAL_PROPERTIES 144 +#define STMT_144_INFO {"STMT_SELECT_EXTERNAL_PROPERTIES", NULL} +#define STMT_144 \ + "SELECT IFNULL((SELECT properties FROM actual_node a " \ + " WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), " \ + " properties), " \ + " local_relpath, depth " \ + "FROM nodes_current n " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + " AND kind = 'dir' AND presence IN ('normal', 'incomplete') " \ + "UNION ALL " \ + "SELECT IFNULL((SELECT properties FROM actual_node a " \ + " WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), " \ + " properties), " \ + " local_relpath, depth " \ + "FROM nodes_current n " \ + "WHERE wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND kind = 'dir' AND presence IN ('normal', 'incomplete') " \ + "" + +#define STMT_SELECT_CURRENT_PROPS_RECURSIVE 145 +#define STMT_145_INFO {"STMT_SELECT_CURRENT_PROPS_RECURSIVE", NULL} +#define STMT_145 \ + "SELECT IFNULL((SELECT properties FROM actual_node a " \ + " WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), " \ + " properties), " \ + " local_relpath " \ + "FROM nodes_current n " \ + "WHERE (wc_id = ?1 AND local_relpath = ?2) " \ + " OR (wc_id = ?1 AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + "" + +#define STMT_PRAGMA_LOCKING_MODE 146 +#define STMT_146_INFO {"STMT_PRAGMA_LOCKING_MODE", NULL} +#define STMT_146 \ + "PRAGMA locking_mode = exclusive " \ + "" + +#define STMT_INSERT_ACTUAL_NODE 147 +#define STMT_147_INFO {"STMT_INSERT_ACTUAL_NODE", NULL} +#define STMT_147 \ + "INSERT OR REPLACE INTO actual_node ( " \ + " wc_id, local_relpath, parent_relpath, properties, changelist, conflict_data) " \ + "VALUES (?1, ?2, ?3, ?4, ?5, ?6) " \ + "" + +#define STMT_UPDATE_ACTUAL_CONFLICT_DATA 148 +#define STMT_148_INFO {"STMT_UPDATE_ACTUAL_CONFLICT_DATA", NULL} +#define STMT_148 \ + "UPDATE actual_node SET conflict_data = ?3 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 " \ + "" + +#define STMT_INSERT_ACTUAL_CONFLICT_DATA 149 +#define STMT_149_INFO {"STMT_INSERT_ACTUAL_CONFLICT_DATA", NULL} +#define STMT_149 \ + "INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath) " \ + "VALUES (?1, ?2, ?3, ?4) " \ + "" + +#define STMT_SELECT_ALL_FILES 150 +#define STMT_150_INFO {"STMT_SELECT_ALL_FILES", NULL} +#define STMT_150 \ + "SELECT local_relpath FROM nodes_current " \ + "WHERE wc_id = ?1 AND parent_relpath = ?2 AND kind = 'file' " \ + "" + +#define STMT_UPDATE_NODE_PROPS 151 +#define STMT_151_INFO {"STMT_UPDATE_NODE_PROPS", NULL} +#define STMT_151 \ + "UPDATE nodes SET properties = ?4 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \ + "" + +#define STMT_PRAGMA_TABLE_INFO_NODES 152 +#define STMT_152_INFO {"STMT_PRAGMA_TABLE_INFO_NODES", NULL} +#define STMT_152 \ + "PRAGMA table_info(\"NODES\") " \ + "" + +#define STMT_CREATE_TARGET_PROP_CACHE 153 +#define STMT_153_INFO {"STMT_CREATE_TARGET_PROP_CACHE", NULL} +#define STMT_153 \ + "DROP TABLE IF EXISTS target_prop_cache; " \ + "CREATE TEMPORARY TABLE target_prop_cache ( " \ + " local_relpath TEXT NOT NULL PRIMARY KEY, " \ + " kind TEXT NOT NULL, " \ + " properties BLOB " \ + "); " \ + "" + +#define STMT_CACHE_TARGET_PROPS 154 +#define STMT_154_INFO {"STMT_CACHE_TARGET_PROPS", NULL} +#define STMT_154 \ + "INSERT INTO target_prop_cache(local_relpath, kind, properties) " \ + " SELECT n.local_relpath, n.kind, " \ + " IFNULL((SELECT properties FROM actual_node AS a " \ + " WHERE a.wc_id = n.wc_id " \ + " AND a.local_relpath = n.local_relpath), " \ + " n.properties) " \ + " FROM targets_list AS t " \ + " JOIN nodes AS n " \ + " ON n.wc_id = ?1 " \ + " AND n.local_relpath = t.local_relpath " \ + " AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3 " \ + " WHERE n3.wc_id = ?1 " \ + " AND n3.local_relpath = t.local_relpath) " \ + " WHERE t.wc_id = ?1 " \ + " AND (presence='normal' OR presence='incomplete') " \ + " ORDER BY t.local_relpath " \ + "" + +#define STMT_CACHE_TARGET_PRISTINE_PROPS 155 +#define STMT_155_INFO {"STMT_CACHE_TARGET_PRISTINE_PROPS", NULL} +#define STMT_155 \ + "INSERT INTO target_prop_cache(local_relpath, kind, properties) " \ + " SELECT n.local_relpath, n.kind, " \ + " CASE n.presence " \ + " WHEN 'base-deleted' " \ + " THEN (SELECT properties FROM nodes AS p " \ + " WHERE p.wc_id = n.wc_id " \ + " AND p.local_relpath = n.local_relpath " \ + " AND p.op_depth < n.op_depth " \ + " ORDER BY p.op_depth DESC ) " \ + " ELSE properties END " \ + " FROM targets_list AS t " \ + " JOIN nodes AS n " \ + " ON n.wc_id = ?1 " \ + " AND n.local_relpath = t.local_relpath " \ + " AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3 " \ + " WHERE n3.wc_id = ?1 " \ + " AND n3.local_relpath = t.local_relpath) " \ + " WHERE t.wc_id = ?1 " \ + " AND (presence = 'normal' " \ + " OR presence = 'incomplete' " \ + " OR presence = 'base-deleted') " \ + " ORDER BY t.local_relpath " \ + "" + +#define STMT_SELECT_ALL_TARGET_PROP_CACHE 156 +#define STMT_156_INFO {"STMT_SELECT_ALL_TARGET_PROP_CACHE", NULL} +#define STMT_156 \ + "SELECT local_relpath, properties FROM target_prop_cache " \ + "ORDER BY local_relpath " \ + "" + +#define STMT_DROP_TARGET_PROP_CACHE 157 +#define STMT_157_INFO {"STMT_DROP_TARGET_PROP_CACHE", NULL} +#define STMT_157 \ + "DROP TABLE target_prop_cache; " \ + "" + +#define STMT_CREATE_REVERT_LIST 158 +#define STMT_158_INFO {"STMT_CREATE_REVERT_LIST", NULL} +#define STMT_158 \ + "DROP TABLE IF EXISTS revert_list; " \ + "CREATE TEMPORARY TABLE revert_list ( " \ + " local_relpath TEXT NOT NULL, " \ + " actual INTEGER NOT NULL, " \ + " conflict_data BLOB, " \ + " notify INTEGER, " \ + " op_depth INTEGER, " \ + " repos_id INTEGER, " \ + " kind TEXT, " \ + " PRIMARY KEY (local_relpath, actual) " \ + " ); " \ + "DROP TRIGGER IF EXISTS trigger_revert_list_nodes; " \ + "CREATE TEMPORARY TRIGGER trigger_revert_list_nodes " \ + "BEFORE DELETE ON nodes " \ + "BEGIN " \ + " INSERT OR REPLACE INTO revert_list(local_relpath, actual, op_depth, " \ + " repos_id, kind) " \ + " SELECT OLD.local_relpath, 0, OLD.op_depth, OLD.repos_id, OLD.kind; " \ + "END; " \ + "DROP TRIGGER IF EXISTS trigger_revert_list_actual_delete; " \ + "CREATE TEMPORARY TRIGGER trigger_revert_list_actual_delete " \ + "BEFORE DELETE ON actual_node " \ + "BEGIN " \ + " INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data, " \ + " notify) " \ + " SELECT OLD.local_relpath, 1, OLD.conflict_data, " \ + " CASE " \ + " WHEN OLD.properties IS NOT NULL " \ + " THEN 1 " \ + " WHEN NOT EXISTS(SELECT 1 FROM NODES n " \ + " WHERE n.wc_id = OLD.wc_id " \ + " AND n.local_relpath = OLD.local_relpath) " \ + " THEN 1 " \ + " ELSE NULL " \ + " END; " \ + "END; " \ + "DROP TRIGGER IF EXISTS trigger_revert_list_actual_update; " \ + "CREATE TEMPORARY TRIGGER trigger_revert_list_actual_update " \ + "BEFORE UPDATE ON actual_node " \ + "BEGIN " \ + " INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data, " \ + " notify) " \ + " SELECT OLD.local_relpath, 1, OLD.conflict_data, " \ + " CASE " \ + " WHEN OLD.properties IS NOT NULL " \ + " THEN 1 " \ + " WHEN NOT EXISTS(SELECT 1 FROM NODES n " \ + " WHERE n.wc_id = OLD.wc_id " \ + " AND n.local_relpath = OLD.local_relpath) " \ + " THEN 1 " \ + " ELSE NULL " \ + " END; " \ + "END " \ + "" + +#define STMT_DROP_REVERT_LIST_TRIGGERS 159 +#define STMT_159_INFO {"STMT_DROP_REVERT_LIST_TRIGGERS", NULL} +#define STMT_159 \ + "DROP TRIGGER trigger_revert_list_nodes; " \ + "DROP TRIGGER trigger_revert_list_actual_delete; " \ + "DROP TRIGGER trigger_revert_list_actual_update " \ + "" + +#define STMT_SELECT_REVERT_LIST 160 +#define STMT_160_INFO {"STMT_SELECT_REVERT_LIST", NULL} +#define STMT_160 \ + "SELECT actual, notify, kind, op_depth, repos_id, conflict_data " \ + "FROM revert_list " \ + "WHERE local_relpath = ?1 " \ + "ORDER BY actual DESC " \ + "" + +#define STMT_SELECT_REVERT_LIST_COPIED_CHILDREN 161 +#define STMT_161_INFO {"STMT_SELECT_REVERT_LIST_COPIED_CHILDREN", NULL} +#define STMT_161 \ + "SELECT local_relpath, kind " \ + "FROM revert_list " \ + "WHERE (((local_relpath) > (CASE (?1) WHEN '' THEN '' ELSE (?1) || '/' END)) AND ((local_relpath) < CASE (?1) WHEN '' THEN X'FFFF' ELSE (?1) || '0' END)) " \ + " AND op_depth >= ?2 " \ + " AND repos_id IS NOT NULL " \ + "ORDER BY local_relpath " \ + "" + +#define STMT_DELETE_REVERT_LIST 162 +#define STMT_162_INFO {"STMT_DELETE_REVERT_LIST", NULL} +#define STMT_162 \ + "DELETE FROM revert_list WHERE local_relpath = ?1 " \ + "" + +#define STMT_SELECT_REVERT_LIST_RECURSIVE 163 +#define STMT_163_INFO {"STMT_SELECT_REVERT_LIST_RECURSIVE", NULL} +#define STMT_163 \ + "SELECT DISTINCT local_relpath " \ + "FROM revert_list " \ + "WHERE (local_relpath = ?1 " \ + " OR (((local_relpath) > (CASE (?1) WHEN '' THEN '' ELSE (?1) || '/' END)) AND ((local_relpath) < CASE (?1) WHEN '' THEN X'FFFF' ELSE (?1) || '0' END))) " \ + " AND (notify OR actual = 0) " \ + "ORDER BY local_relpath " \ + "" + +#define STMT_DELETE_REVERT_LIST_RECURSIVE 164 +#define STMT_164_INFO {"STMT_DELETE_REVERT_LIST_RECURSIVE", NULL} +#define STMT_164 \ + "DELETE FROM revert_list " \ + "WHERE (local_relpath = ?1 " \ + " OR (((local_relpath) > (CASE (?1) WHEN '' THEN '' ELSE (?1) || '/' END)) AND ((local_relpath) < CASE (?1) WHEN '' THEN X'FFFF' ELSE (?1) || '0' END))) " \ + "" + +#define STMT_DROP_REVERT_LIST 165 +#define STMT_165_INFO {"STMT_DROP_REVERT_LIST", NULL} +#define STMT_165 \ + "DROP TABLE IF EXISTS revert_list " \ + "" + +#define STMT_CREATE_DELETE_LIST 166 +#define STMT_166_INFO {"STMT_CREATE_DELETE_LIST", NULL} +#define STMT_166 \ + "DROP TABLE IF EXISTS delete_list; " \ + "CREATE TEMPORARY TABLE delete_list ( " \ + " local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE " \ + " ) " \ + "" + +#define STMT_INSERT_DELETE_LIST 167 +#define STMT_167_INFO {"STMT_INSERT_DELETE_LIST", NULL} +#define STMT_167 \ + "INSERT INTO delete_list(local_relpath) " \ + "SELECT local_relpath FROM nodes AS n " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth >= ?3 " \ + " AND op_depth = (SELECT MAX(s.op_depth) FROM nodes AS s " \ + " WHERE s.wc_id = ?1 " \ + " AND s.local_relpath = n.local_relpath) " \ + " AND presence NOT IN ('base-deleted', 'not-present', 'excluded', 'server-excluded') " \ + " AND file_external IS NULL " \ + "" + +#define STMT_SELECT_DELETE_LIST 168 +#define STMT_168_INFO {"STMT_SELECT_DELETE_LIST", NULL} +#define STMT_168 \ + "SELECT local_relpath FROM delete_list " \ + "ORDER BY local_relpath " \ + "" + +#define STMT_FINALIZE_DELETE 169 +#define STMT_169_INFO {"STMT_FINALIZE_DELETE", NULL} +#define STMT_169 \ + "DROP TABLE IF EXISTS delete_list " \ + "" + +#define STMT_CREATE_UPDATE_MOVE_LIST 170 +#define STMT_170_INFO {"STMT_CREATE_UPDATE_MOVE_LIST", NULL} +#define STMT_170 \ + "DROP TABLE IF EXISTS update_move_list; " \ + "CREATE TEMPORARY TABLE update_move_list ( " \ + " local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE, " \ + " action INTEGER NOT NULL, " \ + " kind INTEGER NOT NULL, " \ + " content_state INTEGER NOT NULL, " \ + " prop_state INTEGER NOT NULL " \ + " ) " \ + "" + +#define STMT_INSERT_UPDATE_MOVE_LIST 171 +#define STMT_171_INFO {"STMT_INSERT_UPDATE_MOVE_LIST", NULL} +#define STMT_171 \ + "INSERT INTO update_move_list(local_relpath, action, kind, content_state, " \ + " prop_state) " \ + "VALUES (?1, ?2, ?3, ?4, ?5) " \ + "" + +#define STMT_SELECT_UPDATE_MOVE_LIST 172 +#define STMT_172_INFO {"STMT_SELECT_UPDATE_MOVE_LIST", NULL} +#define STMT_172 \ + "SELECT local_relpath, action, kind, content_state, prop_state " \ + "FROM update_move_list " \ + "ORDER BY local_relpath " \ + "" + +#define STMT_FINALIZE_UPDATE_MOVE 173 +#define STMT_173_INFO {"STMT_FINALIZE_UPDATE_MOVE", NULL} +#define STMT_173 \ + "DROP TABLE IF EXISTS update_move_list " \ + "" + +#define STMT_SELECT_MIN_MAX_REVISIONS 174 +#define STMT_174_INFO {"STMT_SELECT_MIN_MAX_REVISIONS", NULL} +#define STMT_174 \ + "SELECT MIN(revision), MAX(revision), " \ + " MIN(changed_revision), MAX(changed_revision) FROM nodes " \ + " WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND presence IN ('normal', 'incomplete') " \ + " AND file_external IS NULL " \ + " AND op_depth = 0 " \ + "" + +#define STMT_HAS_SPARSE_NODES 175 +#define STMT_175_INFO {"STMT_HAS_SPARSE_NODES", NULL} +#define STMT_175 \ + "SELECT 1 FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = 0 " \ + " AND (presence IN ('server-excluded', 'excluded') " \ + " OR depth NOT IN ('infinity', 'unknown')) " \ + " AND file_external IS NULL " \ + "LIMIT 1 " \ + "" + +#define STMT_SUBTREE_HAS_TREE_MODIFICATIONS 176 +#define STMT_176_INFO {"STMT_SUBTREE_HAS_TREE_MODIFICATIONS", NULL} +#define STMT_176 \ + "SELECT 1 FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth > 0 " \ + "LIMIT 1 " \ + "" + +#define STMT_SUBTREE_HAS_PROP_MODIFICATIONS 177 +#define STMT_177_INFO {"STMT_SUBTREE_HAS_PROP_MODIFICATIONS", NULL} +#define STMT_177 \ + "SELECT 1 FROM actual_node " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND properties IS NOT NULL " \ + "LIMIT 1 " \ + "" + +#define STMT_HAS_SWITCHED 178 +#define STMT_178_INFO {"STMT_HAS_SWITCHED", NULL} +#define STMT_178 \ + "SELECT 1 " \ + "FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 " \ + " AND file_external IS NULL " \ + " AND presence IN ('normal', 'incomplete') " \ + " AND repos_path IS NOT (CASE WHEN (?2) = '' THEN (CASE WHEN (?3) = '' THEN (local_relpath) WHEN (local_relpath) = '' THEN (?3) ELSE (?3) || '/' || (local_relpath) END) WHEN (?3) = '' THEN (CASE WHEN (?2) = '' THEN (local_relpath) WHEN SUBSTR((local_relpath), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(local_relpath) THEN '' WHEN SUBSTR((local_relpath), LENGTH(?2)+1, 1) = '/' THEN SUBSTR((local_relpath), LENGTH(?2)+2) END END) WHEN SUBSTR((local_relpath), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(local_relpath) THEN (?3) WHEN SUBSTR((local_relpath), LENGTH(?2)+1, 1) = '/' THEN (?3) || SUBSTR((local_relpath), LENGTH(?2)+1) END END) " \ + "LIMIT 1 " \ + "" + +#define STMT_SELECT_BASE_FILES_RECURSIVE 179 +#define STMT_179_INFO {"STMT_SELECT_BASE_FILES_RECURSIVE", NULL} +#define STMT_179 \ + "SELECT local_relpath, translated_size, last_mod_time FROM nodes AS n " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = 0 " \ + " AND kind='file' " \ + " AND presence='normal' " \ + " AND file_external IS NULL " \ + "" + +#define STMT_SELECT_MOVED_FROM_RELPATH 180 +#define STMT_180_INFO {"STMT_SELECT_MOVED_FROM_RELPATH", NULL} +#define STMT_180 \ + "SELECT local_relpath, op_depth FROM nodes " \ + "WHERE wc_id = ?1 AND moved_to = ?2 AND op_depth > 0 " \ + "" + +#define STMT_UPDATE_MOVED_TO_RELPATH 181 +#define STMT_181_INFO {"STMT_UPDATE_MOVED_TO_RELPATH", NULL} +#define STMT_181 \ + "UPDATE nodes SET moved_to = ?4 " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \ + "" + +#define STMT_CLEAR_MOVED_TO_RELPATH 182 +#define STMT_182_INFO {"STMT_CLEAR_MOVED_TO_RELPATH", NULL} +#define STMT_182 \ + "UPDATE nodes SET moved_to = NULL " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 " \ + "" + +#define STMT_CLEAR_MOVED_HERE_RECURSIVE 183 +#define STMT_183_INFO {"STMT_CLEAR_MOVED_HERE_RECURSIVE", NULL} +#define STMT_183 \ + "UPDATE nodes SET moved_here = NULL " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_SELECT_MOVED_HERE_CHILDREN 184 +#define STMT_184_INFO {"STMT_SELECT_MOVED_HERE_CHILDREN", NULL} +#define STMT_184 \ + "SELECT moved_to, local_relpath FROM nodes " \ + "WHERE wc_id = ?1 AND op_depth > 0 " \ + " AND (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + "" + +#define STMT_SELECT_MOVED_FOR_DELETE 185 +#define STMT_185_INFO {"STMT_SELECT_MOVED_FOR_DELETE", NULL} +#define STMT_185 \ + "SELECT local_relpath, moved_to, op_depth FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND moved_to IS NOT NULL " \ + " AND op_depth >= (SELECT MAX(op_depth) FROM nodes o " \ + " WHERE o.wc_id = ?1 " \ + " AND o.local_relpath = ?2) " \ + "" + +#define STMT_UPDATE_MOVED_TO_DESCENDANTS 186 +#define STMT_186_INFO {"STMT_UPDATE_MOVED_TO_DESCENDANTS", NULL} +#define STMT_186 \ + "UPDATE nodes SET moved_to = (CASE WHEN (?2) = '' THEN (CASE WHEN (?3) = '' THEN (moved_to) WHEN (moved_to) = '' THEN (?3) ELSE (?3) || '/' || (moved_to) END) WHEN (?3) = '' THEN (CASE WHEN (?2) = '' THEN (moved_to) WHEN SUBSTR((moved_to), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(moved_to) THEN '' WHEN SUBSTR((moved_to), LENGTH(?2)+1, 1) = '/' THEN SUBSTR((moved_to), LENGTH(?2)+2) END END) WHEN SUBSTR((moved_to), 1, LENGTH(?2)) = (?2) THEN CASE WHEN LENGTH(?2) = LENGTH(moved_to) THEN (?3) WHEN SUBSTR((moved_to), LENGTH(?2)+1, 1) = '/' THEN (?3) || SUBSTR((moved_to), LENGTH(?2)+1) END END) " \ + " WHERE wc_id = ?1 " \ + " AND (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + "" + +#define STMT_CLEAR_MOVED_TO_DESCENDANTS 187 +#define STMT_187_INFO {"STMT_CLEAR_MOVED_TO_DESCENDANTS", NULL} +#define STMT_187 \ + "UPDATE nodes SET moved_to = NULL " \ + " WHERE wc_id = ?1 " \ + " AND (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + "" + +#define STMT_SELECT_MOVED_PAIR2 188 +#define STMT_188_INFO {"STMT_SELECT_MOVED_PAIR2", NULL} +#define STMT_188 \ + "SELECT local_relpath, moved_to, op_depth FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND moved_to IS NOT NULL " \ + " AND NOT (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth >= (SELECT MAX(op_depth) FROM nodes o " \ + " WHERE o.wc_id = ?1 " \ + " AND o.local_relpath = ?2) " \ + "" + +#define STMT_SELECT_MOVED_PAIR3 189 +#define STMT_189_INFO {"STMT_SELECT_MOVED_PAIR3", NULL} +#define STMT_189 \ + "SELECT local_relpath, moved_to, op_depth, kind FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth > ?3 " \ + " AND moved_to IS NOT NULL " \ + "" + +#define STMT_SELECT_MOVED_OUTSIDE 190 +#define STMT_190_INFO {"STMT_SELECT_MOVED_OUTSIDE", NULL} +#define STMT_190 \ + "SELECT local_relpath, moved_to FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth >= ?3 " \ + " AND moved_to IS NOT NULL " \ + " AND NOT (((moved_to) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((moved_to) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + "" + +#define STMT_SELECT_OP_DEPTH_MOVED_PAIR 191 +#define STMT_191_INFO {"STMT_SELECT_OP_DEPTH_MOVED_PAIR", NULL} +#define STMT_191 \ + "SELECT n.local_relpath, n.moved_to, " \ + " (SELECT o.repos_path FROM nodes AS o " \ + " WHERE o.wc_id = n.wc_id " \ + " AND o.local_relpath = n.local_relpath " \ + " AND o.op_depth < ?3 ORDER BY o.op_depth DESC LIMIT 1) " \ + "FROM nodes AS n " \ + "WHERE n.wc_id = ?1 " \ + " AND (((n.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((n.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND n.op_depth = ?3 " \ + " AND n.moved_to IS NOT NULL " \ + "" + +#define STMT_SELECT_MOVED_DESCENDANTS 192 +#define STMT_192_INFO {"STMT_SELECT_MOVED_DESCENDANTS", NULL} +#define STMT_192 \ + "SELECT n.local_relpath, h.moved_to " \ + "FROM nodes n, nodes h " \ + "WHERE n.wc_id = ?1 " \ + " AND h.wc_id = ?1 " \ + " AND (((n.local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((n.local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND h.local_relpath = n.local_relpath " \ + " AND n.op_depth = ?3 " \ + " AND h.op_depth = (SELECT MIN(o.op_depth) " \ + " FROM nodes o " \ + " WHERE o.wc_id = ?1 " \ + " AND o.local_relpath = n.local_relpath " \ + " AND o.op_depth > ?3) " \ + " AND h.moved_to IS NOT NULL " \ + "" + +#define STMT_COMMIT_UPDATE_ORIGIN 193 +#define STMT_193_INFO {"STMT_COMMIT_UPDATE_ORIGIN", NULL} +#define STMT_193 \ + "UPDATE nodes SET repos_id = ?4, " \ + " repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1), " \ + " revision = ?6 " \ + "WHERE wc_id = ?1 " \ + " AND (local_relpath = ?2 " \ + " OR (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END))) " \ + " AND op_depth = ?3 " \ + "" + +#define STMT_HAS_LAYER_BETWEEN 194 +#define STMT_194_INFO {"STMT_HAS_LAYER_BETWEEN", NULL} +#define STMT_194 \ + "SELECT 1 FROM NODES " \ + "WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 AND op_depth < ?4 " \ + "" + +#define STMT_SELECT_REPOS_PATH_REVISION 195 +#define STMT_195_INFO {"STMT_SELECT_REPOS_PATH_REVISION", NULL} +#define STMT_195 \ + "SELECT local_relpath, repos_path, revision FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 " \ + "ORDER BY local_relpath " \ + "" + +#define STMT_SELECT_HAS_NON_FILE_CHILDREN 196 +#define STMT_196_INFO {"STMT_SELECT_HAS_NON_FILE_CHILDREN", NULL} +#define STMT_196 \ + "SELECT 1 FROM nodes " \ + "WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = 0 AND kind != 'file' " \ + "" + +#define STMT_SELECT_HAS_GRANDCHILDREN 197 +#define STMT_197_INFO {"STMT_SELECT_HAS_GRANDCHILDREN", NULL} +#define STMT_197 \ + "SELECT 1 FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((parent_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((parent_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 " \ + " AND file_external IS NULL " \ + "" + +#define STMT_SELECT_ALL_NODES 198 +#define STMT_198_INFO {"STMT_SELECT_ALL_NODES", NULL} +#define STMT_198 \ + "SELECT op_depth, local_relpath, parent_relpath, file_external FROM nodes " \ + "WHERE wc_id = ?1 " \ + "" + +#define STMT_SELECT_IPROPS 199 +#define STMT_199_INFO {"STMT_SELECT_IPROPS", NULL} +#define STMT_199 \ + "SELECT inherited_props FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND local_relpath = ?2 " \ + " AND op_depth = 0 " \ + "" + +#define STMT_UPDATE_IPROP 200 +#define STMT_200_INFO {"STMT_UPDATE_IPROP", NULL} +#define STMT_200 \ + "UPDATE nodes " \ + "SET inherited_props = ?3 " \ + "WHERE (wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0) " \ + "" + +#define STMT_SELECT_IPROPS_NODE 201 +#define STMT_201_INFO {"STMT_SELECT_IPROPS_NODE", NULL} +#define STMT_201 \ + "SELECT local_relpath, repos_path FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND local_relpath = ?2 " \ + " AND op_depth = 0 " \ + " AND (inherited_props not null) " \ + "" + +#define STMT_SELECT_IPROPS_RECURSIVE 202 +#define STMT_202_INFO {"STMT_SELECT_IPROPS_RECURSIVE", NULL} +#define STMT_202 \ + "SELECT local_relpath, repos_path FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND (((local_relpath) > (CASE (?2) WHEN '' THEN '' ELSE (?2) || '/' END)) AND ((local_relpath) < CASE (?2) WHEN '' THEN X'FFFF' ELSE (?2) || '0' END)) " \ + " AND op_depth = 0 " \ + " AND (inherited_props not null) " \ + "" + +#define STMT_SELECT_IPROPS_CHILDREN 203 +#define STMT_203_INFO {"STMT_SELECT_IPROPS_CHILDREN", NULL} +#define STMT_203 \ + "SELECT local_relpath, repos_path FROM nodes " \ + "WHERE wc_id = ?1 " \ + " AND parent_relpath = ?2 " \ + " AND op_depth = 0 " \ + " AND (inherited_props not null) " \ + "" + +#define STMT_CREATE_SCHEMA 204 +#define STMT_204_INFO {"STMT_CREATE_SCHEMA", NULL} +#define STMT_204 \ + "CREATE TABLE REPOSITORY ( " \ + " id INTEGER PRIMARY KEY AUTOINCREMENT, " \ + " root TEXT UNIQUE NOT NULL, " \ + " uuid TEXT NOT NULL " \ + " ); " \ + "CREATE INDEX I_UUID ON REPOSITORY (uuid); " \ + "CREATE INDEX I_ROOT ON REPOSITORY (root); " \ + "CREATE TABLE WCROOT ( " \ + " id INTEGER PRIMARY KEY AUTOINCREMENT, " \ + " local_abspath TEXT UNIQUE " \ + " ); " \ + "CREATE UNIQUE INDEX I_LOCAL_ABSPATH ON WCROOT (local_abspath); " \ + "CREATE TABLE PRISTINE ( " \ + " checksum TEXT NOT NULL PRIMARY KEY, " \ + " compression INTEGER, " \ + " size INTEGER NOT NULL, " \ + " refcount INTEGER NOT NULL, " \ + " md5_checksum TEXT NOT NULL " \ + " ); " \ + "CREATE INDEX I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \ + "CREATE TABLE ACTUAL_NODE ( " \ + " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ + " local_relpath TEXT NOT NULL, " \ + " parent_relpath TEXT, " \ + " properties BLOB, " \ + " conflict_old TEXT, " \ + " conflict_new TEXT, " \ + " conflict_working TEXT, " \ + " prop_reject TEXT, " \ + " changelist TEXT, " \ + " text_mod TEXT, " \ + " tree_conflict_data TEXT, " \ + " conflict_data BLOB, " \ + " older_checksum TEXT REFERENCES PRISTINE (checksum), " \ + " left_checksum TEXT REFERENCES PRISTINE (checksum), " \ + " right_checksum TEXT REFERENCES PRISTINE (checksum), " \ + " PRIMARY KEY (wc_id, local_relpath) " \ + " ); " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ + "CREATE TABLE LOCK ( " \ + " repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), " \ + " repos_relpath TEXT NOT NULL, " \ + " lock_token TEXT NOT NULL, " \ + " lock_owner TEXT, " \ + " lock_comment TEXT, " \ + " lock_date INTEGER, " \ + " PRIMARY KEY (repos_id, repos_relpath) " \ + " ); " \ + "CREATE TABLE WORK_QUEUE ( " \ + " id INTEGER PRIMARY KEY AUTOINCREMENT, " \ + " work BLOB NOT NULL " \ + " ); " \ + "CREATE TABLE WC_LOCK ( " \ + " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ + " local_dir_relpath TEXT NOT NULL, " \ + " locked_levels INTEGER NOT NULL DEFAULT -1, " \ + " PRIMARY KEY (wc_id, local_dir_relpath) " \ + " ); " \ + "PRAGMA user_version = " \ + APR_STRINGIFY(SVN_WC__VERSION) \ + "; " \ + "" + +#define STMT_CREATE_NODES 205 +#define STMT_205_INFO {"STMT_CREATE_NODES", NULL} +#define STMT_205 \ + "CREATE TABLE NODES ( " \ + " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ + " local_relpath TEXT NOT NULL, " \ + " op_depth INTEGER NOT NULL, " \ + " parent_relpath TEXT, " \ + " repos_id INTEGER REFERENCES REPOSITORY (id), " \ + " repos_path TEXT, " \ + " revision INTEGER, " \ + " presence TEXT NOT NULL, " \ + " moved_here INTEGER, " \ + " moved_to TEXT, " \ + " kind TEXT NOT NULL, " \ + " properties BLOB, " \ + " depth TEXT, " \ + " checksum TEXT REFERENCES PRISTINE (checksum), " \ + " symlink_target TEXT, " \ + " changed_revision INTEGER, " \ + " changed_date INTEGER, " \ + " changed_author TEXT, " \ + " translated_size INTEGER, " \ + " last_mod_time INTEGER, " \ + " dav_cache BLOB, " \ + " file_external INTEGER, " \ + " inherited_props BLOB, " \ + " PRIMARY KEY (wc_id, local_relpath, op_depth) " \ + " ); " \ + "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \ + " local_relpath, op_depth); " \ + "CREATE UNIQUE INDEX I_NODES_MOVED ON NODES (wc_id, moved_to, op_depth); " \ + "CREATE VIEW NODES_CURRENT AS " \ + " SELECT * FROM nodes AS n " \ + " WHERE op_depth = (SELECT MAX(op_depth) FROM nodes AS n2 " \ + " WHERE n2.wc_id = n.wc_id " \ + " AND n2.local_relpath = n.local_relpath); " \ + "CREATE VIEW NODES_BASE AS " \ + " SELECT * FROM nodes " \ + " WHERE op_depth = 0; " \ + "" + +#define STMT_CREATE_NODES_TRIGGERS 206 +#define STMT_206_INFO {"STMT_CREATE_NODES_TRIGGERS", NULL} +#define STMT_206 \ + "CREATE TRIGGER nodes_insert_trigger " \ + "AFTER INSERT ON nodes " \ + "WHEN NEW.checksum IS NOT NULL " \ + "BEGIN " \ + " UPDATE pristine SET refcount = refcount + 1 " \ + " WHERE checksum = NEW.checksum; " \ + "END; " \ + "CREATE TRIGGER nodes_delete_trigger " \ + "AFTER DELETE ON nodes " \ + "WHEN OLD.checksum IS NOT NULL " \ + "BEGIN " \ + " UPDATE pristine SET refcount = refcount - 1 " \ + " WHERE checksum = OLD.checksum; " \ + "END; " \ + "CREATE TRIGGER nodes_update_checksum_trigger " \ + "AFTER UPDATE OF checksum ON nodes " \ + "WHEN NEW.checksum IS NOT OLD.checksum " \ + "BEGIN " \ + " UPDATE pristine SET refcount = refcount + 1 " \ + " WHERE checksum = NEW.checksum; " \ + " UPDATE pristine SET refcount = refcount - 1 " \ + " WHERE checksum = OLD.checksum; " \ + "END; " \ + "" + +#define STMT_CREATE_EXTERNALS 207 +#define STMT_207_INFO {"STMT_CREATE_EXTERNALS", NULL} +#define STMT_207 \ + "CREATE TABLE EXTERNALS ( " \ + " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ + " local_relpath TEXT NOT NULL, " \ + " parent_relpath TEXT NOT NULL, " \ + " repos_id INTEGER NOT NULL REFERENCES REPOSITORY (id), " \ + " presence TEXT NOT NULL, " \ + " kind TEXT NOT NULL, " \ + " def_local_relpath TEXT NOT NULL, " \ + " def_repos_relpath TEXT NOT NULL, " \ + " def_operational_revision TEXT, " \ + " def_revision TEXT, " \ + " PRIMARY KEY (wc_id, local_relpath) " \ + "); " \ + "CREATE UNIQUE INDEX I_EXTERNALS_DEFINED ON EXTERNALS (wc_id, " \ + " def_local_relpath, " \ + " local_relpath); " \ + "" + +#define STMT_UPGRADE_TO_20 208 +#define STMT_208_INFO {"STMT_UPGRADE_TO_20", NULL} +#define STMT_208 \ + "UPDATE BASE_NODE SET checksum = (SELECT checksum FROM pristine " \ + " WHERE md5_checksum = BASE_NODE.checksum) " \ + "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = BASE_NODE.checksum); " \ + "UPDATE WORKING_NODE SET checksum = (SELECT checksum FROM pristine " \ + " WHERE md5_checksum = WORKING_NODE.checksum) " \ + "WHERE EXISTS (SELECT 1 FROM pristine " \ + " WHERE md5_checksum = WORKING_NODE.checksum); " \ + "INSERT INTO NODES ( " \ + " wc_id, local_relpath, op_depth, parent_relpath, " \ + " repos_id, repos_path, revision, " \ + " presence, depth, moved_here, moved_to, kind, " \ + " changed_revision, changed_date, changed_author, " \ + " checksum, properties, translated_size, last_mod_time, " \ + " dav_cache, symlink_target, file_external ) " \ + "SELECT wc_id, local_relpath, 0 , parent_relpath, " \ + " repos_id, repos_relpath, revnum, " \ + " presence, depth, NULL , NULL , kind, " \ + " changed_rev, changed_date, changed_author, " \ + " checksum, properties, translated_size, last_mod_time, " \ + " dav_cache, symlink_target, file_external " \ + "FROM BASE_NODE; " \ + "INSERT INTO NODES ( " \ + " wc_id, local_relpath, op_depth, parent_relpath, " \ + " repos_id, repos_path, revision, " \ + " presence, depth, moved_here, moved_to, kind, " \ + " changed_revision, changed_date, changed_author, " \ + " checksum, properties, translated_size, last_mod_time, " \ + " dav_cache, symlink_target, file_external ) " \ + "SELECT wc_id, local_relpath, 2 , parent_relpath, " \ + " copyfrom_repos_id, copyfrom_repos_path, copyfrom_revnum, " \ + " presence, depth, NULL , NULL , kind, " \ + " changed_rev, changed_date, changed_author, " \ + " checksum, properties, translated_size, last_mod_time, " \ + " NULL , symlink_target, NULL " \ + "FROM WORKING_NODE; " \ + "DROP TABLE BASE_NODE; " \ + "DROP TABLE WORKING_NODE; " \ + "PRAGMA user_version = 20; " \ + "" + +#define STMT_UPGRADE_TO_21 209 +#define STMT_209_INFO {"STMT_UPGRADE_TO_21", NULL} +#define STMT_209 \ + "PRAGMA user_version = 21; " \ + "" + +#define STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT 210 +#define STMT_210_INFO {"STMT_UPGRADE_21_SELECT_OLD_TREE_CONFLICT", NULL} +#define STMT_210 \ + "SELECT wc_id, local_relpath, tree_conflict_data " \ + "FROM actual_node " \ + "WHERE tree_conflict_data IS NOT NULL " \ + "" + +#define STMT_UPGRADE_21_ERASE_OLD_CONFLICTS 211 +#define STMT_211_INFO {"STMT_UPGRADE_21_ERASE_OLD_CONFLICTS", NULL} +#define STMT_211 \ + "UPDATE actual_node SET tree_conflict_data = NULL " \ + "" + +#define STMT_UPGRADE_TO_22 212 +#define STMT_212_INFO {"STMT_UPGRADE_TO_22", NULL} +#define STMT_212 \ + "UPDATE actual_node SET tree_conflict_data = conflict_data; " \ + "UPDATE actual_node SET conflict_data = NULL; " \ + "PRAGMA user_version = 22; " \ + "" + +#define STMT_UPGRADE_TO_23 213 +#define STMT_213_INFO {"STMT_UPGRADE_TO_23", NULL} +#define STMT_213 \ + "PRAGMA user_version = 23; " \ + "" + +#define STMT_UPGRADE_23_HAS_WORKING_NODES 214 +#define STMT_214_INFO {"STMT_UPGRADE_23_HAS_WORKING_NODES", NULL} +#define STMT_214 \ + "SELECT 1 FROM nodes WHERE op_depth > 0 " \ + "LIMIT 1 " \ + "" + +#define STMT_UPGRADE_TO_24 215 +#define STMT_215_INFO {"STMT_UPGRADE_TO_24", NULL} +#define STMT_215 \ + "UPDATE pristine SET refcount = " \ + " (SELECT COUNT(*) FROM nodes " \ + " WHERE checksum = pristine.checksum ); " \ + "PRAGMA user_version = 24; " \ + "" + +#define STMT_UPGRADE_TO_25 216 +#define STMT_216_INFO {"STMT_UPGRADE_TO_25", NULL} +#define STMT_216 \ + "DROP VIEW IF EXISTS NODES_CURRENT; " \ + "CREATE VIEW NODES_CURRENT AS " \ + " SELECT * FROM nodes " \ + " JOIN (SELECT wc_id, local_relpath, MAX(op_depth) AS op_depth FROM nodes " \ + " GROUP BY wc_id, local_relpath) AS filter " \ + " ON nodes.wc_id = filter.wc_id " \ + " AND nodes.local_relpath = filter.local_relpath " \ + " AND nodes.op_depth = filter.op_depth; " \ + "PRAGMA user_version = 25; " \ + "" + +#define STMT_UPGRADE_TO_26 217 +#define STMT_217_INFO {"STMT_UPGRADE_TO_26", NULL} +#define STMT_217 \ + "DROP VIEW IF EXISTS NODES_BASE; " \ + "CREATE VIEW NODES_BASE AS " \ + " SELECT * FROM nodes " \ + " WHERE op_depth = 0; " \ + "PRAGMA user_version = 26; " \ + "" + +#define STMT_UPGRADE_TO_27 218 +#define STMT_218_INFO {"STMT_UPGRADE_TO_27", NULL} +#define STMT_218 \ + "PRAGMA user_version = 27; " \ + "" + +#define STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS 219 +#define STMT_219_INFO {"STMT_UPGRADE_27_HAS_ACTUAL_NODES_CONFLICTS", NULL} +#define STMT_219 \ + "SELECT 1 FROM actual_node " \ + "WHERE NOT ((prop_reject IS NULL) AND (conflict_old IS NULL) " \ + " AND (conflict_new IS NULL) AND (conflict_working IS NULL) " \ + " AND (tree_conflict_data IS NULL)) " \ + "LIMIT 1 " \ + "" + +#define STMT_UPGRADE_TO_28 220 +#define STMT_220_INFO {"STMT_UPGRADE_TO_28", NULL} +#define STMT_220 \ + "UPDATE NODES SET checksum = (SELECT checksum FROM pristine " \ + " WHERE md5_checksum = nodes.checksum) " \ + "WHERE EXISTS (SELECT 1 FROM pristine WHERE md5_checksum = nodes.checksum); " \ + "PRAGMA user_version = 28; " \ + "" + +#define STMT_UPGRADE_TO_29 221 +#define STMT_221_INFO {"STMT_UPGRADE_TO_29", NULL} +#define STMT_221 \ + "DROP TRIGGER IF EXISTS nodes_update_checksum_trigger; " \ + "DROP TRIGGER IF EXISTS nodes_insert_trigger; " \ + "DROP TRIGGER IF EXISTS nodes_delete_trigger; " \ + "CREATE TRIGGER nodes_update_checksum_trigger " \ + "AFTER UPDATE OF checksum ON nodes " \ + "WHEN NEW.checksum IS NOT OLD.checksum " \ + "BEGIN " \ + " UPDATE pristine SET refcount = refcount + 1 " \ + " WHERE checksum = NEW.checksum; " \ + " UPDATE pristine SET refcount = refcount - 1 " \ + " WHERE checksum = OLD.checksum; " \ + "END; " \ + "CREATE TRIGGER nodes_insert_trigger " \ + "AFTER INSERT ON nodes " \ + "WHEN NEW.checksum IS NOT NULL " \ + "BEGIN " \ + " UPDATE pristine SET refcount = refcount + 1 " \ + " WHERE checksum = NEW.checksum; " \ + "END; " \ + "CREATE TRIGGER nodes_delete_trigger " \ + "AFTER DELETE ON nodes " \ + "WHEN OLD.checksum IS NOT NULL " \ + "BEGIN " \ + " UPDATE pristine SET refcount = refcount - 1 " \ + " WHERE checksum = OLD.checksum; " \ + "END; " \ + "PRAGMA user_version = 29; " \ + "" + +#define STMT_UPGRADE_TO_30 222 +#define STMT_222_INFO {"STMT_UPGRADE_TO_30", NULL} +#define STMT_222 \ + "CREATE UNIQUE INDEX IF NOT EXISTS I_NODES_MOVED " \ + "ON NODES (wc_id, moved_to, op_depth); " \ + "CREATE INDEX IF NOT EXISTS I_PRISTINE_MD5 ON PRISTINE (md5_checksum); " \ + "UPDATE nodes SET presence = \"server-excluded\" WHERE presence = \"absent\"; " \ + "UPDATE nodes SET file_external=1 WHERE file_external IS NOT NULL; " \ + "" + +#define STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE 223 +#define STMT_223_INFO {"STMT_UPGRADE_30_SELECT_CONFLICT_SEPARATE", NULL} +#define STMT_223 \ + "SELECT wc_id, local_relpath, " \ + " conflict_old, conflict_working, conflict_new, prop_reject, tree_conflict_data " \ + "FROM actual_node " \ + "WHERE conflict_old IS NOT NULL " \ + " OR conflict_working IS NOT NULL " \ + " OR conflict_new IS NOT NULL " \ + " OR prop_reject IS NOT NULL " \ + " OR tree_conflict_data IS NOT NULL " \ + "ORDER by wc_id, local_relpath " \ + "" + +#define STMT_UPGRADE_30_SET_CONFLICT 224 +#define STMT_224_INFO {"STMT_UPGRADE_30_SET_CONFLICT", NULL} +#define STMT_224 \ + "UPDATE actual_node SET conflict_data = ?3, conflict_old = NULL, " \ + " conflict_working = NULL, conflict_new = NULL, prop_reject = NULL, " \ + " tree_conflict_data = NULL " \ + "WHERE wc_id = ?1 and local_relpath = ?2 " \ + "" + +#define STMT_UPGRADE_TO_31_ALTER_TABLE 225 +#define STMT_225_INFO {"STMT_UPGRADE_TO_31_ALTER_TABLE", NULL} +#define STMT_225 \ + "ALTER TABLE NODES ADD COLUMN inherited_props BLOB; " \ + "" + +#define STMT_UPGRADE_TO_31_FINALIZE 226 +#define STMT_226_INFO {"STMT_UPGRADE_TO_31_FINALIZE", NULL} +#define STMT_226 \ + "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \ + "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \ + "DROP INDEX I_NODES_PARENT; " \ + "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \ + " local_relpath, op_depth); " \ + "DROP INDEX I_ACTUAL_PARENT; " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ + "PRAGMA user_version = 31; " \ + "" + +#define STMT_UPGRADE_31_SELECT_WCROOT_NODES 227 +#define STMT_227_INFO {"STMT_UPGRADE_31_SELECT_WCROOT_NODES", NULL} +#define STMT_227 \ + "SELECT l.wc_id, l.local_relpath FROM nodes as l " \ + "LEFT OUTER JOIN nodes as r " \ + "ON l.wc_id = r.wc_id " \ + " AND r.local_relpath = l.parent_relpath " \ + " AND r.op_depth = 0 " \ + "WHERE l.op_depth = 0 " \ + " AND l.repos_path != '' " \ + " AND ((l.repos_id IS NOT r.repos_id) " \ + " OR (l.repos_path IS NOT (CASE WHEN (r.local_relpath) = '' THEN (CASE WHEN (r.repos_path) = '' THEN (l.local_relpath) WHEN (l.local_relpath) = '' THEN (r.repos_path) ELSE (r.repos_path) || '/' || (l.local_relpath) END) WHEN (r.repos_path) = '' THEN (CASE WHEN (r.local_relpath) = '' THEN (l.local_relpath) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN '' WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+2) END END) WHEN SUBSTR((l.local_relpath), 1, LENGTH(r.local_relpath)) = (r.local_relpath) THEN CASE WHEN LENGTH(r.local_relpath) = LENGTH(l.local_relpath) THEN (r.repos_path) WHEN SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1, 1) = '/' THEN (r.repos_path) || SUBSTR((l.local_relpath), LENGTH(r.local_relpath)+1) END END))) " \ + "" + +#define STMT_UPGRADE_TO_32 228 +#define STMT_228_INFO {"STMT_UPGRADE_TO_32", NULL} +#define STMT_228 \ + "DROP INDEX IF EXISTS I_ACTUAL_CHANGELIST; " \ + "DROP INDEX IF EXISTS I_EXTERNALS_PARENT; " \ + "CREATE INDEX I_EXTERNALS_PARENT ON EXTERNALS (wc_id, parent_relpath); " \ + "DROP INDEX I_NODES_PARENT; " \ + "CREATE UNIQUE INDEX I_NODES_PARENT ON NODES (wc_id, parent_relpath, " \ + " local_relpath, op_depth); " \ + "DROP INDEX I_ACTUAL_PARENT; " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ + "-- format: YYY " \ + "" + +#define WC_QUERIES_SQL_99 \ + "CREATE TABLE ACTUAL_NODE_BACKUP ( " \ + " wc_id INTEGER NOT NULL, " \ + " local_relpath TEXT NOT NULL, " \ + " parent_relpath TEXT, " \ + " properties BLOB, " \ + " conflict_old TEXT, " \ + " conflict_new TEXT, " \ + " conflict_working TEXT, " \ + " prop_reject TEXT, " \ + " changelist TEXT, " \ + " text_mod TEXT " \ + " ); " \ + "INSERT INTO ACTUAL_NODE_BACKUP SELECT " \ + " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \ + " conflict_new, conflict_working, prop_reject, changelist, text_mod " \ + "FROM ACTUAL_NODE; " \ + "DROP TABLE ACTUAL_NODE; " \ + "CREATE TABLE ACTUAL_NODE ( " \ + " wc_id INTEGER NOT NULL REFERENCES WCROOT (id), " \ + " local_relpath TEXT NOT NULL, " \ + " parent_relpath TEXT, " \ + " properties BLOB, " \ + " conflict_old TEXT, " \ + " conflict_new TEXT, " \ + " conflict_working TEXT, " \ + " prop_reject TEXT, " \ + " changelist TEXT, " \ + " text_mod TEXT, " \ + " PRIMARY KEY (wc_id, local_relpath) " \ + " ); " \ + "CREATE UNIQUE INDEX I_ACTUAL_PARENT ON ACTUAL_NODE (wc_id, parent_relpath, " \ + " local_relpath); " \ + "INSERT INTO ACTUAL_NODE SELECT " \ + " wc_id, local_relpath, parent_relpath, properties, conflict_old, " \ + " conflict_new, conflict_working, prop_reject, changelist, text_mod " \ + "FROM ACTUAL_NODE_BACKUP; " \ + "DROP TABLE ACTUAL_NODE_BACKUP; " \ + "" + +#define STMT_VERIFICATION_TRIGGERS 229 +#define STMT_229_INFO {"STMT_VERIFICATION_TRIGGERS", NULL} +#define STMT_229 \ + "CREATE TEMPORARY TRIGGER no_repository_updates BEFORE UPDATE ON repository " \ + "BEGIN " \ + " SELECT RAISE(FAIL, 'Updates to REPOSITORY are not allowed.'); " \ + "END; " \ + "CREATE TEMPORARY TRIGGER validation_01 BEFORE INSERT ON nodes " \ + "WHEN NOT ((new.local_relpath = '' AND new.parent_relpath IS NULL) " \ + " OR (relpath_depth(new.local_relpath) " \ + " = relpath_depth(new.parent_relpath) + 1)) " \ + "BEGIN " \ + " SELECT RAISE(FAIL, 'WC DB validity check 01 failed'); " \ + "END; " \ + "CREATE TEMPORARY TRIGGER validation_02 BEFORE INSERT ON nodes " \ + "WHEN NOT new.op_depth <= relpath_depth(new.local_relpath) " \ + "BEGIN " \ + " SELECT RAISE(FAIL, 'WC DB validity check 02 failed'); " \ + "END; " \ + "CREATE TEMPORARY TRIGGER validation_03 BEFORE INSERT ON nodes " \ + "WHEN NOT ( " \ + " (new.op_depth = relpath_depth(new.local_relpath)) " \ + " OR " \ + " (EXISTS (SELECT 1 FROM nodes " \ + " WHERE wc_id = new.wc_id AND op_depth = new.op_depth " \ + " AND local_relpath = new.parent_relpath)) " \ + " ) " \ + " AND NOT (new.file_external IS NOT NULL AND new.op_depth = 0) " \ + "BEGIN " \ + " SELECT RAISE(FAIL, 'WC DB validity check 03 failed'); " \ + "END; " \ + "CREATE TEMPORARY TRIGGER validation_04 BEFORE INSERT ON actual_node " \ + "WHEN NOT (new.local_relpath = '' " \ + " OR EXISTS (SELECT 1 FROM nodes " \ + " WHERE wc_id = new.wc_id " \ + " AND local_relpath = new.parent_relpath)) " \ + "BEGIN " \ + " SELECT RAISE(FAIL, 'WC DB validity check 04 failed'); " \ + "END; " \ + "" + +#define WC_QUERIES_SQL_DECLARE_STATEMENTS(varname) \ + static const char * const varname[] = { \ + STMT_0, \ + STMT_1, \ + STMT_2, \ + STMT_3, \ + STMT_4, \ + STMT_5, \ + STMT_6, \ + STMT_7, \ + STMT_8, \ + STMT_9, \ + STMT_10, \ + STMT_11, \ + STMT_12, \ + STMT_13, \ + STMT_14, \ + STMT_15, \ + STMT_16, \ + STMT_17, \ + STMT_18, \ + STMT_19, \ + STMT_20, \ + STMT_21, \ + STMT_22, \ + STMT_23, \ + STMT_24, \ + STMT_25, \ + STMT_26, \ + STMT_27, \ + STMT_28, \ + STMT_29, \ + STMT_30, \ + STMT_31, \ + STMT_32, \ + STMT_33, \ + STMT_34, \ + STMT_35, \ + STMT_36, \ + STMT_37, \ + STMT_38, \ + STMT_39, \ + STMT_40, \ + STMT_41, \ + STMT_42, \ + STMT_43, \ + STMT_44, \ + STMT_45, \ + STMT_46, \ + STMT_47, \ + STMT_48, \ + STMT_49, \ + STMT_50, \ + STMT_51, \ + STMT_52, \ + STMT_53, \ + STMT_54, \ + STMT_55, \ + STMT_56, \ + STMT_57, \ + STMT_58, \ + STMT_59, \ + STMT_60, \ + STMT_61, \ + STMT_62, \ + STMT_63, \ + STMT_64, \ + STMT_65, \ + STMT_66, \ + STMT_67, \ + STMT_68, \ + STMT_69, \ + STMT_70, \ + STMT_71, \ + STMT_72, \ + STMT_73, \ + STMT_74, \ + STMT_75, \ + STMT_76, \ + STMT_77, \ + STMT_78, \ + STMT_79, \ + STMT_80, \ + STMT_81, \ + STMT_82, \ + STMT_83, \ + STMT_84, \ + STMT_85, \ + STMT_86, \ + STMT_87, \ + STMT_88, \ + STMT_89, \ + STMT_90, \ + STMT_91, \ + STMT_92, \ + STMT_93, \ + STMT_94, \ + STMT_95, \ + STMT_96, \ + STMT_97, \ + STMT_98, \ + STMT_99, \ + STMT_100, \ + STMT_101, \ + STMT_102, \ + STMT_103, \ + STMT_104, \ + STMT_105, \ + STMT_106, \ + STMT_107, \ + STMT_108, \ + STMT_109, \ + STMT_110, \ + STMT_111, \ + STMT_112, \ + STMT_113, \ + STMT_114, \ + STMT_115, \ + STMT_116, \ + STMT_117, \ + STMT_118, \ + STMT_119, \ + STMT_120, \ + STMT_121, \ + STMT_122, \ + STMT_123, \ + STMT_124, \ + STMT_125, \ + STMT_126, \ + STMT_127, \ + STMT_128, \ + STMT_129, \ + STMT_130, \ + STMT_131, \ + STMT_132, \ + STMT_133, \ + STMT_134, \ + STMT_135, \ + STMT_136, \ + STMT_137, \ + STMT_138, \ + STMT_139, \ + STMT_140, \ + STMT_141, \ + STMT_142, \ + STMT_143, \ + STMT_144, \ + STMT_145, \ + STMT_146, \ + STMT_147, \ + STMT_148, \ + STMT_149, \ + STMT_150, \ + STMT_151, \ + STMT_152, \ + STMT_153, \ + STMT_154, \ + STMT_155, \ + STMT_156, \ + STMT_157, \ + STMT_158, \ + STMT_159, \ + STMT_160, \ + STMT_161, \ + STMT_162, \ + STMT_163, \ + STMT_164, \ + STMT_165, \ + STMT_166, \ + STMT_167, \ + STMT_168, \ + STMT_169, \ + STMT_170, \ + STMT_171, \ + STMT_172, \ + STMT_173, \ + STMT_174, \ + STMT_175, \ + STMT_176, \ + STMT_177, \ + STMT_178, \ + STMT_179, \ + STMT_180, \ + STMT_181, \ + STMT_182, \ + STMT_183, \ + STMT_184, \ + STMT_185, \ + STMT_186, \ + STMT_187, \ + STMT_188, \ + STMT_189, \ + STMT_190, \ + STMT_191, \ + STMT_192, \ + STMT_193, \ + STMT_194, \ + STMT_195, \ + STMT_196, \ + STMT_197, \ + STMT_198, \ + STMT_199, \ + STMT_200, \ + STMT_201, \ + STMT_202, \ + STMT_203, \ + STMT_204, \ + STMT_205, \ + STMT_206, \ + STMT_207, \ + STMT_208, \ + STMT_209, \ + STMT_210, \ + STMT_211, \ + STMT_212, \ + STMT_213, \ + STMT_214, \ + STMT_215, \ + STMT_216, \ + STMT_217, \ + STMT_218, \ + STMT_219, \ + STMT_220, \ + STMT_221, \ + STMT_222, \ + STMT_223, \ + STMT_224, \ + STMT_225, \ + STMT_226, \ + STMT_227, \ + STMT_228, \ + STMT_229, \ + NULL \ + } + +#define WC_QUERIES_SQL_DECLARE_STATEMENT_INFO(varname) \ + static const char * const varname[][2] = { \ + STMT_0_INFO, \ + STMT_1_INFO, \ + STMT_2_INFO, \ + STMT_3_INFO, \ + STMT_4_INFO, \ + STMT_5_INFO, \ + STMT_6_INFO, \ + STMT_7_INFO, \ + STMT_8_INFO, \ + STMT_9_INFO, \ + STMT_10_INFO, \ + STMT_11_INFO, \ + STMT_12_INFO, \ + STMT_13_INFO, \ + STMT_14_INFO, \ + STMT_15_INFO, \ + STMT_16_INFO, \ + STMT_17_INFO, \ + STMT_18_INFO, \ + STMT_19_INFO, \ + STMT_20_INFO, \ + STMT_21_INFO, \ + STMT_22_INFO, \ + STMT_23_INFO, \ + STMT_24_INFO, \ + STMT_25_INFO, \ + STMT_26_INFO, \ + STMT_27_INFO, \ + STMT_28_INFO, \ + STMT_29_INFO, \ + STMT_30_INFO, \ + STMT_31_INFO, \ + STMT_32_INFO, \ + STMT_33_INFO, \ + STMT_34_INFO, \ + STMT_35_INFO, \ + STMT_36_INFO, \ + STMT_37_INFO, \ + STMT_38_INFO, \ + STMT_39_INFO, \ + STMT_40_INFO, \ + STMT_41_INFO, \ + STMT_42_INFO, \ + STMT_43_INFO, \ + STMT_44_INFO, \ + STMT_45_INFO, \ + STMT_46_INFO, \ + STMT_47_INFO, \ + STMT_48_INFO, \ + STMT_49_INFO, \ + STMT_50_INFO, \ + STMT_51_INFO, \ + STMT_52_INFO, \ + STMT_53_INFO, \ + STMT_54_INFO, \ + STMT_55_INFO, \ + STMT_56_INFO, \ + STMT_57_INFO, \ + STMT_58_INFO, \ + STMT_59_INFO, \ + STMT_60_INFO, \ + STMT_61_INFO, \ + STMT_62_INFO, \ + STMT_63_INFO, \ + STMT_64_INFO, \ + STMT_65_INFO, \ + STMT_66_INFO, \ + STMT_67_INFO, \ + STMT_68_INFO, \ + STMT_69_INFO, \ + STMT_70_INFO, \ + STMT_71_INFO, \ + STMT_72_INFO, \ + STMT_73_INFO, \ + STMT_74_INFO, \ + STMT_75_INFO, \ + STMT_76_INFO, \ + STMT_77_INFO, \ + STMT_78_INFO, \ + STMT_79_INFO, \ + STMT_80_INFO, \ + STMT_81_INFO, \ + STMT_82_INFO, \ + STMT_83_INFO, \ + STMT_84_INFO, \ + STMT_85_INFO, \ + STMT_86_INFO, \ + STMT_87_INFO, \ + STMT_88_INFO, \ + STMT_89_INFO, \ + STMT_90_INFO, \ + STMT_91_INFO, \ + STMT_92_INFO, \ + STMT_93_INFO, \ + STMT_94_INFO, \ + STMT_95_INFO, \ + STMT_96_INFO, \ + STMT_97_INFO, \ + STMT_98_INFO, \ + STMT_99_INFO, \ + STMT_100_INFO, \ + STMT_101_INFO, \ + STMT_102_INFO, \ + STMT_103_INFO, \ + STMT_104_INFO, \ + STMT_105_INFO, \ + STMT_106_INFO, \ + STMT_107_INFO, \ + STMT_108_INFO, \ + STMT_109_INFO, \ + STMT_110_INFO, \ + STMT_111_INFO, \ + STMT_112_INFO, \ + STMT_113_INFO, \ + STMT_114_INFO, \ + STMT_115_INFO, \ + STMT_116_INFO, \ + STMT_117_INFO, \ + STMT_118_INFO, \ + STMT_119_INFO, \ + STMT_120_INFO, \ + STMT_121_INFO, \ + STMT_122_INFO, \ + STMT_123_INFO, \ + STMT_124_INFO, \ + STMT_125_INFO, \ + STMT_126_INFO, \ + STMT_127_INFO, \ + STMT_128_INFO, \ + STMT_129_INFO, \ + STMT_130_INFO, \ + STMT_131_INFO, \ + STMT_132_INFO, \ + STMT_133_INFO, \ + STMT_134_INFO, \ + STMT_135_INFO, \ + STMT_136_INFO, \ + STMT_137_INFO, \ + STMT_138_INFO, \ + STMT_139_INFO, \ + STMT_140_INFO, \ + STMT_141_INFO, \ + STMT_142_INFO, \ + STMT_143_INFO, \ + STMT_144_INFO, \ + STMT_145_INFO, \ + STMT_146_INFO, \ + STMT_147_INFO, \ + STMT_148_INFO, \ + STMT_149_INFO, \ + STMT_150_INFO, \ + STMT_151_INFO, \ + STMT_152_INFO, \ + STMT_153_INFO, \ + STMT_154_INFO, \ + STMT_155_INFO, \ + STMT_156_INFO, \ + STMT_157_INFO, \ + STMT_158_INFO, \ + STMT_159_INFO, \ + STMT_160_INFO, \ + STMT_161_INFO, \ + STMT_162_INFO, \ + STMT_163_INFO, \ + STMT_164_INFO, \ + STMT_165_INFO, \ + STMT_166_INFO, \ + STMT_167_INFO, \ + STMT_168_INFO, \ + STMT_169_INFO, \ + STMT_170_INFO, \ + STMT_171_INFO, \ + STMT_172_INFO, \ + STMT_173_INFO, \ + STMT_174_INFO, \ + STMT_175_INFO, \ + STMT_176_INFO, \ + STMT_177_INFO, \ + STMT_178_INFO, \ + STMT_179_INFO, \ + STMT_180_INFO, \ + STMT_181_INFO, \ + STMT_182_INFO, \ + STMT_183_INFO, \ + STMT_184_INFO, \ + STMT_185_INFO, \ + STMT_186_INFO, \ + STMT_187_INFO, \ + STMT_188_INFO, \ + STMT_189_INFO, \ + STMT_190_INFO, \ + STMT_191_INFO, \ + STMT_192_INFO, \ + STMT_193_INFO, \ + STMT_194_INFO, \ + STMT_195_INFO, \ + STMT_196_INFO, \ + STMT_197_INFO, \ + STMT_198_INFO, \ + STMT_199_INFO, \ + STMT_200_INFO, \ + STMT_201_INFO, \ + STMT_202_INFO, \ + STMT_203_INFO, \ + STMT_204_INFO, \ + STMT_205_INFO, \ + STMT_206_INFO, \ + STMT_207_INFO, \ + STMT_208_INFO, \ + STMT_209_INFO, \ + STMT_210_INFO, \ + STMT_211_INFO, \ + STMT_212_INFO, \ + STMT_213_INFO, \ + STMT_214_INFO, \ + STMT_215_INFO, \ + STMT_216_INFO, \ + STMT_217_INFO, \ + STMT_218_INFO, \ + STMT_219_INFO, \ + STMT_220_INFO, \ + STMT_221_INFO, \ + STMT_222_INFO, \ + STMT_223_INFO, \ + STMT_224_INFO, \ + STMT_225_INFO, \ + STMT_226_INFO, \ + STMT_227_INFO, \ + STMT_228_INFO, \ + STMT_229_INFO, \ + {NULL, NULL} \ + } diff --git a/subversion/libsvn_wc/wc-queries.sql b/subversion/libsvn_wc/wc-queries.sql new file mode 100644 index 000000000000..0ffe6f07d4e4 --- /dev/null +++ b/subversion/libsvn_wc/wc-queries.sql @@ -0,0 +1,1693 @@ +/* wc-queries.sql -- queries used to interact with the wc-metadata + * SQLite database + * This is intended for use with SQLite 3 + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ------------------------------------------------------------------------- */ + +/* these are used in wc_db.c */ + +-- STMT_SELECT_NODE_INFO +SELECT op_depth, repos_id, repos_path, presence, kind, revision, checksum, + translated_size, changed_revision, changed_date, changed_author, depth, + symlink_target, last_mod_time, properties, moved_here, inherited_props, + moved_to +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 +ORDER BY op_depth DESC + +-- STMT_SELECT_NODE_INFO_WITH_LOCK +SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision, + checksum, translated_size, changed_revision, changed_date, changed_author, + depth, symlink_target, last_mod_time, properties, moved_here, + inherited_props, + /* All the columns until now must match those returned by + STMT_SELECT_NODE_INFO. The implementation of svn_wc__db_read_info() + assumes that these columns are followed by the lock information) */ + lock_token, lock_owner, lock_comment, lock_date +FROM nodes +LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id + AND nodes.repos_path = lock.repos_relpath +WHERE wc_id = ?1 AND local_relpath = ?2 +ORDER BY op_depth DESC + +-- STMT_SELECT_BASE_NODE +SELECT repos_id, repos_path, presence, kind, revision, checksum, + translated_size, changed_revision, changed_date, changed_author, depth, + symlink_target, last_mod_time, properties, file_external +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 + +-- STMT_SELECT_BASE_NODE_WITH_LOCK +SELECT nodes.repos_id, nodes.repos_path, presence, kind, revision, + checksum, translated_size, changed_revision, changed_date, changed_author, + depth, symlink_target, last_mod_time, properties, file_external, + /* All the columns until now must match those returned by + STMT_SELECT_BASE_NODE. The implementation of svn_wc__db_base_get_info() + assumes that these columns are followed by the lock information) */ + lock_token, lock_owner, lock_comment, lock_date +FROM nodes +LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id + AND nodes.repos_path = lock.repos_relpath +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 + +-- STMT_SELECT_BASE_CHILDREN_INFO +SELECT local_relpath, nodes.repos_id, nodes.repos_path, presence, kind, + revision, depth, file_external, + lock_token, lock_owner, lock_comment, lock_date +FROM nodes +LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id + AND nodes.repos_path = lock.repos_relpath +WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = 0 + +-- STMT_SELECT_WORKING_NODE +SELECT op_depth, presence, kind, checksum, translated_size, + changed_revision, changed_date, changed_author, depth, symlink_target, + repos_id, repos_path, revision, + moved_here, moved_to, last_mod_time, properties +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0 +ORDER BY op_depth DESC +LIMIT 1 + +-- STMT_SELECT_DEPTH_NODE +SELECT repos_id, repos_path, presence, kind, revision, checksum, + translated_size, changed_revision, changed_date, changed_author, depth, + symlink_target, last_mod_time, properties +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 + +-- STMT_SELECT_LOWEST_WORKING_NODE +SELECT op_depth, presence, kind, moved_to +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 +ORDER BY op_depth +LIMIT 1 + +-- STMT_SELECT_HIGHEST_WORKING_NODE +SELECT op_depth +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth < ?3 +ORDER BY op_depth DESC +LIMIT 1 + +-- STMT_SELECT_ACTUAL_NODE +SELECT changelist, properties, conflict_data +FROM actual_node +WHERE wc_id = ?1 AND local_relpath = ?2 + +-- STMT_SELECT_NODE_CHILDREN_INFO +/* Getting rows in an advantageous order using + ORDER BY local_relpath, op_depth DESC + turns out to be slower than getting rows in a random order and making the + C code handle it. */ +SELECT op_depth, nodes.repos_id, nodes.repos_path, presence, kind, revision, + checksum, translated_size, changed_revision, changed_date, changed_author, + depth, symlink_target, last_mod_time, properties, lock_token, lock_owner, + lock_comment, lock_date, local_relpath, moved_here, moved_to, file_external +FROM nodes +LEFT OUTER JOIN lock ON nodes.repos_id = lock.repos_id + AND nodes.repos_path = lock.repos_relpath AND op_depth = 0 +WHERE wc_id = ?1 AND parent_relpath = ?2 + +-- STMT_SELECT_NODE_CHILDREN_WALKER_INFO +SELECT local_relpath, op_depth, presence, kind +FROM nodes_current +WHERE wc_id = ?1 AND parent_relpath = ?2 + +-- STMT_SELECT_ACTUAL_CHILDREN_INFO +SELECT local_relpath, changelist, properties, conflict_data +FROM actual_node +WHERE wc_id = ?1 AND parent_relpath = ?2 + +-- STMT_SELECT_REPOSITORY_BY_ID +SELECT root, uuid FROM repository WHERE id = ?1 + +-- STMT_SELECT_WCROOT_NULL +SELECT id FROM wcroot WHERE local_abspath IS NULL + +-- STMT_SELECT_REPOSITORY +SELECT id FROM repository WHERE root = ?1 + +-- STMT_INSERT_REPOSITORY +INSERT INTO repository (root, uuid) VALUES (?1, ?2) + +-- STMT_INSERT_NODE +INSERT OR REPLACE INTO nodes ( + wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, + revision, presence, depth, kind, changed_revision, changed_date, + changed_author, checksum, properties, translated_size, last_mod_time, + dav_cache, symlink_target, file_external, moved_to, moved_here, + inherited_props) +VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, + ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23) + +-- STMT_SELECT_BASE_PRESENT +SELECT local_relpath, kind FROM nodes n +WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 + AND presence in (MAP_NORMAL, MAP_INCOMPLETE) + AND NOT EXISTS(SELECT 1 FROM NODES w + WHERE w.wc_id = ?1 AND w.local_relpath = n.local_relpath + AND op_depth > 0) +ORDER BY local_relpath DESC + +-- STMT_SELECT_WORKING_PRESENT +SELECT local_relpath, kind, checksum, translated_size, last_mod_time +FROM nodes n +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND presence in (MAP_NORMAL, MAP_INCOMPLETE) + AND op_depth = (SELECT MAX(op_depth) + FROM NODES w + WHERE w.wc_id = ?1 + AND w.local_relpath = n.local_relpath) +ORDER BY local_relpath DESC + +-- STMT_DELETE_NODE_RECURSIVE +DELETE FROM NODES +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + +-- STMT_DELETE_NODE +DELETE +FROM NODES +WHERE wc_id = ?1 AND local_relpath = ?2 + +-- STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE +/* The ACTUAL_NODE applies to BASE, unless there is in at least one op_depth + a WORKING node that could have a conflict */ +DELETE FROM actual_node +WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND EXISTS(SELECT 1 FROM NODES b + WHERE b.wc_id = ?1 + AND b.local_relpath = actual_node.local_relpath + AND op_depth = 0) + AND NOT EXISTS(SELECT 1 FROM NODES w + WHERE w.wc_id = ?1 + AND w.local_relpath = actual_node.local_relpath + AND op_depth > 0 + AND presence in (MAP_NORMAL, MAP_INCOMPLETE, MAP_NOT_PRESENT)) + +-- STMT_DELETE_WORKING_BASE_DELETE +DELETE FROM nodes +WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND presence = MAP_BASE_DELETED + AND op_depth > 0 + AND op_depth = (SELECT MIN(op_depth) FROM nodes n + WHERE n.wc_id = ?1 + AND n.local_relpath = nodes.local_relpath + AND op_depth > 0) + +-- STMT_DELETE_WORKING_RECURSIVE +DELETE FROM nodes +WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth > 0 + +-- STMT_DELETE_BASE_RECURSIVE +DELETE FROM nodes +WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 + +-- STMT_DELETE_WORKING_OP_DEPTH +DELETE FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 + +-- STMT_DELETE_WORKING_OP_DEPTH_ABOVE +DELETE FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth > ?3 + +-- STMT_SELECT_LOCAL_RELPATH_OP_DEPTH +SELECT local_relpath +FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 + +-- STMT_SELECT_CHILDREN_OP_DEPTH +SELECT local_relpath, kind +FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = ?3 +ORDER BY local_relpath DESC + +-- STMT_COPY_NODE_MOVE +INSERT OR REPLACE INTO nodes ( + wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, + revision, presence, depth, kind, changed_revision, changed_date, + changed_author, checksum, properties, translated_size, last_mod_time, + symlink_target, moved_here, moved_to ) +SELECT + wc_id, ?4 /*local_relpath */, ?5 /*op_depth*/, ?6 /* parent_relpath */, + repos_id, + repos_path, revision, presence, depth, kind, changed_revision, + changed_date, changed_author, checksum, properties, translated_size, + last_mod_time, symlink_target, 1, + (SELECT dst.moved_to FROM nodes AS dst + WHERE dst.wc_id = ?1 + AND dst.local_relpath = ?4 + AND dst.op_depth = ?5) +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 + +-- STMT_SELECT_OP_DEPTH_CHILDREN +SELECT local_relpath, kind FROM nodes +WHERE wc_id = ?1 + AND parent_relpath = ?2 + AND op_depth = ?3 + AND presence != MAP_BASE_DELETED + AND file_external is NULL + +/* Used by non-recursive revert to detect higher level children, and + actual-only rows that would be left orphans, if the revert + proceeded. */ +-- STMT_SELECT_GE_OP_DEPTH_CHILDREN +SELECT 1 FROM nodes +WHERE wc_id = ?1 AND parent_relpath = ?2 + AND (op_depth > ?3 OR (op_depth = ?3 AND presence != MAP_BASE_DELETED)) +UNION ALL +SELECT 1 FROM ACTUAL_NODE a +WHERE wc_id = ?1 AND parent_relpath = ?2 + AND NOT EXISTS (SELECT 1 FROM nodes n + WHERE wc_id = ?1 AND n.local_relpath = a.local_relpath) + +/* Delete the nodes shadowed by local_relpath. Not valid for the wc-root */ +-- STMT_DELETE_SHADOWED_RECURSIVE +DELETE FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND (op_depth < ?3 + OR (op_depth = ?3 AND presence = MAP_BASE_DELETED)) + +-- STMT_CLEAR_MOVED_TO_FROM_DEST +UPDATE NODES SET moved_to = NULL +WHERE wc_id = ?1 + AND moved_to = ?2 + +/* Get not-present descendants of a copied node. Not valid for the wc-root */ +-- STMT_SELECT_NOT_PRESENT_DESCENDANTS +SELECT local_relpath FROM nodes +WHERE wc_id = ?1 AND op_depth = ?3 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND presence = MAP_NOT_PRESENT + +-- STMT_COMMIT_DESCENDANTS_TO_BASE +UPDATE NODES SET op_depth = 0, + repos_id = ?4, + repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1), + revision = ?6, + dav_cache = NULL, + moved_here = NULL, + presence = CASE presence + WHEN MAP_NORMAL THEN MAP_NORMAL + WHEN MAP_EXCLUDED THEN MAP_EXCLUDED + ELSE MAP_NOT_PRESENT + END +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = ?3 + +-- STMT_SELECT_NODE_CHILDREN +/* Return all paths that are children of the directory (?1, ?2) in any + op-depth, including children of any underlying, replaced directories. */ +SELECT local_relpath FROM nodes +WHERE wc_id = ?1 AND parent_relpath = ?2 + +-- STMT_SELECT_WORKING_CHILDREN +/* Return all paths that are children of the working version of the + directory (?1, ?2). A given path is not included just because it is a + child of an underlying (replaced) directory, it has to be in the + working version of the directory. */ +SELECT local_relpath FROM nodes +WHERE wc_id = ?1 AND parent_relpath = ?2 + AND (op_depth > (SELECT MAX(op_depth) FROM nodes + WHERE wc_id = ?1 AND local_relpath = ?2) + OR + (op_depth = (SELECT MAX(op_depth) FROM nodes + WHERE wc_id = ?1 AND local_relpath = ?2) + AND presence != MAP_BASE_DELETED)) + +-- STMT_SELECT_NODE_PROPS +SELECT properties, presence FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 +ORDER BY op_depth DESC + +-- STMT_SELECT_ACTUAL_PROPS +SELECT properties FROM actual_node +WHERE wc_id = ?1 AND local_relpath = ?2 + +-- STMT_UPDATE_ACTUAL_PROPS +UPDATE actual_node SET properties = ?3 +WHERE wc_id = ?1 AND local_relpath = ?2 + +-- STMT_INSERT_ACTUAL_PROPS +INSERT INTO actual_node (wc_id, local_relpath, parent_relpath, properties) +VALUES (?1, ?2, ?3, ?4) + +-- STMT_INSERT_LOCK +INSERT OR REPLACE INTO lock +(repos_id, repos_relpath, lock_token, lock_owner, lock_comment, + lock_date) +VALUES (?1, ?2, ?3, ?4, ?5, ?6) + +/* Not valid for the working copy root */ +-- STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE +SELECT nodes.repos_id, nodes.repos_path, lock_token +FROM nodes +LEFT JOIN lock ON nodes.repos_id = lock.repos_id + AND nodes.repos_path = lock.repos_relpath +WHERE wc_id = ?1 AND op_depth = 0 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + +-- STMT_INSERT_WCROOT +INSERT INTO wcroot (local_abspath) +VALUES (?1) + +-- STMT_UPDATE_BASE_NODE_DAV_CACHE +UPDATE nodes SET dav_cache = ?3 +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 + +-- STMT_SELECT_BASE_DAV_CACHE +SELECT dav_cache FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 + +-- STMT_SELECT_DELETION_INFO +SELECT (SELECT b.presence FROM nodes AS b + WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0), + work.presence, work.op_depth +FROM nodes_current AS work +WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0 +LIMIT 1 + +-- STMT_SELECT_DELETION_INFO_SCAN +/* ### FIXME. moved.moved_to IS NOT NULL works when there is + only one move but we need something else when there are several. */ +SELECT (SELECT b.presence FROM nodes AS b + WHERE b.wc_id = ?1 AND b.local_relpath = ?2 AND b.op_depth = 0), + work.presence, work.op_depth, moved.moved_to +FROM nodes_current AS work +LEFT OUTER JOIN nodes AS moved + ON moved.wc_id = work.wc_id + AND moved.local_relpath = work.local_relpath + AND moved.moved_to IS NOT NULL +WHERE work.wc_id = ?1 AND work.local_relpath = ?2 AND work.op_depth > 0 +LIMIT 1 + +-- STMT_SELECT_OP_DEPTH_MOVED_TO +SELECT op_depth, moved_to, repos_path, revision +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 + AND op_depth <= (SELECT MIN(op_depth) FROM nodes + WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3) +ORDER BY op_depth DESC + +-- STMT_SELECT_MOVED_TO +SELECT moved_to +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 + +-- STMT_SELECT_MOVED_HERE +SELECT moved_here, presence, repos_path, revision +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth >= ?3 +ORDER BY op_depth + +-- STMT_SELECT_MOVED_BACK +SELECT u.local_relpath, + u.presence, u.repos_id, u.repos_path, u.revision, + l.presence, l.repos_id, l.repos_path, l.revision, + u.moved_here, u.moved_to +FROM nodes u +LEFT OUTER JOIN nodes l ON l.wc_id = ?1 + AND l.local_relpath = u.local_relpath + AND l.op_depth = ?3 +WHERE u.wc_id = ?1 + AND u.local_relpath = ?2 + AND u.op_depth = ?4 +UNION ALL +SELECT u.local_relpath, + u.presence, u.repos_id, u.repos_path, u.revision, + l.presence, l.repos_id, l.repos_path, l.revision, + u.moved_here, NULL +FROM nodes u +LEFT OUTER JOIN nodes l ON l.wc_id=?1 + AND l.local_relpath=u.local_relpath + AND l.op_depth=?3 +WHERE u.wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(u.local_relpath, ?2) + AND u.op_depth = ?4 + +-- STMT_DELETE_MOVED_BACK +DELETE FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 + +-- STMT_DELETE_LOCK +DELETE FROM lock +WHERE repos_id = ?1 AND repos_relpath = ?2 + +-- STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE +UPDATE nodes SET dav_cache = NULL +WHERE dav_cache IS NOT NULL AND wc_id = ?1 AND op_depth = 0 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + +-- STMT_RECURSIVE_UPDATE_NODE_REPO +UPDATE nodes SET repos_id = ?4, dav_cache = NULL +/* ### The Sqlite optimizer needs help here ### + * WHERE wc_id = ?1 + * AND repos_id = ?3 + * AND (local_relpath = ?2 + * OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))*/ +WHERE (wc_id = ?1 AND local_relpath = ?2 AND repos_id = ?3) + OR (wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND repos_id = ?3) + + +-- STMT_UPDATE_LOCK_REPOS_ID +UPDATE lock SET repos_id = ?2 +WHERE repos_id = ?1 + +-- STMT_UPDATE_NODE_FILEINFO +UPDATE nodes SET translated_size = ?3, last_mod_time = ?4 +WHERE wc_id = ?1 AND local_relpath = ?2 + AND op_depth = (SELECT MAX(op_depth) FROM nodes + WHERE wc_id = ?1 AND local_relpath = ?2) + +-- STMT_INSERT_ACTUAL_CONFLICT +INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath) +VALUES (?1, ?2, ?3, ?4) + +-- STMT_UPDATE_ACTUAL_CONFLICT +UPDATE actual_node SET conflict_data = ?3 +WHERE wc_id = ?1 AND local_relpath = ?2 + +-- STMT_UPDATE_ACTUAL_CHANGELISTS +UPDATE actual_node SET changelist = ?3 +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND local_relpath = (SELECT local_relpath FROM targets_list AS t + WHERE wc_id = ?1 + AND t.local_relpath = actual_node.local_relpath + AND kind = MAP_FILE) + +-- STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST +UPDATE actual_node SET changelist = NULL + WHERE wc_id = ?1 AND local_relpath = ?2 + +-- STMT_MARK_SKIPPED_CHANGELIST_DIRS +/* 7 corresponds to svn_wc_notify_skip */ +INSERT INTO changelist_list (wc_id, local_relpath, notify, changelist) +SELECT wc_id, local_relpath, 7, ?3 +FROM targets_list +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND kind = MAP_DIR + +-- STMT_RESET_ACTUAL_WITH_CHANGELIST +REPLACE INTO actual_node ( + wc_id, local_relpath, parent_relpath, changelist) +VALUES (?1, ?2, ?3, ?4) + +-- STMT_CREATE_CHANGELIST_LIST +DROP TABLE IF EXISTS changelist_list; +CREATE TEMPORARY TABLE changelist_list ( + wc_id INTEGER NOT NULL, + local_relpath TEXT NOT NULL, + notify INTEGER NOT NULL, + changelist TEXT NOT NULL, + /* Order NOTIFY descending to make us show clears (27) before adds (26) */ + PRIMARY KEY (wc_id, local_relpath, notify DESC) +) + +/* Create notify items for when a node is removed from a changelist and + when a node is added to a changelist. Make sure nothing is notified + if there were no changes. +*/ +-- STMT_CREATE_CHANGELIST_TRIGGER +DROP TRIGGER IF EXISTS trigger_changelist_list_change; +CREATE TEMPORARY TRIGGER trigger_changelist_list_change +BEFORE UPDATE ON actual_node +WHEN old.changelist IS NOT new.changelist +BEGIN + /* 27 corresponds to svn_wc_notify_changelist_clear */ + INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) + SELECT old.wc_id, old.local_relpath, 27, old.changelist + WHERE old.changelist is NOT NULL; + + /* 26 corresponds to svn_wc_notify_changelist_set */ + INSERT INTO changelist_list(wc_id, local_relpath, notify, changelist) + SELECT new.wc_id, new.local_relpath, 26, new.changelist + WHERE new.changelist IS NOT NULL; +END + +-- STMT_FINALIZE_CHANGELIST +DROP TRIGGER trigger_changelist_list_change; +DROP TABLE changelist_list; +DROP TABLE targets_list + +-- STMT_SELECT_CHANGELIST_LIST +SELECT wc_id, local_relpath, notify, changelist +FROM changelist_list +ORDER BY wc_id, local_relpath ASC, notify DESC + +-- STMT_CREATE_TARGETS_LIST +DROP TABLE IF EXISTS targets_list; +CREATE TEMPORARY TABLE targets_list ( + wc_id INTEGER NOT NULL, + local_relpath TEXT NOT NULL, + parent_relpath TEXT, + kind TEXT NOT NULL, + PRIMARY KEY (wc_id, local_relpath) + ); +/* need more indicies? */ + +-- STMT_DROP_TARGETS_LIST +DROP TABLE targets_list + +-- STMT_INSERT_TARGET +INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) +SELECT wc_id, local_relpath, parent_relpath, kind +FROM nodes_current +WHERE wc_id = ?1 + AND local_relpath = ?2 + +-- STMT_INSERT_TARGET_DEPTH_FILES +INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) +SELECT wc_id, local_relpath, parent_relpath, kind +FROM nodes_current +WHERE wc_id = ?1 + AND parent_relpath = ?2 + AND kind = MAP_FILE + +-- STMT_INSERT_TARGET_DEPTH_IMMEDIATES +INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) +SELECT wc_id, local_relpath, parent_relpath, kind +FROM nodes_current +WHERE wc_id = ?1 + AND parent_relpath = ?2 + +-- STMT_INSERT_TARGET_DEPTH_INFINITY +INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) +SELECT wc_id, local_relpath, parent_relpath, kind +FROM nodes_current +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + +-- STMT_INSERT_TARGET_WITH_CHANGELIST +INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) +SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind + FROM actual_node AS A JOIN nodes_current AS N + ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath + WHERE N.wc_id = ?1 + AND N.local_relpath = ?2 + AND A.changelist = ?3 + +-- STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES +INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) +SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind + FROM actual_node AS A JOIN nodes_current AS N + ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath + WHERE N.wc_id = ?1 + AND N.parent_relpath = ?2 + AND kind = MAP_FILE + AND A.changelist = ?3 + +-- STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES +INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) +SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind + FROM actual_node AS A JOIN nodes_current AS N + ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath + WHERE N.wc_id = ?1 + AND N.parent_relpath = ?2 + AND A.changelist = ?3 + +-- STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY +INSERT INTO targets_list(wc_id, local_relpath, parent_relpath, kind) +SELECT N.wc_id, N.local_relpath, N.parent_relpath, N.kind + FROM actual_node AS A JOIN nodes_current AS N + ON A.wc_id = N.wc_id AND A.local_relpath = N.local_relpath + WHERE N.wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(N.local_relpath, ?2) + AND A.changelist = ?3 + +/* Only used by commented dump_targets() in wc_db.c */ +/*-- STMT_SELECT_TARGETS +SELECT local_relpath, parent_relpath from targets_list*/ + +-- STMT_INSERT_ACTUAL_EMPTIES +INSERT OR IGNORE INTO actual_node ( + wc_id, local_relpath, parent_relpath) +SELECT wc_id, local_relpath, parent_relpath +FROM targets_list + +-- STMT_DELETE_ACTUAL_EMPTY +DELETE FROM actual_node +WHERE wc_id = ?1 AND local_relpath = ?2 + AND properties IS NULL + AND conflict_data IS NULL + AND changelist IS NULL + AND text_mod IS NULL + AND older_checksum IS NULL + AND right_checksum IS NULL + AND left_checksum IS NULL + +-- STMT_DELETE_ACTUAL_EMPTIES +DELETE FROM actual_node +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND properties IS NULL + AND conflict_data IS NULL + AND changelist IS NULL + AND text_mod IS NULL + AND older_checksum IS NULL + AND right_checksum IS NULL + AND left_checksum IS NULL + +-- STMT_DELETE_BASE_NODE +DELETE FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 + +-- STMT_DELETE_WORKING_NODE +DELETE FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 + AND op_depth = (SELECT MAX(op_depth) FROM nodes + WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0) + +-- STMT_DELETE_LOWEST_WORKING_NODE +DELETE FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 + AND op_depth = (SELECT MIN(op_depth) FROM nodes + WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3) + AND presence = MAP_BASE_DELETED + +-- STMT_DELETE_ALL_LAYERS +DELETE FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 + +-- STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE +DELETE FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth >= ?3 + +-- STMT_DELETE_ACTUAL_NODE +DELETE FROM actual_node +WHERE wc_id = ?1 AND local_relpath = ?2 + +/* Will not delete recursive when run on the wcroot */ +-- STMT_DELETE_ACTUAL_NODE_RECURSIVE +DELETE FROM actual_node +WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + +-- STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST +DELETE FROM actual_node +WHERE wc_id = ?1 + AND local_relpath = ?2 + AND (changelist IS NULL + OR NOT EXISTS (SELECT 1 FROM nodes_current c + WHERE c.wc_id = ?1 AND c.local_relpath = ?2 + AND c.kind = MAP_FILE)) + +-- STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE +DELETE FROM actual_node +WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND (changelist IS NULL + OR NOT EXISTS (SELECT 1 FROM nodes_current c + WHERE c.wc_id = ?1 + AND c.local_relpath = actual_node.local_relpath + AND c.kind = MAP_FILE)) + +-- STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST +UPDATE actual_node +SET properties = NULL, + text_mod = NULL, + conflict_data = NULL, + tree_conflict_data = NULL, + older_checksum = NULL, + left_checksum = NULL, + right_checksum = NULL +WHERE wc_id = ?1 AND local_relpath = ?2 + +-- STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE +UPDATE actual_node +SET properties = NULL, + text_mod = NULL, + conflict_data = NULL, + tree_conflict_data = NULL, + older_checksum = NULL, + left_checksum = NULL, + right_checksum = NULL +WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + +-- STMT_UPDATE_NODE_BASE_DEPTH +UPDATE nodes SET depth = ?3 +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 + AND kind=MAP_DIR + +-- STMT_UPDATE_NODE_BASE_PRESENCE +UPDATE nodes SET presence = ?3 +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 + +-- STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH +UPDATE nodes SET presence = ?3, revision = ?4, repos_path = ?5 +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 + +-- STMT_LOOK_FOR_WORK +SELECT id FROM work_queue LIMIT 1 + +-- STMT_INSERT_WORK_ITEM +INSERT INTO work_queue (work) VALUES (?1) + +-- STMT_SELECT_WORK_ITEM +SELECT id, work FROM work_queue ORDER BY id LIMIT 1 + +-- STMT_DELETE_WORK_ITEM +DELETE FROM work_queue WHERE id = ?1 + +-- STMT_INSERT_OR_IGNORE_PRISTINE +INSERT OR IGNORE INTO pristine (checksum, md5_checksum, size, refcount) +VALUES (?1, ?2, ?3, 0) + +-- STMT_INSERT_PRISTINE +INSERT INTO pristine (checksum, md5_checksum, size, refcount) +VALUES (?1, ?2, ?3, 0) + +-- STMT_SELECT_PRISTINE +SELECT md5_checksum +FROM pristine +WHERE checksum = ?1 + +-- STMT_SELECT_PRISTINE_SIZE +SELECT size +FROM pristine +WHERE checksum = ?1 LIMIT 1 + +-- STMT_SELECT_PRISTINE_BY_MD5 +SELECT checksum +FROM pristine +WHERE md5_checksum = ?1 + +-- STMT_SELECT_UNREFERENCED_PRISTINES +SELECT checksum +FROM pristine +WHERE refcount = 0 + +-- STMT_DELETE_PRISTINE_IF_UNREFERENCED +DELETE FROM pristine +WHERE checksum = ?1 AND refcount = 0 + +-- STMT_SELECT_COPY_PRISTINES +/* For the root itself */ +SELECT n.checksum, md5_checksum, size +FROM nodes_current n +LEFT JOIN pristine p ON n.checksum = p.checksum +WHERE wc_id = ?1 + AND n.local_relpath = ?2 + AND n.checksum IS NOT NULL +UNION ALL +/* And all descendants */ +SELECT n.checksum, md5_checksum, size +FROM nodes n +LEFT JOIN pristine p ON n.checksum = p.checksum +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(n.local_relpath, ?2) + AND op_depth >= + (SELECT MAX(op_depth) FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2) + AND n.checksum IS NOT NULL + +-- STMT_VACUUM +VACUUM + +-- STMT_SELECT_CONFLICT_VICTIMS +SELECT local_relpath, conflict_data +FROM actual_node +WHERE wc_id = ?1 AND parent_relpath = ?2 AND + NOT (conflict_data IS NULL) + +-- STMT_INSERT_WC_LOCK +INSERT INTO wc_lock (wc_id, local_dir_relpath, locked_levels) +VALUES (?1, ?2, ?3) + +-- STMT_SELECT_WC_LOCK +SELECT locked_levels FROM wc_lock +WHERE wc_id = ?1 AND local_dir_relpath = ?2 + +-- STMT_SELECT_ANCESTOR_WCLOCKS +SELECT local_dir_relpath, locked_levels FROM wc_lock +WHERE wc_id = ?1 + AND ((local_dir_relpath >= ?3 AND local_dir_relpath <= ?2) + OR local_dir_relpath = '') + +-- STMT_DELETE_WC_LOCK +DELETE FROM wc_lock +WHERE wc_id = ?1 AND local_dir_relpath = ?2 + +-- STMT_FIND_WC_LOCK +SELECT local_dir_relpath FROM wc_lock +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_dir_relpath, ?2) + +-- STMT_DELETE_WC_LOCK_ORPHAN +DELETE FROM wc_lock +WHERE wc_id = ?1 AND local_dir_relpath = ?2 +AND NOT EXISTS (SELECT 1 FROM nodes + WHERE nodes.wc_id = ?1 + AND nodes.local_relpath = wc_lock.local_dir_relpath) + +-- STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE +DELETE FROM wc_lock +WHERE wc_id = ?1 + AND (local_dir_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_dir_relpath, ?2)) + AND NOT EXISTS (SELECT 1 FROM nodes + WHERE nodes.wc_id = ?1 + AND nodes.local_relpath = wc_lock.local_dir_relpath) + +-- STMT_APPLY_CHANGES_TO_BASE_NODE +/* translated_size and last_mod_time are not mentioned here because they will + be tweaked after the working-file is installed. When we replace an existing + BASE node (read: bump), preserve its file_external status. */ +INSERT OR REPLACE INTO nodes ( + wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, + revision, presence, depth, kind, changed_revision, changed_date, + changed_author, checksum, properties, dav_cache, symlink_target, + inherited_props, file_external ) +VALUES (?1, ?2, 0, + ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, + (SELECT file_external FROM nodes + WHERE wc_id = ?1 + AND local_relpath = ?2 + AND op_depth = 0)) + +-- STMT_INSTALL_WORKING_NODE_FOR_DELETE +INSERT OR REPLACE INTO nodes ( + wc_id, local_relpath, op_depth, + parent_relpath, presence, kind) +VALUES(?1, ?2, ?3, ?4, MAP_BASE_DELETED, ?5) + +-- STMT_DELETE_NO_LOWER_LAYER +DELETE FROM nodes + WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 + AND NOT EXISTS (SELECT 1 FROM nodes n + WHERE n.wc_id = ?1 + AND n.local_relpath = nodes.local_relpath + AND n.op_depth = ?4 + AND n.presence IN (MAP_NORMAL, MAP_INCOMPLETE)) + +-- STMT_REPLACE_WITH_BASE_DELETED +INSERT OR REPLACE INTO nodes (wc_id, local_relpath, op_depth, parent_relpath, + kind, moved_to, presence) +SELECT wc_id, local_relpath, op_depth, parent_relpath, + kind, moved_to, MAP_BASE_DELETED + FROM nodes + WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 + +/* If this query is updated, STMT_INSERT_DELETE_LIST should too. */ +-- STMT_INSERT_DELETE_FROM_NODE_RECURSIVE +INSERT INTO nodes ( + wc_id, local_relpath, op_depth, parent_relpath, presence, kind) +SELECT wc_id, local_relpath, ?4 /*op_depth*/, parent_relpath, MAP_BASE_DELETED, + kind +FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 + AND presence NOT IN (MAP_BASE_DELETED, MAP_NOT_PRESENT, MAP_EXCLUDED, MAP_SERVER_EXCLUDED) + AND file_external IS NULL + +-- STMT_INSERT_WORKING_NODE_FROM_BASE_COPY +INSERT INTO nodes ( + wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path, + revision, presence, depth, kind, changed_revision, changed_date, + changed_author, checksum, properties, translated_size, last_mod_time, + symlink_target ) +SELECT wc_id, local_relpath, ?3 /*op_depth*/, parent_relpath, repos_id, + repos_path, revision, presence, depth, kind, changed_revision, + changed_date, changed_author, checksum, properties, translated_size, + last_mod_time, symlink_target +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 + +-- STMT_INSERT_DELETE_FROM_BASE +INSERT INTO nodes ( + wc_id, local_relpath, op_depth, parent_relpath, presence, kind) +SELECT wc_id, local_relpath, ?3 /*op_depth*/, parent_relpath, + MAP_BASE_DELETED, kind +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 + +/* Not valid on the wc-root */ +-- STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE +UPDATE nodes SET op_depth = ?3 + 1 +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = ?3 + +-- STMT_UPDATE_OP_DEPTH_RECURSIVE +UPDATE nodes SET op_depth = ?4, moved_here = NULL +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 + +-- STMT_DOES_NODE_EXIST +SELECT 1 FROM nodes WHERE wc_id = ?1 AND local_relpath = ?2 +LIMIT 1 + +-- STMT_HAS_SERVER_EXCLUDED_DESCENDANTS +SELECT local_relpath FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 AND presence = MAP_SERVER_EXCLUDED +LIMIT 1 + +/* Select all excluded nodes. Not valid on the WC-root */ +-- STMT_SELECT_ALL_EXCLUDED_DESCENDANTS +SELECT local_relpath FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 + AND (presence = MAP_SERVER_EXCLUDED OR presence = MAP_EXCLUDED) + +/* Creates a copy from one top level NODE to a different location */ +-- STMT_INSERT_WORKING_NODE_COPY_FROM +INSERT OR REPLACE INTO nodes ( + wc_id, local_relpath, op_depth, parent_relpath, repos_id, + repos_path, revision, presence, depth, moved_here, kind, changed_revision, + changed_date, changed_author, checksum, properties, translated_size, + last_mod_time, symlink_target, moved_to ) +SELECT wc_id, ?3 /*local_relpath*/, ?4 /*op_depth*/, ?5 /*parent_relpath*/, + repos_id, repos_path, revision, ?6 /*presence*/, depth, + ?7/*moved_here*/, kind, changed_revision, changed_date, + changed_author, checksum, properties, translated_size, + last_mod_time, symlink_target, + (SELECT dst.moved_to FROM nodes AS dst + WHERE dst.wc_id = ?1 + AND dst.local_relpath = ?3 + AND dst.op_depth = ?4) +FROM nodes_current +WHERE wc_id = ?1 AND local_relpath = ?2 + +-- STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH +INSERT OR REPLACE INTO nodes ( + wc_id, local_relpath, op_depth, parent_relpath, repos_id, + repos_path, revision, presence, depth, moved_here, kind, changed_revision, + changed_date, changed_author, checksum, properties, translated_size, + last_mod_time, symlink_target, moved_to ) +SELECT wc_id, ?3 /*local_relpath*/, ?4 /*op_depth*/, ?5 /*parent_relpath*/, + repos_id, repos_path, revision, ?6 /*presence*/, depth, + ?8 /*moved_here*/, kind, changed_revision, changed_date, + changed_author, checksum, properties, translated_size, + last_mod_time, symlink_target, + (SELECT dst.moved_to FROM nodes AS dst + WHERE dst.wc_id = ?1 + AND dst.local_relpath = ?3 + AND dst.op_depth = ?4) +FROM nodes +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?7 + +-- STMT_UPDATE_BASE_REVISION +UPDATE nodes SET revision = ?3 +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 + +-- STMT_UPDATE_BASE_REPOS +UPDATE nodes SET repos_id = ?3, repos_path = ?4 +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0 + +-- STMT_ACTUAL_HAS_CHILDREN +SELECT 1 FROM actual_node +WHERE wc_id = ?1 AND parent_relpath = ?2 +LIMIT 1 + +-- STMT_INSERT_EXTERNAL +INSERT OR REPLACE INTO externals ( + wc_id, local_relpath, parent_relpath, presence, kind, def_local_relpath, + repos_id, def_repos_relpath, def_operational_revision, def_revision) +VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10) + +-- STMT_SELECT_EXTERNAL_INFO +SELECT presence, kind, def_local_relpath, repos_id, + def_repos_relpath, def_operational_revision, def_revision +FROM externals WHERE wc_id = ?1 AND local_relpath = ?2 +LIMIT 1 + +-- STMT_DELETE_FILE_EXTERNALS +DELETE FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 + AND file_external IS NOT NULL + +-- STMT_DELETE_FILE_EXTERNAL_REGISTATIONS +DELETE FROM externals +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND kind != MAP_DIR + +-- STMT_DELETE_EXTERNAL_REGISTATIONS +DELETE FROM externals +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + +/* Select all committable externals, i.e. only unpegged ones on the same + * repository as the target path ?2, that are defined by WC ?1 to + * live below the target path. It does not matter which ancestor has the + * svn:externals definition, only the local path at which the external is + * supposed to be checked out is queried. + * Arguments: + * ?1: wc_id. + * ?2: the target path, local relpath inside ?1. + * + * ### NOTE: This statement deliberately removes file externals that live + * inside an unversioned dir, because commit still breaks on those. + * Once that's been fixed, the conditions below "--->8---" become obsolete. */ +-- STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW +SELECT local_relpath, kind, def_repos_relpath, + (SELECT root FROM repository AS r WHERE r.id = e.repos_id) +FROM externals e +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND def_revision IS NULL + AND repos_id = (SELECT repos_id + FROM nodes AS n + WHERE n.wc_id = ?1 + AND n.local_relpath = '' + AND n.op_depth = 0) + AND ((kind='dir') + OR EXISTS (SELECT 1 FROM nodes + WHERE nodes.wc_id = e.wc_id + AND nodes.local_relpath = e.parent_relpath)) + +-- STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW +SELECT local_relpath, kind, def_repos_relpath, + (SELECT root FROM repository AS r WHERE r.id = e.repos_id) +FROM externals e +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(e.local_relpath, ?2) + AND parent_relpath = ?2 + AND def_revision IS NULL + AND repos_id = (SELECT repos_id + FROM nodes AS n + WHERE n.wc_id = ?1 + AND n.local_relpath = '' + AND n.op_depth = 0) + AND ((kind='dir') + OR EXISTS (SELECT 1 FROM nodes + WHERE nodes.wc_id = e.wc_id + AND nodes.local_relpath = e.parent_relpath)) + +-- STMT_SELECT_EXTERNALS_DEFINED +SELECT local_relpath, def_local_relpath +FROM externals +/* ### The Sqlite optimizer needs help here ### + * WHERE wc_id = ?1 + * AND (def_local_relpath = ?2 + * OR IS_STRICT_DESCENDANT_OF(def_local_relpath, ?2)) */ +WHERE (wc_id = ?1 AND def_local_relpath = ?2) + OR (wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(def_local_relpath, ?2)) + +-- STMT_DELETE_EXTERNAL +DELETE FROM externals +WHERE wc_id = ?1 AND local_relpath = ?2 + +-- STMT_SELECT_EXTERNAL_PROPERTIES +/* ### It would be nice if Sqlite would handle + * SELECT IFNULL((SELECT properties FROM actual_node a + * WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), + * properties), + * local_relpath, depth + * FROM nodes_current n + * WHERE wc_id = ?1 + * AND (local_relpath = ?2 + * OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + * AND kind = MAP_DIR AND presence IN (MAP_NORMAL, MAP_INCOMPLETE) + * ### But it would take a double table scan execution plan for it. + * ### Maybe there is something else going on? */ +SELECT IFNULL((SELECT properties FROM actual_node a + WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), + properties), + local_relpath, depth +FROM nodes_current n +WHERE wc_id = ?1 AND local_relpath = ?2 + AND kind = MAP_DIR AND presence IN (MAP_NORMAL, MAP_INCOMPLETE) +UNION ALL +SELECT IFNULL((SELECT properties FROM actual_node a + WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), + properties), + local_relpath, depth +FROM nodes_current n +WHERE wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND kind = MAP_DIR AND presence IN (MAP_NORMAL, MAP_INCOMPLETE) + +-- STMT_SELECT_CURRENT_PROPS_RECURSIVE +/* ### Ugly OR to make sqlite use the proper optimizations */ +SELECT IFNULL((SELECT properties FROM actual_node a + WHERE a.wc_id = ?1 AND A.local_relpath = n.local_relpath), + properties), + local_relpath +FROM nodes_current n +WHERE (wc_id = ?1 AND local_relpath = ?2) + OR (wc_id = ?1 AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + +-- STMT_PRAGMA_LOCKING_MODE +PRAGMA locking_mode = exclusive + +/* ------------------------------------------------------------------------- */ + +/* these are used in entries.c */ + +-- STMT_INSERT_ACTUAL_NODE +INSERT OR REPLACE INTO actual_node ( + wc_id, local_relpath, parent_relpath, properties, changelist, conflict_data) +VALUES (?1, ?2, ?3, ?4, ?5, ?6) + +/* ------------------------------------------------------------------------- */ + +/* these are used in upgrade.c */ + +-- STMT_UPDATE_ACTUAL_CONFLICT_DATA +UPDATE actual_node SET conflict_data = ?3 +WHERE wc_id = ?1 AND local_relpath = ?2 + +-- STMT_INSERT_ACTUAL_CONFLICT_DATA +INSERT INTO actual_node (wc_id, local_relpath, conflict_data, parent_relpath) +VALUES (?1, ?2, ?3, ?4) + +-- STMT_SELECT_ALL_FILES +SELECT local_relpath FROM nodes_current +WHERE wc_id = ?1 AND parent_relpath = ?2 AND kind = MAP_FILE + +-- STMT_UPDATE_NODE_PROPS +UPDATE nodes SET properties = ?4 +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 + +-- STMT_PRAGMA_TABLE_INFO_NODES +PRAGMA table_info("NODES") + +/* -------------------------------------------------------------------------- + * Complex queries for callback walks, caching results in a temporary table. + * + * These target table are then used for joins against NODES, or for reporting + */ + +-- STMT_CREATE_TARGET_PROP_CACHE +DROP TABLE IF EXISTS target_prop_cache; +CREATE TEMPORARY TABLE target_prop_cache ( + local_relpath TEXT NOT NULL PRIMARY KEY, + kind TEXT NOT NULL, + properties BLOB +); +/* ### Need index? +CREATE UNIQUE INDEX temp__node_props_cache_unique + ON temp__node_props_cache (local_relpath) */ + +-- STMT_CACHE_TARGET_PROPS +INSERT INTO target_prop_cache(local_relpath, kind, properties) + SELECT n.local_relpath, n.kind, + IFNULL((SELECT properties FROM actual_node AS a + WHERE a.wc_id = n.wc_id + AND a.local_relpath = n.local_relpath), + n.properties) + FROM targets_list AS t + JOIN nodes AS n + ON n.wc_id = ?1 + AND n.local_relpath = t.local_relpath + AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3 + WHERE n3.wc_id = ?1 + AND n3.local_relpath = t.local_relpath) + WHERE t.wc_id = ?1 + AND (presence=MAP_NORMAL OR presence=MAP_INCOMPLETE) + ORDER BY t.local_relpath + +-- STMT_CACHE_TARGET_PRISTINE_PROPS +INSERT INTO target_prop_cache(local_relpath, kind, properties) + SELECT n.local_relpath, n.kind, + CASE n.presence + WHEN MAP_BASE_DELETED + THEN (SELECT properties FROM nodes AS p + WHERE p.wc_id = n.wc_id + AND p.local_relpath = n.local_relpath + AND p.op_depth < n.op_depth + ORDER BY p.op_depth DESC /* LIMIT 1 */) + ELSE properties END + FROM targets_list AS t + JOIN nodes AS n + ON n.wc_id = ?1 + AND n.local_relpath = t.local_relpath + AND n.op_depth = (SELECT MAX(op_depth) FROM nodes AS n3 + WHERE n3.wc_id = ?1 + AND n3.local_relpath = t.local_relpath) + WHERE t.wc_id = ?1 + AND (presence = MAP_NORMAL + OR presence = MAP_INCOMPLETE + OR presence = MAP_BASE_DELETED) + ORDER BY t.local_relpath + +-- STMT_SELECT_ALL_TARGET_PROP_CACHE +SELECT local_relpath, properties FROM target_prop_cache +ORDER BY local_relpath + +-- STMT_DROP_TARGET_PROP_CACHE +DROP TABLE target_prop_cache; + +-- STMT_CREATE_REVERT_LIST +DROP TABLE IF EXISTS revert_list; +CREATE TEMPORARY TABLE revert_list ( + /* need wc_id if/when revert spans multiple working copies */ + local_relpath TEXT NOT NULL, + actual INTEGER NOT NULL, /* 1 if an actual row, 0 if a nodes row */ + conflict_data BLOB, + notify INTEGER, /* 1 if an actual row had props or tree conflict */ + op_depth INTEGER, + repos_id INTEGER, + kind TEXT, + PRIMARY KEY (local_relpath, actual) + ); +DROP TRIGGER IF EXISTS trigger_revert_list_nodes; +CREATE TEMPORARY TRIGGER trigger_revert_list_nodes +BEFORE DELETE ON nodes +BEGIN + INSERT OR REPLACE INTO revert_list(local_relpath, actual, op_depth, + repos_id, kind) + SELECT OLD.local_relpath, 0, OLD.op_depth, OLD.repos_id, OLD.kind; +END; +DROP TRIGGER IF EXISTS trigger_revert_list_actual_delete; +CREATE TEMPORARY TRIGGER trigger_revert_list_actual_delete +BEFORE DELETE ON actual_node +BEGIN + INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data, + notify) + SELECT OLD.local_relpath, 1, OLD.conflict_data, + CASE + WHEN OLD.properties IS NOT NULL + THEN 1 + WHEN NOT EXISTS(SELECT 1 FROM NODES n + WHERE n.wc_id = OLD.wc_id + AND n.local_relpath = OLD.local_relpath) + THEN 1 + ELSE NULL + END; +END; +DROP TRIGGER IF EXISTS trigger_revert_list_actual_update; +CREATE TEMPORARY TRIGGER trigger_revert_list_actual_update +BEFORE UPDATE ON actual_node +BEGIN + INSERT OR REPLACE INTO revert_list(local_relpath, actual, conflict_data, + notify) + SELECT OLD.local_relpath, 1, OLD.conflict_data, + CASE + WHEN OLD.properties IS NOT NULL + THEN 1 + WHEN NOT EXISTS(SELECT 1 FROM NODES n + WHERE n.wc_id = OLD.wc_id + AND n.local_relpath = OLD.local_relpath) + THEN 1 + ELSE NULL + END; +END + +-- STMT_DROP_REVERT_LIST_TRIGGERS +DROP TRIGGER trigger_revert_list_nodes; +DROP TRIGGER trigger_revert_list_actual_delete; +DROP TRIGGER trigger_revert_list_actual_update + +-- STMT_SELECT_REVERT_LIST +SELECT actual, notify, kind, op_depth, repos_id, conflict_data +FROM revert_list +WHERE local_relpath = ?1 +ORDER BY actual DESC + +-- STMT_SELECT_REVERT_LIST_COPIED_CHILDREN +SELECT local_relpath, kind +FROM revert_list +WHERE IS_STRICT_DESCENDANT_OF(local_relpath, ?1) + AND op_depth >= ?2 + AND repos_id IS NOT NULL +ORDER BY local_relpath + +-- STMT_DELETE_REVERT_LIST +DELETE FROM revert_list WHERE local_relpath = ?1 + +-- STMT_SELECT_REVERT_LIST_RECURSIVE +SELECT DISTINCT local_relpath +FROM revert_list +WHERE (local_relpath = ?1 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?1)) + AND (notify OR actual = 0) +ORDER BY local_relpath + +-- STMT_DELETE_REVERT_LIST_RECURSIVE +DELETE FROM revert_list +WHERE (local_relpath = ?1 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?1)) + +-- STMT_DROP_REVERT_LIST +DROP TABLE IF EXISTS revert_list + +-- STMT_CREATE_DELETE_LIST +DROP TABLE IF EXISTS delete_list; +CREATE TEMPORARY TABLE delete_list ( +/* ### we should put the wc_id in here in case a delete spans multiple + ### working copies. queries, etc will need to be adjusted. */ + local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE + ) + +/* This matches the selection in STMT_INSERT_DELETE_FROM_NODE_RECURSIVE. + A subquery is used instead of nodes_current to avoid a table scan */ +-- STMT_INSERT_DELETE_LIST +INSERT INTO delete_list(local_relpath) +SELECT local_relpath FROM nodes AS n +WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth >= ?3 + AND op_depth = (SELECT MAX(s.op_depth) FROM nodes AS s + WHERE s.wc_id = ?1 + AND s.local_relpath = n.local_relpath) + AND presence NOT IN (MAP_BASE_DELETED, MAP_NOT_PRESENT, MAP_EXCLUDED, MAP_SERVER_EXCLUDED) + AND file_external IS NULL + +-- STMT_SELECT_DELETE_LIST +SELECT local_relpath FROM delete_list +ORDER BY local_relpath + +-- STMT_FINALIZE_DELETE +DROP TABLE IF EXISTS delete_list + +-- STMT_CREATE_UPDATE_MOVE_LIST +DROP TABLE IF EXISTS update_move_list; +CREATE TEMPORARY TABLE update_move_list ( +/* ### we should put the wc_id in here in case a move update spans multiple + ### working copies. queries, etc will need to be adjusted. */ + local_relpath TEXT PRIMARY KEY NOT NULL UNIQUE, + action INTEGER NOT NULL, + kind INTEGER NOT NULL, + content_state INTEGER NOT NULL, + prop_state INTEGER NOT NULL + ) + +-- STMT_INSERT_UPDATE_MOVE_LIST +INSERT INTO update_move_list(local_relpath, action, kind, content_state, + prop_state) +VALUES (?1, ?2, ?3, ?4, ?5) + +-- STMT_SELECT_UPDATE_MOVE_LIST +SELECT local_relpath, action, kind, content_state, prop_state +FROM update_move_list +ORDER BY local_relpath + +-- STMT_FINALIZE_UPDATE_MOVE +DROP TABLE IF EXISTS update_move_list + +/* ------------------------------------------------------------------------- */ + +/* Queries for revision status. */ + +-- STMT_SELECT_MIN_MAX_REVISIONS +SELECT MIN(revision), MAX(revision), + MIN(changed_revision), MAX(changed_revision) FROM nodes + WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND presence IN (MAP_NORMAL, MAP_INCOMPLETE) + AND file_external IS NULL + AND op_depth = 0 + +-- STMT_HAS_SPARSE_NODES +SELECT 1 FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = 0 + AND (presence IN (MAP_SERVER_EXCLUDED, MAP_EXCLUDED) + OR depth NOT IN (MAP_DEPTH_INFINITY, MAP_DEPTH_UNKNOWN)) + AND file_external IS NULL +LIMIT 1 + +-- STMT_SUBTREE_HAS_TREE_MODIFICATIONS +SELECT 1 FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth > 0 +LIMIT 1 + +-- STMT_SUBTREE_HAS_PROP_MODIFICATIONS +SELECT 1 FROM actual_node +WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND properties IS NOT NULL +LIMIT 1 + +-- STMT_HAS_SWITCHED +SELECT 1 +FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 + AND file_external IS NULL + AND presence IN (MAP_NORMAL, MAP_INCOMPLETE) + AND repos_path IS NOT RELPATH_SKIP_JOIN(?2, ?3, local_relpath) +LIMIT 1 + +-- STMT_SELECT_BASE_FILES_RECURSIVE +SELECT local_relpath, translated_size, last_mod_time FROM nodes AS n +WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = 0 + AND kind=MAP_FILE + AND presence=MAP_NORMAL + AND file_external IS NULL + +/* ### FIXME: op-depth? What about multiple moves? */ +-- STMT_SELECT_MOVED_FROM_RELPATH +SELECT local_relpath, op_depth FROM nodes +WHERE wc_id = ?1 AND moved_to = ?2 AND op_depth > 0 + +-- STMT_UPDATE_MOVED_TO_RELPATH +UPDATE nodes SET moved_to = ?4 +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 + +-- STMT_CLEAR_MOVED_TO_RELPATH +UPDATE nodes SET moved_to = NULL +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3 + +-- STMT_CLEAR_MOVED_HERE_RECURSIVE +UPDATE nodes SET moved_here = NULL +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 + +/* This statement returns pairs of move-roots below the path ?2 in WC_ID ?1. + * Each row returns a moved-here path (always a child of ?2) in the first + * column, and its matching moved-away (deleted) path in the second column. */ +-- STMT_SELECT_MOVED_HERE_CHILDREN +SELECT moved_to, local_relpath FROM nodes +WHERE wc_id = ?1 AND op_depth > 0 + AND IS_STRICT_DESCENDANT_OF(moved_to, ?2) + +-- STMT_SELECT_MOVED_FOR_DELETE +SELECT local_relpath, moved_to, op_depth FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND moved_to IS NOT NULL + AND op_depth >= (SELECT MAX(op_depth) FROM nodes o + WHERE o.wc_id = ?1 + AND o.local_relpath = ?2) + +-- STMT_UPDATE_MOVED_TO_DESCENDANTS +UPDATE nodes SET moved_to = RELPATH_SKIP_JOIN(?2, ?3, moved_to) + WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(moved_to, ?2) + +-- STMT_CLEAR_MOVED_TO_DESCENDANTS +UPDATE nodes SET moved_to = NULL + WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(moved_to, ?2) + + +/* This statement returns pairs of move-roots below the path ?2 in WC_ID ?1, + * where the source of the move is within the subtree rooted at path ?2, and + * the destination of the move is outside the subtree rooted at path ?2. */ +-- STMT_SELECT_MOVED_PAIR2 +SELECT local_relpath, moved_to, op_depth FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND moved_to IS NOT NULL + AND NOT IS_STRICT_DESCENDANT_OF(moved_to, ?2) + AND op_depth >= (SELECT MAX(op_depth) FROM nodes o + WHERE o.wc_id = ?1 + AND o.local_relpath = ?2) + +-- STMT_SELECT_MOVED_PAIR3 +SELECT local_relpath, moved_to, op_depth, kind FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth > ?3 + AND moved_to IS NOT NULL + +-- STMT_SELECT_MOVED_OUTSIDE +SELECT local_relpath, moved_to FROM nodes +WHERE wc_id = ?1 + AND (local_relpath = ?2 OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth >= ?3 + AND moved_to IS NOT NULL + AND NOT IS_STRICT_DESCENDANT_OF(moved_to, ?2) + +-- STMT_SELECT_OP_DEPTH_MOVED_PAIR +SELECT n.local_relpath, n.moved_to, + (SELECT o.repos_path FROM nodes AS o + WHERE o.wc_id = n.wc_id + AND o.local_relpath = n.local_relpath + AND o.op_depth < ?3 ORDER BY o.op_depth DESC LIMIT 1) +FROM nodes AS n +WHERE n.wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(n.local_relpath, ?2) + AND n.op_depth = ?3 + AND n.moved_to IS NOT NULL + +-- STMT_SELECT_MOVED_DESCENDANTS +SELECT n.local_relpath, h.moved_to +FROM nodes n, nodes h +WHERE n.wc_id = ?1 + AND h.wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(n.local_relpath, ?2) + AND h.local_relpath = n.local_relpath + AND n.op_depth = ?3 + AND h.op_depth = (SELECT MIN(o.op_depth) + FROM nodes o + WHERE o.wc_id = ?1 + AND o.local_relpath = n.local_relpath + AND o.op_depth > ?3) + AND h.moved_to IS NOT NULL + +-- STMT_COMMIT_UPDATE_ORIGIN +/* Note that the only reason this SUBSTR() trick is valid is that you + can move neither the working copy nor the repository root. + + SUBSTR(local_relpath, LENGTH(?2)+1) includes the '/' of the path */ +UPDATE nodes SET repos_id = ?4, + repos_path = ?5 || SUBSTR(local_relpath, LENGTH(?2)+1), + revision = ?6 +WHERE wc_id = ?1 + AND (local_relpath = ?2 + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2)) + AND op_depth = ?3 + +-- STMT_HAS_LAYER_BETWEEN +SELECT 1 FROM NODES +WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > ?3 AND op_depth < ?4 + +-- STMT_SELECT_REPOS_PATH_REVISION +SELECT local_relpath, repos_path, revision FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 +ORDER BY local_relpath + +-- STMT_SELECT_HAS_NON_FILE_CHILDREN +SELECT 1 FROM nodes +WHERE wc_id = ?1 AND parent_relpath = ?2 AND op_depth = 0 AND kind != MAP_FILE + +-- STMT_SELECT_HAS_GRANDCHILDREN +SELECT 1 FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(parent_relpath, ?2) + AND op_depth = 0 + AND file_external IS NULL + +/* ------------------------------------------------------------------------- */ + +/* Queries for verification. */ + +-- STMT_SELECT_ALL_NODES +SELECT op_depth, local_relpath, parent_relpath, file_external FROM nodes +WHERE wc_id = ?1 + +/* ------------------------------------------------------------------------- */ + +/* Queries for cached inherited properties. */ + +/* Select the inherited properties of a single base node. */ +-- STMT_SELECT_IPROPS +SELECT inherited_props FROM nodes +WHERE wc_id = ?1 + AND local_relpath = ?2 + AND op_depth = 0 + +/* Update the inherited properties of a single base node. */ +-- STMT_UPDATE_IPROP +UPDATE nodes +SET inherited_props = ?3 +WHERE (wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0) + +/* Select a single path if its base node has cached inherited properties. */ +-- STMT_SELECT_IPROPS_NODE +SELECT local_relpath, repos_path FROM nodes +WHERE wc_id = ?1 + AND local_relpath = ?2 + AND op_depth = 0 + AND (inherited_props not null) + +/* Select all paths whose base nodes are below a given path, which + have cached inherited properties. */ +-- STMT_SELECT_IPROPS_RECURSIVE +SELECT local_relpath, repos_path FROM nodes +WHERE wc_id = ?1 + AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2) + AND op_depth = 0 + AND (inherited_props not null) + +-- STMT_SELECT_IPROPS_CHILDREN +SELECT local_relpath, repos_path FROM nodes +WHERE wc_id = ?1 + AND parent_relpath = ?2 + AND op_depth = 0 + AND (inherited_props not null) + +/* ------------------------------------------------------------------------- */ + +/* Grab all the statements related to the schema. */ + +-- include: wc-metadata +-- include: wc-checks diff --git a/subversion/libsvn_wc/wc.h b/subversion/libsvn_wc/wc.h new file mode 100644 index 000000000000..9438e2b3b2e0 --- /dev/null +++ b/subversion/libsvn_wc/wc.h @@ -0,0 +1,808 @@ +/* + * wc.h : shared stuff internal to the svn_wc library. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#ifndef SVN_LIBSVN_WC_H +#define SVN_LIBSVN_WC_H + +#include <apr_pools.h> +#include <apr_hash.h> + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_wc.h" + +#include "private/svn_sqlite.h" +#include "private/svn_wc_private.h" +#include "private/svn_skel.h" + +#include "wc_db.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define SVN_WC__PROP_REJ_EXT ".prej" + +/* We can handle this format or anything lower, and we (should) error + * on anything higher. + * + * There is no format version 0; we started with 1. + * + * The bump to 2 introduced the ".svn-work" extension. For example, + * ".svn/props/foo" became ".svn/props/foo.svn-work". + * + * The bump to 3 introduced the entry attribute + * old-and-busted.c::ENTRIES_ATTR_ABSENT. + * + * The bump to 4 renamed the magic "svn:this_dir" entry name to "". + * + * == 1.0.x shipped with format 4 + * == 1.1.x shipped with format 4 + * == 1.2.x shipped with format 4 + * == 1.3.x shipped with format 4 + * + * The bump to 5 added support for replacing files with history (the + * "revert base"). This was introduced in 1.4.0, but buggy until 1.4.6. + * + * The bump to 6 introduced caching of property modification state and + * certain properties in the entries file. + * + * The bump to 7 changed the entries file format from XML to a custom + * text-based format. + * + * The bump to 8 placed wcprops in one file per directory (named + * upgrade.c::WCPROPS_ALL_DATA) + * + * == 1.4.x shipped with format 8 + * + * The bump to 9 added changelists, keep-local, and sticky depth (for + * selective/sparse checkouts) to each entry. + * + * == 1.5.x shipped with format 9 + * + * The bump to 10 added tree-conflicts, file externals and a different + * canonicalization of urls. + * + * == 1.6.x shipped with format 10 + * + * The bump to 11 cleared the has_props, has_prop_mods, cachable_props, + * and present_props values in the entries file. Older clients expect + * proper values for these fields. + * + * The bump to 12 switched from 'entries' to the SQLite database 'wc.db'. + * + * The bump to 13 added the WORK_QUEUE table into 'wc.db', moved the + * wcprops into the 'dav_cache' column in BASE_NODE, and stopped using + * the 'incomplete_children' column of BASE_NODE. + * + * The bump to 14 added the WCLOCKS table (and migrated locks from the + * filesystem into wc.db), and some columns to ACTUAL_NODE for future + * use. + * + * The bump to 15 switched from depth='exclude' on directories to using + * presence='exclude' within the BASE_NODE and WORKING_NODE tables. + * This change also enabled exclude support on files and symlinks. + * + * The bump to 16 added 'locked_levels' to WC_LOCK, setting any existing + * locks to a level of 0. The 'md5_checksum' column was added to PRISTINE + * for future use. + * + * The bump to 17 added a '.svn/pristine' dir and moved the text bases into + * the Pristine Store (the PRISTINE table and '.svn/pristine' dir), and + * removed the '/.svn/text-base' dir. + * + * The bump to 18 moved the properties from separate files in the props and + * prop-base directory (and .svn for the dir itself) into the wc.db file, + * and then removed the props and prop-base dir. + * + * The bump to 19 introduced the 'single DB' per working copy. All metadata + * is held in a single '.svn/wc.db' in the root directory of the working + * copy. Bumped in r991236. + * + * The bump to 20 introduced NODES and drops BASE_NODE and WORKING_NODE, + * op_depth is always 0 or 2. Bumped in r1005388. + * + * The bump to 21 moved tree conflict storage from the parent to the + * conflicted node. Bumped in r1034436. + * + * The bump to 22 moved tree conflict storage from conflict_data column + * to the tree_conflict_data column. Bumped in r1040255. + * + * The bump to 23 introduced multi-layer op_depth processing for NODES. + * Bumped in r1044384. + * + * The bump to 24 started using the 'refcount' column of the PRISTINE table + * correctly, instead of always setting it to '1'. Bumped in r1058523. + * + * The bump to 25 introduced the NODES_CURRENT view. Bumped in r1071283. + * + * The bump to 26 introduced the NODES_BASE view. Bumped in r1076617. + * + * The bump to 27 stored conflict files as relpaths rather than basenames. + * Bumped in r1089593. + * + * The bump to 28 converted any remaining references to MD5 checksums + * to SHA1 checksums. Bumped in r1095214. + * + * The bump to 29 renamed the pristine files from '<SHA1>' to '<SHA1>.svn-base' + * and introduced the EXTERNALS store. Bumped in r1129286. + * + * == 1.7.x shipped with format 29 + * + * The bump to 30 switched the conflict storage to a skel inside conflict_data. + * Also clears some known invalid state. Bumped in r1387742. + * + * The bump to 31 added the inherited_props column in the NODES table. + * Bumped in r1395109. + * + * Please document any further format changes here. + */ + +#define SVN_WC__VERSION 31 + + +/* Formats <= this have no concept of "revert text-base/props". */ +#define SVN_WC__NO_REVERT_FILES 4 + +/* A version <= this has wcprops stored in one file per entry. */ +#define SVN_WC__WCPROPS_MANY_FILES_VERSION 7 + +/* A version < this can have urls that aren't canonical according to the new + rules. See issue #2475. */ +#define SVN_WC__CHANGED_CANONICAL_URLS 10 + +/* The format number written to wc-ng working copies so that old clients + can recognize them as "newer Subversion"'s working copies. */ +#define SVN_WC__NON_ENTRIES 12 +#define SVN_WC__NON_ENTRIES_STRING "12\n" + +/* A version < this uses the old 'entries' file mechanism. */ +#define SVN_WC__WC_NG_VERSION 12 + +/* In this version, the wcprops are "lost" between files and wc.db. We want + to ignore them in upgrades. */ +#define SVN_WC__WCPROPS_LOST 12 + +/* A version < this has no work queue (see workqueue.h). */ +#define SVN_WC__HAS_WORK_QUEUE 13 + +/* Return a string indicating the released version (or versions) of + * Subversion that used WC format number WC_FORMAT, or some other + * suitable string if no released version used WC_FORMAT. + * + * ### It's not ideal to encode this sort of knowledge in this low-level + * library. On the other hand, it doesn't need to be updated often and + * should be easily found when it does need to be updated. */ +const char * +svn_wc__version_string_from_format(int wc_format); + +/* Return true iff error E indicates an "is not a working copy" type + of error, either because something wasn't a working copy at all, or + because it's a working copy from a previous version (in need of + upgrade). */ +#define SVN_WC__ERR_IS_NOT_CURRENT_WC(e) \ + ((e->apr_err == SVN_ERR_WC_NOT_WORKING_COPY) || \ + (e->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)) + + + +/*** Context handling ***/ +struct svn_wc_context_t +{ + /* The wc_db handle for this working copy. */ + svn_wc__db_t *db; + + /* Close the DB when we destroy this context? + (This is used inside backward compat wrappers, and should only be + modified by the proper create() functions. */ + svn_boolean_t close_db_on_destroy; + + /* The state pool for this context. */ + apr_pool_t *state_pool; +}; + +/** + * Just like svn_wc_context_create(), only use the provided DB to construct + * the context. + * + * Even though DB is not allocated from the same pool at *WC_CTX, it is + * expected to remain open throughout the life of *WC_CTX. + */ +svn_error_t * +svn_wc__context_create_with_db(svn_wc_context_t **wc_ctx, + svn_config_t *config, + svn_wc__db_t *db, + apr_pool_t *result_pool); + + +/*** Committed Queue ***/ + +/** + * Return the pool associated with QUEUE. (This so we can keep some + * deprecated functions that need to peek inside the QUEUE struct in + * deprecated.c). + */ +apr_pool_t * +svn_wc__get_committed_queue_pool(const struct svn_wc_committed_queue_t *queue); + + +/** Internal helper for svn_wc_process_committed_queue2(). + * + * ### If @a queue is NULL, then ...? + * ### else: + * Bump an item from @a queue (the one associated with @a + * local_abspath) to @a new_revnum after a commit succeeds, recursing + * if @a recurse is set. + * + * @a new_date is the (server-side) date of the new revision, or 0. + * + * @a rev_author is the (server-side) author of the new + * revision; it may be @c NULL. + * + * @a new_dav_cache is a hash of dav property changes to be made to + * the @a local_abspath. + * ### [JAF] Is it? See svn_wc_queue_committed3(). It ends up being + * ### assigned as a whole to wc.db:BASE_NODE:dav_cache. + * + * If @a no_unlock is set, don't release any user locks on @a + * local_abspath; otherwise release them as part of this processing. + * + * If @a keep_changelist is set, don't remove any changeset assignments + * from @a local_abspath; otherwise, clear it of such assignments. + * + * If @a sha1_checksum is non-NULL, use it to identify the node's pristine + * text. + * + * Set TOP_OF_RECURSE to TRUE to show that this the top of a possibly + * recursive commit operation. + */ +svn_error_t * +svn_wc__process_committed_internal(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t recurse, + svn_boolean_t top_of_recurse, + svn_revnum_t new_revnum, + apr_time_t new_date, + const char *rev_author, + apr_hash_t *new_dav_cache, + svn_boolean_t no_unlock, + svn_boolean_t keep_changelist, + const svn_checksum_t *sha1_checksum, + const svn_wc_committed_queue_t *queue, + apr_pool_t *scratch_pool); + + +/*** Update traversals. ***/ + +struct svn_wc_traversal_info_t +{ + /* The pool in which this structure and everything inside it is + allocated. */ + apr_pool_t *pool; + + /* The before and after values of the SVN_PROP_EXTERNALS property, + * for each directory on which that property changed. These have + * the same layout as those returned by svn_wc_edited_externals(). + * + * The hashes, their keys, and their values are allocated in the + * above pool. + */ + apr_hash_t *externals_old; + apr_hash_t *externals_new; + + /* The ambient depths of the working copy directories. The keys are + working copy paths (as for svn_wc_edited_externals()), the values + are the result of svn_depth_to_word(depth_of_each_dir). */ + apr_hash_t *depths; +}; + + +/*** Names and file/dir operations in the administrative area. ***/ + +/** The files within the administrative subdir. **/ +#define SVN_WC__ADM_FORMAT "format" +#define SVN_WC__ADM_ENTRIES "entries" +#define SVN_WC__ADM_TMP "tmp" +#define SVN_WC__ADM_PRISTINE "pristine" +#define SVN_WC__ADM_NONEXISTENT_PATH "nonexistent-path" + +/* The basename of the ".prej" file, if a directory ever has property + conflicts. This .prej file will appear *within* the conflicted + directory. */ +#define SVN_WC__THIS_DIR_PREJ "dir_conflicts" + + +/* A few declarations for stuff in util.c. + * If this section gets big, move it all out into a new util.h file. */ + +/* Ensure that DIR exists. */ +svn_error_t *svn_wc__ensure_directory(const char *path, apr_pool_t *pool); + + +/* Return a hash keyed by 'const char *' property names and with + 'svn_string_t *' values built from PROPS (which is an array of + pointers to svn_prop_t's) or to NULL if PROPS is NULL or empty. + PROPS items which lack a value will be ignored. If PROPS contains + multiple properties with the same name, each successive such item + reached in a walk from the beginning to the end of the array will + overwrite the previous in the returned hash. + + NOTE: While the returned hash will be allocated in RESULT_POOL, the + items it holds will share storage with those in PROPS. + + ### This is rather the reverse of svn_prop_hash_to_array(), except + ### that function's arrays contains svn_prop_t's, whereas this + ### one's contains *pointers* to svn_prop_t's. So much for + ### consistency. */ +apr_hash_t * +svn_wc__prop_array_to_hash(const apr_array_header_t *props, + apr_pool_t *result_pool); + + +/* Set *MODIFIED_P to non-zero if LOCAL_ABSPATH's text is modified with + * regard to the base revision, else set *MODIFIED_P to zero. + * + * If EXACT_COMPARISON is FALSE, translate LOCAL_ABSPATH's EOL + * style and keywords to repository-normal form according to its properties, + * and compare the result with the text base. + * Usually, EXACT_COMPARISON should be FALSE. + * + * If LOCAL_ABSPATH does not exist, consider it unmodified. If it exists + * but is not under revision control (not even scheduled for + * addition), return the error SVN_WC_PATH_NOT_FOUND. + * + * If the text is unmodified and a write-lock is held this function + * will ensure that the last-known-unmodified timestamp and + * filesize of the file as recorded in DB matches the corresponding + * attributes of the actual file. (This is often referred to as + * "timestamp repair", and serves to help future unforced is-modified + * checks return quickly if the file remains untouched.) + */ +svn_error_t * +svn_wc__internal_file_modified_p(svn_boolean_t *modified_p, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t exact_comparison, + apr_pool_t *scratch_pool); + + +/* Prepare to merge a file content change into the working copy. + + This does not merge properties; see svn_wc__merge_props() for that. + This does not necessarily change the file TARGET_ABSPATH on disk; it + may instead return work items that will replace the file on disk when + they are run. ### Can we be more consistent about this? + + Merge the difference between LEFT_ABSPATH and RIGHT_ABSPATH into + TARGET_ABSPATH. + + Set *WORK_ITEMS to the appropriate work queue operations. + + If there are any conflicts, append a conflict description to + *CONFLICT_SKEL. (First allocate *CONFLICT_SKEL from RESULT_POOL if + it is initially NULL. CONFLICT_SKEL itself must not be NULL.) + Also, unless it is considered to be a 'binary' file, mark any + conflicts in the text of the file TARGET_ABSPATH using LEFT_LABEL, + RIGHT_LABEL and TARGET_LABEL. + + Set *MERGE_OUTCOME to indicate the result. + + When DRY_RUN is true, no actual changes are made to the working copy. + + If DIFF3_CMD is specified, the given external diff3 tool will + be used instead of our built in diff3 routines. + + When MERGE_OPTIONS are specified, they are used by the internal + diff3 routines, or passed to the external diff3 tool. + + WRI_ABSPATH describes in which working copy information should be + retrieved. (Interesting for merging file externals). + + OLD_ACTUAL_PROPS is the set of actual properties before merging; used for + detranslating the file before merging. This is necessary because, in + the case of updating, the update can have sent new properties, so we + cannot simply fetch and use the current actual properties. + + ### Is OLD_ACTUAL_PROPS still necessary, now that we first prepare the + content change and property change and then apply them both to + the WC together? + + Property changes sent by the update are provided in PROP_DIFF. + + For a complete description, see svn_wc_merge5() for which this is + the (loggy) implementation. + + *WORK_ITEMS will be allocated in RESULT_POOL. All temporary allocations + will be performed in SCRATCH_POOL. +*/ +svn_error_t * +svn_wc__internal_merge(svn_skel_t **work_items, + svn_skel_t **conflict_skel, + enum svn_wc_merge_outcome_t *merge_outcome, + svn_wc__db_t *db, + const char *left_abspath, + const char *right_abspath, + const char *target_abspath, + const char *wri_abspath, + const char *left_label, + const char *right_label, + const char *target_label, + apr_hash_t *old_actual_props, + svn_boolean_t dry_run, + const char *diff3_cmd, + const apr_array_header_t *merge_options, + const apr_array_header_t *prop_diff, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* A default error handler for svn_wc_walk_entries3(). Returns ERR in + all cases. */ +svn_error_t * +svn_wc__walker_default_error_handler(const char *path, + svn_error_t *err, + void *walk_baton, + apr_pool_t *pool); + +/* Set *EDITOR and *EDIT_BATON to an ambient-depth-based filtering + * editor that wraps WRAPPED_EDITOR and WRAPPED_BATON. This is only + * required for operations where the requested depth is @c + * svn_depth_unknown and the server's editor driver doesn't understand + * depth. It is safe for *EDITOR and *EDIT_BATON to start as + * WRAPPED_EDITOR and WRAPPED_BATON. + * + * ANCHOR, TARGET, and DB are as in svn_wc_get_update_editor3. + * + * @a requested_depth must be one of the following depth values: + * @c svn_depth_infinity, @c svn_depth_empty, @c svn_depth_files, + * @c svn_depth_immediates, or @c svn_depth_unknown. + * + * Allocations are done in POOL. + */ +svn_error_t * +svn_wc__ambient_depth_filter_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_wc__db_t *db, + const char *anchor_abspath, + const char *target, + const svn_delta_editor_t *wrapped_editor, + void *wrapped_edit_baton, + apr_pool_t *result_pool); + + +/* Similar to svn_wc_conflicted_p3(), but with a wc_db parameter in place of + * a wc_context. */ +svn_error_t * +svn_wc__internal_conflicted_p(svn_boolean_t *text_conflicted_p, + svn_boolean_t *prop_conflicted_p, + svn_boolean_t *tree_conflicted_p, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* Similar to svn_wc__internal_conflicted_p(), but ignores + * moved-away-edit tree conflicts. If CONFLICT_IGNORED_P is not NULL + * then sets *CONFLICT_IGNORED_P TRUE if a tree-conflict is ignored + * and FALSE otherwise. Also ignores text and property conflicts if + * TREE_ONLY is TRUE */ +svn_error_t * +svn_wc__conflicted_for_update_p(svn_boolean_t *conflicted_p, + svn_boolean_t *conflict_ignored_p, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t tree_only, + apr_pool_t *scratch_pool); + + +/* Internal version of svn_wc_transmit_text_deltas3(). */ +svn_error_t * +svn_wc__internal_transmit_text_deltas(const char **tempfile, + const svn_checksum_t **new_text_base_md5_checksum, + const svn_checksum_t **new_text_base_sha1_checksum, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t fulltext, + const svn_delta_editor_t *editor, + void *file_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Internal version of svn_wc_transmit_prop_deltas2(). */ +svn_error_t * +svn_wc__internal_transmit_prop_deltas(svn_wc__db_t *db, + const char *local_abspath, + const svn_delta_editor_t *editor, + void *baton, + apr_pool_t *scratch_pool); + +/* Library-internal version of svn_wc_ensure_adm4(). */ +svn_error_t * +svn_wc__internal_ensure_adm(svn_wc__db_t *db, + const char *local_abspath, + const char *url, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_depth_t depth, + apr_pool_t *scratch_pool); + + +/* Library-internal version of svn_wc__changelist_match(). */ +svn_boolean_t +svn_wc__internal_changelist_match(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *clhash, + apr_pool_t *scratch_pool); + +/* Library-internal version of svn_wc_walk_status(), which see. */ +svn_error_t * +svn_wc__internal_walk_status(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t get_all, + svn_boolean_t no_ignore, + svn_boolean_t ignore_text_mods, + const apr_array_header_t *ignore_patterns, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/** A callback invoked by the generic node-walker function. */ +typedef svn_error_t *(*svn_wc__node_found_func_t)(const char *local_abspath, + svn_node_kind_t kind, + void *walk_baton, + apr_pool_t *scratch_pool); + +/* Call @a walk_callback with @a walk_baton for @a local_abspath and all + nodes underneath it, restricted by @a walk_depth, and possibly + @a changelists. + + If @a show_hidden is true, include hidden nodes, else ignore them. + If CHANGELISTS is non-NULL and non-empty, filter thereon. */ +svn_error_t * +svn_wc__internal_walk_children(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t show_hidden, + const apr_array_header_t *changelists, + svn_wc__node_found_func_t walk_callback, + void *walk_baton, + svn_depth_t walk_depth, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* Library-internal version of svn_wc_remove_from_revision_control2, + which see.*/ +svn_error_t * +svn_wc__internal_remove_from_revision_control(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t destroy_wf, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* Library-internal version of svn_wc__node_get_schedule(). */ +svn_error_t * +svn_wc__internal_node_get_schedule(svn_wc_schedule_t *schedule, + svn_boolean_t *copied, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* Internal version of svn_wc__node_get_origin() */ +svn_error_t * +svn_wc__internal_get_origin(svn_boolean_t *is_copy, + svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + const char **copy_root_abspath, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t scan_deleted, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Internal version of svn_wc__node_get_repos_info() */ +svn_error_t * +svn_wc__internal_get_repos_info(svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Upgrade the wc sqlite database given in SDB for the wc located at + WCROOT_ABSPATH. It's current/starting format is given by START_FORMAT. + After the upgrade is complete (to as far as the automatic upgrade will + perform), the resulting format is RESULT_FORMAT. All allocations are + performed in SCRATCH_POOL. */ +svn_error_t * +svn_wc__upgrade_sdb(int *result_format, + const char *wcroot_abspath, + svn_sqlite__db_t *sdb, + int start_format, + apr_pool_t *scratch_pool); + +/* Create a conflict skel from the old separated data */ +svn_error_t * +svn_wc__upgrade_conflict_skel_from_raw(svn_skel_t **conflicts, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_relpath, + const char *conflict_old, + const char *conflict_wrk, + const char *conflict_new, + const char *prej_file, + const char *tree_conflict_data, + apr_size_t tree_conflict_len, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +svn_error_t * +svn_wc__wipe_postupgrade(const char *dir_abspath, + svn_boolean_t whole_admin, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* Ensure LOCAL_ABSPATH is still locked in DB. Returns the error + * SVN_ERR_WC_NOT_LOCKED if this is not the case. + */ +svn_error_t * +svn_wc__write_check(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* Read into CONFLICTS svn_wc_conflict_description2_t* structs + * for all conflicts that have LOCAL_ABSPATH as victim. + * + * Victim must be versioned or be part of a tree conflict. + * + * If CREATE_TEMPFILES is TRUE, create temporary files for property conflicts. + * + * Allocate *CONFLICTS in RESULT_POOL and do temporary allocations in + * SCRATCH_POOL + */ +svn_error_t * +svn_wc__read_conflicts(const apr_array_header_t **conflicts, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t create_tempfiles, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Perform the actual merge of file changes between an original file, + identified by ORIGINAL_CHECKSUM (an empty file if NULL) to a new file + identified by NEW_CHECKSUM in the working copy identified by WRI_ABSPATH. + + Merge the result into LOCAL_ABSPATH, which is part of the working copy + identified by WRI_ABSPATH. Use OLD_REVISION and TARGET_REVISION for naming + the intermediate files. + + Set *FOUND_TEXT_CONFLICT to TRUE when the merge encountered a conflict, + otherwise to FALSE. + + The rest of the arguments are passed to svn_wc__internal_merge. + */ +svn_error_t * +svn_wc__perform_file_merge(svn_skel_t **work_items, + svn_skel_t **conflict_skel, + svn_boolean_t *found_conflict, + svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const svn_checksum_t *new_checksum, + const svn_checksum_t *original_checksum, + apr_hash_t *old_actual_props, + const apr_array_header_t *ext_patterns, + svn_revnum_t old_revision, + svn_revnum_t target_revision, + const apr_array_header_t *propchanges, + const char *diff3_cmd, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Couple of random helpers for the Ev2 shims. + ### These will eventually be obsoleted and removed. */ +struct svn_wc__shim_fetch_baton_t +{ + svn_wc__db_t *db; + const char *base_abspath; + svn_boolean_t fetch_base; +}; + +/* Using a BATON of struct shim_fetch_baton, return KIND for PATH. */ +svn_error_t * +svn_wc__fetch_kind_func(svn_node_kind_t *kind, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *scratch_pool); + +/* Using a BATON of struct shim_fetch_baton, return PROPS for PATH. */ +svn_error_t * +svn_wc__fetch_props_func(apr_hash_t **props, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Using a BATON of struct shim_fetch_baton, return a delta base for PATH. */ +svn_error_t * +svn_wc__fetch_base_func(const char **filename, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Find duplicate targets in *EXTERNALS, a list of svn_wc_external_item2_t* + * elements, and store each target string in *DUPLICATE_TARGETS as const + * char * elements. *DUPLICATE_TARGETS will be NULL if no duplicates were + * found. */ +svn_error_t * +svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets, + apr_array_header_t *externals, + apr_pool_t *pool, + apr_pool_t *scratch_pool); + +/* Revert tree LOCAL_ABSPATH to depth DEPTH and notify for all + reverts. */ +svn_error_t * +svn_wc__revert_internal(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t use_commit_times, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); + +svn_error_t * +svn_wc__node_has_local_mods(svn_boolean_t *modified, + svn_boolean_t *all_edits_are_deletes, + svn_wc__db_t *db, + const char *local_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_WC_H */ diff --git a/subversion/libsvn_wc/wc_db.c b/subversion/libsvn_wc/wc_db.c new file mode 100644 index 000000000000..7e1e8777eea7 --- /dev/null +++ b/subversion/libsvn_wc/wc_db.c @@ -0,0 +1,15050 @@ +/* + * wc_db.c : manipulating the administrative database + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#define SVN_WC__I_AM_WC_DB + +#include <assert.h> +#include <apr_pools.h> +#include <apr_hash.h> + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" +#include "svn_sorts.h" +#include "svn_wc.h" +#include "svn_checksum.h" +#include "svn_pools.h" + +#include "wc.h" +#include "wc_db.h" +#include "adm_files.h" +#include "wc-queries.h" +#include "entries.h" +#include "lock.h" +#include "conflicts.h" +#include "wc_db_private.h" +#include "workqueue.h" +#include "token-map.h" + +#include "svn_private_config.h" +#include "private/svn_sqlite.h" +#include "private/svn_skel.h" +#include "private/svn_wc_private.h" +#include "private/svn_token.h" + + +#define NOT_IMPLEMENTED() SVN__NOT_IMPLEMENTED() + + +/* + * Some filename constants. + */ +#define SDB_FILE "wc.db" + +#define WCROOT_TEMPDIR_RELPATH "tmp" + + +/* + * PARAMETER ASSERTIONS + * + * Every (semi-)public entrypoint in this file has a set of assertions on + * the parameters passed into the function. Since this is a brand new API, + * we want to make sure that everybody calls it properly. The original WC + * code had years to catch stray bugs, but we do not have that luxury in + * the wc-nb rewrite. Any extra assurances that we can find will be + * welcome. The asserts will ensure we have no doubt about the values + * passed into the function. + * + * Some parameters are *not* specifically asserted. Typically, these are + * params that will be used immediately, so something like a NULL value + * will be obvious. + * + * ### near 1.7 release, it would be a Good Thing to review the assertions + * ### and decide if any can be removed or switched to assert() in order + * ### to remove their runtime cost in the production release. + * + * + * DATABASE OPERATIONS + * + * Each function should leave the database in a consistent state. If it + * does *not*, then the implication is some other function needs to be + * called to restore consistency. Subtle requirements like that are hard + * to maintain over a long period of time, so this API will not allow it. + * + * + * STANDARD VARIABLE NAMES + * + * db working copy database (this module) + * sdb SQLite database (not to be confused with 'db') + * wc_id a WCROOT id associated with a node + */ + +#define INVALID_REPOS_ID ((apr_int64_t) -1) +#define UNKNOWN_WC_ID ((apr_int64_t) -1) +#define FORMAT_FROM_SDB (-1) + +/* Check if column number I, a property-skel column, contains a non-empty + set of properties. The empty set of properties is stored as "()", so we + have properties if the size of the column is larger than 2. */ +#define SQLITE_PROPERTIES_AVAILABLE(stmt, i) \ + (svn_sqlite__column_bytes(stmt, i) > 2) + +int +svn_wc__db_op_depth_for_upgrade(const char *local_relpath) +{ + return relpath_depth(local_relpath); +} + + +/* Representation of a new base row for the NODES table */ +typedef struct insert_base_baton_t { + /* common to all insertions into BASE */ + svn_wc__db_status_t status; + svn_node_kind_t kind; + apr_int64_t repos_id; + const char *repos_relpath; + svn_revnum_t revision; + + /* Only used when repos_id == INVALID_REPOS_ID */ + const char *repos_root_url; + const char *repos_uuid; + + /* common to all "normal" presence insertions */ + const apr_hash_t *props; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + const apr_hash_t *dav_cache; + + /* for inserting directories */ + const apr_array_header_t *children; + svn_depth_t depth; + + /* for inserting files */ + const svn_checksum_t *checksum; + + /* for inserting symlinks */ + const char *target; + + svn_boolean_t file_external; + + /* may need to insert/update ACTUAL to record a conflict */ + const svn_skel_t *conflict; + + /* may need to insert/update ACTUAL to record new properties */ + svn_boolean_t update_actual_props; + const apr_hash_t *new_actual_props; + + /* A depth-first ordered array of svn_prop_inherited_item_t * + structures representing the properties inherited by the base + node. */ + apr_array_header_t *iprops; + + /* maybe we should copy information from a previous record? */ + svn_boolean_t keep_recorded_info; + + /* insert a base-deleted working node as well as a base node */ + svn_boolean_t insert_base_deleted; + + /* delete the current working nodes above BASE */ + svn_boolean_t delete_working; + + /* may have work items to queue in this transaction */ + const svn_skel_t *work_items; + +} insert_base_baton_t; + + +/* Representation of a new working row for the NODES table */ +typedef struct insert_working_baton_t { + /* common to all insertions into WORKING (including NODE_DATA) */ + svn_wc__db_status_t presence; + svn_node_kind_t kind; + int op_depth; + + /* common to all "normal" presence insertions */ + const apr_hash_t *props; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + apr_int64_t original_repos_id; + const char *original_repos_relpath; + svn_revnum_t original_revnum; + svn_boolean_t moved_here; + + /* for inserting directories */ + const apr_array_header_t *children; + svn_depth_t depth; + + /* for inserting (copied/moved-here) files */ + const svn_checksum_t *checksum; + + /* for inserting symlinks */ + const char *target; + + svn_boolean_t update_actual_props; + const apr_hash_t *new_actual_props; + + /* may have work items to queue in this transaction */ + const svn_skel_t *work_items; + + /* may have conflict to install in this transaction */ + const svn_skel_t *conflict; + + /* If the value is > 0 and < op_depth, also insert a not-present + at op-depth NOT_PRESENT_OP_DEPTH, based on this same information */ + int not_present_op_depth; + +} insert_working_baton_t; + +/* Representation of a new row for the EXTERNALS table */ +typedef struct insert_external_baton_t { + /* common to all insertions into EXTERNALS */ + svn_node_kind_t kind; + svn_wc__db_status_t presence; + + /* The repository of the external */ + apr_int64_t repos_id; + /* for file and symlink externals */ + const char *repos_relpath; + svn_revnum_t revision; + + /* Only used when repos_id == INVALID_REPOS_ID */ + const char *repos_root_url; + const char *repos_uuid; + + /* for file and symlink externals */ + const apr_hash_t *props; + apr_array_header_t *iprops; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + const apr_hash_t *dav_cache; + + /* for inserting files */ + const svn_checksum_t *checksum; + + /* for inserting symlinks */ + const char *target; + + const char *record_ancestor_relpath; + const char *recorded_repos_relpath; + svn_revnum_t recorded_peg_revision; + svn_revnum_t recorded_revision; + + /* may need to insert/update ACTUAL to record a conflict */ + const svn_skel_t *conflict; + + /* may need to insert/update ACTUAL to record new properties */ + svn_boolean_t update_actual_props; + const apr_hash_t *new_actual_props; + + /* maybe we should copy information from a previous record? */ + svn_boolean_t keep_recorded_info; + + /* may have work items to queue in this transaction */ + const svn_skel_t *work_items; + +} insert_external_baton_t; + + +/* Forward declarations */ +static svn_error_t * +add_work_items(svn_sqlite__db_t *sdb, + const svn_skel_t *skel, + apr_pool_t *scratch_pool); + +static svn_error_t * +set_actual_props(apr_int64_t wc_id, + const char *local_relpath, + apr_hash_t *props, + svn_sqlite__db_t *db, + apr_pool_t *scratch_pool); + +static svn_error_t * +insert_incomplete_children(svn_sqlite__db_t *sdb, + apr_int64_t wc_id, + const char *local_relpath, + apr_int64_t repos_id, + const char *repos_relpath, + svn_revnum_t revision, + const apr_array_header_t *children, + int op_depth, + apr_pool_t *scratch_pool); + +static svn_error_t * +db_read_pristine_props(apr_hash_t **props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_boolean_t deleted_ok, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +static svn_error_t * +read_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + const char **original_repos_relpath, + apr_int64_t *original_repos_id, + svn_revnum_t *original_revision, + svn_wc__db_lock_t **lock, + svn_filesize_t *recorded_size, + apr_time_t *recorded_time, + const char **changelist, + svn_boolean_t *conflicted, + svn_boolean_t *op_root, + svn_boolean_t *had_props, + svn_boolean_t *props_mod, + svn_boolean_t *have_base, + svn_boolean_t *have_more_work, + svn_boolean_t *have_work, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +static svn_error_t * +scan_addition(svn_wc__db_status_t *status, + const char **op_root_relpath, + const char **repos_relpath, + apr_int64_t *repos_id, + const char **original_repos_relpath, + apr_int64_t *original_repos_id, + svn_revnum_t *original_revision, + const char **moved_from_relpath, + const char **moved_from_op_root_relpath, + int *moved_from_op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +static svn_error_t * +convert_to_working_status(svn_wc__db_status_t *working_status, + svn_wc__db_status_t status); + +static svn_error_t * +wclock_owns_lock(svn_boolean_t *own_lock, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_boolean_t exact, + apr_pool_t *scratch_pool); + +static svn_error_t * +db_is_switched(svn_boolean_t *is_switched, + svn_node_kind_t *kind, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool); + + +/* Return the absolute path, in local path style, of LOCAL_RELPATH + in WCROOT. */ +static const char * +path_for_error_message(const svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool) +{ + const char *local_abspath + = svn_dirent_join(wcroot->abspath, local_relpath, result_pool); + + return svn_dirent_local_style(local_abspath, result_pool); +} + + +/* Return a file size from column SLOT of the SQLITE statement STMT, or + SVN_INVALID_FILESIZE if the column value is NULL. */ +static svn_filesize_t +get_recorded_size(svn_sqlite__stmt_t *stmt, int slot) +{ + if (svn_sqlite__column_is_null(stmt, slot)) + return SVN_INVALID_FILESIZE; + return svn_sqlite__column_int64(stmt, slot); +} + + +/* Return a lock info structure constructed from the given columns of the + SQLITE statement STMT, or return NULL if the token column value is null. */ +static svn_wc__db_lock_t * +lock_from_columns(svn_sqlite__stmt_t *stmt, + int col_token, + int col_owner, + int col_comment, + int col_date, + apr_pool_t *result_pool) +{ + svn_wc__db_lock_t *lock; + + if (svn_sqlite__column_is_null(stmt, col_token)) + { + lock = NULL; + } + else + { + lock = apr_pcalloc(result_pool, sizeof(svn_wc__db_lock_t)); + lock->token = svn_sqlite__column_text(stmt, col_token, result_pool); + lock->owner = svn_sqlite__column_text(stmt, col_owner, result_pool); + lock->comment = svn_sqlite__column_text(stmt, col_comment, result_pool); + lock->date = svn_sqlite__column_int64(stmt, col_date); + } + return lock; +} + + +svn_error_t * +svn_wc__db_fetch_repos_info(const char **repos_root_url, + const char **repos_uuid, + svn_sqlite__db_t *sdb, + apr_int64_t repos_id, + apr_pool_t *result_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + if (!repos_root_url && !repos_uuid) + return SVN_NO_ERROR; + + if (repos_id == INVALID_REPOS_ID) + { + if (repos_root_url) + *repos_root_url = NULL; + if (repos_uuid) + *repos_uuid = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_SELECT_REPOSITORY_BY_ID)); + SVN_ERR(svn_sqlite__bindf(stmt, "i", repos_id)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + return svn_error_createf(SVN_ERR_WC_CORRUPT, svn_sqlite__reset(stmt), + _("No REPOSITORY table entry for id '%ld'"), + (long int)repos_id); + + if (repos_root_url) + *repos_root_url = svn_sqlite__column_text(stmt, 0, result_pool); + if (repos_uuid) + *repos_uuid = svn_sqlite__column_text(stmt, 1, result_pool); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +/* Set *REPOS_ID, *REVISION and *REPOS_RELPATH from the given columns of the + SQLITE statement STMT, or to NULL/SVN_INVALID_REVNUM if the respective + column value is null. Any of the output parameters may be NULL if not + required. */ +static void +repos_location_from_columns(apr_int64_t *repos_id, + svn_revnum_t *revision, + const char **repos_relpath, + svn_sqlite__stmt_t *stmt, + int col_repos_id, + int col_revision, + int col_repos_relpath, + apr_pool_t *result_pool) +{ + if (repos_id) + { + /* Fetch repository information via REPOS_ID. */ + if (svn_sqlite__column_is_null(stmt, col_repos_id)) + *repos_id = INVALID_REPOS_ID; + else + *repos_id = svn_sqlite__column_int64(stmt, col_repos_id); + } + if (revision) + { + *revision = svn_sqlite__column_revnum(stmt, col_revision); + } + if (repos_relpath) + { + *repos_relpath = svn_sqlite__column_text(stmt, col_repos_relpath, + result_pool); + } +} + + +/* Get the statement given by STMT_IDX, and bind the appropriate wc_id and + local_relpath based upon LOCAL_ABSPATH. Store it in *STMT, and use + SCRATCH_POOL for temporary allocations. + + Note: WC_ID and LOCAL_RELPATH must be arguments 1 and 2 in the statement. */ +static svn_error_t * +get_statement_for_path(svn_sqlite__stmt_t **stmt, + svn_wc__db_t *db, + const char *local_abspath, + int stmt_idx, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(stmt, wcroot->sdb, stmt_idx)); + SVN_ERR(svn_sqlite__bindf(*stmt, "is", wcroot->wc_id, local_relpath)); + + return SVN_NO_ERROR; +} + + +/* For a given REPOS_ROOT_URL/REPOS_UUID pair, return the existing REPOS_ID + value. If one does not exist, then create a new one. */ +static svn_error_t * +create_repos_id(apr_int64_t *repos_id, + const char *repos_root_url, + const char *repos_uuid, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *get_stmt; + svn_sqlite__stmt_t *insert_stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&get_stmt, sdb, STMT_SELECT_REPOSITORY)); + SVN_ERR(svn_sqlite__bindf(get_stmt, "s", repos_root_url)); + SVN_ERR(svn_sqlite__step(&have_row, get_stmt)); + + if (have_row) + { + *repos_id = svn_sqlite__column_int64(get_stmt, 0); + return svn_error_trace(svn_sqlite__reset(get_stmt)); + } + SVN_ERR(svn_sqlite__reset(get_stmt)); + + /* NOTE: strictly speaking, there is a race condition between the + above query and the insertion below. We're simply going to ignore + that, as it means two processes are *modifying* the working copy + at the same time, *and* new repositores are becoming visible. + This is rare enough, let alone the miniscule chance of hitting + this race condition. Further, simply failing out will leave the + database in a consistent state, and the user can just re-run the + failed operation. */ + + SVN_ERR(svn_sqlite__get_statement(&insert_stmt, sdb, + STMT_INSERT_REPOSITORY)); + SVN_ERR(svn_sqlite__bindf(insert_stmt, "ss", repos_root_url, repos_uuid)); + return svn_error_trace(svn_sqlite__insert(repos_id, insert_stmt)); +} + + +/* Initialize the baton with appropriate "blank" values. This allows the + insertion function to leave certain columns null. */ +static void +blank_ibb(insert_base_baton_t *pibb) +{ + memset(pibb, 0, sizeof(*pibb)); + pibb->revision = SVN_INVALID_REVNUM; + pibb->changed_rev = SVN_INVALID_REVNUM; + pibb->depth = svn_depth_infinity; + pibb->repos_id = INVALID_REPOS_ID; +} + + +svn_error_t * +svn_wc__db_extend_parent_delete(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_node_kind_t kind, + int op_depth, + apr_pool_t *scratch_pool) +{ + svn_boolean_t have_row; + svn_sqlite__stmt_t *stmt; + int parent_op_depth; + const char *parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool); + + SVN_ERR_ASSERT(local_relpath[0]); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_LOWEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, parent_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + parent_op_depth = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + { + int existing_op_depth; + + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + existing_op_depth = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + if (!have_row || parent_op_depth < existing_op_depth) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSTALL_WORKING_NODE_FOR_DELETE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdst", wcroot->wc_id, + local_relpath, parent_op_depth, + parent_relpath, kind_map, kind)); + SVN_ERR(svn_sqlite__update(NULL, stmt)); + } + } + + return SVN_NO_ERROR; +} + + +/* This is the reverse of svn_wc__db_extend_parent_delete. + + When removing a node if the parent has a higher working node then + the parent node and this node are both deleted or replaced and any + delete over this node must be removed. + */ +svn_error_t * +svn_wc__db_retract_parent_delete(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_LOWEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + + + +/* Insert the base row represented by (insert_base_baton_t *) BATON. */ +static svn_error_t * +insert_base_node(const insert_base_baton_t *pibb, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + apr_int64_t repos_id = pibb->repos_id; + svn_sqlite__stmt_t *stmt; + svn_filesize_t recorded_size = SVN_INVALID_FILESIZE; + apr_int64_t recorded_time; + + /* The directory at the WCROOT has a NULL parent_relpath. Otherwise, + bind the appropriate parent_relpath. */ + const char *parent_relpath = + (*local_relpath == '\0') ? NULL + : svn_relpath_dirname(local_relpath, scratch_pool); + + if (pibb->repos_id == INVALID_REPOS_ID) + SVN_ERR(create_repos_id(&repos_id, pibb->repos_root_url, pibb->repos_uuid, + wcroot->sdb, scratch_pool)); + + SVN_ERR_ASSERT(repos_id != INVALID_REPOS_ID); + SVN_ERR_ASSERT(pibb->repos_relpath != NULL); + + if (pibb->keep_recorded_info) + { + svn_boolean_t have_row; + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_BASE_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + /* Preserve size and modification time if caller asked us to. */ + recorded_size = get_recorded_size(stmt, 6); + recorded_time = svn_sqlite__column_int64(stmt, 12); + } + SVN_ERR(svn_sqlite__reset(stmt)); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdsisr" + "tstr" /* 8 - 11 */ + "isnnnnns", /* 12 - 19 */ + wcroot->wc_id, /* 1 */ + local_relpath, /* 2 */ + 0, /* op_depth is 0 for base */ + parent_relpath, /* 4 */ + repos_id, + pibb->repos_relpath, + pibb->revision, + presence_map, pibb->status, /* 8 */ + (pibb->kind == svn_node_dir) ? /* 9 */ + svn_token__to_word(depth_map, pibb->depth) : NULL, + kind_map, pibb->kind, /* 10 */ + pibb->changed_rev, /* 11 */ + pibb->changed_date, /* 12 */ + pibb->changed_author, /* 13 */ + (pibb->kind == svn_node_symlink) ? + pibb->target : NULL)); /* 19 */ + if (pibb->kind == svn_node_file) + { + if (!pibb->checksum + && pibb->status != svn_wc__db_status_not_present + && pibb->status != svn_wc__db_status_excluded + && pibb->status != svn_wc__db_status_server_excluded) + return svn_error_createf(SVN_ERR_WC_CORRUPT, svn_sqlite__reset(stmt), + _("The file '%s' has no checksum."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, pibb->checksum, + scratch_pool)); + + if (recorded_size != SVN_INVALID_FILESIZE) + { + SVN_ERR(svn_sqlite__bind_int64(stmt, 16, recorded_size)); + SVN_ERR(svn_sqlite__bind_int64(stmt, 17, recorded_time)); + } + } + + /* Set properties. Must be null if presence not normal or incomplete. */ + assert(pibb->status == svn_wc__db_status_normal + || pibb->status == svn_wc__db_status_incomplete + || pibb->props == NULL); + SVN_ERR(svn_sqlite__bind_properties(stmt, 15, pibb->props, + scratch_pool)); + + SVN_ERR(svn_sqlite__bind_iprops(stmt, 23, pibb->iprops, + scratch_pool)); + + if (pibb->dav_cache) + SVN_ERR(svn_sqlite__bind_properties(stmt, 18, pibb->dav_cache, + scratch_pool)); + + if (pibb->file_external) + SVN_ERR(svn_sqlite__bind_int(stmt, 20, 1)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + + if (pibb->update_actual_props) + { + /* Cast away const, to allow calling property helpers */ + apr_hash_t *base_props = (apr_hash_t *)pibb->props; + apr_hash_t *new_actual_props = (apr_hash_t *)pibb->new_actual_props; + + if (base_props != NULL + && new_actual_props != NULL + && (apr_hash_count(base_props) == apr_hash_count(new_actual_props))) + { + apr_array_header_t *diffs; + + SVN_ERR(svn_prop_diffs(&diffs, new_actual_props, base_props, + scratch_pool)); + + if (diffs->nelts == 0) + new_actual_props = NULL; + } + + SVN_ERR(set_actual_props(wcroot->wc_id, local_relpath, new_actual_props, + wcroot->sdb, scratch_pool)); + } + + if (pibb->kind == svn_node_dir && pibb->children) + SVN_ERR(insert_incomplete_children(wcroot->sdb, wcroot->wc_id, + local_relpath, + repos_id, + pibb->repos_relpath, + pibb->revision, + pibb->children, + 0 /* BASE */, + scratch_pool)); + + /* When this is not the root node, check shadowing behavior */ + if (*local_relpath) + { + if (parent_relpath + && ((pibb->status == svn_wc__db_status_normal) + || (pibb->status == svn_wc__db_status_incomplete)) + && ! pibb->file_external) + { + SVN_ERR(svn_wc__db_extend_parent_delete(wcroot, local_relpath, + pibb->kind, 0, + scratch_pool)); + } + else if (pibb->status == svn_wc__db_status_not_present + || pibb->status == svn_wc__db_status_server_excluded + || pibb->status == svn_wc__db_status_excluded) + { + SVN_ERR(svn_wc__db_retract_parent_delete(wcroot, local_relpath, 0, + scratch_pool)); + } + } + + if (pibb->delete_working) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + if (pibb->insert_base_deleted) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_DELETE_FROM_BASE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wcroot->wc_id, local_relpath, + relpath_depth(local_relpath))); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + SVN_ERR(add_work_items(wcroot->sdb, pibb->work_items, scratch_pool)); + if (pibb->conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + pibb->conflict, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* Initialize the baton with appropriate "blank" values. This allows the + insertion function to leave certain columns null. */ +static void +blank_iwb(insert_working_baton_t *piwb) +{ + memset(piwb, 0, sizeof(*piwb)); + piwb->changed_rev = SVN_INVALID_REVNUM; + piwb->depth = svn_depth_infinity; + + /* ORIGINAL_REPOS_ID and ORIGINAL_REVNUM could use some kind of "nil" + value, but... meh. We'll avoid them if ORIGINAL_REPOS_RELPATH==NULL. */ +} + + +/* Insert a row in NODES for each (const char *) child name in CHILDREN, + whose parent directory is LOCAL_RELPATH, at op_depth=OP_DEPTH. Set each + child's presence to 'incomplete', kind to 'unknown', repos_id to REPOS_ID, + repos_path by appending the child name to REPOS_PATH, and revision to + REVISION (which should match the parent's revision). + + If REPOS_ID is INVALID_REPOS_ID, set each child's repos_id to null. */ +static svn_error_t * +insert_incomplete_children(svn_sqlite__db_t *sdb, + apr_int64_t wc_id, + const char *local_relpath, + apr_int64_t repos_id, + const char *repos_path, + svn_revnum_t revision, + const apr_array_header_t *children, + int op_depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int i; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_t *moved_to_relpaths = apr_hash_make(scratch_pool); + + SVN_ERR_ASSERT(repos_path != NULL || op_depth > 0); + SVN_ERR_ASSERT((repos_id != INVALID_REPOS_ID) + == (repos_path != NULL)); + + /* If we're inserting WORKING nodes, we might be replacing existing + * nodes which were moved-away. We need to retain the moved-to relpath of + * such nodes in order not to lose move information during replace. */ + if (op_depth > 0) + { + for (i = children->nelts; i--; ) + { + const char *name = APR_ARRAY_IDX(children, i, const char *); + svn_boolean_t have_row; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, + svn_relpath_join(local_relpath, name, + iterpool))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row && !svn_sqlite__column_is_null(stmt, 14)) + svn_hash_sets(moved_to_relpaths, name, + svn_sqlite__column_text(stmt, 14, scratch_pool)); + + SVN_ERR(svn_sqlite__reset(stmt)); + } + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE)); + + for (i = children->nelts; i--; ) + { + const char *name = APR_ARRAY_IDX(children, i, const char *); + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnrsnsnnnnnnnnnnsn", + wc_id, + svn_relpath_join(local_relpath, name, + iterpool), + op_depth, + local_relpath, + revision, + "incomplete", /* 8, presence */ + "unknown", /* 10, kind */ + /* 21, moved_to */ + svn_hash_gets(moved_to_relpaths, name))); + if (repos_id != INVALID_REPOS_ID) + { + SVN_ERR(svn_sqlite__bind_int64(stmt, 5, repos_id)); + SVN_ERR(svn_sqlite__bind_text(stmt, 6, + svn_relpath_join(repos_path, name, + iterpool))); + } + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Insert the working row represented by (insert_working_baton_t *) BATON. */ +static svn_error_t * +insert_working_node(const insert_working_baton_t *piwb, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + const char *parent_relpath; + const char *moved_to_relpath = NULL; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(piwb->op_depth > 0); + + /* We cannot insert a WORKING_NODE row at the wcroot. */ + SVN_ERR_ASSERT(*local_relpath != '\0'); + parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool); + + /* Preserve existing moved-to information for this relpath, + * which might exist in case we're replacing an existing base-deleted + * node. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_MOVED_TO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + piwb->op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + moved_to_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdsnnntstrisn" + "nnnn" /* properties translated_size last_mod_time dav_cache */ + "sns", /* symlink_target, file_external, moved_to */ + wcroot->wc_id, local_relpath, + piwb->op_depth, + parent_relpath, + presence_map, piwb->presence, + (piwb->kind == svn_node_dir) + ? svn_token__to_word(depth_map, piwb->depth) : NULL, + kind_map, piwb->kind, + piwb->changed_rev, + piwb->changed_date, + piwb->changed_author, + /* Note: incomplete nodes may have a NULL target. */ + (piwb->kind == svn_node_symlink) + ? piwb->target : NULL, + moved_to_relpath)); + + if (piwb->moved_here) + { + SVN_ERR(svn_sqlite__bind_int(stmt, 8, TRUE)); + } + + if (piwb->kind == svn_node_file) + { + SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, piwb->checksum, + scratch_pool)); + } + + if (piwb->original_repos_relpath != NULL) + { + SVN_ERR(svn_sqlite__bind_int64(stmt, 5, piwb->original_repos_id)); + SVN_ERR(svn_sqlite__bind_text(stmt, 6, piwb->original_repos_relpath)); + SVN_ERR(svn_sqlite__bind_revnum(stmt, 7, piwb->original_revnum)); + } + + /* Set properties. Must be null if presence not normal or incomplete. */ + assert(piwb->presence == svn_wc__db_status_normal + || piwb->presence == svn_wc__db_status_incomplete + || piwb->props == NULL); + SVN_ERR(svn_sqlite__bind_properties(stmt, 15, piwb->props, scratch_pool)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + + /* Insert incomplete children, if specified. + The children are part of the same op and so have the same op_depth. + (The only time we'd want a different depth is during a recursive + simple add, but we never insert children here during a simple add.) */ + if (piwb->kind == svn_node_dir && piwb->children) + SVN_ERR(insert_incomplete_children(wcroot->sdb, wcroot->wc_id, + local_relpath, + INVALID_REPOS_ID /* inherit repos_id */, + NULL /* inherit repos_path */, + piwb->original_revnum, + piwb->children, + piwb->op_depth, + scratch_pool)); + + if (piwb->update_actual_props) + { + /* Cast away const, to allow calling property helpers */ + apr_hash_t *base_props = (apr_hash_t *)piwb->props; + apr_hash_t *new_actual_props = (apr_hash_t *)piwb->new_actual_props; + + if (base_props != NULL + && new_actual_props != NULL + && (apr_hash_count(base_props) == apr_hash_count(new_actual_props))) + { + apr_array_header_t *diffs; + + SVN_ERR(svn_prop_diffs(&diffs, new_actual_props, base_props, + scratch_pool)); + + if (diffs->nelts == 0) + new_actual_props = NULL; + } + + SVN_ERR(set_actual_props(wcroot->wc_id, local_relpath, new_actual_props, + wcroot->sdb, scratch_pool)); + } + + if (piwb->kind == svn_node_dir) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_ACTUAL_CLEAR_CHANGELIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_EMPTY)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + if (piwb->not_present_op_depth > 0 + && piwb->not_present_op_depth < piwb->op_depth) + { + /* And also insert a not-present node to tell the commit processing that + a child of the parent node was not copied. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_NODE)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtnt", + wcroot->wc_id, local_relpath, + piwb->not_present_op_depth, parent_relpath, + piwb->original_repos_id, + piwb->original_repos_relpath, + piwb->original_revnum, + presence_map, svn_wc__db_status_not_present, + /* NULL */ + kind_map, piwb->kind)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + SVN_ERR(add_work_items(wcroot->sdb, piwb->work_items, scratch_pool)); + if (piwb->conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + piwb->conflict, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* Each name is allocated in RESULT_POOL and stored into CHILDREN as a key + pointed to the same name. */ +static svn_error_t * +add_children_to_hash(apr_hash_t *children, + int stmt_idx, + svn_sqlite__db_t *sdb, + apr_int64_t wc_id, + const char *parent_relpath, + apr_pool_t *result_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, stmt_idx)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, parent_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *name = svn_relpath_basename(child_relpath, result_pool); + + svn_hash_sets(children, name, name); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + return svn_sqlite__reset(stmt); +} + + +/* Set *CHILDREN to a new array of the (const char *) basenames of the + immediate children, whatever their status, of the working node at + LOCAL_RELPATH. */ +static svn_error_t * +gather_children2(const apr_array_header_t **children, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *names_hash = apr_hash_make(scratch_pool); + apr_array_header_t *names_array; + + /* All of the names get allocated in RESULT_POOL. It + appears to be faster to use the hash to remove duplicates than to + use DISTINCT in the SQL query. */ + SVN_ERR(add_children_to_hash(names_hash, STMT_SELECT_WORKING_CHILDREN, + wcroot->sdb, wcroot->wc_id, + local_relpath, result_pool)); + + SVN_ERR(svn_hash_keys(&names_array, names_hash, result_pool)); + *children = names_array; + return SVN_NO_ERROR; +} + +/* Return in *CHILDREN all of the children of the directory LOCAL_RELPATH, + of any status, in all op-depths in the NODES table. */ +static svn_error_t * +gather_children(const apr_array_header_t **children, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *names_hash = apr_hash_make(scratch_pool); + apr_array_header_t *names_array; + + /* All of the names get allocated in RESULT_POOL. It + appears to be faster to use the hash to remove duplicates than to + use DISTINCT in the SQL query. */ + SVN_ERR(add_children_to_hash(names_hash, STMT_SELECT_NODE_CHILDREN, + wcroot->sdb, wcroot->wc_id, + local_relpath, result_pool)); + + SVN_ERR(svn_hash_keys(&names_array, names_hash, result_pool)); + *children = names_array; + return SVN_NO_ERROR; +} + + +/* Set *CHILDREN to a new array of (const char *) names of the children of + the repository directory corresponding to WCROOT:LOCAL_RELPATH:OP_DEPTH - + that is, only the children that are at the same op-depth as their parent. */ +static svn_error_t * +gather_repo_children(const apr_array_header_t **children, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *result + = apr_array_make(result_pool, 0, sizeof(const char *)); + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_OP_DEPTH_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + + /* Allocate the name in RESULT_POOL so we won't have to copy it. */ + APR_ARRAY_PUSH(result, const char *) + = svn_relpath_basename(child_relpath, result_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + *children = result; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_get_children_op_depth(apr_hash_t **children, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + *children = apr_hash_make(result_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_OP_DEPTH_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + svn_node_kind_t *child_kind = apr_palloc(result_pool, sizeof(svn_node_kind_t)); + + *child_kind = svn_sqlite__column_token(stmt, 1, kind_map); + svn_hash_sets(*children, + svn_relpath_basename(child_relpath, result_pool), + child_kind); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + + +/* Return TRUE if CHILD_ABSPATH is an immediate child of PARENT_ABSPATH. + * Else, return FALSE. */ +static svn_boolean_t +is_immediate_child_path(const char *parent_abspath, const char *child_abspath) +{ + const char *local_relpath = svn_dirent_skip_ancestor(parent_abspath, + child_abspath); + + /* To be an immediate child local_relpath should have one (not empty) + component */ + return local_relpath && *local_relpath && !strchr(local_relpath, '/'); +} + + +/* Remove the access baton for LOCAL_ABSPATH from ACCESS_CACHE. */ +static void +remove_from_access_cache(apr_hash_t *access_cache, + const char *local_abspath) +{ + svn_wc_adm_access_t *adm_access; + + adm_access = svn_hash_gets(access_cache, local_abspath); + if (adm_access) + svn_wc__adm_access_set_entries(adm_access, NULL); +} + + +/* Flush the access baton for LOCAL_ABSPATH, and any of its children up to + * the specified DEPTH, from the access baton cache in WCROOT. + * Also flush the access baton for the parent of LOCAL_ABSPATH.I + * + * This function must be called when the access baton cache goes stale, + * i.e. data about LOCAL_ABSPATH will need to be read again from disk. + * + * Use SCRATCH_POOL for temporary allocations. */ +static svn_error_t * +flush_entries(svn_wc__db_wcroot_t *wcroot, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + const char *parent_abspath; + + if (apr_hash_count(wcroot->access_cache) == 0) + return SVN_NO_ERROR; + + remove_from_access_cache(wcroot->access_cache, local_abspath); + + if (depth > svn_depth_empty) + { + apr_hash_index_t *hi; + + /* Flush access batons of children within the specified depth. */ + for (hi = apr_hash_first(scratch_pool, wcroot->access_cache); + hi; + hi = apr_hash_next(hi)) + { + const char *item_abspath = svn__apr_hash_index_key(hi); + + if ((depth == svn_depth_files || depth == svn_depth_immediates) && + is_immediate_child_path(local_abspath, item_abspath)) + { + remove_from_access_cache(wcroot->access_cache, item_abspath); + } + else if (depth == svn_depth_infinity && + svn_dirent_is_ancestor(local_abspath, item_abspath)) + { + remove_from_access_cache(wcroot->access_cache, item_abspath); + } + } + } + + /* We're going to be overly aggressive here and just flush the parent + without doing much checking. This may hurt performance for + legacy API consumers, but that's not our problem. :) */ + parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + remove_from_access_cache(wcroot->access_cache, parent_abspath); + + return SVN_NO_ERROR; +} + + +/* Add a single WORK_ITEM into the given SDB's WORK_QUEUE table. This does + not perform its work within a transaction, assuming the caller will + manage that. */ +static svn_error_t * +add_single_work_item(svn_sqlite__db_t *sdb, + const svn_skel_t *work_item, + apr_pool_t *scratch_pool) +{ + svn_stringbuf_t *serialized; + svn_sqlite__stmt_t *stmt; + + serialized = svn_skel__unparse(work_item, scratch_pool); + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_WORK_ITEM)); + SVN_ERR(svn_sqlite__bind_blob(stmt, 1, serialized->data, serialized->len)); + return svn_error_trace(svn_sqlite__insert(NULL, stmt)); +} + + +/* Add work item(s) to the given SDB. Also see add_single_work_item(). This + SKEL is usually passed to the various wc_db operation functions. It may + be NULL, indicating no additional work items are needed, it may be a + single work item, or it may be a list of work items. */ +static svn_error_t * +add_work_items(svn_sqlite__db_t *sdb, + const svn_skel_t *skel, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + + /* Maybe there are no work items to insert. */ + if (skel == NULL) + return SVN_NO_ERROR; + + /* Should have a list. */ + SVN_ERR_ASSERT(!skel->is_atom); + + /* Is the list a single work item? Or a list of work items? */ + if (SVN_WC__SINGLE_WORK_ITEM(skel)) + return svn_error_trace(add_single_work_item(sdb, skel, scratch_pool)); + + /* SKEL is a list-of-lists, aka list of work items. */ + + iterpool = svn_pool_create(scratch_pool); + for (skel = skel->children; skel; skel = skel->next) + { + svn_pool_clear(iterpool); + + SVN_ERR(add_single_work_item(sdb, skel, iterpool)); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +/* Determine whether the node exists for a given WCROOT and LOCAL_RELPATH. */ +static svn_error_t * +does_node_exist(svn_boolean_t *exists, + const svn_wc__db_wcroot_t *wcroot, + const char *local_relpath) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DOES_NODE_EXIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(exists, stmt)); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +/* Helper for create_db(). Initializes our wc.db schema. + */ +static svn_error_t * +init_db(/* output values */ + apr_int64_t *repos_id, + apr_int64_t *wc_id, + /* input values */ + svn_sqlite__db_t *db, + const char *repos_root_url, + const char *repos_uuid, + const char *root_node_repos_relpath, + svn_revnum_t root_node_revision, + svn_depth_t root_node_depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + /* Create the database's schema. */ + SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_SCHEMA)); + SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_NODES)); + SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_NODES_TRIGGERS)); + SVN_ERR(svn_sqlite__exec_statements(db, STMT_CREATE_EXTERNALS)); + + /* Insert the repository. */ + SVN_ERR(create_repos_id(repos_id, repos_root_url, repos_uuid, + db, scratch_pool)); + + /* Insert the wcroot. */ + /* ### Right now, this just assumes wc metadata is being stored locally. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_WCROOT)); + SVN_ERR(svn_sqlite__insert(wc_id, stmt)); + + if (root_node_repos_relpath) + { + svn_wc__db_status_t status = svn_wc__db_status_normal; + + if (root_node_revision > 0) + status = svn_wc__db_status_incomplete; /* Will be filled by update */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtst", + *wc_id, /* 1 */ + "", /* 2 */ + 0, /* op_depth is 0 for base */ + NULL, /* 4 */ + *repos_id, + root_node_repos_relpath, + root_node_revision, + presence_map, status, /* 8 */ + svn_token__to_word(depth_map, + root_node_depth), + kind_map, svn_node_dir /* 10 */)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + } + + return SVN_NO_ERROR; +} + +/* Create an sqlite database at DIR_ABSPATH/SDB_FNAME and insert + records for REPOS_ID (using REPOS_ROOT_URL and REPOS_UUID) into + REPOSITORY and for WC_ID into WCROOT. Return the DB connection + in *SDB. + + If ROOT_NODE_REPOS_RELPATH is not NULL, insert a BASE node at + the working copy root with repository relpath ROOT_NODE_REPOS_RELPATH, + revision ROOT_NODE_REVISION and depth ROOT_NODE_DEPTH. + */ +static svn_error_t * +create_db(svn_sqlite__db_t **sdb, + apr_int64_t *repos_id, + apr_int64_t *wc_id, + const char *dir_abspath, + const char *repos_root_url, + const char *repos_uuid, + const char *sdb_fname, + const char *root_node_repos_relpath, + svn_revnum_t root_node_revision, + svn_depth_t root_node_depth, + svn_boolean_t exclusive, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_wc__db_util_open_db(sdb, dir_abspath, sdb_fname, + svn_sqlite__mode_rwcreate, exclusive, + NULL /* my_statements */, + result_pool, scratch_pool)); + + SVN_SQLITE__WITH_LOCK(init_db(repos_id, wc_id, + *sdb, repos_root_url, repos_uuid, + root_node_repos_relpath, root_node_revision, + root_node_depth, scratch_pool), + *sdb); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_init(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t initial_rev, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__db_t *sdb; + apr_int64_t repos_id; + apr_int64_t wc_id; + svn_wc__db_wcroot_t *wcroot; + svn_boolean_t sqlite_exclusive = FALSE; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(repos_relpath != NULL); + SVN_ERR_ASSERT(depth == svn_depth_empty + || depth == svn_depth_files + || depth == svn_depth_immediates + || depth == svn_depth_infinity); + + /* ### REPOS_ROOT_URL and REPOS_UUID may be NULL. ... more doc: tbd */ + + SVN_ERR(svn_config_get_bool((svn_config_t *)db->config, &sqlite_exclusive, + SVN_CONFIG_SECTION_WORKING_COPY, + SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE, + FALSE)); + + /* Create the SDB and insert the basic rows. */ + SVN_ERR(create_db(&sdb, &repos_id, &wc_id, local_abspath, repos_root_url, + repos_uuid, SDB_FILE, + repos_relpath, initial_rev, depth, sqlite_exclusive, + db->state_pool, scratch_pool)); + + /* Create the WCROOT for this directory. */ + SVN_ERR(svn_wc__db_pdh_create_wcroot(&wcroot, + apr_pstrdup(db->state_pool, local_abspath), + sdb, wc_id, FORMAT_FROM_SDB, + FALSE /* auto-upgrade */, + FALSE /* enforce_empty_wq */, + db->state_pool, scratch_pool)); + + /* The WCROOT is complete. Stash it into DB. */ + svn_hash_sets(db->dir_data, wcroot->abspath, wcroot); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_to_relpath(const char **local_relpath, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &relpath, db, + wri_abspath, result_pool, scratch_pool)); + + /* This function is indirectly called from the upgrade code, so we + can't verify the wcroot here. Just check that it is not NULL */ + CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool); + + if (svn_dirent_is_ancestor(wcroot->abspath, local_abspath)) + { + *local_relpath = apr_pstrdup(result_pool, + svn_dirent_skip_ancestor(wcroot->abspath, + local_abspath)); + } + else + /* Probably moving from $TMP. Should we allow this? */ + *local_relpath = apr_pstrdup(result_pool, local_abspath); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_from_relpath(const char **local_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *unused_relpath; +#if 0 + SVN_ERR_ASSERT(svn_relpath_is_canonical(local_relpath)); +#endif + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &unused_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + + /* This function is indirectly called from the upgrade code, so we + can't verify the wcroot here. Just check that it is not NULL */ + CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool); + + + *local_abspath = svn_dirent_join(wcroot->abspath, + local_relpath, + result_pool); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_get_wcroot(const char **wcroot_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *unused_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &unused_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + + /* Can't use VERIFY_USABLE_WCROOT, as this should be usable to detect + where call upgrade */ + CHECK_MINIMAL_WCROOT(wcroot, wri_abspath, scratch_pool); + + *wcroot_abspath = apr_pstrdup(result_pool, wcroot->abspath); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_add_directory(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const apr_array_header_t *children, + svn_depth_t depth, + apr_hash_t *dav_cache, + const svn_skel_t *conflict, + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_base_baton_t ibb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(repos_relpath != NULL); + SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool)); + SVN_ERR_ASSERT(repos_uuid != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); + SVN_ERR_ASSERT(props != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev)); +#if 0 + SVN_ERR_ASSERT(children != NULL); +#endif + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + + blank_ibb(&ibb); + + /* Calculate repos_id in insert_base_node() to avoid extra transaction */ + ibb.repos_root_url = repos_root_url; + ibb.repos_uuid = repos_uuid; + + ibb.status = svn_wc__db_status_normal; + ibb.kind = svn_node_dir; + ibb.repos_relpath = repos_relpath; + ibb.revision = revision; + + ibb.iprops = new_iprops; + ibb.props = props; + ibb.changed_rev = changed_rev; + ibb.changed_date = changed_date; + ibb.changed_author = changed_author; + + ibb.children = children; + ibb.depth = depth; + + ibb.dav_cache = dav_cache; + ibb.conflict = conflict; + ibb.work_items = work_items; + + if (update_actual_props) + { + ibb.update_actual_props = TRUE; + ibb.new_actual_props = new_actual_props; + } + + /* Insert the directory and all its children transactionally. + + Note: old children can stick around, even if they are no longer present + in this directory's revision. */ + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_base_add_incomplete_directory(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_depth_t depth, + svn_boolean_t insert_base_deleted, + svn_boolean_t delete_working, + svn_skel_t *conflict, + svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + struct insert_base_baton_t ibb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); + SVN_ERR_ASSERT(repos_relpath && repos_root_url && repos_uuid); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + + VERIFY_USABLE_WCROOT(wcroot); + + blank_ibb(&ibb); + + /* Calculate repos_id in insert_base_node() to avoid extra transaction */ + ibb.repos_root_url = repos_root_url; + ibb.repos_uuid = repos_uuid; + + ibb.status = svn_wc__db_status_incomplete; + ibb.kind = svn_node_dir; + ibb.repos_relpath = repos_relpath; + ibb.revision = revision; + ibb.depth = depth; + ibb.insert_base_deleted = insert_base_deleted; + ibb.delete_working = delete_working; + + ibb.conflict = conflict; + ibb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_add_file(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const svn_checksum_t *checksum, + apr_hash_t *dav_cache, + svn_boolean_t delete_working, + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, + svn_boolean_t keep_recorded_info, + svn_boolean_t insert_base_deleted, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_base_baton_t ibb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(repos_relpath != NULL); + SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool)); + SVN_ERR_ASSERT(repos_uuid != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); + SVN_ERR_ASSERT(props != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev)); + SVN_ERR_ASSERT(checksum != NULL); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + + blank_ibb(&ibb); + + /* Calculate repos_id in insert_base_node() to avoid extra transaction */ + ibb.repos_root_url = repos_root_url; + ibb.repos_uuid = repos_uuid; + + ibb.status = svn_wc__db_status_normal; + ibb.kind = svn_node_file; + ibb.repos_relpath = repos_relpath; + ibb.revision = revision; + + ibb.props = props; + ibb.changed_rev = changed_rev; + ibb.changed_date = changed_date; + ibb.changed_author = changed_author; + + ibb.checksum = checksum; + + ibb.dav_cache = dav_cache; + ibb.iprops = new_iprops; + + if (update_actual_props) + { + ibb.update_actual_props = TRUE; + ibb.new_actual_props = new_actual_props; + } + + ibb.keep_recorded_info = keep_recorded_info; + ibb.insert_base_deleted = insert_base_deleted; + ibb.delete_working = delete_working; + + ibb.conflict = conflict; + ibb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); + + /* If this used to be a directory we should remove children so pass + * depth infinity. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_add_symlink(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const char *target, + apr_hash_t *dav_cache, + svn_boolean_t delete_working, + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, + svn_boolean_t keep_recorded_info, + svn_boolean_t insert_base_deleted, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_base_baton_t ibb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(repos_relpath != NULL); + SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool)); + SVN_ERR_ASSERT(repos_uuid != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); + SVN_ERR_ASSERT(props != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev)); + SVN_ERR_ASSERT(target != NULL); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + blank_ibb(&ibb); + + /* Calculate repos_id in insert_base_node() to avoid extra transaction */ + ibb.repos_root_url = repos_root_url; + ibb.repos_uuid = repos_uuid; + + ibb.status = svn_wc__db_status_normal; + ibb.kind = svn_node_symlink; + ibb.repos_relpath = repos_relpath; + ibb.revision = revision; + + ibb.props = props; + ibb.changed_rev = changed_rev; + ibb.changed_date = changed_date; + ibb.changed_author = changed_author; + + ibb.target = target; + + ibb.dav_cache = dav_cache; + ibb.iprops = new_iprops; + + if (update_actual_props) + { + ibb.update_actual_props = TRUE; + ibb.new_actual_props = new_actual_props; + } + + ibb.keep_recorded_info = keep_recorded_info; + ibb.insert_base_deleted = insert_base_deleted; + ibb.delete_working = delete_working; + + ibb.conflict = conflict; + ibb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); + + /* If this used to be a directory we should remove children so pass + * depth infinity. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + return SVN_NO_ERROR; +} + + +static svn_error_t * +add_excluded_or_not_present_node(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_node_kind_t kind, + svn_wc__db_status_t status, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_base_baton_t ibb; + const char *dir_abspath, *name; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(repos_relpath != NULL); + SVN_ERR_ASSERT(svn_uri_is_canonical(repos_root_url, scratch_pool)); + SVN_ERR_ASSERT(repos_uuid != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); + SVN_ERR_ASSERT(status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_not_present); + + /* These absent presence nodes are only useful below a parent node that is + present. To avoid problems with working copies obstructing the child + we calculate the wcroot and local_relpath of the parent and then add + our own relpath. */ + + svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + local_relpath = svn_relpath_join(local_relpath, name, scratch_pool); + + blank_ibb(&ibb); + + /* Calculate repos_id in insert_base_node() to avoid extra transaction */ + ibb.repos_root_url = repos_root_url; + ibb.repos_uuid = repos_uuid; + + ibb.status = status; + ibb.kind = kind; + ibb.repos_relpath = repos_relpath; + ibb.revision = revision; + + /* Depending upon KIND, any of these might get used. */ + ibb.children = NULL; + ibb.depth = svn_depth_unknown; + ibb.checksum = NULL; + ibb.target = NULL; + + ibb.conflict = conflict; + ibb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_base_node(&ibb, wcroot, local_relpath, scratch_pool), + wcroot); + + /* If this used to be a directory we should remove children so pass + * depth infinity. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_add_excluded_node(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_node_kind_t kind, + svn_wc__db_status_t status, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded); + + return add_excluded_or_not_present_node( + db, local_abspath, repos_relpath, repos_root_url, repos_uuid, revision, + kind, status, conflict, work_items, scratch_pool); +} + + +svn_error_t * +svn_wc__db_base_add_not_present_node(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_node_kind_t kind, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + return add_excluded_or_not_present_node( + db, local_abspath, repos_relpath, repos_root_url, repos_uuid, revision, + kind, svn_wc__db_status_not_present, conflict, work_items, scratch_pool); +} + +/* Recursively clear moved-here information at the copy-half of the move + * which moved the node at SRC_RELPATH away. This transforms the move into + * a simple copy. */ +static svn_error_t * +clear_moved_here(const char *src_relpath, + svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + const char *dst_relpath; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_MOVED_TO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + src_relpath, relpath_depth(src_relpath))); + SVN_ERR(svn_sqlite__step_row(stmt)); + dst_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_HERE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + dst_relpath, relpath_depth(dst_relpath))); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_base_remove(). + */ +static svn_error_t * +db_base_remove(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, /* For checking conflicts */ + svn_boolean_t keep_as_working, + svn_boolean_t queue_deletes, + svn_revnum_t not_present_revision, + svn_skel_t *conflict, + svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_wc__db_status_t status; + apr_int64_t repos_id; + const char *repos_relpath; + svn_node_kind_t kind; + svn_boolean_t keep_working; + + SVN_ERR(svn_wc__db_base_get_info_internal(&status, &kind, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (status == svn_wc__db_status_normal + && keep_as_working) + { + SVN_ERR(svn_wc__db_op_make_copy(db, + svn_dirent_join(wcroot->abspath, + local_relpath, + scratch_pool), + NULL, NULL, + scratch_pool)); + keep_working = TRUE; + } + else + { + /* Check if there is already a working node */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&keep_working, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + } + + /* Step 1: Create workqueue operations to remove files and dirs in the + local-wc */ + if (!keep_working + && queue_deletes + && (status == svn_wc__db_status_normal + || status == svn_wc__db_status_incomplete)) + { + svn_skel_t *work_item; + const char *local_abspath; + + local_abspath = svn_dirent_join(wcroot->abspath, local_relpath, + scratch_pool); + if (kind == svn_node_dir) + { + apr_pool_t *iterpool; + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_BASE_PRESENT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + const char *node_relpath = svn_sqlite__column_text(stmt, 0, NULL); + svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 1, + kind_map); + const char *node_abspath; + svn_error_t *err; + + svn_pool_clear(iterpool); + + node_abspath = svn_dirent_join(wcroot->abspath, node_relpath, + iterpool); + + if (node_kind == svn_node_dir) + err = svn_wc__wq_build_dir_remove(&work_item, + db, wcroot->abspath, + node_abspath, FALSE, + iterpool, iterpool); + else + err = svn_wc__wq_build_file_remove(&work_item, + db, + wcroot->abspath, + node_abspath, + iterpool, iterpool); + + if (!err) + err = add_work_items(wcroot->sdb, work_item, iterpool); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_wc__wq_build_dir_remove(&work_item, + db, wcroot->abspath, + local_abspath, FALSE, + scratch_pool, iterpool)); + svn_pool_destroy(iterpool); + } + else + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, + db, wcroot->abspath, + local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(add_work_items(wcroot->sdb, work_item, scratch_pool)); + } + + /* Step 2: Delete ACTUAL nodes */ + if (! keep_working) + { + /* There won't be a record in NODE left for this node, so we want + to remove *all* ACTUAL nodes, including ACTUAL ONLY. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + else if (! keep_as_working) + { + /* Delete only the ACTUAL nodes that apply to a delete of a BASE node */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_FOR_BASE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + /* Else: Everything has been turned into a copy, so we want to keep all + ACTUAL_NODE records */ + + /* Step 3: Delete WORKING nodes */ + if (conflict) + { + apr_pool_t *iterpool; + + /* + * When deleting a conflicted node, moves of any moved-outside children + * of the node must be broken. Else, the destination will still be marked + * moved-here after the move source disappears from the working copy. + * + * ### FIXME: It would be nicer to have the conflict resolver + * break the move instead. It might also be a good idea to + * flag a tree conflict on each moved-away child. But doing so + * might introduce actual-only nodes without direct parents, + * and we're not yet sure if other existing code is prepared + * to handle such nodes. To be revisited post-1.8. + */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_OUTSIDE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, + relpath_depth(local_relpath))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + const char *child_relpath; + svn_error_t *err; + + svn_pool_clear(iterpool); + child_relpath = svn_sqlite__column_text(stmt, 0, iterpool); + err = clear_moved_here(child_relpath, wcroot, iterpool); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + SVN_ERR(svn_sqlite__reset(stmt)); + } + if (keep_working) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WORKING_BASE_DELETE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + else + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WORKING_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Step 4: Delete the BASE node descendants */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_BASE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* Step 5: handle the BASE node itself */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_BASE_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_wc__db_retract_parent_delete(wcroot, local_relpath, 0, + scratch_pool)); + + /* Step 6: Delete actual node if we don't keep working */ + if (! keep_working) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + if (SVN_IS_VALID_REVNUM(not_present_revision)) + { + struct insert_base_baton_t ibb; + blank_ibb(&ibb); + + ibb.repos_id = repos_id; + ibb.status = svn_wc__db_status_not_present; + ibb.kind = kind; + ibb.repos_relpath = repos_relpath; + ibb.revision = not_present_revision; + + /* Depending upon KIND, any of these might get used. */ + ibb.children = NULL; + ibb.depth = svn_depth_unknown; + ibb.checksum = NULL; + ibb.target = NULL; + + SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool)); + } + + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + if (conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_remove(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t keep_as_working, + svn_boolean_t queue_deletes, + svn_revnum_t not_present_revision, + svn_skel_t *conflict, + svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(db_base_remove(wcroot, local_relpath, + db, keep_as_working, queue_deletes, + not_present_revision, + conflict, work_items, scratch_pool), + wcroot); + + /* If this used to be a directory we should remove children so pass + * depth infinity. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_get_info_internal(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + svn_wc__db_lock_t **lock, + svn_boolean_t *had_props, + apr_hash_t **props, + svn_boolean_t *update_root, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_error_t *err = SVN_NO_ERROR; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + lock ? STMT_SELECT_BASE_NODE_WITH_LOCK + : STMT_SELECT_BASE_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + svn_wc__db_status_t node_status = svn_sqlite__column_token(stmt, 2, + presence_map); + svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 3, kind_map); + + if (kind) + { + *kind = node_kind; + } + if (status) + { + *status = node_status; + } + repos_location_from_columns(repos_id, revision, repos_relpath, + stmt, 0, 4, 1, result_pool); + SVN_ERR_ASSERT(!repos_id || *repos_id != INVALID_REPOS_ID); + SVN_ERR_ASSERT(!repos_relpath || *repos_relpath); + if (lock) + { + *lock = lock_from_columns(stmt, 15, 16, 17, 18, result_pool); + } + if (changed_rev) + { + *changed_rev = svn_sqlite__column_revnum(stmt, 7); + } + if (changed_date) + { + *changed_date = svn_sqlite__column_int64(stmt, 8); + } + if (changed_author) + { + /* Result may be NULL. */ + *changed_author = svn_sqlite__column_text(stmt, 9, result_pool); + } + if (depth) + { + if (node_kind != svn_node_dir) + { + *depth = svn_depth_unknown; + } + else + { + *depth = svn_sqlite__column_token_null(stmt, 10, depth_map, + svn_depth_unknown); + } + } + if (checksum) + { + if (node_kind != svn_node_file) + { + *checksum = NULL; + } + else + { + err = svn_sqlite__column_checksum(checksum, stmt, 5, + result_pool); + if (err != NULL) + err = svn_error_createf( + err->apr_err, err, + _("The node '%s' has a corrupt checksum value."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + } + if (target) + { + if (node_kind != svn_node_symlink) + *target = NULL; + else + *target = svn_sqlite__column_text(stmt, 11, result_pool); + } + if (had_props) + { + *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 13); + } + if (props) + { + if (node_status == svn_wc__db_status_normal + || node_status == svn_wc__db_status_incomplete) + { + SVN_ERR(svn_sqlite__column_properties(props, stmt, 13, + result_pool, scratch_pool)); + if (*props == NULL) + *props = apr_hash_make(result_pool); + } + else + { + assert(svn_sqlite__column_is_null(stmt, 13)); + *props = NULL; + } + } + if (update_root) + { + /* It's an update root iff it's a file external. */ + *update_root = svn_sqlite__column_boolean(stmt, 14); + } + } + else + { + err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + + /* Note: given the composition, no need to wrap for tracing. */ + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); +} + + +svn_error_t * +svn_wc__db_base_get_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + svn_wc__db_lock_t **lock, + svn_boolean_t *had_props, + apr_hash_t **props, + svn_boolean_t *update_root, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + apr_int64_t repos_id; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_wc__db_base_get_info_internal(status, kind, revision, + repos_relpath, &repos_id, + changed_rev, changed_date, + changed_author, depth, + checksum, target, lock, + had_props, props, update_root, + wcroot, local_relpath, + result_pool, scratch_pool)); + SVN_ERR_ASSERT(repos_id != INVALID_REPOS_ID); + SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, + wcroot->sdb, repos_id, result_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_base_get_children_info(apr_hash_t **nodes, + svn_wc__db_t *db, + const char *dir_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + *nodes = apr_hash_make(result_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_BASE_CHILDREN_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + struct svn_wc__db_base_info_t *info; + svn_error_t *err; + apr_int64_t repos_id; + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *name = svn_relpath_basename(child_relpath, result_pool); + + info = apr_pcalloc(result_pool, sizeof(*info)); + + repos_id = svn_sqlite__column_int64(stmt, 1); + info->repos_relpath = svn_sqlite__column_text(stmt, 2, result_pool); + info->status = svn_sqlite__column_token(stmt, 3, presence_map); + info->kind = svn_sqlite__column_token(stmt, 4, kind_map); + info->revnum = svn_sqlite__column_revnum(stmt, 5); + + info->depth = svn_sqlite__column_token_null(stmt, 6, depth_map, + svn_depth_unknown); + + info->update_root = svn_sqlite__column_boolean(stmt, 7); + + info->lock = lock_from_columns(stmt, 8, 9, 10, 11, result_pool); + + err = svn_wc__db_fetch_repos_info(&info->repos_root_url, NULL, + wcroot->sdb, repos_id, result_pool); + + if (err) + return svn_error_trace( + svn_error_compose_create(err, + svn_sqlite__reset(stmt))); + + + svn_hash_sets(*nodes, name, info); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_get_props(apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t presence; + + SVN_ERR(svn_wc__db_base_get_info(&presence, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, props, NULL, + db, local_abspath, + result_pool, scratch_pool)); + if (presence != svn_wc__db_status_normal + && presence != svn_wc__db_status_incomplete) + { + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("The node '%s' has a BASE status that" + " has no properties."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_get_children(const apr_array_header_t **children, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + return gather_repo_children(children, wcroot, local_relpath, 0, + result_pool, scratch_pool); +} + + +svn_error_t * +svn_wc__db_base_set_dav_cache(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int affected_rows; + + SVN_ERR(get_statement_for_path(&stmt, db, local_abspath, + STMT_UPDATE_BASE_NODE_DAV_CACHE, + scratch_pool)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, scratch_pool)); + + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + if (affected_rows != 1) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_get_dav_cache(apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(get_statement_for_path(&stmt, db, local_abspath, + STMT_SELECT_BASE_DAV_CACHE, scratch_pool)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + SVN_ERR(svn_sqlite__column_properties(props, stmt, 0, result_pool, + scratch_pool)); + return svn_error_trace(svn_sqlite__reset(stmt)); +} + + +svn_error_t * +svn_wc__db_base_clear_dav_cache_recursive(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_BASE_NODE_RECURSIVE_DAV_CACHE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_depth_get_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + svn_boolean_t *had_props, + apr_hash_t **props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_error_t *err = SVN_NO_ERROR; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_DEPTH_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wcroot->wc_id, local_relpath, op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + svn_wc__db_status_t node_status = svn_sqlite__column_token(stmt, 2, + presence_map); + svn_node_kind_t node_kind = svn_sqlite__column_token(stmt, 3, kind_map); + + if (kind) + { + *kind = node_kind; + } + if (status) + { + *status = node_status; + + if (op_depth > 0) + SVN_ERR(convert_to_working_status(status, *status)); + } + repos_location_from_columns(repos_id, revision, repos_relpath, + stmt, 0, 4, 1, result_pool); + + if (changed_rev) + { + *changed_rev = svn_sqlite__column_revnum(stmt, 7); + } + if (changed_date) + { + *changed_date = svn_sqlite__column_int64(stmt, 8); + } + if (changed_author) + { + /* Result may be NULL. */ + *changed_author = svn_sqlite__column_text(stmt, 9, result_pool); + } + if (depth) + { + if (node_kind != svn_node_dir) + { + *depth = svn_depth_unknown; + } + else + { + *depth = svn_sqlite__column_token_null(stmt, 10, depth_map, + svn_depth_unknown); + } + } + if (checksum) + { + if (node_kind != svn_node_file) + { + *checksum = NULL; + } + else + { + err = svn_sqlite__column_checksum(checksum, stmt, 5, + result_pool); + if (err != NULL) + err = svn_error_createf( + err->apr_err, err, + _("The node '%s' has a corrupt checksum value."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + } + if (target) + { + if (node_kind != svn_node_symlink) + *target = NULL; + else + *target = svn_sqlite__column_text(stmt, 11, result_pool); + } + if (had_props) + { + *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 13); + } + if (props) + { + if (node_status == svn_wc__db_status_normal + || node_status == svn_wc__db_status_incomplete) + { + SVN_ERR(svn_sqlite__column_properties(props, stmt, 13, + result_pool, scratch_pool)); + if (*props == NULL) + *props = apr_hash_make(result_pool); + } + else + { + assert(svn_sqlite__column_is_null(stmt, 13)); + *props = NULL; + } + } + } + else + { + err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + + /* Note: given the composition, no need to wrap for tracing. */ + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); +} + + +/* Baton for passing args to with_triggers(). */ +struct with_triggers_baton_t { + int create_trigger; + int drop_trigger; + svn_wc__db_txn_callback_t cb_func; + void *cb_baton; +}; + +/* Helper for creating SQLite triggers, running the main transaction + callback, and then dropping the triggers. It guarantees that the + triggers will not survive the transaction. This could be used for + any general prefix/postscript statements where the postscript + *must* be executed if the transaction completes. + + Implements svn_wc__db_txn_callback_t. */ +static svn_error_t * +with_triggers(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + struct with_triggers_baton_t *b = baton; + svn_error_t *err1; + svn_error_t *err2; + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, b->create_trigger)); + + err1 = b->cb_func(b->cb_baton, wcroot, local_relpath, scratch_pool); + + err2 = svn_sqlite__exec_statements(wcroot->sdb, b->drop_trigger); + + return svn_error_trace(svn_error_compose_create(err1, err2)); +} + + +/* Prototype for the "work callback" used by with_finalization(). */ +typedef svn_error_t * (*work_callback_t)( + void *baton, + svn_wc__db_wcroot_t *wcroot, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); + +/* Utility function to provide several features, with a guaranteed + finalization (ie. to drop temporary tables). + + 1) for WCROOT and LOCAL_RELPATH, run TXN_CB(TXN_BATON) within a + sqlite transaction + 2) if (1) is successful and a NOTIFY_FUNC is provided, then run + the "work" step: WORK_CB(WORK_BATON). + 3) execute FINALIZE_STMT_IDX no matter what errors may be thrown + from the above two steps. + + CANCEL_FUNC, CANCEL_BATON, NOTIFY_FUNC and NOTIFY_BATON are their + typical values. These are passed to the work callback, which typically + provides notification about the work done by TXN_CB. */ +static svn_error_t * +with_finalization(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_txn_callback_t txn_cb, + void *txn_baton, + work_callback_t work_cb, + void *work_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + int finalize_stmt_idx, + apr_pool_t *scratch_pool) +{ + svn_error_t *err1; + svn_error_t *err2; + + err1 = svn_wc__db_with_txn(wcroot, local_relpath, txn_cb, txn_baton, + scratch_pool); + + if (err1 == NULL && notify_func != NULL) + { + err2 = work_cb(work_baton, wcroot, + cancel_func, cancel_baton, + notify_func, notify_baton, + scratch_pool); + err1 = svn_error_compose_create(err1, err2); + } + + err2 = svn_sqlite__exec_statements(wcroot->sdb, finalize_stmt_idx); + + return svn_error_trace(svn_error_compose_create(err1, err2)); +} + + +/* Initialize the baton with appropriate "blank" values. This allows the + insertion function to leave certain columns null. */ +static void +blank_ieb(insert_external_baton_t *ieb) +{ + memset(ieb, 0, sizeof(*ieb)); + ieb->revision = SVN_INVALID_REVNUM; + ieb->changed_rev = SVN_INVALID_REVNUM; + ieb->repos_id = INVALID_REPOS_ID; + + ieb->recorded_peg_revision = SVN_INVALID_REVNUM; + ieb->recorded_revision = SVN_INVALID_REVNUM; +} + +/* Insert the externals row represented by (insert_external_baton_t *) BATON. + * + * Implements svn_wc__db_txn_callback_t. */ +static svn_error_t * +insert_external_node(const insert_external_baton_t *ieb, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_error_t *err; + svn_boolean_t update_root; + apr_int64_t repos_id; + svn_sqlite__stmt_t *stmt; + + if (ieb->repos_id != INVALID_REPOS_ID) + repos_id = ieb->repos_id; + else + SVN_ERR(create_repos_id(&repos_id, ieb->repos_root_url, ieb->repos_uuid, + wcroot->sdb, scratch_pool)); + + /* And there must be no existing BASE node or it must be a file external */ + err = svn_wc__db_base_get_info_internal(&status, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &update_root, + wcroot, local_relpath, + scratch_pool, scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + } + else if (status == svn_wc__db_status_normal && !update_root) + return svn_error_create(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, NULL); + + if (ieb->kind == svn_node_file + || ieb->kind == svn_node_symlink) + { + struct insert_base_baton_t ibb; + + blank_ibb(&ibb); + + ibb.status = svn_wc__db_status_normal; + ibb.kind = ieb->kind; + + ibb.repos_id = repos_id; + ibb.repos_relpath = ieb->repos_relpath; + ibb.revision = ieb->revision; + + ibb.props = ieb->props; + ibb.iprops = ieb->iprops; + ibb.changed_rev = ieb->changed_rev; + ibb.changed_date = ieb->changed_date; + ibb.changed_author = ieb->changed_author; + + ibb.dav_cache = ieb->dav_cache; + + ibb.checksum = ieb->checksum; + ibb.target = ieb->target; + + ibb.conflict = ieb->conflict; + + ibb.update_actual_props = ieb->update_actual_props; + ibb.new_actual_props = ieb->new_actual_props; + + ibb.keep_recorded_info = ieb->keep_recorded_info; + + ibb.work_items = ieb->work_items; + + ibb.file_external = TRUE; + + SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool)); + } + else + SVN_ERR(add_work_items(wcroot->sdb, ieb->work_items, scratch_pool)); + + /* The externals table only support presence normal and excluded */ + SVN_ERR_ASSERT(ieb->presence == svn_wc__db_status_normal + || ieb->presence == svn_wc__db_status_excluded); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_EXTERNAL)); + + SVN_ERR(svn_sqlite__bindf(stmt, "issttsis", + wcroot->wc_id, + local_relpath, + svn_relpath_dirname(local_relpath, + scratch_pool), + presence_map, ieb->presence, + kind_map, ieb->kind, + ieb->record_ancestor_relpath, + repos_id, + ieb->recorded_repos_relpath)); + + if (SVN_IS_VALID_REVNUM(ieb->recorded_peg_revision)) + SVN_ERR(svn_sqlite__bind_revnum(stmt, 9, ieb->recorded_peg_revision)); + + if (SVN_IS_VALID_REVNUM(ieb->recorded_revision)) + SVN_ERR(svn_sqlite__bind_revnum(stmt, 10, ieb->recorded_revision)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_external_add_file(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + + const apr_hash_t *props, + apr_array_header_t *iprops, + + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + + const svn_checksum_t *checksum, + + const apr_hash_t *dav_cache, + + const char *record_ancestor_abspath, + const char *recorded_repos_relpath, + svn_revnum_t recorded_peg_revision, + svn_revnum_t recorded_revision, + + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + + svn_boolean_t keep_recorded_info, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_external_baton_t ieb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (! wri_abspath) + wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, + record_ancestor_abspath)); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath)); + + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + + blank_ieb(&ieb); + + ieb.kind = svn_node_file; + ieb.presence = svn_wc__db_status_normal; + + ieb.repos_root_url = repos_root_url; + ieb.repos_uuid = repos_uuid; + + ieb.repos_relpath = repos_relpath; + ieb.revision = revision; + + ieb.props = props; + ieb.iprops = iprops; + + ieb.changed_rev = changed_rev; + ieb.changed_date = changed_date; + ieb.changed_author = changed_author; + + ieb.checksum = checksum; + + ieb.dav_cache = dav_cache; + + ieb.record_ancestor_relpath = svn_dirent_skip_ancestor( + wcroot->abspath, + record_ancestor_abspath); + ieb.recorded_repos_relpath = recorded_repos_relpath; + ieb.recorded_peg_revision = recorded_peg_revision; + ieb.recorded_revision = recorded_revision; + + ieb.update_actual_props = update_actual_props; + ieb.new_actual_props = new_actual_props; + + ieb.keep_recorded_info = keep_recorded_info; + + ieb.conflict = conflict; + ieb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_external_node(&ieb, wcroot, local_relpath, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_external_add_symlink(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const char *target, + const apr_hash_t *dav_cache, + const char *record_ancestor_abspath, + const char *recorded_repos_relpath, + svn_revnum_t recorded_peg_revision, + svn_revnum_t recorded_revision, + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + svn_boolean_t keep_recorded_info, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_external_baton_t ieb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (! wri_abspath) + wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, + record_ancestor_abspath)); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath)); + + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + + blank_ieb(&ieb); + + ieb.kind = svn_node_symlink; + ieb.presence = svn_wc__db_status_normal; + + ieb.repos_root_url = repos_root_url; + ieb.repos_uuid = repos_uuid; + + ieb.repos_relpath = repos_relpath; + ieb.revision = revision; + + ieb.props = props; + + ieb.changed_rev = changed_rev; + ieb.changed_date = changed_date; + ieb.changed_author = changed_author; + + ieb.target = target; + + ieb.dav_cache = dav_cache; + + ieb.record_ancestor_relpath = svn_dirent_skip_ancestor( + wcroot->abspath, + record_ancestor_abspath); + ieb.recorded_repos_relpath = recorded_repos_relpath; + ieb.recorded_peg_revision = recorded_peg_revision; + ieb.recorded_revision = recorded_revision; + + ieb.update_actual_props = update_actual_props; + ieb.new_actual_props = new_actual_props; + + ieb.keep_recorded_info = keep_recorded_info; + + ieb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_external_node(&ieb, wcroot, local_relpath, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_external_add_dir(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *repos_root_url, + const char *repos_uuid, + const char *record_ancestor_abspath, + const char *recorded_repos_relpath, + svn_revnum_t recorded_peg_revision, + svn_revnum_t recorded_revision, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_external_baton_t ieb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (! wri_abspath) + wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, + record_ancestor_abspath)); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath)); + + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + + blank_ieb(&ieb); + + ieb.kind = svn_node_dir; + ieb.presence = svn_wc__db_status_normal; + + ieb.repos_root_url = repos_root_url; + ieb.repos_uuid = repos_uuid; + + ieb.record_ancestor_relpath = svn_dirent_skip_ancestor( + wcroot->abspath, + record_ancestor_abspath); + ieb.recorded_repos_relpath = recorded_repos_relpath; + ieb.recorded_peg_revision = recorded_peg_revision; + ieb.recorded_revision = recorded_revision; + + ieb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_external_node(&ieb, wcroot, local_relpath, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_external_remove(). */ +static svn_error_t * +db_external_remove(const svn_skel_t *work_items, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_EXTERNAL)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + + /* ### What about actual? */ + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_external_remove(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (! wri_abspath) + wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath)); + + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + + SVN_WC__DB_WITH_TXN(db_external_remove(work_items, wcroot, local_relpath, + scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_external_read(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + const char **definining_abspath, + const char **repos_root_url, + const char **repos_uuid, + const char **recorded_repos_relpath, + svn_revnum_t *recorded_peg_revision, + svn_revnum_t *recorded_revision, + svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_info; + svn_error_t *err = NULL; + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (! wri_abspath) + wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR_ASSERT(svn_dirent_is_ancestor(wcroot->abspath, local_abspath)); + + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_EXTERNAL_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_info, stmt)); + + if (have_info) + { + if (status) + *status = svn_sqlite__column_token(stmt, 0, presence_map); + + if (kind) + *kind = svn_sqlite__column_token(stmt, 1, kind_map); + + if (definining_abspath) + { + const char *record_relpath = svn_sqlite__column_text(stmt, 2, NULL); + + *definining_abspath = svn_dirent_join(wcroot->abspath, + record_relpath, result_pool); + } + + if (repos_root_url || repos_uuid) + { + apr_int64_t repos_id; + + repos_id = svn_sqlite__column_int64(stmt, 3); + + err = svn_error_compose_create( + err, + svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, + wcroot->sdb, repos_id, + result_pool)); + } + + if (recorded_repos_relpath) + *recorded_repos_relpath = svn_sqlite__column_text(stmt, 4, + result_pool); + + if (recorded_peg_revision) + *recorded_peg_revision = svn_sqlite__column_revnum(stmt, 5); + + if (recorded_revision) + *recorded_revision = svn_sqlite__column_revnum(stmt, 6); + } + else + { + err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' is not an external."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + return svn_error_trace( + svn_error_compose_create(err, svn_sqlite__reset(stmt))); +} + +svn_error_t * +svn_wc__db_committable_externals_below(apr_array_header_t **externals, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t immediates_only, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + svn_sqlite__stmt_t *stmt; + const char *local_relpath; + svn_boolean_t have_row; + svn_wc__committable_external_info_t *info; + svn_node_kind_t db_kind; + apr_array_header_t *result = NULL; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + immediates_only + ? STMT_SELECT_COMMITTABLE_EXTERNALS_IMMEDIATELY_BELOW + : STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + result = apr_array_make(result_pool, 0, + sizeof(svn_wc__committable_external_info_t *)); + + while (have_row) + { + info = apr_palloc(result_pool, sizeof(*info)); + + local_relpath = svn_sqlite__column_text(stmt, 0, NULL); + info->local_abspath = svn_dirent_join(wcroot->abspath, local_relpath, + result_pool); + + db_kind = svn_sqlite__column_token(stmt, 1, kind_map); + SVN_ERR_ASSERT(db_kind == svn_node_file || db_kind == svn_node_dir); + info->kind = db_kind; + + info->repos_relpath = svn_sqlite__column_text(stmt, 2, result_pool); + info->repos_root_url = svn_sqlite__column_text(stmt, 3, result_pool); + + APR_ARRAY_PUSH(result, svn_wc__committable_external_info_t *) = info; + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + *externals = result; + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_wc__db_externals_defined_below(apr_hash_t **externals, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + svn_sqlite__stmt_t *stmt; + const char *local_relpath; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_EXTERNALS_DEFINED)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + *externals = apr_hash_make(result_pool); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + const char *def_local_relpath; + + local_relpath = svn_sqlite__column_text(stmt, 0, NULL); + def_local_relpath = svn_sqlite__column_text(stmt, 1, NULL); + + svn_hash_sets(*externals, + svn_dirent_join(wcroot->abspath, local_relpath, + result_pool), + svn_dirent_join(wcroot->abspath, def_local_relpath, + result_pool)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_wc__db_externals_gather_definitions(apr_hash_t **externals, + apr_hash_t **depths, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + svn_sqlite__stmt_t *stmt; + const char *local_relpath; + svn_boolean_t have_row; + svn_error_t *err = NULL; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, iterpool)); + VERIFY_USABLE_WCROOT(wcroot); + + *externals = apr_hash_make(result_pool); + if (depths != NULL) + *depths = apr_hash_make(result_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_EXTERNAL_PROPERTIES)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + apr_hash_t *node_props; + const char *external_value; + + svn_pool_clear(iterpool); + err = svn_sqlite__column_properties(&node_props, stmt, 0, iterpool, + iterpool); + + if (err) + break; + + external_value = svn_prop_get_value(node_props, SVN_PROP_EXTERNALS); + + if (external_value) + { + const char *node_abspath; + const char *node_relpath = svn_sqlite__column_text(stmt, 1, NULL); + + node_abspath = svn_dirent_join(wcroot->abspath, node_relpath, + result_pool); + + svn_hash_sets(*externals, node_abspath, + apr_pstrdup(result_pool, external_value)); + + if (depths) + { + svn_depth_t depth + = svn_sqlite__column_token_null(stmt, 2, depth_map, + svn_depth_unknown); + + svn_hash_sets(*depths, node_abspath, + /* Use static string */ + svn_token__to_word(depth_map, depth)); + } + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + svn_pool_destroy(iterpool); + + return svn_error_trace(svn_error_compose_create(err, + svn_sqlite__reset(stmt))); +} + +/* Copy the ACTUAL data for SRC_RELPATH and tweak it to refer to DST_RELPATH. + The new ACTUAL data won't have any conflicts. */ +static svn_error_t * +copy_actual(svn_wc__db_wcroot_t *src_wcroot, + const char *src_relpath, + svn_wc__db_wcroot_t *dst_wcroot, + const char *dst_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", src_wcroot->wc_id, src_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + apr_size_t props_size; + const char *changelist; + const char *properties; + + /* Skipping conflict data... */ + changelist = svn_sqlite__column_text(stmt, 0, scratch_pool); + /* No need to parse the properties when simply copying. */ + properties = svn_sqlite__column_blob(stmt, 1, &props_size, scratch_pool); + + if (changelist || properties) + { + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb, + STMT_INSERT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "issbs", + dst_wcroot->wc_id, dst_relpath, + svn_relpath_dirname(dst_relpath, scratch_pool), + properties, props_size, changelist)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +/* Helper for svn_wc__db_op_copy to handle copying from one db to + another */ +static svn_error_t * +cross_db_copy(svn_wc__db_wcroot_t *src_wcroot, + const char *src_relpath, + svn_wc__db_wcroot_t *dst_wcroot, + const char *dst_relpath, + svn_wc__db_status_t dst_status, + int dst_op_depth, + int dst_np_op_depth, + svn_node_kind_t kind, + const apr_array_header_t *children, + apr_int64_t copyfrom_id, + const char *copyfrom_relpath, + svn_revnum_t copyfrom_rev, + apr_pool_t *scratch_pool) +{ + insert_working_baton_t iwb; + svn_revnum_t changed_rev; + apr_time_t changed_date; + const char *changed_author; + const svn_checksum_t *checksum; + apr_hash_t *props; + svn_depth_t depth; + + SVN_ERR_ASSERT(kind == svn_node_file + || kind == svn_node_dir + ); + + SVN_ERR(read_info(NULL, NULL, NULL, NULL, NULL, + &changed_rev, &changed_date, &changed_author, &depth, + &checksum, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + src_wcroot, src_relpath, scratch_pool, scratch_pool)); + + SVN_ERR(db_read_pristine_props(&props, src_wcroot, src_relpath, FALSE, + scratch_pool, scratch_pool)); + + blank_iwb(&iwb); + iwb.presence = dst_status; + iwb.kind = kind; + + iwb.props = props; + iwb.changed_rev = changed_rev; + iwb.changed_date = changed_date; + iwb.changed_author = changed_author; + iwb.original_repos_id = copyfrom_id; + iwb.original_repos_relpath = copyfrom_relpath; + iwb.original_revnum = copyfrom_rev; + iwb.moved_here = FALSE; + + iwb.op_depth = dst_op_depth; + + iwb.checksum = checksum; + iwb.children = children; + iwb.depth = depth; + + iwb.not_present_op_depth = dst_np_op_depth; + + SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath, scratch_pool)); + + SVN_ERR(copy_actual(src_wcroot, src_relpath, + dst_wcroot, dst_relpath, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Helper for scan_deletion_txn. Extracts the moved-to information, if + any, from STMT. Sets *SCAN to FALSE if moved-to was available. */ +static svn_error_t * +get_moved_to(const char **moved_to_relpath_p, + const char **moved_to_op_root_relpath_p, + svn_boolean_t *scan, + svn_sqlite__stmt_t *stmt, + const char *current_relpath, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *moved_to_relpath = svn_sqlite__column_text(stmt, 3, NULL); + + if (moved_to_relpath) + { + const char *moved_to_op_root_relpath = moved_to_relpath; + + if (strcmp(current_relpath, local_relpath)) + { + /* LOCAL_RELPATH is a child inside the move op-root. */ + const char *moved_child_relpath; + + /* The CURRENT_RELPATH is the op_root of the delete-half of + * the move. LOCAL_RELPATH is a child that was moved along. + * Compute the child's new location within the move target. */ + moved_child_relpath = svn_relpath_skip_ancestor(current_relpath, + local_relpath); + SVN_ERR_ASSERT(moved_child_relpath && + strlen(moved_child_relpath) > 0); + moved_to_relpath = svn_relpath_join(moved_to_op_root_relpath, + moved_child_relpath, + result_pool); + } + + if (moved_to_op_root_relpath && moved_to_op_root_relpath_p) + *moved_to_op_root_relpath_p + = apr_pstrdup(result_pool, moved_to_op_root_relpath); + + if (moved_to_relpath && moved_to_relpath_p) + *moved_to_relpath_p + = apr_pstrdup(result_pool, moved_to_relpath); + + *scan = FALSE; + } + + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_scan_deletion(). + */ +static svn_error_t * +scan_deletion_txn(const char **base_del_relpath, + const char **moved_to_relpath, + const char **work_del_relpath, + const char **moved_to_op_root_relpath, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *current_relpath = local_relpath; + svn_sqlite__stmt_t *stmt; + svn_wc__db_status_t work_presence; + svn_boolean_t have_row, scan, have_base; + int op_depth; + + /* Initialize all the OUT parameters. */ + if (base_del_relpath != NULL) + *base_del_relpath = NULL; + if (moved_to_relpath != NULL) + *moved_to_relpath = NULL; + if (work_del_relpath != NULL) + *work_del_relpath = NULL; + if (moved_to_op_root_relpath != NULL) + *moved_to_op_root_relpath = NULL; + + /* If looking for moved-to info then we need to scan every path + until we find it. If not looking for moved-to we only need to + check op-roots and parents of op-roots. */ + scan = (moved_to_op_root_relpath || moved_to_relpath); + + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + scan ? STMT_SELECT_DELETION_INFO_SCAN + : STMT_SELECT_DELETION_INFO)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, current_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + work_presence = svn_sqlite__column_token(stmt, 1, presence_map); + have_base = !svn_sqlite__column_is_null(stmt, 0); + if (work_presence != svn_wc__db_status_not_present + && work_presence != svn_wc__db_status_base_deleted) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + svn_sqlite__reset(stmt), + _("Expected node '%s' to be deleted."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + op_depth = svn_sqlite__column_int(stmt, 2); + + /* Special case: LOCAL_RELPATH not-present within a WORKING tree, we + treat this as an op-root. At commit time we need to explicitly + delete such nodes otherwise they will be present in the + repository copy. */ + if (work_presence == svn_wc__db_status_not_present + && work_del_relpath && !*work_del_relpath) + { + *work_del_relpath = apr_pstrdup(result_pool, current_relpath); + + if (!scan && !base_del_relpath) + { + /* We have all we need, exit early */ + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + } + + + while (TRUE) + { + svn_error_t *err; + const char *parent_relpath; + int current_depth = relpath_depth(current_relpath); + + /* Step CURRENT_RELPATH to op-root */ + + while (TRUE) + { + if (scan) + { + err = get_moved_to(moved_to_relpath, moved_to_op_root_relpath, + &scan, stmt, current_relpath, + wcroot, local_relpath, + result_pool, scratch_pool); + if (err || (!scan + && !base_del_relpath + && !work_del_relpath)) + { + /* We have all we need (or an error occurred) */ + SVN_ERR(svn_sqlite__reset(stmt)); + return svn_error_trace(err); + } + } + + if (current_depth <= op_depth) + break; + + current_relpath = svn_relpath_dirname(current_relpath, scratch_pool); + --current_depth; + + if (scan || current_depth == op_depth) + { + SVN_ERR(svn_sqlite__reset(stmt)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + current_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR_ASSERT(have_row); + have_base = !svn_sqlite__column_is_null(stmt, 0); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Now CURRENT_RELPATH is an op-root, have a look at the parent. */ + + SVN_ERR_ASSERT(current_relpath[0] != '\0'); /* Catch invalid data */ + parent_relpath = svn_relpath_dirname(current_relpath, scratch_pool); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, parent_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + { + /* No row means no WORKING node which mean we just fell off + the WORKING tree, so CURRENT_RELPATH is the op-root + closest to the wc root. */ + if (have_base && base_del_relpath) + *base_del_relpath = apr_pstrdup(result_pool, current_relpath); + break; + } + + /* Still in the WORKING tree so the first time we get here + CURRENT_RELPATH is a delete op-root in the WORKING tree. */ + if (work_del_relpath && !*work_del_relpath) + { + *work_del_relpath = apr_pstrdup(result_pool, current_relpath); + + if (!scan && !base_del_relpath) + break; /* We have all we need */ + } + + current_relpath = parent_relpath; + op_depth = svn_sqlite__column_int(stmt, 2); + have_base = !svn_sqlite__column_is_null(stmt, 0); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_scan_deletion(const char **base_del_abspath, + const char **moved_to_abspath, + const char **work_del_abspath, + const char **moved_to_op_root_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *base_del_relpath, *moved_to_relpath, *work_del_relpath; + const char *moved_to_op_root_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + scan_deletion_txn(&base_del_relpath, &moved_to_relpath, + &work_del_relpath, &moved_to_op_root_relpath, + wcroot, local_relpath, result_pool, scratch_pool), + wcroot); + + if (base_del_abspath) + { + *base_del_abspath = (base_del_relpath + ? svn_dirent_join(wcroot->abspath, + base_del_relpath, result_pool) + : NULL); + } + if (moved_to_abspath) + { + *moved_to_abspath = (moved_to_relpath + ? svn_dirent_join(wcroot->abspath, + moved_to_relpath, result_pool) + : NULL); + } + if (work_del_abspath) + { + *work_del_abspath = (work_del_relpath + ? svn_dirent_join(wcroot->abspath, + work_del_relpath, result_pool) + : NULL); + } + if (moved_to_op_root_abspath) + { + *moved_to_op_root_abspath = (moved_to_op_root_relpath + ? svn_dirent_join(wcroot->abspath, + moved_to_op_root_relpath, + result_pool) + : NULL); + } + + return SVN_NO_ERROR; +} + + +/* Set *COPYFROM_ID, *COPYFROM_RELPATH, *COPYFROM_REV to the values + appropriate for the copy. Also return *STATUS, *KIND and *HAVE_WORK, *OP_ROOT + since they are available. This is a helper for + svn_wc__db_op_copy. */ +static svn_error_t * +get_info_for_copy(apr_int64_t *copyfrom_id, + const char **copyfrom_relpath, + svn_revnum_t *copyfrom_rev, + svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_boolean_t *op_root, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *repos_relpath; + svn_revnum_t revision; + svn_wc__db_status_t node_status; + apr_int64_t repos_id; + svn_boolean_t is_op_root; + + SVN_ERR(read_info(&node_status, kind, &revision, &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, copyfrom_relpath, + copyfrom_id, copyfrom_rev, NULL, NULL, NULL, NULL, + NULL, &is_op_root, NULL, NULL, + NULL /* have_base */, + NULL /* have_more_work */, + NULL /* have_work */, + wcroot, local_relpath, result_pool, scratch_pool)); + + if (op_root) + *op_root = is_op_root; + + if (node_status == svn_wc__db_status_excluded) + { + /* The parent cannot be excluded, so look at the parent and then + adjust the relpath */ + const char *parent_relpath, *base_name; + + svn_dirent_split(&parent_relpath, &base_name, local_relpath, + scratch_pool); + SVN_ERR(get_info_for_copy(copyfrom_id, copyfrom_relpath, copyfrom_rev, + NULL, NULL, NULL, + wcroot, parent_relpath, + scratch_pool, scratch_pool)); + if (*copyfrom_relpath) + *copyfrom_relpath = svn_relpath_join(*copyfrom_relpath, base_name, + result_pool); + } + else if (node_status == svn_wc__db_status_added) + { + SVN_ERR(scan_addition(&node_status, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, wcroot, local_relpath, + scratch_pool, scratch_pool)); + } + else if (node_status == svn_wc__db_status_deleted && is_op_root) + { + const char *base_del_relpath, *work_del_relpath; + + SVN_ERR(scan_deletion_txn(&base_del_relpath, NULL, + &work_del_relpath, + NULL, wcroot, local_relpath, + scratch_pool, scratch_pool)); + if (work_del_relpath) + { + const char *op_root_relpath; + const char *parent_del_relpath = svn_relpath_dirname(work_del_relpath, + scratch_pool); + + /* Similar to, but not the same as, the _scan_addition and + _join above. Can we use get_copyfrom here? */ + SVN_ERR(scan_addition(NULL, &op_root_relpath, + NULL, NULL, /* repos_* */ + copyfrom_relpath, copyfrom_id, copyfrom_rev, + NULL, NULL, NULL, wcroot, parent_del_relpath, + scratch_pool, scratch_pool)); + *copyfrom_relpath + = svn_relpath_join(*copyfrom_relpath, + svn_relpath_skip_ancestor(op_root_relpath, + local_relpath), + result_pool); + } + else if (base_del_relpath) + { + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, copyfrom_rev, + copyfrom_relpath, + copyfrom_id, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wcroot, local_relpath, + result_pool, + scratch_pool)); + } + else + SVN_ERR_MALFUNCTION(); + } + else if (node_status == svn_wc__db_status_deleted) + { + /* Keep original_* from read_info() to allow seeing the difference + between base-deleted and not present */ + } + else + { + *copyfrom_relpath = repos_relpath; + *copyfrom_rev = revision; + *copyfrom_id = repos_id; + } + + if (status) + *status = node_status; + + return SVN_NO_ERROR; +} + + +/* Set *OP_DEPTH to the highest op depth of WCROOT:LOCAL_RELPATH. */ +static svn_error_t * +op_depth_of(int *op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR_ASSERT(have_row); + *op_depth = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + + +/* Determine at which OP_DEPTH a copy of COPYFROM_REPOS_ID, COPYFROM_RELPATH at + revision COPYFROM_REVISION should be inserted as LOCAL_RELPATH. Do this + by checking if this would be a direct child of a copy of its parent + directory. If it is then set *OP_DEPTH to the op_depth of its parent. + + If the node is not a direct copy at the same revision of the parent + *NP_OP_DEPTH will be set to the op_depth of the parent when a not-present + node should be inserted at this op_depth. This will be the case when the + parent already defined an incomplete child with the same name. Otherwise + *NP_OP_DEPTH will be set to -1. + + If the parent node is not the parent of the to be copied node, then + *OP_DEPTH will be set to the proper op_depth for a new operation root. + + Set *PARENT_OP_DEPTH to the op_depth of the parent. + + */ +static svn_error_t * +op_depth_for_copy(int *op_depth, + int *np_op_depth, + int *parent_op_depth, + apr_int64_t copyfrom_repos_id, + const char *copyfrom_relpath, + svn_revnum_t copyfrom_revision, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + const char *parent_relpath, *name; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int incomplete_op_depth = -1; + int min_op_depth = 1; /* Never touch BASE */ + + *op_depth = relpath_depth(local_relpath); + *np_op_depth = -1; + + svn_relpath_split(&parent_relpath, &name, local_relpath, scratch_pool); + *parent_op_depth = relpath_depth(parent_relpath); + + if (!copyfrom_relpath) + return SVN_NO_ERROR; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + svn_wc__db_status_t status = svn_sqlite__column_token(stmt, 1, + presence_map); + + min_op_depth = svn_sqlite__column_int(stmt, 0); + if (status == svn_wc__db_status_incomplete) + incomplete_op_depth = min_op_depth; + } + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, parent_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + svn_wc__db_status_t presence = svn_sqlite__column_token(stmt, 1, + presence_map); + + *parent_op_depth = svn_sqlite__column_int(stmt, 0); + if (*parent_op_depth < min_op_depth) + { + /* We want to create a copy; not overwrite the lower layers */ + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + + /* You can only add children below a node that exists. + In WORKING that must be status added, which is represented + as presence normal */ + SVN_ERR_ASSERT(presence == svn_wc__db_status_normal); + + if ((incomplete_op_depth < 0) + || (incomplete_op_depth == *parent_op_depth)) + { + apr_int64_t parent_copyfrom_repos_id + = svn_sqlite__column_int64(stmt, 10); + const char *parent_copyfrom_relpath + = svn_sqlite__column_text(stmt, 11, NULL); + svn_revnum_t parent_copyfrom_revision + = svn_sqlite__column_revnum(stmt, 12); + + if (parent_copyfrom_repos_id == copyfrom_repos_id) + { + if (copyfrom_revision == parent_copyfrom_revision + && !strcmp(copyfrom_relpath, + svn_relpath_join(parent_copyfrom_relpath, name, + scratch_pool))) + *op_depth = *parent_op_depth; + else if (incomplete_op_depth > 0) + *np_op_depth = incomplete_op_depth; + } + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + + +/* Like svn_wc__db_op_copy(), but with WCROOT+LOCAL_RELPATH + * instead of DB+LOCAL_ABSPATH. A non-zero MOVE_OP_DEPTH implies that the + * copy operation is part of a move, and indicates the op-depth of the + * move destination op-root. */ +static svn_error_t * +db_op_copy(svn_wc__db_wcroot_t *src_wcroot, + const char *src_relpath, + svn_wc__db_wcroot_t *dst_wcroot, + const char *dst_relpath, + const svn_skel_t *work_items, + int move_op_depth, + apr_pool_t *scratch_pool) +{ + const char *copyfrom_relpath; + svn_revnum_t copyfrom_rev; + svn_wc__db_status_t status; + svn_wc__db_status_t dst_presence; + svn_boolean_t op_root; + apr_int64_t copyfrom_id; + int dst_op_depth; + int dst_np_op_depth; + int dst_parent_op_depth; + svn_node_kind_t kind; + const apr_array_header_t *children; + + SVN_ERR(get_info_for_copy(©from_id, ©from_relpath, ©from_rev, + &status, &kind, &op_root, src_wcroot, + src_relpath, scratch_pool, scratch_pool)); + + SVN_ERR(op_depth_for_copy(&dst_op_depth, &dst_np_op_depth, + &dst_parent_op_depth, + copyfrom_id, copyfrom_relpath, copyfrom_rev, + dst_wcroot, dst_relpath, scratch_pool)); + + SVN_ERR_ASSERT(kind == svn_node_file || kind == svn_node_dir); + + /* ### New status, not finished, see notes/wc-ng/copying */ + switch (status) + { + case svn_wc__db_status_normal: + case svn_wc__db_status_added: + case svn_wc__db_status_moved_here: + case svn_wc__db_status_copied: + dst_presence = svn_wc__db_status_normal; + break; + case svn_wc__db_status_deleted: + if (op_root) + { + /* If the lower layer is already shadowcopied we can skip adding + a not present node. */ + svn_error_t *err; + svn_wc__db_status_t dst_status; + + err = read_info(&dst_status, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + dst_wcroot, dst_relpath, scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + svn_error_clear(err); + else + return svn_error_trace(err); + } + else if (dst_status == svn_wc__db_status_deleted) + { + /* Node is already deleted; skip the NODES work, but do + install wq items if requested */ + SVN_ERR(add_work_items(dst_wcroot->sdb, work_items, + scratch_pool)); + return SVN_NO_ERROR; + } + } + else + { + /* This node is either a not-present node (which should be copied), or + a base-delete of some lower layer (which shouldn't). + Subversion <= 1.7 always added a not-present node here, which is + safe (as it postpones the hard work until commit time and then we + ask the repository), but it breaks some move scenarios. + */ + + if (! copyfrom_relpath) + { + SVN_ERR(add_work_items(dst_wcroot->sdb, work_items, + scratch_pool)); + return SVN_NO_ERROR; + } + + /* Fall through. Install not present node */ + } + case svn_wc__db_status_not_present: + case svn_wc__db_status_excluded: + /* These presence values should not create a new op depth */ + if (dst_np_op_depth > 0) + { + dst_op_depth = dst_np_op_depth; + dst_np_op_depth = -1; + } + if (status == svn_wc__db_status_excluded) + dst_presence = svn_wc__db_status_excluded; + else + dst_presence = svn_wc__db_status_not_present; + break; + case svn_wc__db_status_server_excluded: + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot copy '%s' excluded by server"), + path_for_error_message(src_wcroot, + src_relpath, + scratch_pool)); + default: + /* Perhaps we should allow incomplete to incomplete? We can't + avoid incomplete working nodes as one step in copying a + directory is to add incomplete children. */ + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot handle status of '%s'"), + path_for_error_message(src_wcroot, + src_relpath, + scratch_pool)); + } + + if (kind == svn_node_dir) + { + int src_op_depth; + + SVN_ERR(op_depth_of(&src_op_depth, src_wcroot, src_relpath)); + SVN_ERR(gather_repo_children(&children, src_wcroot, src_relpath, + src_op_depth, scratch_pool, scratch_pool)); + } + else + children = NULL; + + if (src_wcroot == dst_wcroot) + { + svn_sqlite__stmt_t *stmt; + const char *dst_parent_relpath = svn_relpath_dirname(dst_relpath, + scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, + STMT_INSERT_WORKING_NODE_COPY_FROM)); + + SVN_ERR(svn_sqlite__bindf(stmt, "issdst", + src_wcroot->wc_id, src_relpath, + dst_relpath, + dst_op_depth, + dst_parent_relpath, + presence_map, dst_presence)); + + if (move_op_depth > 0) + { + if (relpath_depth(dst_relpath) == move_op_depth) + { + /* We're moving the root of the move operation. + * + * When an added node or the op-root of a copy is moved, + * there is no 'moved-from' corresponding to the moved-here + * node. So the net effect is the same as copy+delete. + * Perform a normal copy operation in these cases. */ + if (!(status == svn_wc__db_status_added || + (status == svn_wc__db_status_copied && op_root))) + SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1)); + } + else + { + svn_sqlite__stmt_t *info_stmt; + svn_boolean_t have_row; + + /* We're moving a child along with the root of the move. + * + * Set moved-here depending on dst_parent, propagating the + * above decision to moved-along children at the same op_depth. + * We can't use scan_addition() to detect moved-here because + * the delete-half of the move might not yet exist. */ + SVN_ERR(svn_sqlite__get_statement(&info_stmt, dst_wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(info_stmt, "is", dst_wcroot->wc_id, + dst_parent_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, info_stmt)); + SVN_ERR_ASSERT(have_row); + if (svn_sqlite__column_boolean(info_stmt, 15) && + dst_op_depth == dst_parent_op_depth) + { + SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1)); + SVN_ERR(svn_sqlite__reset(info_stmt)); + } + else + { + SVN_ERR(svn_sqlite__reset(info_stmt)); + + /* If the child has been moved into the tree we're moving, + * keep its moved-here bit set. */ + SVN_ERR(svn_sqlite__get_statement(&info_stmt, + dst_wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(info_stmt, "is", + dst_wcroot->wc_id, src_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, info_stmt)); + SVN_ERR_ASSERT(have_row); + if (svn_sqlite__column_boolean(info_stmt, 15)) + SVN_ERR(svn_sqlite__bind_int(stmt, 7, 1)); + SVN_ERR(svn_sqlite__reset(info_stmt)); + } + } + } + + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* ### Copying changelist is OK for a move but what about a copy? */ + SVN_ERR(copy_actual(src_wcroot, src_relpath, + dst_wcroot, dst_relpath, scratch_pool)); + + if (dst_np_op_depth > 0) + { + /* We introduce a not-present node at the parent's op_depth to + properly start a new op-depth at our own op_depth. This marks + us as an op_root for commit and allows reverting just this + operation */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb, + STMT_INSERT_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdsisrtnt", + src_wcroot->wc_id, dst_relpath, + dst_np_op_depth, dst_parent_relpath, + copyfrom_id, copyfrom_relpath, + copyfrom_rev, + presence_map, + svn_wc__db_status_not_present, + /* NULL */ + kind_map, kind)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + /* Insert incomplete children, if relevant. + The children are part of the same op and so have the same op_depth. + (The only time we'd want a different depth is during a recursive + simple add, but we never insert children here during a simple add.) */ + if (kind == svn_node_dir + && dst_presence == svn_wc__db_status_normal) + SVN_ERR(insert_incomplete_children( + dst_wcroot->sdb, + dst_wcroot->wc_id, + dst_relpath, + copyfrom_id, + copyfrom_relpath, + copyfrom_rev, + children, + dst_op_depth, + scratch_pool)); + } + else + { + SVN_ERR(cross_db_copy(src_wcroot, src_relpath, dst_wcroot, + dst_relpath, dst_presence, dst_op_depth, + dst_np_op_depth, kind, + children, copyfrom_id, copyfrom_relpath, + copyfrom_rev, scratch_pool)); + } + + SVN_ERR(add_work_items(dst_wcroot->sdb, work_items, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Baton for passing args to op_copy_txn(). */ +struct op_copy_baton +{ + svn_wc__db_wcroot_t *src_wcroot; + const char *src_relpath; + + svn_wc__db_wcroot_t *dst_wcroot; + const char *dst_relpath; + + const svn_skel_t *work_items; + + svn_boolean_t is_move; + const char *dst_op_root_relpath; +}; + +/* Helper for svn_wc__db_op_copy(). + * + * Implements svn_sqlite__transaction_callback_t. */ +static svn_error_t * +op_copy_txn(void * baton, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + struct op_copy_baton *ocb = baton; + int move_op_depth; + + if (sdb != ocb->dst_wcroot->sdb) + { + /* Source and destination databases differ; so also start a lock + in the destination database, by calling ourself in a lock. */ + + return svn_error_trace( + svn_sqlite__with_lock(ocb->dst_wcroot->sdb, + op_copy_txn, ocb, scratch_pool)); + } + + /* From this point we can assume a lock in the src and dst databases */ + + if (ocb->is_move) + move_op_depth = relpath_depth(ocb->dst_op_root_relpath); + else + move_op_depth = 0; + + SVN_ERR(db_op_copy(ocb->src_wcroot, ocb->src_relpath, + ocb->dst_wcroot, ocb->dst_relpath, + ocb->work_items, move_op_depth, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_copy(svn_wc__db_t *db, + const char *src_abspath, + const char *dst_abspath, + const char *dst_op_root_abspath, + svn_boolean_t is_move, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + struct op_copy_baton ocb = {0}; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_op_root_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.src_wcroot, + &ocb.src_relpath, db, + src_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(ocb.src_wcroot); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.dst_wcroot, + &ocb.dst_relpath, + db, dst_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(ocb.dst_wcroot); + + ocb.work_items = work_items; + ocb.is_move = is_move; + ocb.dst_op_root_relpath = svn_dirent_skip_ancestor(ocb.dst_wcroot->abspath, + dst_op_root_abspath); + + /* Call with the sdb in src_wcroot. It might call itself again to + also obtain a lock in dst_wcroot */ + SVN_ERR(svn_sqlite__with_lock(ocb.src_wcroot->sdb, op_copy_txn, &ocb, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* The txn body of svn_wc__db_op_handle_move_back */ +static svn_error_t * +handle_move_back(svn_boolean_t *moved_back, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const char *moved_from_relpath, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_wc__db_status_t status; + svn_boolean_t op_root; + svn_boolean_t have_more_work; + int from_op_depth = 0; + svn_boolean_t have_row; + svn_boolean_t different = FALSE; + + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + + SVN_ERR(svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &op_root, NULL, NULL, NULL, + &have_more_work, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (status != svn_wc__db_status_added || !op_root) + return SVN_NO_ERROR; + + /* We have two cases here: BASE-move-back and WORKING-move-back */ + if (have_more_work) + SVN_ERR(op_depth_of(&from_op_depth, wcroot, + svn_relpath_dirname(local_relpath, scratch_pool))); + else + from_op_depth = 0; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_BACK)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isdd", wcroot->wc_id, + local_relpath, + from_op_depth, + relpath_depth(local_relpath))); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + SVN_ERR_ASSERT(have_row); /* We checked that the node is an op-root */ + + { + svn_boolean_t moved_here = svn_sqlite__column_boolean(stmt, 9); + const char *moved_to = svn_sqlite__column_text(stmt, 10, NULL); + + if (!moved_here + || !moved_to + || strcmp(moved_to, moved_from_relpath)) + { + different = TRUE; + have_row = FALSE; + } + } + + while (have_row) + { + svn_wc__db_status_t upper_status; + svn_wc__db_status_t lower_status; + + upper_status = svn_sqlite__column_token(stmt, 1, presence_map); + + if (svn_sqlite__column_is_null(stmt, 5)) + { + /* No lower layer replaced. */ + if (upper_status != svn_wc__db_status_not_present) + { + different = TRUE; + break; + } + continue; + } + + lower_status = svn_sqlite__column_token(stmt, 5, presence_map); + + if (upper_status != lower_status) + { + different = TRUE; + break; + } + + if (upper_status == svn_wc__db_status_not_present + || upper_status == svn_wc__db_status_excluded) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + continue; /* Nothing to check */ + } + else if (upper_status != svn_wc__db_status_normal) + { + /* Not a normal move. Mixed revision move? */ + different = TRUE; + break; + } + + { + const char *upper_repos_relpath; + const char *lower_repos_relpath; + + upper_repos_relpath = svn_sqlite__column_text(stmt, 3, NULL); + lower_repos_relpath = svn_sqlite__column_text(stmt, 7, NULL); + + if (! upper_repos_relpath + || strcmp(upper_repos_relpath, lower_repos_relpath)) + { + different = TRUE; + break; + } + } + + { + svn_revnum_t upper_rev; + svn_revnum_t lower_rev; + + upper_rev = svn_sqlite__column_revnum(stmt, 4); + lower_rev = svn_sqlite__column_revnum(stmt, 8); + + if (upper_rev != lower_rev) + { + different = TRUE; + break; + } + } + + { + apr_int64_t upper_repos_id; + apr_int64_t lower_repos_id; + + upper_repos_id = svn_sqlite__column_int64(stmt, 2); + lower_repos_id = svn_sqlite__column_int64(stmt, 6); + + if (upper_repos_id != lower_repos_id) + { + different = TRUE; + break; + } + } + + /* Check moved_here? */ + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + if (! different) + { + /* Ok, we can now safely remove this complete move, because we + determined that it 100% matches the layer below it. */ + + /* ### We could copy the recorded timestamps from the higher to the + lower layer in an attempt to improve status performance, but + generally these values should be the same anyway as it was + a no-op move. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_MOVED_BACK)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, + relpath_depth(local_relpath))); + + SVN_ERR(svn_sqlite__step_done(stmt)); + + if (moved_back) + *moved_back = TRUE; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_handle_move_back(svn_boolean_t *moved_back, + svn_wc__db_t *db, + const char *local_abspath, + const char *moved_from_abspath, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *moved_from_relpath; + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (moved_back) + *moved_back = FALSE; + + moved_from_relpath = svn_dirent_skip_ancestor(wcroot->abspath, + moved_from_abspath); + + if (! local_relpath[0] + || !moved_from_relpath) + { + /* WC-Roots can't be moved */ + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + return SVN_NO_ERROR; + } + + SVN_WC__DB_WITH_TXN(handle_move_back(moved_back, wcroot, local_relpath, + moved_from_relpath, work_items, + scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* The recursive implementation of svn_wc__db_op_copy_shadowed_layer. + * + * A non-zero MOVE_OP_DEPTH implies that the copy operation is part of + * a move, and indicates the op-depth of the move destination op-root. */ +static svn_error_t * +db_op_copy_shadowed_layer(svn_wc__db_wcroot_t *src_wcroot, + const char *src_relpath, + int src_op_depth, + svn_wc__db_wcroot_t *dst_wcroot, + const char *dst_relpath, + int dst_op_depth, + int del_op_depth, + apr_int64_t repos_id, + const char *repos_relpath, + svn_revnum_t revision, + int move_op_depth, + apr_pool_t *scratch_pool) +{ + const apr_array_header_t *children; + apr_pool_t *iterpool; + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_revnum_t node_revision; + const char *node_repos_relpath; + apr_int64_t node_repos_id; + svn_sqlite__stmt_t *stmt; + svn_wc__db_status_t dst_presence; + int i; + + { + svn_error_t *err; + err = svn_wc__db_depth_get_info(&status, &kind, &node_revision, + &node_repos_relpath, &node_repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, + src_wcroot, src_relpath, src_op_depth, + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); + return SVN_NO_ERROR; /* There is no shadowed node at src_op_depth */ + } + } + + if (src_op_depth == 0) + { + /* If the node is switched or has a different revision then its parent + we shouldn't copy it. (We can't as we would have to insert it at + an unshadowed depth) */ + if (status == svn_wc__db_status_not_present + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_server_excluded + || node_revision != revision + || node_repos_id != repos_id + || strcmp(node_repos_relpath, repos_relpath)) + { + /* Add a not-present node in the destination wcroot */ + struct insert_working_baton_t iwb; + const char *repos_root_url; + const char *repos_uuid; + + SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, &repos_uuid, + src_wcroot->sdb, node_repos_id, + scratch_pool)); + + SVN_ERR(create_repos_id(&node_repos_id, repos_root_url, repos_uuid, + dst_wcroot->sdb, scratch_pool)); + + blank_iwb(&iwb); + + iwb.op_depth = dst_op_depth; + if (status != svn_wc__db_status_excluded) + iwb.presence = svn_wc__db_status_not_present; + else + iwb.presence = svn_wc__db_status_excluded; + + iwb.kind = kind; + + iwb.original_repos_id = node_repos_id; + iwb.original_revnum = node_revision; + iwb.original_repos_relpath = node_repos_relpath; + + SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath, + scratch_pool)); + + return SVN_NO_ERROR; + } + } + + iterpool = svn_pool_create(scratch_pool); + + switch (status) + { + case svn_wc__db_status_normal: + case svn_wc__db_status_added: + case svn_wc__db_status_moved_here: + case svn_wc__db_status_copied: + dst_presence = svn_wc__db_status_normal; + break; + case svn_wc__db_status_deleted: + case svn_wc__db_status_not_present: + dst_presence = svn_wc__db_status_not_present; + break; + case svn_wc__db_status_excluded: + dst_presence = svn_wc__db_status_excluded; + break; + case svn_wc__db_status_server_excluded: + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot copy '%s' excluded by server"), + path_for_error_message(src_wcroot, + src_relpath, + scratch_pool)); + default: + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot handle status of '%s'"), + path_for_error_message(src_wcroot, + src_relpath, + scratch_pool)); + } + + if (dst_presence == svn_wc__db_status_normal + && src_wcroot == dst_wcroot) /* ### Remove limitation */ + { + SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, + STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH)); + + SVN_ERR(svn_sqlite__bindf(stmt, "issdstd", + src_wcroot->wc_id, src_relpath, + dst_relpath, + dst_op_depth, + svn_relpath_dirname(dst_relpath, iterpool), + presence_map, dst_presence, + src_op_depth)); + + /* moved_here */ + if (dst_op_depth == move_op_depth) + SVN_ERR(svn_sqlite__bind_int(stmt, 8, TRUE)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + + { + /* And mark it deleted to allow proper shadowing */ + struct insert_working_baton_t iwb; + + blank_iwb(&iwb); + + iwb.op_depth = del_op_depth; + iwb.presence = svn_wc__db_status_base_deleted; + + iwb.kind = kind; + + SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath, + scratch_pool)); + } + } + else + { + struct insert_working_baton_t iwb; + if (dst_presence == svn_wc__db_status_normal) /* Fallback for multi-db */ + dst_presence = svn_wc__db_status_not_present; + + /* And mark it deleted to allow proper shadowing */ + + blank_iwb(&iwb); + + iwb.op_depth = dst_op_depth; + iwb.presence = dst_presence; + iwb.kind = kind; + + SVN_ERR(insert_working_node(&iwb, dst_wcroot, dst_relpath, + scratch_pool)); + } + + SVN_ERR(gather_repo_children(&children, src_wcroot, src_relpath, + src_op_depth, scratch_pool, iterpool)); + + for (i = 0; i < children->nelts; i++) + { + const char *name = APR_ARRAY_IDX(children, i, const char *); + const char *child_src_relpath; + const char *child_dst_relpath; + const char *child_repos_relpath = NULL; + + svn_pool_clear(iterpool); + child_src_relpath = svn_relpath_join(src_relpath, name, iterpool); + child_dst_relpath = svn_relpath_join(dst_relpath, name, iterpool); + + if (repos_relpath) + child_repos_relpath = svn_relpath_join(repos_relpath, name, iterpool); + + SVN_ERR(db_op_copy_shadowed_layer( + src_wcroot, child_src_relpath, src_op_depth, + dst_wcroot, child_dst_relpath, dst_op_depth, + del_op_depth, + repos_id, child_repos_relpath, revision, + move_op_depth, scratch_pool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Helper for svn_wc__db_op_copy_shadowed_layer(). + * + * Implements svn_sqlite__transaction_callback_t. */ +static svn_error_t * +op_copy_shadowed_layer_txn(void *baton, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + struct op_copy_baton *ocb = baton; + const char *src_parent_relpath; + const char *dst_parent_relpath; + int src_op_depth; + int dst_op_depth; + int del_op_depth; + const char *repos_relpath = NULL; + apr_int64_t repos_id = INVALID_REPOS_ID; + svn_revnum_t revision = SVN_INVALID_REVNUM; + + if (sdb != ocb->dst_wcroot->sdb) + { + /* Source and destination databases differ; so also start a lock + in the destination database, by calling ourself in a lock. */ + + return svn_error_trace( + svn_sqlite__with_lock(ocb->dst_wcroot->sdb, + op_copy_shadowed_layer_txn, + ocb, scratch_pool)); + } + + /* From this point we can assume a lock in the src and dst databases */ + + + /* src_relpath and dst_relpath can't be wcroot as we need their parents */ + SVN_ERR_ASSERT(*ocb->src_relpath && *ocb->dst_relpath); + + src_parent_relpath = svn_relpath_dirname(ocb->src_relpath, scratch_pool); + dst_parent_relpath = svn_relpath_dirname(ocb->dst_relpath, scratch_pool); + + /* src_parent must be status normal or added; get its op-depth */ + SVN_ERR(op_depth_of(&src_op_depth, ocb->src_wcroot, src_parent_relpath)); + + /* dst_parent must be status added; get its op-depth */ + SVN_ERR(op_depth_of(&dst_op_depth, ocb->dst_wcroot, dst_parent_relpath)); + + del_op_depth = relpath_depth(ocb->dst_relpath); + + /* Get some information from the parent */ + SVN_ERR(svn_wc__db_depth_get_info(NULL, NULL, &revision, &repos_relpath, + &repos_id, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + ocb->src_wcroot, + src_parent_relpath, src_op_depth, + scratch_pool, scratch_pool)); + + if (repos_relpath == NULL) + { + /* The node is a local addition and has no shadowed information */ + return SVN_NO_ERROR; + } + + /* And calculate the child repos relpath */ + repos_relpath = svn_relpath_join(repos_relpath, + svn_relpath_basename(ocb->src_relpath, + NULL), + scratch_pool); + + SVN_ERR(db_op_copy_shadowed_layer( + ocb->src_wcroot, ocb->src_relpath, src_op_depth, + ocb->dst_wcroot, ocb->dst_relpath, dst_op_depth, + del_op_depth, + repos_id, repos_relpath, revision, + (ocb->is_move ? dst_op_depth : 0), + scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_copy_shadowed_layer(svn_wc__db_t *db, + const char *src_abspath, + const char *dst_abspath, + svn_boolean_t is_move, + apr_pool_t *scratch_pool) +{ + struct op_copy_baton ocb = {0}; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.src_wcroot, + &ocb.src_relpath, db, + src_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(ocb.src_wcroot); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&ocb.dst_wcroot, + &ocb.dst_relpath, + db, dst_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(ocb.dst_wcroot); + + ocb.is_move = is_move; + ocb.dst_op_root_relpath = NULL; /* not used by op_copy_shadowed_layer_txn */ + + ocb.work_items = NULL; + + /* Call with the sdb in src_wcroot. It might call itself again to + also obtain a lock in dst_wcroot */ + SVN_ERR(svn_sqlite__with_lock(ocb.src_wcroot->sdb, + op_copy_shadowed_layer_txn, + &ocb, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* If there are any server-excluded base nodes then the copy must fail + as it's not possible to commit such a copy. + Return an error if there are any server-excluded nodes. */ +static svn_error_t * +catch_copy_of_server_excluded(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + const char *server_excluded_relpath; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_HAS_SERVER_EXCLUDED_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + server_excluded_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + return svn_error_createf(SVN_ERR_AUTHZ_UNREADABLE, NULL, + _("Cannot copy '%s' excluded by server"), + path_for_error_message(wcroot, + server_excluded_relpath, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_copy_dir(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const char *original_repos_relpath, + const char *original_root_url, + const char *original_uuid, + svn_revnum_t original_revision, + const apr_array_header_t *children, + svn_boolean_t is_move, + svn_depth_t depth, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_working_baton_t iwb; + int parent_op_depth; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(props != NULL); + /* ### any assertions for CHANGED_* ? */ + /* ### any assertions for ORIGINAL_* ? */ +#if 0 + SVN_ERR_ASSERT(children != NULL); +#endif + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + blank_iwb(&iwb); + + iwb.presence = svn_wc__db_status_normal; + iwb.kind = svn_node_dir; + + iwb.props = props; + iwb.changed_rev = changed_rev; + iwb.changed_date = changed_date; + iwb.changed_author = changed_author; + + if (original_root_url != NULL) + { + SVN_ERR(create_repos_id(&iwb.original_repos_id, + original_root_url, original_uuid, + wcroot->sdb, scratch_pool)); + iwb.original_repos_relpath = original_repos_relpath; + iwb.original_revnum = original_revision; + } + + /* ### Should we do this inside the transaction? */ + SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth, + &parent_op_depth, iwb.original_repos_id, + original_repos_relpath, original_revision, + wcroot, local_relpath, scratch_pool)); + + iwb.children = children; + iwb.depth = depth; + iwb.moved_here = is_move && (parent_op_depth == 0 || + iwb.op_depth == parent_op_depth); + + iwb.work_items = work_items; + iwb.conflict = conflict; + + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); + SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_copy_file(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const char *original_repos_relpath, + const char *original_root_url, + const char *original_uuid, + svn_revnum_t original_revision, + const svn_checksum_t *checksum, + svn_boolean_t update_actual_props, + const apr_hash_t *new_actual_props, + svn_boolean_t is_move, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_working_baton_t iwb; + int parent_op_depth; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(props != NULL); + /* ### any assertions for CHANGED_* ? */ + SVN_ERR_ASSERT((! original_repos_relpath && ! original_root_url + && ! original_uuid && ! checksum + && original_revision == SVN_INVALID_REVNUM) + || (original_repos_relpath && original_root_url + && original_uuid && checksum + && original_revision != SVN_INVALID_REVNUM)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + blank_iwb(&iwb); + + iwb.presence = svn_wc__db_status_normal; + iwb.kind = svn_node_file; + + iwb.props = props; + iwb.changed_rev = changed_rev; + iwb.changed_date = changed_date; + iwb.changed_author = changed_author; + + if (original_root_url != NULL) + { + SVN_ERR(create_repos_id(&iwb.original_repos_id, + original_root_url, original_uuid, + wcroot->sdb, scratch_pool)); + iwb.original_repos_relpath = original_repos_relpath; + iwb.original_revnum = original_revision; + } + + /* ### Should we do this inside the transaction? */ + SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth, + &parent_op_depth, iwb.original_repos_id, + original_repos_relpath, original_revision, + wcroot, local_relpath, scratch_pool)); + + iwb.checksum = checksum; + iwb.moved_here = is_move && (parent_op_depth == 0 || + iwb.op_depth == parent_op_depth); + + if (update_actual_props) + { + iwb.update_actual_props = update_actual_props; + iwb.new_actual_props = new_actual_props; + } + + iwb.work_items = work_items; + iwb.conflict = conflict; + + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_copy_symlink(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const char *original_repos_relpath, + const char *original_root_url, + const char *original_uuid, + svn_revnum_t original_revision, + const char *target, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_working_baton_t iwb; + int parent_op_depth; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(props != NULL); + /* ### any assertions for CHANGED_* ? */ + /* ### any assertions for ORIGINAL_* ? */ + SVN_ERR_ASSERT(target != NULL); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + blank_iwb(&iwb); + + iwb.presence = svn_wc__db_status_normal; + iwb.kind = svn_node_symlink; + + iwb.props = props; + iwb.changed_rev = changed_rev; + iwb.changed_date = changed_date; + iwb.changed_author = changed_author; + iwb.moved_here = FALSE; + + if (original_root_url != NULL) + { + SVN_ERR(create_repos_id(&iwb.original_repos_id, + original_root_url, original_uuid, + wcroot->sdb, scratch_pool)); + iwb.original_repos_relpath = original_repos_relpath; + iwb.original_revnum = original_revision; + } + + /* ### Should we do this inside the transaction? */ + SVN_ERR(op_depth_for_copy(&iwb.op_depth, &iwb.not_present_op_depth, + &parent_op_depth, iwb.original_repos_id, + original_repos_relpath, original_revision, + wcroot, local_relpath, scratch_pool)); + + iwb.target = target; + + iwb.work_items = work_items; + iwb.conflict = conflict; + + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_add_directory(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *dir_abspath; + const char *name; + insert_working_baton_t iwb; + + /* Resolve wcroot via parent directory to avoid obstruction handling */ + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + blank_iwb(&iwb); + + local_relpath = svn_relpath_join(local_relpath, name, scratch_pool); + iwb.presence = svn_wc__db_status_normal; + iwb.kind = svn_node_dir; + iwb.op_depth = relpath_depth(local_relpath); + if (props && apr_hash_count((apr_hash_t *)props)) + { + iwb.update_actual_props = TRUE; + iwb.new_actual_props = props; + } + + iwb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); + /* Use depth infinity to make sure we have no invalid cached information + * about children of this dir. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_add_file(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_working_baton_t iwb; + const char *dir_abspath; + const char *name; + + /* Resolve wcroot via parent directory to avoid obstruction handling */ + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + blank_iwb(&iwb); + + local_relpath = svn_relpath_join(local_relpath, name, scratch_pool); + iwb.presence = svn_wc__db_status_normal; + iwb.kind = svn_node_file; + iwb.op_depth = relpath_depth(local_relpath); + if (props && apr_hash_count((apr_hash_t *)props)) + { + iwb.update_actual_props = TRUE; + iwb.new_actual_props = props; + } + + iwb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_add_symlink(svn_wc__db_t *db, + const char *local_abspath, + const char *target, + const apr_hash_t *props, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + insert_working_baton_t iwb; + const char *dir_abspath; + const char *name; + + /* Resolve wcroot via parent directory to avoid obstruction handling */ + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(target != NULL); + + svn_dirent_split(&dir_abspath, &name, local_abspath, scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + dir_abspath, scratch_pool, scratch_pool)); + + VERIFY_USABLE_WCROOT(wcroot); + + blank_iwb(&iwb); + + local_relpath = svn_relpath_join(local_relpath, name, scratch_pool); + iwb.presence = svn_wc__db_status_normal; + iwb.kind = svn_node_symlink; + iwb.op_depth = relpath_depth(local_relpath); + if (props && apr_hash_count((apr_hash_t *)props)) + { + iwb.update_actual_props = TRUE; + iwb.new_actual_props = props; + } + + iwb.target = target; + + iwb.work_items = work_items; + + SVN_WC__DB_WITH_TXN( + insert_working_node(&iwb, wcroot, local_relpath, scratch_pool), + wcroot); + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Record RECORDED_SIZE and RECORDED_TIME into top layer in NODES */ +static svn_error_t * +db_record_fileinfo(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_int64_t recorded_size, + apr_int64_t recorded_time, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int affected_rows; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_NODE_FILEINFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isii", wcroot->wc_id, local_relpath, + recorded_size, recorded_time)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + SVN_ERR_ASSERT(affected_rows == 1); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_global_record_fileinfo(svn_wc__db_t *db, + const char *local_abspath, + svn_filesize_t recorded_size, + apr_time_t recorded_time, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(db_record_fileinfo(wcroot, local_relpath, + recorded_size, recorded_time, scratch_pool)); + + /* We *totally* monkeyed the entries. Toss 'em. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* Set the ACTUAL_NODE properties column for (WC_ID, LOCAL_RELPATH) to + * PROPS. + * + * Note: PROPS=NULL means the actual props are the same as the pristine + * props; to indicate no properties when the pristine has some props, + * PROPS must be an empty hash. */ +static svn_error_t * +set_actual_props(apr_int64_t wc_id, + const char *local_relpath, + apr_hash_t *props, + svn_sqlite__db_t *db, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int affected_rows; + + SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_UPDATE_ACTUAL_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, scratch_pool)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + if (affected_rows == 1 || !props) + return SVN_NO_ERROR; /* We are done */ + + /* We have to insert a row in ACTUAL */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_ACTUAL_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); + if (*local_relpath != '\0') + SVN_ERR(svn_sqlite__bind_text(stmt, 3, + svn_relpath_dirname(local_relpath, + scratch_pool))); + SVN_ERR(svn_sqlite__bind_properties(stmt, 4, props, scratch_pool)); + return svn_error_trace(svn_sqlite__step_done(stmt)); +} + + +/* The body of svn_wc__db_op_set_props(). + + Set the 'properties' column in the 'ACTUAL_NODE' table to BATON->props. + Create an entry in the ACTUAL table for the node if it does not yet + have one. + To specify no properties, BATON->props must be an empty hash, not NULL. + BATON is of type 'struct set_props_baton_t'. +*/ +static svn_error_t * +set_props_txn(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_hash_t *props, + svn_boolean_t clear_recorded_info, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + apr_hash_t *pristine_props; + + /* Check if the props are modified. If no changes, then wipe out the + ACTUAL props. PRISTINE_PROPS==NULL means that any + ACTUAL props are okay as provided, so go ahead and set them. */ + SVN_ERR(db_read_pristine_props(&pristine_props, wcroot, local_relpath, FALSE, + scratch_pool, scratch_pool)); + if (props && pristine_props) + { + apr_array_header_t *prop_diffs; + + SVN_ERR(svn_prop_diffs(&prop_diffs, props, pristine_props, + scratch_pool)); + if (prop_diffs->nelts == 0) + props = NULL; + } + + SVN_ERR(set_actual_props(wcroot->wc_id, local_relpath, + props, wcroot->sdb, scratch_pool)); + + if (clear_recorded_info) + { + SVN_ERR(db_record_fileinfo(wcroot, local_relpath, + SVN_INVALID_FILESIZE, 0, + scratch_pool)); + } + + /* And finally. */ + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + if (conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_set_props(svn_wc__db_t *db, + const char *local_abspath, + apr_hash_t *props, + svn_boolean_t clear_recorded_info, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(set_props_txn(wcroot, local_relpath, props, + clear_recorded_info, conflict, work_items, + scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_modified(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + NOT_IMPLEMENTED(); +} + +/* */ +static svn_error_t * +populate_targets_tree(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_depth_t depth, + const apr_array_header_t *changelist_filter, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int affected_rows = 0; + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_TARGETS_LIST)); + + if (changelist_filter && changelist_filter->nelts > 0) + { + /* Iterate over the changelists, adding the nodes which match. + Common case: we only have one changelist, so this only + happens once. */ + int i; + int stmt_idx; + + switch (depth) + { + case svn_depth_empty: + stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST; + break; + + case svn_depth_files: + stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_FILES; + break; + + case svn_depth_immediates: + stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_IMMEDIATES; + break; + + case svn_depth_infinity: + stmt_idx = STMT_INSERT_TARGET_WITH_CHANGELIST_DEPTH_INFINITY; + break; + + default: + /* We don't know how to handle unknown or exclude. */ + SVN_ERR_MALFUNCTION(); + break; + } + + for (i = 0; i < changelist_filter->nelts; i++) + { + int sub_affected; + const char *changelist = APR_ARRAY_IDX(changelist_filter, i, + const char *); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_TARGET_WITH_CHANGELIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, + local_relpath, changelist)); + SVN_ERR(svn_sqlite__update(&sub_affected, stmt)); + + /* If the root is matched by the changelist, we don't have to match + the children. As that tells us the root is a file */ + if (!sub_affected && depth > svn_depth_empty) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, + local_relpath, changelist)); + SVN_ERR(svn_sqlite__update(&sub_affected, stmt)); + } + + affected_rows += sub_affected; + } + } + else /* No changelist filtering */ + { + int stmt_idx; + int sub_affected; + + switch (depth) + { + case svn_depth_empty: + stmt_idx = STMT_INSERT_TARGET; + break; + + case svn_depth_files: + stmt_idx = STMT_INSERT_TARGET_DEPTH_FILES; + break; + + case svn_depth_immediates: + stmt_idx = STMT_INSERT_TARGET_DEPTH_IMMEDIATES; + break; + + case svn_depth_infinity: + stmt_idx = STMT_INSERT_TARGET_DEPTH_INFINITY; + break; + + default: + /* We don't know how to handle unknown or exclude. */ + SVN_ERR_MALFUNCTION(); + break; + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_TARGET)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__update(&sub_affected, stmt)); + affected_rows += sub_affected; + + if (depth > svn_depth_empty) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__update(&sub_affected, stmt)); + affected_rows += sub_affected; + } + } + + /* Does the target exist? */ + if (affected_rows == 0) + { + svn_boolean_t exists; + SVN_ERR(does_node_exist(&exists, wcroot, local_relpath)); + + if (!exists) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +#if 0 +static svn_error_t * +dump_targets(svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_TARGETS)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *target = svn_sqlite__column_text(stmt, 0, NULL); + SVN_DBG(("Target: '%s'\n", target)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} +#endif + + +struct set_changelist_baton_t +{ + const char *new_changelist; + const apr_array_header_t *changelist_filter; + svn_depth_t depth; +}; + + +/* The main part of svn_wc__db_op_set_changelist(). + * + * Implements svn_wc__db_txn_callback_t. */ +static svn_error_t * +set_changelist_txn(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + struct set_changelist_baton_t *scb = baton; + svn_sqlite__stmt_t *stmt; + + SVN_ERR(populate_targets_tree(wcroot, local_relpath, scb->depth, + scb->changelist_filter, scratch_pool)); + + /* Ensure we have actual nodes for our targets. */ + if (scb->new_changelist) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_ACTUAL_EMPTIES)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Now create our notification table. */ + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_CHANGELIST_LIST)); + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_CHANGELIST_TRIGGER)); + + /* Update our changelists. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_ACTUAL_CHANGELISTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, + scb->new_changelist)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + if (scb->new_changelist) + { + /* We have to notify that we skipped directories, so do that now. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_MARK_SKIPPED_CHANGELIST_DIRS)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, + scb->new_changelist)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* We may have left empty ACTUAL nodes, so remove them. This is only a + potential problem if we removed changelists. */ + if (!scb->new_changelist) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_EMPTIES)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + return SVN_NO_ERROR; +} + + +/* Send notifications for svn_wc__db_op_set_changelist(). + * + * Implements work_callback_t. */ +static svn_error_t * +do_changelist_notify(void *baton, + svn_wc__db_wcroot_t *wcroot, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_CHANGELIST_LIST)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + /* ### wc_id is column 0. use it one day... */ + const char *notify_relpath = svn_sqlite__column_text(stmt, 1, NULL); + svn_wc_notify_action_t action = svn_sqlite__column_int(stmt, 2); + svn_wc_notify_t *notify; + const char *notify_abspath; + + svn_pool_clear(iterpool); + + if (cancel_func) + { + svn_error_t *err = cancel_func(cancel_baton); + + if (err) + return svn_error_trace(svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + } + + notify_abspath = svn_dirent_join(wcroot->abspath, notify_relpath, + iterpool); + notify = svn_wc_create_notify(notify_abspath, action, iterpool); + notify->changelist_name = svn_sqlite__column_text(stmt, 3, NULL); + notify_func(notify_baton, notify, iterpool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + + +svn_error_t * +svn_wc__db_op_set_changelist(svn_wc__db_t *db, + const char *local_abspath, + const char *new_changelist, + const apr_array_header_t *changelist_filter, + svn_depth_t depth, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + struct set_changelist_baton_t scb; + + scb.new_changelist = new_changelist; + scb.changelist_filter = changelist_filter; + scb.depth = depth; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* Flush the entries before we do the work. Even if no work is performed, + the flush isn't a problem. */ + SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool)); + + /* Perform the set-changelist operation (transactionally), perform any + notifications necessary, and then clean out our temporary tables. */ + return svn_error_trace(with_finalization(wcroot, local_relpath, + set_changelist_txn, &scb, + do_changelist_notify, NULL, + cancel_func, cancel_baton, + notify_func, notify_baton, + STMT_FINALIZE_CHANGELIST, + scratch_pool)); +} + +/* Implementation of svn_wc__db_op_mark_conflict() */ +svn_error_t * +svn_wc__db_mark_conflict_internal(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const svn_skel_t *conflict_skel, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t got_row; + svn_boolean_t is_complete; + + SVN_ERR(svn_wc__conflict_skel_is_complete(&is_complete, conflict_skel)); + SVN_ERR_ASSERT(is_complete); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (got_row) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_ACTUAL_CONFLICT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + } + else + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_ACTUAL_CONFLICT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + if (*local_relpath != '\0') + SVN_ERR(svn_sqlite__bind_text(stmt, 4, + svn_relpath_dirname(local_relpath, + scratch_pool))); + } + + { + svn_stringbuf_t *sb = svn_skel__unparse(conflict_skel, scratch_pool); + + SVN_ERR(svn_sqlite__bind_blob(stmt, 3, sb->data, sb->len)); + } + + SVN_ERR(svn_sqlite__update(NULL, stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_mark_conflict(svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflict_skel, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict_skel, scratch_pool)); + + /* ### Should be handled in the same transaction as setting the conflict */ + if (work_items) + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; + +} + +/* The body of svn_wc__db_op_mark_resolved(). + */ +static svn_error_t * +db_op_mark_resolved(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + svn_boolean_t resolved_text, + svn_boolean_t resolved_props, + svn_boolean_t resolved_tree, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int total_affected_rows = 0; + svn_boolean_t resolved_all; + apr_size_t conflict_len; + const void *conflict_data; + svn_skel_t *conflicts; + + /* Check if we have a conflict in ACTUAL */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (! have_row) + { + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (have_row) + return SVN_NO_ERROR; + + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + conflict_data = svn_sqlite__column_blob(stmt, 2, &conflict_len, + scratch_pool); + conflicts = svn_skel__parse(conflict_data, conflict_len, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_wc__conflict_skel_resolve(&resolved_all, conflicts, + db, wcroot->abspath, + resolved_text, + resolved_props ? "" : NULL, + resolved_tree, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_ACTUAL_CONFLICT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + if (! resolved_all) + { + svn_stringbuf_t *sb = svn_skel__unparse(conflicts, scratch_pool); + + SVN_ERR(svn_sqlite__bind_blob(stmt, 3, sb->data, sb->len)); + } + + SVN_ERR(svn_sqlite__update(&total_affected_rows, stmt)); + + /* Now, remove the actual node if it doesn't have any more useful + information. We only need to do this if we've remove data ourselves. */ + if (total_affected_rows > 0) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_EMPTY)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_mark_resolved(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t resolved_text, + svn_boolean_t resolved_props, + svn_boolean_t resolved_tree, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + db_op_mark_resolved(wcroot, local_relpath, db, + resolved_text, resolved_props, resolved_tree, + work_items, scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Clear moved-to information at the delete-half of the move which + * moved LOCAL_RELPATH here. This transforms the move into a simple delete. */ +static svn_error_t * +clear_moved_to(const char *local_relpath, + svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + const char *moved_from_relpath; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_FROM_RELPATH)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + { + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + + moved_from_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_TO_RELPATH)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + moved_from_relpath, + relpath_depth(moved_from_relpath))); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +/* One of the two alternative bodies of svn_wc__db_op_revert(). + * + * Implements svn_wc__db_txn_callback_t. */ +static svn_error_t * +op_revert_txn(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_t *db = baton; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int op_depth; + svn_boolean_t moved_here; + int affected_rows; + const char *moved_to; + + /* ### Similar structure to op_revert_recursive_txn, should they be + combined? */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + { + SVN_ERR(svn_sqlite__reset(stmt)); + + /* There was no NODE row, so attempt to delete an ACTUAL_NODE row. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + if (affected_rows) + { + /* Can't do non-recursive actual-only revert if actual-only + children exist. Raise an error to cancel the transaction. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_ACTUAL_HAS_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + return svn_error_createf(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, + _("Can't revert '%s' without" + " reverting children"), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + return SVN_NO_ERROR; + } + + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + op_depth = svn_sqlite__column_int(stmt, 0); + moved_here = svn_sqlite__column_boolean(stmt, 15); + moved_to = svn_sqlite__column_text(stmt, 17, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (moved_to) + { + SVN_ERR(svn_wc__db_resolve_break_moved_away_internal(wcroot, + local_relpath, + scratch_pool)); + } + else + { + svn_skel_t *conflict; + + SVN_ERR(svn_wc__db_read_conflict_internal(&conflict, wcroot, + local_relpath, + scratch_pool, scratch_pool)); + if (conflict) + { + svn_wc_operation_t operation; + svn_boolean_t tree_conflicted; + + SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, NULL, + &tree_conflicted, + db, wcroot->abspath, + conflict, + scratch_pool, scratch_pool)); + if (tree_conflicted + && (operation == svn_wc_operation_update + || operation == svn_wc_operation_switch)) + { + svn_wc_conflict_reason_t reason; + svn_wc_conflict_action_t action; + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, + NULL, + db, wcroot->abspath, + conflict, + scratch_pool, + scratch_pool)); + + if (reason == svn_wc_conflict_reason_deleted) + SVN_ERR(svn_wc__db_resolve_delete_raise_moved_away( + db, svn_dirent_join(wcroot->abspath, local_relpath, + scratch_pool), + NULL, NULL /* ### How do we notify this? */, + scratch_pool)); + } + } + } + + if (op_depth > 0 && op_depth == relpath_depth(local_relpath)) + { + /* Can't do non-recursive revert if children exist */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_GE_OP_DEPTH_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + return svn_error_createf(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, + _("Can't revert '%s' without" + " reverting children"), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + + /* Rewrite the op-depth of all deleted children making the + direct children into roots of deletes. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_OP_DEPTH_INCREASE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* ### This removes the lock, but what about the access baton? */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WC_LOCK_ORPHAN)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* If this node was moved-here, clear moved-to at the move source. */ + if (moved_here) + SVN_ERR(clear_moved_to(local_relpath, wcroot, scratch_pool)); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + if (!affected_rows) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + } + + return SVN_NO_ERROR; +} + + +/* One of the two alternative bodies of svn_wc__db_op_revert(). + * + * Implements svn_wc__db_txn_callback_t. */ +static svn_error_t * +op_revert_recursive_txn(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int op_depth; + int select_op_depth; + svn_boolean_t moved_here; + int affected_rows; + apr_pool_t *iterpool; + + /* ### Similar structure to op_revert_txn, should they be + combined? */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + { + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + if (affected_rows) + return SVN_NO_ERROR; /* actual-only revert */ + + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + op_depth = svn_sqlite__column_int(stmt, 0); + moved_here = svn_sqlite__column_boolean(stmt, 15); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (op_depth > 0 && op_depth != relpath_depth(local_relpath)) + return svn_error_createf(SVN_ERR_WC_INVALID_OPERATION_DEPTH, NULL, + _("Can't revert '%s' without" + " reverting parent"), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + + /* Remove moved-here from move destinations outside the tree. */ + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, STMT_SELECT_MOVED_OUTSIDE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *move_src_relpath = svn_sqlite__column_text(stmt, 0, NULL); + svn_error_t *err; + + err = svn_wc__db_resolve_break_moved_away_internal(wcroot, + move_src_relpath, + scratch_pool); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Don't delete BASE nodes */ + select_op_depth = op_depth ? op_depth : 1; + + /* Reverting any non wc-root node */ + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, select_op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* ### This removes the locks, but what about the access batons? */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_HERE_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + const char *moved_here_child_relpath; + svn_error_t *err; + + svn_pool_clear(iterpool); + + moved_here_child_relpath = svn_sqlite__column_text(stmt, 0, iterpool); + err = clear_moved_to(moved_here_child_relpath, wcroot, iterpool); + if (err) + return svn_error_trace(svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + svn_pool_destroy(iterpool); + + /* Clear potential moved-to pointing at the target node itself. */ + if (op_depth > 0 && op_depth == relpath_depth(local_relpath) + && moved_here) + SVN_ERR(clear_moved_to(local_relpath, wcroot, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_revert(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + struct with_triggers_baton_t wtb = { STMT_CREATE_REVERT_LIST, + STMT_DROP_REVERT_LIST_TRIGGERS, + NULL, NULL}; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + switch (depth) + { + case svn_depth_empty: + wtb.cb_func = op_revert_txn; + wtb.cb_baton = db; + break; + case svn_depth_infinity: + wtb.cb_func = op_revert_recursive_txn; + break; + default: + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Unsupported depth for revert of '%s'"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(with_triggers(&wtb, wcroot, local_relpath, scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, depth, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_revert_list_read(). + */ +static svn_error_t * +revert_list_read(svn_boolean_t *reverted, + const apr_array_header_t **marker_paths, + svn_boolean_t *copied_here, + svn_node_kind_t *kind, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + *reverted = FALSE; + *marker_paths = NULL; + *copied_here = FALSE; + *kind = svn_node_unknown; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_REVERT_LIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + svn_boolean_t is_actual = svn_sqlite__column_boolean(stmt, 0); + svn_boolean_t another_row = FALSE; + + if (is_actual) + { + apr_size_t conflict_len; + const void *conflict_data; + + conflict_data = svn_sqlite__column_blob(stmt, 5, &conflict_len, + scratch_pool); + if (conflict_data) + { + svn_skel_t *conflicts = svn_skel__parse(conflict_data, + conflict_len, + scratch_pool); + + SVN_ERR(svn_wc__conflict_read_markers(marker_paths, + db, wcroot->abspath, + conflicts, + result_pool, + scratch_pool)); + } + + if (!svn_sqlite__column_is_null(stmt, 1)) /* notify */ + *reverted = TRUE; + + SVN_ERR(svn_sqlite__step(&another_row, stmt)); + } + + if (!is_actual || another_row) + { + *reverted = TRUE; + if (!svn_sqlite__column_is_null(stmt, 4)) /* repos_id */ + { + int op_depth = svn_sqlite__column_int(stmt, 3); + *copied_here = (op_depth == relpath_depth(local_relpath)); + } + *kind = svn_sqlite__column_token(stmt, 2, kind_map); + } + + } + SVN_ERR(svn_sqlite__reset(stmt)); + + if (have_row) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_REVERT_LIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_revert_list_read(svn_boolean_t *reverted, + const apr_array_header_t **marker_files, + svn_boolean_t *copied_here, + svn_node_kind_t *kind, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + revert_list_read(reverted, marker_files, copied_here, kind, + wcroot, local_relpath, db, + result_pool, scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_revert_list_read_copied_children(). + */ +static svn_error_t * +revert_list_read_copied_children(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const apr_array_header_t **children_p, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_array_header_t *children; + + children = + apr_array_make(result_pool, 0, + sizeof(svn_wc__db_revert_list_copied_child_info_t *)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_REVERT_LIST_COPIED_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "sd", + local_relpath, relpath_depth(local_relpath))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + svn_wc__db_revert_list_copied_child_info_t *child_info; + const char *child_relpath; + + child_info = apr_palloc(result_pool, sizeof(*child_info)); + + child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + child_info->abspath = svn_dirent_join(wcroot->abspath, child_relpath, + result_pool); + child_info->kind = svn_sqlite__column_token(stmt, 1, kind_map); + APR_ARRAY_PUSH( + children, + svn_wc__db_revert_list_copied_child_info_t *) = child_info; + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + *children_p = children; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_revert_list_read_copied_children(const apr_array_header_t **children, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + revert_list_read_copied_children(wcroot, local_relpath, children, + result_pool, scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_revert_list_notify(svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, scratch_pool, iterpool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_REVERT_LIST_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + return svn_error_trace(svn_sqlite__reset(stmt)); /* optimise for no row */ + while (have_row) + { + const char *notify_relpath = svn_sqlite__column_text(stmt, 0, NULL); + + svn_pool_clear(iterpool); + + notify_func(notify_baton, + svn_wc_create_notify(svn_dirent_join(wcroot->abspath, + notify_relpath, + iterpool), + svn_wc_notify_revert, + iterpool), + iterpool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_REVERT_LIST_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_revert_list_done(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_DROP_REVERT_LIST)); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_op_remove_node(). + */ +static svn_error_t * +remove_node_txn(svn_boolean_t *left_changes, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + svn_boolean_t destroy_wc, + svn_boolean_t destroy_changes, + svn_revnum_t not_present_rev, + svn_wc__db_status_t not_present_status, + svn_node_kind_t not_present_kind, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + apr_int64_t repos_id; + const char *repos_relpath; + + /* Note that unlike many similar functions it is a valid scenario for this + function to be called on a wcroot! */ + + /* db set when destroying wc */ + SVN_ERR_ASSERT(!destroy_wc || db != NULL); + + if (left_changes) + *left_changes = FALSE; + + /* Need info for not_present node? */ + if (SVN_IS_VALID_REVNUM(not_present_rev)) + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (destroy_wc + && (!destroy_changes || *local_relpath == '\0')) + { + svn_boolean_t have_row; + apr_pool_t *iterpool; + svn_error_t *err = NULL; + + /* Install WQ items for deleting the unmodified files and all dirs */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_PRESENT)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + + while (have_row) + { + const char *child_relpath; + const char *child_abspath; + svn_node_kind_t child_kind; + svn_boolean_t have_checksum; + svn_filesize_t recorded_size; + apr_int64_t recorded_time; + const svn_io_dirent2_t *dirent; + svn_boolean_t modified_p = TRUE; + svn_skel_t *work_item = NULL; + + svn_pool_clear(iterpool); + + child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + child_kind = svn_sqlite__column_token(stmt, 1, kind_map); + + child_abspath = svn_dirent_join(wcroot->abspath, child_relpath, + iterpool); + + if (child_kind == svn_node_file) + { + have_checksum = !svn_sqlite__column_is_null(stmt, 2); + recorded_size = get_recorded_size(stmt, 3); + recorded_time = svn_sqlite__column_int64(stmt, 4); + } + + if (cancel_func) + err = cancel_func(cancel_baton); + + if (err) + break; + + err = svn_io_stat_dirent2(&dirent, child_abspath, FALSE, TRUE, + iterpool, iterpool); + + if (err) + break; + + if (destroy_changes + || dirent->kind != svn_node_file + || child_kind != svn_node_file) + { + /* Not interested in keeping changes */ + modified_p = FALSE; + } + else if (child_kind == svn_node_file + && dirent->kind == svn_node_file + && dirent->filesize == recorded_size + && dirent->mtime == recorded_time) + { + modified_p = FALSE; /* File matches recorded state */ + } + else if (have_checksum) + err = svn_wc__internal_file_modified_p(&modified_p, + db, child_abspath, + FALSE, iterpool); + + if (err) + break; + + if (modified_p) + { + if (left_changes) + *left_changes = TRUE; + } + else if (child_kind == svn_node_dir) + { + err = svn_wc__wq_build_dir_remove(&work_item, + db, wcroot->abspath, + child_abspath, FALSE, + iterpool, iterpool); + } + else /* svn_node_file || svn_node_symlink */ + { + err = svn_wc__wq_build_file_remove(&work_item, + db, wcroot->abspath, + child_abspath, + iterpool, iterpool); + } + + if (err) + break; + + if (work_item) + { + err = add_work_items(wcroot->sdb, work_item, iterpool); + if (err) + break; + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + } + + if (destroy_wc && *local_relpath != '\0') + { + /* Create work item for destroying the root */ + svn_wc__db_status_t status; + svn_node_kind_t kind; + SVN_ERR(read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (status == svn_wc__db_status_normal + || status == svn_wc__db_status_added + || status == svn_wc__db_status_incomplete) + { + svn_skel_t *work_item = NULL; + const char *local_abspath = svn_dirent_join(wcroot->abspath, + local_relpath, + scratch_pool); + + if (kind == svn_node_dir) + { + SVN_ERR(svn_wc__wq_build_dir_remove(&work_item, + db, wcroot->abspath, + local_abspath, + destroy_changes + /* recursive */, + scratch_pool, scratch_pool)); + } + else + { + svn_boolean_t modified_p = FALSE; + + if (!destroy_changes) + { + SVN_ERR(svn_wc__internal_file_modified_p(&modified_p, + db, local_abspath, + FALSE, + scratch_pool)); + } + + if (!modified_p) + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, + db, wcroot->abspath, + local_abspath, + scratch_pool, + scratch_pool)); + else + { + if (left_changes) + *left_changes = TRUE; + } + } + + SVN_ERR(add_work_items(wcroot->sdb, work_item, scratch_pool)); + } + } + + /* Remove all nodes below local_relpath */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_NODE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* Delete the root NODE when this is not the working copy root */ + if (local_relpath[0] != '\0') + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE_RECURSIVE)); + + /* Delete all actual nodes at or below local_relpath */ + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* Should we leave a not-present node? */ + if (SVN_IS_VALID_REVNUM(not_present_rev)) + { + insert_base_baton_t ibb; + blank_ibb(&ibb); + + ibb.repos_id = repos_id; + + SVN_ERR_ASSERT(not_present_status == svn_wc__db_status_not_present + || not_present_status == svn_wc__db_status_excluded); + + ibb.status = not_present_status; + ibb.kind = not_present_kind; + + ibb.repos_relpath = repos_relpath; + ibb.revision = not_present_rev; + + SVN_ERR(insert_base_node(&ibb, wcroot, local_relpath, scratch_pool)); + } + + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + if (conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_remove_node(svn_boolean_t *left_changes, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t destroy_wc, + svn_boolean_t destroy_changes, + svn_revnum_t not_present_revision, + svn_wc__db_status_t not_present_status, + svn_node_kind_t not_present_kind, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(remove_node_txn(left_changes, + wcroot, local_relpath, db, + destroy_wc, destroy_changes, + not_present_revision, not_present_status, + not_present_kind, conflict, work_items, + cancel_func, cancel_baton, scratch_pool), + wcroot); + + /* Flush everything below this node in all ways */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_op_set_base_depth(). + */ +static svn_error_t * +db_op_set_base_depth(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int affected_rows; + + /* Flush any entries before we start monkeying the database. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_NODE_BASE_DEPTH)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, + svn_token__to_word(depth_map, depth))); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + if (affected_rows == 0) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + "The node '%s' is not a committed directory", + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_set_base_depth(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(depth >= svn_depth_empty && depth <= svn_depth_infinity); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* ### We set depth on working and base to match entry behavior. + Maybe these should be separated later? */ + SVN_WC__DB_WITH_TXN(db_op_set_base_depth(wcroot, local_relpath, depth, + scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +info_below_working(svn_boolean_t *have_base, + svn_boolean_t *have_work, + svn_wc__db_status_t *status, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int below_op_depth, /* < 0 is ignored */ + apr_pool_t *scratch_pool); + + +/* Convert STATUS, the raw status obtained from the presence map, to + the status appropriate for a working (op_depth > 0) node and return + it in *WORKING_STATUS. */ +static svn_error_t * +convert_to_working_status(svn_wc__db_status_t *working_status, + svn_wc__db_status_t status) +{ + svn_wc__db_status_t work_status = status; + + SVN_ERR_ASSERT(work_status == svn_wc__db_status_normal + || work_status == svn_wc__db_status_not_present + || work_status == svn_wc__db_status_base_deleted + || work_status == svn_wc__db_status_incomplete + || work_status == svn_wc__db_status_excluded); + + if (work_status == svn_wc__db_status_excluded) + { + *working_status = svn_wc__db_status_excluded; + } + else if (work_status == svn_wc__db_status_not_present + || work_status == svn_wc__db_status_base_deleted) + { + /* The caller should scan upwards to detect whether this + deletion has occurred because this node has been moved + away, or it is a regular deletion. Also note that the + deletion could be of the BASE tree, or a child of + something that has been copied/moved here. */ + + *working_status = svn_wc__db_status_deleted; + } + else /* normal or incomplete */ + { + /* The caller should scan upwards to detect whether this + addition has occurred because of a simple addition, + a copy, or is the destination of a move. */ + *working_status = svn_wc__db_status_added; + } + + return SVN_NO_ERROR; +} + + +/* Return the status of the node, if any, below the "working" node (or + below BELOW_OP_DEPTH if >= 0). + Set *HAVE_BASE or *HAVE_WORK to indicate if a base node or lower + working node is present, and *STATUS to the status of the first + layer below the selected node. */ +static svn_error_t * +info_below_working(svn_boolean_t *have_base, + svn_boolean_t *have_work, + svn_wc__db_status_t *status, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int below_op_depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + *have_base = *have_work = FALSE; + *status = svn_wc__db_status_normal; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (below_op_depth >= 0) + { + while (have_row && + (svn_sqlite__column_int(stmt, 0) > below_op_depth)) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + } + if (have_row) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + *status = svn_sqlite__column_token(stmt, 3, presence_map); + + while (have_row) + { + int op_depth = svn_sqlite__column_int(stmt, 0); + + if (op_depth > 0) + *have_work = TRUE; + else + *have_base = TRUE; + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + if (*have_work) + SVN_ERR(convert_to_working_status(status, *status)); + + return SVN_NO_ERROR; +} + +/* Helper function for op_delete_txn */ +static svn_error_t * +delete_update_movedto(svn_wc__db_wcroot_t *wcroot, + const char *child_moved_from_relpath, + int op_depth, + const char *new_moved_to_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int affected; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_MOVED_TO_RELPATH)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isds", + wcroot->wc_id, + child_moved_from_relpath, + op_depth, + new_moved_to_relpath)); + SVN_ERR(svn_sqlite__update(&affected, stmt)); + assert(affected == 1); + + return SVN_NO_ERROR; +} + + +struct op_delete_baton_t { + const char *moved_to_relpath; /* NULL if delete is not part of a move */ + svn_skel_t *conflict; + svn_skel_t *work_items; + svn_boolean_t delete_dir_externals; + svn_boolean_t notify; +}; + +/* This structure is used while rewriting move information for nodes. + * + * The most simple case of rewriting move information happens when + * a moved-away subtree is moved again: mv A B; mv B C + * The second move requires rewriting moved-to info at or within A. + * + * Another example is a move of a subtree which had nodes moved into it: + * mv A B/F; mv B G + * This requires rewriting such that A/F is marked has having moved to G/F. + * + * Another case is where a node becomes a nested moved node. + * A nested move happens when a subtree child is moved before or after + * the subtree itself is moved. For example: + * mv A/F A/G; mv A B + * In this case, the move A/F -> A/G is rewritten to B/F -> B/G. + * Note that the following sequence results in the same DB state: + * mv A B; mv B/F B/G + * We do not care about the order the moves were performed in. + * For details, see http://wiki.apache.org/subversion/MultiLayerMoves + */ +struct moved_node_t { + /* The source of the move. */ + const char *local_relpath; + + /* The move destination. */ + const char *moved_to_relpath; + + /* The op-depth of the deleted node at the source of the move. */ + int op_depth; +}; + +static svn_error_t * +delete_node(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + struct op_delete_baton_t *b = baton; + svn_wc__db_status_t status; + svn_boolean_t have_row, op_root; + svn_boolean_t add_work = FALSE; + svn_sqlite__stmt_t *stmt; + int select_depth; /* Depth of what is to be deleted */ + svn_boolean_t refetch_depth = FALSE; + svn_node_kind_t kind; + apr_array_header_t *moved_nodes = NULL; + int delete_depth = relpath_depth(local_relpath); + + SVN_ERR(read_info(&status, + &kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &op_root, NULL, NULL, + NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (status == svn_wc__db_status_deleted + || status == svn_wc__db_status_not_present) + return SVN_NO_ERROR; + + /* Don't copy BASE directories with server excluded nodes */ + if (status == svn_wc__db_status_normal && kind == svn_node_dir) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_HAS_SERVER_EXCLUDED_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + const char *absent_path = svn_sqlite__column_text(stmt, 0, + scratch_pool); + + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + svn_sqlite__reset(stmt), + _("Cannot delete '%s' as '%s' is excluded by server"), + path_for_error_message(wcroot, local_relpath, + scratch_pool), + path_for_error_message(wcroot, absent_path, + scratch_pool)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + } + else if (status == svn_wc__db_status_server_excluded) + { + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot delete '%s' as it is excluded by server"), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + else if (status == svn_wc__db_status_excluded) + { + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Cannot delete '%s' as it is excluded"), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + + if (b->moved_to_relpath) + { + const char *moved_from_relpath = NULL; + struct moved_node_t *moved_node; + int move_op_depth; + + moved_nodes = apr_array_make(scratch_pool, 1, + sizeof(struct moved_node_t *)); + + /* The node is being moved-away. + * Figure out if the node was moved-here before, or whether this + * is the first time the node is moved. */ + if (status == svn_wc__db_status_added) + SVN_ERR(scan_addition(&status, NULL, NULL, NULL, NULL, NULL, NULL, + &moved_from_relpath, + NULL, + &move_op_depth, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (op_root && moved_from_relpath) + { + const char *part = svn_relpath_skip_ancestor(local_relpath, + moved_from_relpath); + + /* Existing move-root is moved to another location */ + moved_node = apr_palloc(scratch_pool, sizeof(struct moved_node_t)); + if (!part) + moved_node->local_relpath = moved_from_relpath; + else + moved_node->local_relpath = svn_relpath_join(b->moved_to_relpath, + part, scratch_pool); + moved_node->op_depth = move_op_depth; + moved_node->moved_to_relpath = b->moved_to_relpath; + + APR_ARRAY_PUSH(moved_nodes, const struct moved_node_t *) = moved_node; + } + else if (!op_root && (status == svn_wc__db_status_normal + || status == svn_wc__db_status_copied + || status == svn_wc__db_status_moved_here)) + { + /* The node is becoming a move-root for the first time, + * possibly because of a nested move operation. */ + moved_node = apr_palloc(scratch_pool, sizeof(struct moved_node_t)); + moved_node->local_relpath = local_relpath; + moved_node->op_depth = delete_depth; + moved_node->moved_to_relpath = b->moved_to_relpath; + + APR_ARRAY_PUSH(moved_nodes, const struct moved_node_t *) = moved_node; + } + /* Else: We can't track history of local additions and/or of things we are + about to delete. */ + + /* And update all moved_to values still pointing to this location */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_MOVED_TO_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, + local_relpath, + b->moved_to_relpath)); + SVN_ERR(svn_sqlite__update(NULL, stmt)); + } + else + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_TO_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__update(NULL, stmt)); + } + + /* Find children that were moved out of the subtree rooted at this node. + * We'll need to update their op-depth columns because their deletion + * is now implied by the deletion of their parent (i.e. this node). */ + { + apr_pool_t *iterpool; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_FOR_DELETE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + struct moved_node_t *mn; + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *mv_to_relpath = svn_sqlite__column_text(stmt, 1, NULL); + int child_op_depth = svn_sqlite__column_int(stmt, 2); + svn_boolean_t fixup = FALSE; + + if (!b->moved_to_relpath + && ! svn_relpath_skip_ancestor(local_relpath, mv_to_relpath)) + { + /* Update the op-depth of an moved node below this tree */ + fixup = TRUE; + child_op_depth = delete_depth; + } + else if (b->moved_to_relpath + && delete_depth == child_op_depth) + { + /* Update the op-depth of a tree shadowed by this tree */ + fixup = TRUE; + child_op_depth = delete_depth; + } + else if (b->moved_to_relpath + && child_op_depth >= delete_depth + && !svn_relpath_skip_ancestor(local_relpath, mv_to_relpath)) + { + /* Update the move destination of something that is now moved + away further */ + + child_relpath = svn_relpath_skip_ancestor(local_relpath, child_relpath); + + if (child_relpath) + { + child_relpath = svn_relpath_join(b->moved_to_relpath, child_relpath, scratch_pool); + + if (child_op_depth > delete_depth + && svn_relpath_skip_ancestor(local_relpath, child_relpath)) + child_op_depth = delete_depth; + else + child_op_depth = relpath_depth(child_relpath); + + fixup = TRUE; + } + } + + if (fixup) + { + mn = apr_pcalloc(scratch_pool, sizeof(struct moved_node_t)); + + mn->local_relpath = apr_pstrdup(scratch_pool, child_relpath); + mn->moved_to_relpath = apr_pstrdup(scratch_pool, mv_to_relpath); + mn->op_depth = child_op_depth; + + if (!moved_nodes) + moved_nodes = apr_array_make(scratch_pool, 1, + sizeof(struct moved_node_t *)); + APR_ARRAY_PUSH(moved_nodes, struct moved_node_t *) = mn; + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + SVN_ERR(svn_sqlite__reset(stmt)); + } + + if (op_root) + { + svn_boolean_t below_base; + svn_boolean_t below_work; + svn_wc__db_status_t below_status; + + /* Use STMT_SELECT_NODE_INFO directly instead of read_info plus + info_below_working */ + SVN_ERR(info_below_working(&below_base, &below_work, &below_status, + wcroot, local_relpath, -1, scratch_pool)); + if ((below_base || below_work) + && below_status != svn_wc__db_status_not_present + && below_status != svn_wc__db_status_deleted) + { + add_work = TRUE; + refetch_depth = TRUE; + } + + select_depth = relpath_depth(local_relpath); + } + else + { + add_work = TRUE; + if (status != svn_wc__db_status_normal) + SVN_ERR(op_depth_of(&select_depth, wcroot, local_relpath)); + else + select_depth = 0; /* Deleting BASE node */ + } + + /* ### Put actual-only nodes into the list? */ + if (b->notify) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_DELETE_LIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wcroot->wc_id, local_relpath, select_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_NODES_ABOVE_DEPTH_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wcroot->wc_id, local_relpath, delete_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + if (refetch_depth) + SVN_ERR(op_depth_of(&select_depth, wcroot, local_relpath)); + + /* Delete ACTUAL_NODE rows, but leave those that have changelist + and a NODES row. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_ACTUAL_NODE_LEAVING_CHANGELIST_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WC_LOCK_ORPHAN_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + if (add_work) + { + /* Delete the node at LOCAL_RELPATH, and possibly mark it as moved. */ + + /* Delete the node and possible descendants. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_DELETE_FROM_NODE_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdd", + wcroot->wc_id, local_relpath, + select_depth, delete_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + if (moved_nodes) + { + int i; + + for (i = 0; i < moved_nodes->nelts; ++i) + { + const struct moved_node_t *moved_node + = APR_ARRAY_IDX(moved_nodes, i, void *); + + SVN_ERR(delete_update_movedto(wcroot, + moved_node->local_relpath, + moved_node->op_depth, + moved_node->moved_to_relpath, + scratch_pool)); + } + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_FILE_EXTERNALS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + b->delete_dir_externals + ? STMT_DELETE_EXTERNAL_REGISTATIONS + : STMT_DELETE_FILE_EXTERNAL_REGISTATIONS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + SVN_ERR(add_work_items(wcroot->sdb, b->work_items, scratch_pool)); + if (b->conflict) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + b->conflict, scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +op_delete_txn(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_CREATE_DELETE_LIST)); + SVN_ERR(delete_node(baton, wcroot, local_relpath, scratch_pool)); + return SVN_NO_ERROR; +} + + +struct op_delete_many_baton_t { + apr_array_header_t *rel_targets; + svn_boolean_t delete_dir_externals; + const svn_skel_t *work_items; +} op_delete_many_baton_t; + +static svn_error_t * +op_delete_many_txn(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + struct op_delete_many_baton_t *odmb = baton; + struct op_delete_baton_t odb; + int i; + apr_pool_t *iterpool; + + odb.moved_to_relpath = NULL; + odb.conflict = NULL; + odb.work_items = NULL; + odb.delete_dir_externals = odmb->delete_dir_externals; + odb.notify = TRUE; + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_CREATE_DELETE_LIST)); + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < odmb->rel_targets->nelts; i++) + { + const char *target_relpath = APR_ARRAY_IDX(odmb->rel_targets, i, + const char *); + + + svn_pool_clear(iterpool); + SVN_ERR(delete_node(&odb, wcroot, target_relpath, iterpool)); + } + svn_pool_destroy(iterpool); + + SVN_ERR(add_work_items(wcroot->sdb, odmb->work_items, scratch_pool)); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +do_delete_notify(void *baton, + svn_wc__db_wcroot_t *wcroot, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_DELETE_LIST)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + const char *notify_relpath; + const char *notify_abspath; + + svn_pool_clear(iterpool); + + notify_relpath = svn_sqlite__column_text(stmt, 0, NULL); + notify_abspath = svn_dirent_join(wcroot->abspath, + notify_relpath, + iterpool); + + notify_func(notify_baton, + svn_wc_create_notify(notify_abspath, + svn_wc_notify_delete, + iterpool), + iterpool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + + SVN_ERR(svn_sqlite__reset(stmt)); + + /* We only allow cancellation after notification for all deleted nodes + * has happened. The nodes are already deleted so we should notify for + * all of them. */ + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_delete(svn_wc__db_t *db, + const char *local_abspath, + const char *moved_to_abspath, + svn_boolean_t delete_dir_externals, + svn_skel_t *conflict, + svn_skel_t *work_items, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + svn_wc__db_wcroot_t *moved_to_wcroot; + const char *local_relpath; + const char *moved_to_relpath; + struct op_delete_baton_t odb; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (moved_to_abspath) + { + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&moved_to_wcroot, + &moved_to_relpath, + db, moved_to_abspath, + scratch_pool, + scratch_pool)); + VERIFY_USABLE_WCROOT(moved_to_wcroot); + + if (strcmp(wcroot->abspath, moved_to_wcroot->abspath) != 0) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Cannot move '%s' to '%s' because they " + "are not in the same working copy"), + svn_dirent_local_style(local_abspath, + scratch_pool), + svn_dirent_local_style(moved_to_abspath, + scratch_pool)); + } + else + moved_to_relpath = NULL; + + odb.moved_to_relpath = moved_to_relpath; + odb.conflict = conflict; + odb.work_items = work_items; + odb.delete_dir_externals = delete_dir_externals; + + if (notify_func) + { + /* Perform the deletion operation (transactionally), perform any + notifications necessary, and then clean out our temporary tables. */ + odb.notify = TRUE; + SVN_ERR(with_finalization(wcroot, local_relpath, + op_delete_txn, &odb, + do_delete_notify, NULL, + cancel_func, cancel_baton, + notify_func, notify_baton, + STMT_FINALIZE_DELETE, + scratch_pool)); + } + else + { + /* Avoid the trigger work */ + odb.notify = FALSE; + SVN_WC__DB_WITH_TXN( + delete_node(&odb, wcroot, local_relpath, scratch_pool), + wcroot); + } + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_infinity, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_delete_many(svn_wc__db_t *db, + apr_array_header_t *targets, + svn_boolean_t delete_dir_externals, + const svn_skel_t *work_items, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + struct op_delete_many_baton_t odmb; + int i; + apr_pool_t *iterpool; + + odmb.rel_targets = apr_array_make(scratch_pool, targets->nelts, + sizeof(const char *)); + odmb.work_items = work_items; + odmb.delete_dir_externals = delete_dir_externals; + iterpool = svn_pool_create(scratch_pool); + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, + APR_ARRAY_IDX(targets, 0, + const char *), + scratch_pool, iterpool)); + VERIFY_USABLE_WCROOT(wcroot); + for (i = 0; i < targets->nelts; i++) + { + const char *local_abspath = APR_ARRAY_IDX(targets, i, const char*); + svn_wc__db_wcroot_t *target_wcroot; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&target_wcroot, + &local_relpath, db, + APR_ARRAY_IDX(targets, i, + const char *), + scratch_pool, iterpool)); + VERIFY_USABLE_WCROOT(target_wcroot); + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* Assert that all targets are within the same working copy. */ + SVN_ERR_ASSERT(wcroot->wc_id == target_wcroot->wc_id); + + APR_ARRAY_PUSH(odmb.rel_targets, const char *) = local_relpath; + SVN_ERR(flush_entries(target_wcroot, local_abspath, svn_depth_infinity, + iterpool)); + + } + svn_pool_destroy(iterpool); + + /* Perform the deletion operation (transactionally), perform any + notifications necessary, and then clean out our temporary tables. */ + return svn_error_trace(with_finalization(wcroot, wcroot->abspath, + op_delete_many_txn, &odmb, + do_delete_notify, NULL, + cancel_func, cancel_baton, + notify_func, notify_baton, + STMT_FINALIZE_DELETE, + scratch_pool)); +} + + +/* Like svn_wc__db_read_info(), but taking WCROOT+LOCAL_RELPATH instead of + DB+LOCAL_ABSPATH, and outputting repos ids instead of URL+UUID. */ +static svn_error_t * +read_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + const char **original_repos_relpath, + apr_int64_t *original_repos_id, + svn_revnum_t *original_revision, + svn_wc__db_lock_t **lock, + svn_filesize_t *recorded_size, + apr_time_t *recorded_time, + const char **changelist, + svn_boolean_t *conflicted, + svn_boolean_t *op_root, + svn_boolean_t *had_props, + svn_boolean_t *props_mod, + svn_boolean_t *have_base, + svn_boolean_t *have_more_work, + svn_boolean_t *have_work, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt_info; + svn_sqlite__stmt_t *stmt_act; + svn_boolean_t have_info; + svn_boolean_t have_act; + svn_error_t *err = NULL; + + /* Obtain the most likely to exist record first, to make sure we don't + have to obtain the SQLite read-lock multiple times */ + SVN_ERR(svn_sqlite__get_statement(&stmt_info, wcroot->sdb, + lock ? STMT_SELECT_NODE_INFO_WITH_LOCK + : STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt_info, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_info, stmt_info)); + + if (changelist || conflicted || props_mod) + { + SVN_ERR(svn_sqlite__get_statement(&stmt_act, wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt_act, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_act, stmt_act)); + } + else + { + have_act = FALSE; + stmt_act = NULL; + } + + if (have_info) + { + int op_depth; + svn_node_kind_t node_kind; + + op_depth = svn_sqlite__column_int(stmt_info, 0); + node_kind = svn_sqlite__column_token(stmt_info, 4, kind_map); + + if (status) + { + *status = svn_sqlite__column_token(stmt_info, 3, presence_map); + + if (op_depth != 0) /* WORKING */ + err = svn_error_compose_create(err, + convert_to_working_status(status, + *status)); + } + if (kind) + { + *kind = node_kind; + } + if (op_depth != 0) + { + if (repos_id) + *repos_id = INVALID_REPOS_ID; + if (revision) + *revision = SVN_INVALID_REVNUM; + if (repos_relpath) + /* Our path is implied by our parent somewhere up the tree. + With the NULL value and status, the caller will know to + search up the tree for the base of our path. */ + *repos_relpath = NULL; + } + else + { + /* Fetch repository information. If we have a + WORKING_NODE (and have been added), then the repository + we're being added to will be dependent upon a parent. The + caller can scan upwards to locate the repository. */ + repos_location_from_columns(repos_id, revision, repos_relpath, + stmt_info, 1, 5, 2, result_pool); + } + if (changed_rev) + { + *changed_rev = svn_sqlite__column_revnum(stmt_info, 8); + } + if (changed_date) + { + *changed_date = svn_sqlite__column_int64(stmt_info, 9); + } + if (changed_author) + { + *changed_author = svn_sqlite__column_text(stmt_info, 10, + result_pool); + } + if (recorded_time) + { + *recorded_time = svn_sqlite__column_int64(stmt_info, 13); + } + if (depth) + { + if (node_kind != svn_node_dir) + { + *depth = svn_depth_unknown; + } + else + { + *depth = svn_sqlite__column_token_null(stmt_info, 11, depth_map, + svn_depth_unknown); + } + } + if (checksum) + { + if (node_kind != svn_node_file) + { + *checksum = NULL; + } + else + { + + err = svn_error_compose_create( + err, svn_sqlite__column_checksum(checksum, stmt_info, 6, + result_pool)); + } + } + if (recorded_size) + { + *recorded_size = get_recorded_size(stmt_info, 7); + } + if (target) + { + if (node_kind != svn_node_symlink) + *target = NULL; + else + *target = svn_sqlite__column_text(stmt_info, 12, result_pool); + } + if (changelist) + { + if (have_act) + *changelist = svn_sqlite__column_text(stmt_act, 0, result_pool); + else + *changelist = NULL; + } + if (op_depth == 0) + { + if (original_repos_id) + *original_repos_id = INVALID_REPOS_ID; + if (original_revision) + *original_revision = SVN_INVALID_REVNUM; + if (original_repos_relpath) + *original_repos_relpath = NULL; + } + else + { + repos_location_from_columns(original_repos_id, + original_revision, + original_repos_relpath, + stmt_info, 1, 5, 2, result_pool); + } + if (props_mod) + { + *props_mod = have_act && !svn_sqlite__column_is_null(stmt_act, 1); + } + if (had_props) + { + *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt_info, 14); + } + if (conflicted) + { + if (have_act) + { + *conflicted = + !svn_sqlite__column_is_null(stmt_act, 2); /* conflict_data */ + } + else + *conflicted = FALSE; + } + + if (lock) + { + if (op_depth != 0) + *lock = NULL; + else + *lock = lock_from_columns(stmt_info, 17, 18, 19, 20, result_pool); + } + + if (have_work) + *have_work = (op_depth != 0); + + if (op_root) + { + *op_root = ((op_depth > 0) + && (op_depth == relpath_depth(local_relpath))); + } + + if (have_base || have_more_work) + { + if (have_more_work) + *have_more_work = FALSE; + + while (!err && op_depth != 0) + { + err = svn_sqlite__step(&have_info, stmt_info); + + if (err || !have_info) + break; + + op_depth = svn_sqlite__column_int(stmt_info, 0); + + if (have_more_work) + { + if (op_depth > 0) + *have_more_work = TRUE; + + if (!have_base) + break; + } + } + + if (have_base) + *have_base = (op_depth == 0); + } + } + else if (have_act) + { + /* A row in ACTUAL_NODE should never exist without a corresponding + node in BASE_NODE and/or WORKING_NODE unless it flags a tree conflict. */ + if (svn_sqlite__column_is_null(stmt_act, 2)) /* conflict_data */ + err = svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Corrupt data for '%s'"), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + /* ### What should we return? Should we have a separate + function for reading actual-only nodes? */ + + /* As a safety measure, until we decide if we want to use + read_info for actual-only nodes, make sure the caller asked + for the conflict status. */ + SVN_ERR_ASSERT(conflicted); + + if (status) + *status = svn_wc__db_status_normal; /* What! No it's not! */ + if (kind) + *kind = svn_node_unknown; + if (revision) + *revision = SVN_INVALID_REVNUM; + if (repos_relpath) + *repos_relpath = NULL; + if (repos_id) + *repos_id = INVALID_REPOS_ID; + if (changed_rev) + *changed_rev = SVN_INVALID_REVNUM; + if (changed_date) + *changed_date = 0; + if (depth) + *depth = svn_depth_unknown; + if (checksum) + *checksum = NULL; + if (target) + *target = NULL; + if (original_repos_relpath) + *original_repos_relpath = NULL; + if (original_repos_id) + *original_repos_id = INVALID_REPOS_ID; + if (original_revision) + *original_revision = SVN_INVALID_REVNUM; + if (lock) + *lock = NULL; + if (recorded_size) + *recorded_size = 0; + if (recorded_time) + *recorded_time = 0; + if (changelist) + *changelist = svn_sqlite__column_text(stmt_act, 0, result_pool); + if (op_root) + *op_root = FALSE; + if (had_props) + *had_props = FALSE; + if (props_mod) + *props_mod = FALSE; + if (conflicted) + *conflicted = TRUE; + if (have_base) + *have_base = FALSE; + if (have_more_work) + *have_more_work = FALSE; + if (have_work) + *have_work = FALSE; + } + else + { + err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + + if (stmt_act != NULL) + err = svn_error_compose_create(err, svn_sqlite__reset(stmt_act)); + + if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + err = svn_error_quick_wrap(err, + apr_psprintf(scratch_pool, + "Error reading node '%s'", + local_relpath)); + + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt_info))); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_read_info_internal(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + const char **original_repos_relpath, + apr_int64_t *original_repos_id, + svn_revnum_t *original_revision, + svn_wc__db_lock_t **lock, + svn_filesize_t *recorded_size, + apr_time_t *recorded_time, + const char **changelist, + svn_boolean_t *conflicted, + svn_boolean_t *op_root, + svn_boolean_t *had_props, + svn_boolean_t *props_mod, + svn_boolean_t *have_base, + svn_boolean_t *have_more_work, + svn_boolean_t *have_work, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_error_trace( + read_info(status, kind, revision, repos_relpath, repos_id, + changed_rev, changed_date, changed_author, + depth, checksum, target, original_repos_relpath, + original_repos_id, original_revision, lock, + recorded_size, recorded_time, changelist, conflicted, + op_root, had_props, props_mod, + have_base, have_more_work, have_work, + wcroot, local_relpath, result_pool, scratch_pool)); +} + + +svn_error_t * +svn_wc__db_read_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + const char **original_repos_relpath, + const char **original_root_url, + const char **original_uuid, + svn_revnum_t *original_revision, + svn_wc__db_lock_t **lock, + svn_filesize_t *recorded_size, + apr_time_t *recorded_time, + const char **changelist, + svn_boolean_t *conflicted, + svn_boolean_t *op_root, + svn_boolean_t *have_props, + svn_boolean_t *props_mod, + svn_boolean_t *have_base, + svn_boolean_t *have_more_work, + svn_boolean_t *have_work, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + apr_int64_t repos_id, original_repos_id; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(read_info(status, kind, revision, repos_relpath, &repos_id, + changed_rev, changed_date, changed_author, + depth, checksum, target, original_repos_relpath, + &original_repos_id, original_revision, lock, + recorded_size, recorded_time, changelist, conflicted, + op_root, have_props, props_mod, + have_base, have_more_work, have_work, + wcroot, local_relpath, result_pool, scratch_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, + wcroot->sdb, repos_id, result_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(original_root_url, original_uuid, + wcroot->sdb, original_repos_id, + result_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +is_wclocked(svn_boolean_t *locked, + svn_wc__db_wcroot_t *wcroot, + const char *dir_relpath, + apr_pool_t *scratch_pool); + +/* What we really want to store about a node. This relies on the + offset of svn_wc__db_info_t being zero. */ +struct read_children_info_item_t +{ + struct svn_wc__db_info_t info; + int op_depth; + int nr_layers; +}; + +static svn_error_t * +read_children_info(svn_wc__db_wcroot_t *wcroot, + const char *dir_relpath, + apr_hash_t *conflicts, + apr_hash_t *nodes, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + const char *repos_root_url = NULL; + const char *repos_uuid = NULL; + apr_int64_t last_repos_id = INVALID_REPOS_ID; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_CHILDREN_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, dir_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + /* CHILD item points to what we have about the node. We only provide + CHILD->item to our caller. */ + struct read_children_info_item_t *child_item; + const char *child_relpath = svn_sqlite__column_text(stmt, 19, NULL); + const char *name = svn_relpath_basename(child_relpath, NULL); + svn_error_t *err; + int op_depth; + svn_boolean_t new_child; + + child_item = svn_hash_gets(nodes, name); + if (child_item) + new_child = FALSE; + else + { + child_item = apr_pcalloc(result_pool, sizeof(*child_item)); + new_child = TRUE; + } + + op_depth = svn_sqlite__column_int(stmt, 0); + + /* Do we have new or better information? */ + if (new_child || op_depth > child_item->op_depth) + { + struct svn_wc__db_info_t *child = &child_item->info; + child_item->op_depth = op_depth; + + child->kind = svn_sqlite__column_token(stmt, 4, kind_map); + + child->status = svn_sqlite__column_token(stmt, 3, presence_map); + if (op_depth != 0) + { + if (child->status == svn_wc__db_status_incomplete) + child->incomplete = TRUE; + err = convert_to_working_status(&child->status, child->status); + if (err) + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + } + + if (op_depth != 0) + child->revnum = SVN_INVALID_REVNUM; + else + child->revnum = svn_sqlite__column_revnum(stmt, 5); + + if (op_depth != 0) + child->repos_relpath = NULL; + else + child->repos_relpath = svn_sqlite__column_text(stmt, 2, + result_pool); + + if (op_depth != 0 || svn_sqlite__column_is_null(stmt, 1)) + { + child->repos_root_url = NULL; + child->repos_uuid = NULL; + } + else + { + const char *last_repos_root_url = NULL; + + apr_int64_t repos_id = svn_sqlite__column_int64(stmt, 1); + if (!repos_root_url || + (last_repos_id != INVALID_REPOS_ID && + repos_id != last_repos_id)) + { + last_repos_root_url = repos_root_url; + err = svn_wc__db_fetch_repos_info(&repos_root_url, + &repos_uuid, + wcroot->sdb, repos_id, + result_pool); + if (err) + SVN_ERR(svn_error_compose_create(err, + svn_sqlite__reset(stmt))); + } + + if (last_repos_id == INVALID_REPOS_ID) + last_repos_id = repos_id; + + /* Assume working copy is all one repos_id so that a + single cached value is sufficient. */ + if (repos_id != last_repos_id) + { + err= svn_error_createf( + SVN_ERR_WC_DB_ERROR, NULL, + _("The node '%s' comes from unexpected repository " + "'%s', expected '%s'; if this node is a file " + "external using the correct URL in the external " + "definition can fix the problem, see issue #4087"), + child_relpath, repos_root_url, last_repos_root_url); + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + } + child->repos_root_url = repos_root_url; + child->repos_uuid = repos_uuid; + } + + child->changed_rev = svn_sqlite__column_revnum(stmt, 8); + + child->changed_date = svn_sqlite__column_int64(stmt, 9); + + child->changed_author = svn_sqlite__column_text(stmt, 10, + result_pool); + + if (child->kind != svn_node_dir) + child->depth = svn_depth_unknown; + else + { + child->depth = svn_sqlite__column_token_null(stmt, 11, depth_map, + svn_depth_unknown); + if (new_child) + SVN_ERR(is_wclocked(&child->locked, wcroot, child_relpath, + scratch_pool)); + } + + child->recorded_time = svn_sqlite__column_int64(stmt, 13); + child->recorded_size = get_recorded_size(stmt, 7); + child->has_checksum = !svn_sqlite__column_is_null(stmt, 6); + child->copied = op_depth > 0 && !svn_sqlite__column_is_null(stmt, 2); + child->had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 14); +#ifdef HAVE_SYMLINK + if (child->had_props) + { + apr_hash_t *properties; + err = svn_sqlite__column_properties(&properties, stmt, 14, + scratch_pool, scratch_pool); + if (err) + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + + child->special = (child->had_props + && svn_hash_gets(properties, SVN_PROP_SPECIAL)); + } +#endif + if (op_depth == 0) + child->op_root = FALSE; + else + child->op_root = (op_depth == relpath_depth(child_relpath)); + + svn_hash_sets(nodes, apr_pstrdup(result_pool, name), child); + } + + if (op_depth == 0) + { + child_item->info.have_base = TRUE; + + /* Get the lock info, available only at op_depth 0. */ + child_item->info.lock = lock_from_columns(stmt, 15, 16, 17, 18, + result_pool); + + /* FILE_EXTERNAL flag only on op_depth 0. */ + child_item->info.file_external = svn_sqlite__column_boolean(stmt, + 22); + } + else + { + const char *moved_to_relpath; + + child_item->nr_layers++; + child_item->info.have_more_work = (child_item->nr_layers > 1); + + /* Moved-to can only exist at op_depth > 0. */ + /* ### Should we really do this for every layer where op_depth > 0 + in undefined order? */ + moved_to_relpath = svn_sqlite__column_text(stmt, 21, NULL); + if (moved_to_relpath) + child_item->info.moved_to_abspath = + svn_dirent_join(wcroot->abspath, moved_to_relpath, result_pool); + + /* Moved-here can only exist at op_depth > 0. */ + /* ### Should we really do this for every layer where op_depth > 0 + in undefined order? */ + child_item->info.moved_here = svn_sqlite__column_boolean(stmt, 20); + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_CHILDREN_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, dir_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + struct read_children_info_item_t *child_item; + struct svn_wc__db_info_t *child; + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *name = svn_relpath_basename(child_relpath, NULL); + + child_item = svn_hash_gets(nodes, name); + if (!child_item) + { + child_item = apr_pcalloc(result_pool, sizeof(*child_item)); + child_item->info.status = svn_wc__db_status_not_present; + } + + child = &child_item->info; + + child->changelist = svn_sqlite__column_text(stmt, 1, result_pool); + + child->props_mod = !svn_sqlite__column_is_null(stmt, 2); +#ifdef HAVE_SYMLINK + if (child->props_mod) + { + svn_error_t *err; + apr_hash_t *properties; + + err = svn_sqlite__column_properties(&properties, stmt, 2, + scratch_pool, scratch_pool); + if (err) + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + child->special = (NULL != svn_hash_gets(properties, + SVN_PROP_SPECIAL)); + } +#endif + + child->conflicted = !svn_sqlite__column_is_null(stmt, 3); /* conflict */ + + if (child->conflicted) + svn_hash_sets(conflicts, apr_pstrdup(result_pool, name), ""); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_children_info(apr_hash_t **nodes, + apr_hash_t **conflicts, + svn_wc__db_t *db, + const char *dir_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *dir_relpath; + + *conflicts = apr_hash_make(result_pool); + *nodes = apr_hash_make(result_pool); + SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &dir_relpath, db, + dir_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + read_children_info(wcroot, dir_relpath, *conflicts, *nodes, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_pristine_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, /* dirs only */ + const svn_checksum_t **checksum, /* files only */ + const char **target, /* symlinks only */ + svn_boolean_t *had_props, + apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_error_t *err = NULL; + int op_depth; + svn_wc__db_status_t raw_status; + svn_node_kind_t node_kind; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* Obtain the most likely to exist record first, to make sure we don't + have to obtain the SQLite read-lock multiple times */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + op_depth = svn_sqlite__column_int(stmt, 0); + raw_status = svn_sqlite__column_token(stmt, 3, presence_map); + + if (op_depth > 0 && raw_status == svn_wc__db_status_base_deleted) + { + SVN_ERR(svn_sqlite__step_row(stmt)); + + op_depth = svn_sqlite__column_int(stmt, 0); + raw_status = svn_sqlite__column_token(stmt, 3, presence_map); + } + + node_kind = svn_sqlite__column_token(stmt, 4, kind_map); + + if (status) + { + if (op_depth > 0) + { + err = svn_error_compose_create(err, + convert_to_working_status( + status, + raw_status)); + } + else + *status = raw_status; + } + if (kind) + { + *kind = node_kind; + } + if (changed_rev) + { + *changed_rev = svn_sqlite__column_revnum(stmt, 8); + } + if (changed_date) + { + *changed_date = svn_sqlite__column_int64(stmt, 9); + } + if (changed_author) + { + *changed_author = svn_sqlite__column_text(stmt, 10, + result_pool); + } + if (depth) + { + if (node_kind != svn_node_dir) + { + *depth = svn_depth_unknown; + } + else + { + *depth = svn_sqlite__column_token_null(stmt, 11, depth_map, + svn_depth_unknown); + } + } + if (checksum) + { + if (node_kind != svn_node_file) + { + *checksum = NULL; + } + else + { + svn_error_t *err2; + err2 = svn_sqlite__column_checksum(checksum, stmt, 6, result_pool); + + if (err2 != NULL) + { + if (err) + err = svn_error_compose_create( + err, + svn_error_createf( + err->apr_err, err2, + _("The node '%s' has a corrupt checksum value."), + path_for_error_message(wcroot, local_relpath, + scratch_pool))); + else + err = err2; + } + } + } + if (target) + { + if (node_kind != svn_node_symlink) + *target = NULL; + else + *target = svn_sqlite__column_text(stmt, 12, result_pool); + } + if (had_props) + { + *had_props = SQLITE_PROPERTIES_AVAILABLE(stmt, 14); + } + if (props) + { + if (raw_status == svn_wc__db_status_normal + || raw_status == svn_wc__db_status_incomplete) + { + SVN_ERR(svn_sqlite__column_properties(props, stmt, 14, + result_pool, scratch_pool)); + if (*props == NULL) + *props = apr_hash_make(result_pool); + } + else + { + assert(svn_sqlite__column_is_null(stmt, 14)); + *props = NULL; + } + } + + return svn_error_trace( + svn_error_compose_create(err, + svn_sqlite__reset(stmt))); +} + +svn_error_t * +svn_wc__db_read_children_walker_info(apr_hash_t **nodes, + svn_wc__db_t *db, + const char *dir_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *dir_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &dir_relpath, db, + dir_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_CHILDREN_WALKER_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, dir_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + *nodes = apr_hash_make(result_pool); + while (have_row) + { + struct svn_wc__db_walker_info_t *child; + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *name = svn_relpath_basename(child_relpath, NULL); + int op_depth = svn_sqlite__column_int(stmt, 1); + svn_error_t *err; + + child = apr_palloc(result_pool, sizeof(*child)); + child->status = svn_sqlite__column_token(stmt, 2, presence_map); + if (op_depth > 0) + { + err = convert_to_working_status(&child->status, child->status); + if (err) + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + } + child->kind = svn_sqlite__column_token(stmt, 3, kind_map); + svn_hash_sets(*nodes, apr_pstrdup(result_pool, name), child); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_node_install_info(const char **wcroot_abspath, + const svn_checksum_t **sha1_checksum, + apr_hash_t **pristine_props, + apr_time_t *changed_date, + svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_error_t *err = NULL; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + if (!wri_abspath) + wri_abspath = local_abspath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (local_abspath != wri_abspath + && strcmp(local_abspath, wri_abspath)) + { + if (!svn_dirent_is_ancestor(wcroot->abspath, local_abspath)) + return svn_error_createf( + SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' is not in working copy '%s'"), + svn_dirent_local_style(local_abspath, scratch_pool), + svn_dirent_local_style(wcroot->abspath, scratch_pool)); + + local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, local_abspath); + } + + if (wcroot_abspath != NULL) + *wcroot_abspath = apr_pstrdup(result_pool, wcroot->abspath); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + if (!err && sha1_checksum) + err = svn_sqlite__column_checksum(sha1_checksum, stmt, 6, result_pool); + + if (!err && pristine_props) + { + err = svn_sqlite__column_properties(pristine_props, stmt, 14, + result_pool, scratch_pool); + /* Null means no props (assuming presence normal or incomplete). */ + if (*pristine_props == NULL) + *pristine_props = apr_hash_make(result_pool); + } + + if (changed_date) + *changed_date = svn_sqlite__column_int64(stmt, 9); + } + else + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' is not installable"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + + return SVN_NO_ERROR; +} + + + +/* The body of svn_wc__db_read_url(). + */ +static svn_error_t * +read_url_txn(const char **url, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + const char *repos_relpath; + const char *repos_root_url; + apr_int64_t repos_id; + svn_boolean_t have_base; + + SVN_ERR(read_info(&status, NULL, NULL, &repos_relpath, &repos_id, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + &have_base, NULL, NULL, + wcroot, local_relpath, scratch_pool, scratch_pool)); + + if (repos_relpath == NULL) + { + if (status == svn_wc__db_status_added) + { + SVN_ERR(scan_addition(NULL, NULL, &repos_relpath, &repos_id, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + } + else if (status == svn_wc__db_status_deleted) + { + const char *base_del_relpath; + const char *work_del_relpath; + + SVN_ERR(scan_deletion_txn(&base_del_relpath, NULL, + &work_del_relpath, + NULL, wcroot, + local_relpath, + scratch_pool, + scratch_pool)); + + if (base_del_relpath) + { + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, + &repos_id, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wcroot, + base_del_relpath, + scratch_pool, + scratch_pool)); + + repos_relpath = svn_relpath_join( + repos_relpath, + svn_dirent_skip_ancestor(base_del_relpath, + local_relpath), + scratch_pool); + } + else + { + /* The parent of the WORKING delete, must be an addition */ + const char *work_relpath = NULL; + + /* work_del_relpath should not be NULL. However, we have + * observed instances where that assumption was not met. + * Bail out in that case instead of crashing with a segfault. + */ + SVN_ERR_ASSERT(work_del_relpath != NULL); + work_relpath = svn_relpath_dirname(work_del_relpath, + scratch_pool); + + SVN_ERR(scan_addition(NULL, NULL, &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, work_relpath, + scratch_pool, scratch_pool)); + + repos_relpath = svn_relpath_join( + repos_relpath, + svn_dirent_skip_ancestor(work_relpath, + local_relpath), + scratch_pool); + } + } + else if (status == svn_wc__db_status_excluded) + { + const char *parent_relpath; + const char *name; + const char *url2; + + /* Set 'url' to the *full URL* of the parent WC dir, + * and 'name' to the *single path component* that is the + * basename of this WC directory, so that joining them will result + * in the correct full URL. */ + svn_relpath_split(&parent_relpath, &name, local_relpath, + scratch_pool); + SVN_ERR(read_url_txn(&url2, wcroot, parent_relpath, + scratch_pool, scratch_pool)); + + *url = svn_path_url_add_component2(url2, name, result_pool); + + return SVN_NO_ERROR; + } + else + { + /* All working statee are explicitly handled and all base statee + have a repos_relpath */ + SVN_ERR_MALFUNCTION(); + } + } + + SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, NULL, wcroot->sdb, + repos_id, scratch_pool)); + + SVN_ERR_ASSERT(repos_root_url != NULL && repos_relpath != NULL); + *url = svn_path_url_add_component2(repos_root_url, repos_relpath, + result_pool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_read_url(const char **url, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(read_url_txn(url, wcroot, local_relpath, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + + +/* Call RECEIVER_FUNC, passing RECEIVER_BATON, an absolute path, and + a hash table mapping <tt>char *</tt> names onto svn_string_t * + values for any properties of immediate or recursive child nodes of + LOCAL_ABSPATH, the actual query being determined by STMT_IDX. + If FILES_ONLY is true, only report properties for file child nodes. + Check for cancellation between calls of RECEIVER_FUNC. +*/ +typedef struct cache_props_baton_t +{ + svn_depth_t depth; + svn_boolean_t pristine; + const apr_array_header_t *changelists; + svn_cancel_func_t cancel_func; + void *cancel_baton; +} cache_props_baton_t; + + +static svn_error_t * +cache_props_recursive(void *cb_baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + cache_props_baton_t *baton = cb_baton; + svn_sqlite__stmt_t *stmt; + int stmt_idx; + + SVN_ERR(populate_targets_tree(wcroot, local_relpath, baton->depth, + baton->changelists, scratch_pool)); + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_TARGET_PROP_CACHE)); + + if (baton->pristine) + stmt_idx = STMT_CACHE_TARGET_PRISTINE_PROPS; + else + stmt_idx = STMT_CACHE_TARGET_PROPS; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, stmt_idx)); + SVN_ERR(svn_sqlite__bind_int64(stmt, 1, wcroot->wc_id)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_read_props_streamily(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t pristine, + const apr_array_header_t *changelists, + svn_wc__proplist_receiver_t receiver_func, + void *receiver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + cache_props_baton_t baton; + svn_boolean_t have_row; + apr_pool_t *iterpool; + svn_error_t *err = NULL; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(receiver_func); + SVN_ERR_ASSERT((depth == svn_depth_files) || + (depth == svn_depth_immediates) || + (depth == svn_depth_infinity)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + baton.depth = depth; + baton.pristine = pristine; + baton.changelists = changelists; + baton.cancel_func = cancel_func; + baton.cancel_baton = cancel_baton; + + SVN_ERR(with_finalization(wcroot, local_relpath, + cache_props_recursive, &baton, + NULL, NULL, + cancel_func, cancel_baton, + NULL, NULL, + STMT_DROP_TARGETS_LIST, + scratch_pool)); + + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ALL_TARGET_PROP_CACHE)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (!err && have_row) + { + apr_hash_t *props; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__column_properties(&props, stmt, 1, iterpool, + iterpool)); + + /* see if someone wants to cancel this operation. */ + if (cancel_func) + err = cancel_func(cancel_baton); + + if (!err && props && apr_hash_count(props) != 0) + { + const char *child_relpath; + const char *child_abspath; + + child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + child_abspath = svn_dirent_join(wcroot->abspath, + child_relpath, iterpool); + + err = receiver_func(receiver_baton, child_abspath, props, iterpool); + } + + err = svn_error_compose_create(err, svn_sqlite__step(&have_row, stmt)); + } + + err = svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + svn_pool_destroy(iterpool); + + SVN_ERR(svn_error_compose_create( + err, + svn_sqlite__exec_statements(wcroot->sdb, + STMT_DROP_TARGET_PROP_CACHE))); + return SVN_NO_ERROR; +} + + +/* Helper for svn_wc__db_read_props(). + */ +static svn_error_t * +db_read_props(apr_hash_t **props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_error_t *err = NULL; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row && !svn_sqlite__column_is_null(stmt, 0)) + { + err = svn_sqlite__column_properties(props, stmt, 0, + result_pool, scratch_pool); + } + else + have_row = FALSE; + + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + + if (have_row) + return SVN_NO_ERROR; + + /* No local changes. Return the pristine props for this node. */ + SVN_ERR(db_read_pristine_props(props, wcroot, local_relpath, FALSE, + result_pool, scratch_pool)); + if (*props == NULL) + { + /* Pristine properties are not defined for this node. + ### we need to determine whether this node is in a state that + ### allows for ACTUAL properties (ie. not deleted). for now, + ### just say all nodes, no matter the state, have at least an + ### empty set of props. */ + *props = apr_hash_make(result_pool); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_read_props(apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(db_read_props(props, wcroot, local_relpath, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +db_read_pristine_props(apr_hash_t **props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_boolean_t deleted_ok, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_wc__db_status_t presence; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_NODE_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + + /* Examine the presence: */ + presence = svn_sqlite__column_token(stmt, 1, presence_map); + + /* For "base-deleted", it is obvious the pristine props are located + below the current node. Fetch the NODE from the next record. */ + if (presence == svn_wc__db_status_base_deleted && deleted_ok) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + SVN_ERR_ASSERT(have_row); + + presence = svn_sqlite__column_token(stmt, 1, presence_map); + } + + /* normal or copied: Fetch properties (during update we want + properties for incomplete as well) */ + if (presence == svn_wc__db_status_normal + || presence == svn_wc__db_status_incomplete) + { + svn_error_t *err; + + err = svn_sqlite__column_properties(props, stmt, 0, result_pool, + scratch_pool); + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + + if (!*props) + *props = apr_hash_make(result_pool); + + return SVN_NO_ERROR; + } + + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + svn_sqlite__reset(stmt), + _("The node '%s' has a status that" + " has no properties."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); +} + + +svn_error_t * +svn_wc__db_read_pristine_props(apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(db_read_pristine_props(props, wcroot, local_relpath, TRUE, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_prop_retrieve_recursive(apr_hash_t **values, + svn_wc__db_t *db, + const char *local_abspath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_CURRENT_PROPS_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + *values = apr_hash_make(result_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + apr_hash_t *node_props; + svn_string_t *value; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__column_properties(&node_props, stmt, 0, + iterpool, iterpool)); + + value = (node_props + ? svn_hash_gets(node_props, propname) + : NULL); + + if (value) + { + svn_hash_sets(*values, + svn_dirent_join(wcroot->abspath, + svn_sqlite__column_text(stmt, 1, NULL), + result_pool), + svn_string_dup(value, result_pool)); + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + svn_pool_destroy(iterpool); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +/* The body of svn_wc__db_read_cached_iprops(). */ +static svn_error_t * +db_read_cached_iprops(apr_array_header_t **iprops, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_IPROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + + SVN_ERR(svn_sqlite__column_iprops(iprops, stmt, 0, + result_pool, scratch_pool)); + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_cached_iprops(apr_array_header_t **iprops, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* Don't use with_txn yet, as we perform just a single transaction */ + SVN_ERR(db_read_cached_iprops(iprops, wcroot, local_relpath, + result_pool, scratch_pool)); + + if (!*iprops) + { + *iprops = apr_array_make(result_pool, 0, + sizeof(svn_prop_inherited_item_t *)); + } + + return SVN_NO_ERROR; +} + +/* Remove all prop name value pairs from PROP_HASH where the property + name is not PROPNAME. */ +static void +filter_unwanted_props(apr_hash_t *prop_hash, + const char * propname, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, prop_hash); + hi; + hi = apr_hash_next(hi)) + { + const char *ipropname = svn__apr_hash_index_key(hi); + + if (strcmp(ipropname, propname) != 0) + svn_hash_sets(prop_hash, ipropname, NULL); + } + return; +} + +/* Get the changed properties as stored in the ACTUAL table */ +static svn_error_t * +db_get_changed_props(apr_hash_t **actual_props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row && !svn_sqlite__column_is_null(stmt, 0)) + SVN_ERR(svn_sqlite__column_properties(actual_props, stmt, 0, + result_pool, scratch_pool)); + else + *actual_props = NULL; /* Cached when we read that record */ + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +/* The body of svn_wc__db_read_inherited_props(). */ +static svn_error_t * +db_read_inherited_props(apr_array_header_t **inherited_props, + apr_hash_t **actual_props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i; + apr_array_header_t *cached_iprops = NULL; + apr_array_header_t *iprops; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + svn_sqlite__stmt_t *stmt; + const char *relpath; + const char *expected_parent_repos_relpath = NULL; + const char *parent_relpath; + + iprops = apr_array_make(result_pool, 1, + sizeof(svn_prop_inherited_item_t *)); + *inherited_props = iprops; + + if (actual_props) + *actual_props = NULL; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + + relpath = local_relpath; + + /* Walk up to the root of the WC looking for inherited properties. When we + reach the WC root also check for cached inherited properties. */ + for (relpath = local_relpath; relpath; relpath = parent_relpath) + { + svn_boolean_t have_row; + int op_depth; + svn_wc__db_status_t status; + apr_hash_t *node_props; + + parent_relpath = relpath[0] ? svn_relpath_dirname(relpath, scratch_pool) + : NULL; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + return svn_error_createf( + SVN_ERR_WC_PATH_NOT_FOUND, svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, relpath, + scratch_pool)); + + op_depth = svn_sqlite__column_int(stmt, 0); + + status = svn_sqlite__column_token(stmt, 3, presence_map); + + if (status != svn_wc__db_status_normal + && status != svn_wc__db_status_incomplete) + return svn_error_createf( + SVN_ERR_WC_PATH_UNEXPECTED_STATUS, svn_sqlite__reset(stmt), + _("The node '%s' has a status that has no properties."), + path_for_error_message(wcroot, relpath, + scratch_pool)); + + if (op_depth > 0) + { + /* WORKING node. Nothing to check */ + } + else if (expected_parent_repos_relpath) + { + const char *repos_relpath = svn_sqlite__column_text(stmt, 2, NULL); + + if (strcmp(expected_parent_repos_relpath, repos_relpath) != 0) + { + /* The child of this node has a different parent than this node + (It is "switched"), so we can stop here. Note that switched + with the same parent is not interesting for us here. */ + SVN_ERR(svn_sqlite__reset(stmt)); + break; + } + + expected_parent_repos_relpath = + svn_relpath_dirname(expected_parent_repos_relpath, scratch_pool); + } + else + { + const char *repos_relpath = svn_sqlite__column_text(stmt, 2, NULL); + + expected_parent_repos_relpath = + svn_relpath_dirname(repos_relpath, scratch_pool); + } + + if (op_depth == 0 + && !svn_sqlite__column_is_null(stmt, 16)) + { + /* The node contains a cache. No reason to look further */ + SVN_ERR(svn_sqlite__column_iprops(&cached_iprops, stmt, 16, + result_pool, iterpool)); + + parent_relpath = NULL; /* Stop after this */ + } + + SVN_ERR(svn_sqlite__column_properties(&node_props, stmt, 14, + iterpool, iterpool)); + + SVN_ERR(svn_sqlite__reset(stmt)); + + /* If PARENT_ABSPATH is a parent of LOCAL_ABSPATH, then LOCAL_ABSPATH + can inherit properties from it. */ + if (relpath != local_relpath) + { + apr_hash_t *changed_props; + + SVN_ERR(db_get_changed_props(&changed_props, wcroot, relpath, + result_pool, iterpool)); + + if (changed_props) + node_props = changed_props; + else if (node_props) + node_props = svn_prop_hash_dup(node_props, result_pool); + + if (node_props && apr_hash_count(node_props)) + { + /* If we only want PROPNAME filter out any other properties. */ + if (propname) + filter_unwanted_props(node_props, propname, iterpool); + + if (apr_hash_count(node_props)) + { + svn_prop_inherited_item_t *iprop_elt = + apr_pcalloc(result_pool, + sizeof(svn_prop_inherited_item_t)); + iprop_elt->path_or_url = svn_dirent_join(wcroot->abspath, + relpath, + result_pool); + + iprop_elt->prop_hash = node_props; + /* Build the output array in depth-first order. */ + svn_sort__array_insert(&iprop_elt, iprops, 0); + } + } + } + else if (actual_props) + { + apr_hash_t *changed_props; + + SVN_ERR(db_get_changed_props(&changed_props, wcroot, relpath, + result_pool, iterpool)); + + if (changed_props) + *actual_props = changed_props; + else if (node_props) + *actual_props = svn_prop_hash_dup(node_props, result_pool); + } + } + + if (cached_iprops) + { + for (i = cached_iprops->nelts - 1; i >= 0; i--) + { + svn_prop_inherited_item_t *cached_iprop = + APR_ARRAY_IDX(cached_iprops, i, svn_prop_inherited_item_t *); + + /* An empty property hash in the iprops cache means there are no + inherited properties. */ + if (apr_hash_count(cached_iprop->prop_hash) == 0) + continue; + + if (propname) + filter_unwanted_props(cached_iprop->prop_hash, propname, + scratch_pool); + + /* If we didn't filter everything then keep this iprop. */ + if (apr_hash_count(cached_iprop->prop_hash)) + svn_sort__array_insert(&cached_iprop, iprops, 0); + } + } + + if (actual_props && !*actual_props) + *actual_props = apr_hash_make(result_pool); + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_inherited_props(apr_array_header_t **iprops, + apr_hash_t **actual_props, + svn_wc__db_t *db, + const char *local_abspath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(db_read_inherited_props(iprops, actual_props, + wcroot, local_relpath, propname, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_get_children_with_cached_iprops(). + */ +static svn_error_t * +get_children_with_cached_iprops(apr_hash_t **iprop_paths, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + *iprop_paths = apr_hash_make(result_pool); + + /* First check if LOCAL_RELPATH itself has iprops */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_IPROPS_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + const char *relpath_with_cache = svn_sqlite__column_text(stmt, 0, + NULL); + const char *abspath_with_cache = svn_dirent_join(wcroot->abspath, + relpath_with_cache, + result_pool); + svn_hash_sets(*iprop_paths, abspath_with_cache, + svn_sqlite__column_text(stmt, 1, result_pool)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + if (depth == svn_depth_empty) + return SVN_NO_ERROR; + + /* Now fetch information for children or all descendants */ + if (depth == svn_depth_files + || depth == svn_depth_immediates) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_IPROPS_CHILDREN)); + } + else /* Default to svn_depth_infinity. */ + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_IPROPS_RECURSIVE)); + } + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + const char *relpath_with_cache = svn_sqlite__column_text(stmt, 0, + NULL); + const char *abspath_with_cache = svn_dirent_join(wcroot->abspath, + relpath_with_cache, + result_pool); + svn_hash_sets(*iprop_paths, abspath_with_cache, + svn_sqlite__column_text(stmt, 1, result_pool)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + /* For depth files we should filter non files */ + if (depth == svn_depth_files) + { + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + for (hi = apr_hash_first(scratch_pool, *iprop_paths); + hi; + hi = apr_hash_next(hi)) + { + const char *child_abspath = svn__apr_hash_index_key(hi); + const char *child_relpath; + svn_node_kind_t child_kind; + + svn_pool_clear(iterpool); + + child_relpath = svn_dirent_is_child(local_relpath, child_abspath, + NULL); + + if (! child_relpath) + { + continue; /* local_relpath itself */ + } + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &child_kind, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wcroot, child_relpath, + scratch_pool, + scratch_pool)); + + /* Filter if not a file */ + if (child_kind != svn_node_file) + { + svn_hash_sets(*iprop_paths, child_abspath, NULL); + } + } + + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_get_children_with_cached_iprops(apr_hash_t **iprop_paths, + svn_depth_t depth, + const char *local_abspath, + svn_wc__db_t *db, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, + scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + get_children_with_cached_iprops(iprop_paths, wcroot, local_relpath, + depth, result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_children_of_working_node(const apr_array_header_t **children, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + return gather_children2(children, wcroot, local_relpath, + result_pool, scratch_pool); +} + +/* Helper for svn_wc__db_node_check_replace(). + */ +static svn_error_t * +check_replace_txn(svn_boolean_t *is_replace_root_p, + svn_boolean_t *base_replace_p, + svn_boolean_t *is_replace_p, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_boolean_t is_replace = FALSE; + int replaced_op_depth; + svn_wc__db_status_t replaced_status; + + /* Our caller initialized the output values to FALSE */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + { + svn_wc__db_status_t status; + + status = svn_sqlite__column_token(stmt, 3, presence_map); + + if (status != svn_wc__db_status_normal) + return svn_error_trace(svn_sqlite__reset(stmt)); + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + return svn_error_trace(svn_sqlite__reset(stmt)); + + replaced_status = svn_sqlite__column_token(stmt, 3, presence_map); + + /* If the layer below the add describes a not present or a deleted node, + this is not a replacement. Deleted can only occur if an ancestor is + the delete root. */ + if (replaced_status != svn_wc__db_status_not_present + && replaced_status != svn_wc__db_status_excluded + && replaced_status != svn_wc__db_status_server_excluded + && replaced_status != svn_wc__db_status_base_deleted) + { + is_replace = TRUE; + if (is_replace_p) + *is_replace_p = TRUE; + } + + replaced_op_depth = svn_sqlite__column_int(stmt, 0); + + if (base_replace_p) + { + int op_depth = svn_sqlite__column_int(stmt, 0); + + while (op_depth != 0 && have_row) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + op_depth = svn_sqlite__column_int(stmt, 0); + } + + if (have_row && op_depth == 0) + { + svn_wc__db_status_t base_status; + + base_status = svn_sqlite__column_token(stmt, 3, presence_map); + + *base_replace_p = (base_status != svn_wc__db_status_not_present); + } + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + if (!is_replace_root_p || !is_replace) + return SVN_NO_ERROR; + + if (replaced_status != svn_wc__db_status_base_deleted) + { + int parent_op_depth; + + /* Check the current op-depth of the parent to see if we are a replacement + root */ + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + svn_relpath_dirname(local_relpath, + scratch_pool))); + + SVN_ERR(svn_sqlite__step_row(stmt)); /* Parent must exist as 'normal' */ + + parent_op_depth = svn_sqlite__column_int(stmt, 0); + + if (parent_op_depth >= replaced_op_depth) + { + /* Did we replace inside our directory? */ + + *is_replace_root_p = (parent_op_depth == replaced_op_depth); + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + parent_op_depth = svn_sqlite__column_int(stmt, 0); + + SVN_ERR(svn_sqlite__reset(stmt)); + + if (!have_row) + *is_replace_root_p = TRUE; /* Parent is no replacement */ + else if (parent_op_depth < replaced_op_depth) + *is_replace_root_p = TRUE; /* Parent replaces a lower layer */ + /*else // No replacement root */ + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_node_check_replace(svn_boolean_t *is_replace_root, + svn_boolean_t *base_replace, + svn_boolean_t *is_replace, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (is_replace_root) + *is_replace_root = FALSE; + if (base_replace) + *base_replace = FALSE; + if (is_replace) + *is_replace = FALSE; + + if (local_relpath[0] == '\0') + return SVN_NO_ERROR; /* Working copy root can't be replaced */ + + SVN_WC__DB_WITH_TXN( + check_replace_txn(is_replace_root, base_replace, is_replace, + wcroot, local_relpath, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_read_children(const apr_array_header_t **children, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + return gather_children(children, wcroot, local_relpath, + result_pool, scratch_pool); +} + + +/* */ +static svn_error_t * +relocate_txn(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_boolean_t have_base_node, + apr_int64_t old_repos_id, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + apr_int64_t new_repos_id; + + /* This function affects all the children of the given local_relpath, + but the way that it does this is through the repos inheritance mechanism. + So, we only need to rewrite the repos_id of the given local_relpath, + as well as any children with a non-null repos_id, as well as various + repos_id fields in the locks and working_node tables. + */ + + /* Get the repos_id for the new repository. */ + SVN_ERR(create_repos_id(&new_repos_id, repos_root_url, repos_uuid, + wcroot->sdb, scratch_pool)); + + /* Set the (base and working) repos_ids and clear the dav_caches */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_RECURSIVE_UPDATE_NODE_REPO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isii", wcroot->wc_id, local_relpath, + old_repos_id, new_repos_id)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + if (have_base_node) + { + /* Update any locks for the root or its children. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_LOCK_REPOS_ID)); + SVN_ERR(svn_sqlite__bindf(stmt, "ii", old_repos_id, new_repos_id)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_global_relocate(svn_wc__db_t *db, + const char *local_dir_abspath, + const char *repos_root_url, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *local_dir_relpath; + svn_wc__db_status_t status; + const char *repos_uuid; + svn_boolean_t have_base_node; + apr_int64_t old_repos_id; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath)); + /* ### assert that we were passed a directory? */ + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_dir_relpath, + db, local_dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + local_relpath = local_dir_relpath; + + SVN_ERR(read_info(&status, + NULL, NULL, NULL, &old_repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, + &have_base_node, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (status == svn_wc__db_status_excluded) + { + /* The parent cannot be excluded, so look at the parent and then + adjust the relpath */ + const char *parent_relpath = svn_relpath_dirname(local_dir_relpath, + scratch_pool); + SVN_ERR(read_info(&status, + NULL, NULL, NULL, &old_repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + wcroot, parent_relpath, + scratch_pool, scratch_pool)); + local_dir_relpath = parent_relpath; + } + + if (old_repos_id == INVALID_REPOS_ID) + { + /* Do we need to support relocating something that is + added/deleted/excluded without relocating the parent? If not + then perhaps relpath, root_url and uuid should be passed down + to the children so that they don't have to scan? */ + + if (status == svn_wc__db_status_deleted) + { + const char *work_del_relpath; + + SVN_ERR(scan_deletion_txn(NULL, NULL, + &work_del_relpath, NULL, + wcroot, local_dir_relpath, + scratch_pool, + scratch_pool)); + if (work_del_relpath) + { + /* Deleted within a copy/move */ + + /* The parent of the delete is added. */ + status = svn_wc__db_status_added; + local_dir_relpath = svn_relpath_dirname(work_del_relpath, + scratch_pool); + } + } + + if (status == svn_wc__db_status_added) + { + SVN_ERR(scan_addition(NULL, NULL, NULL, &old_repos_id, + NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, local_dir_relpath, + scratch_pool, scratch_pool)); + } + else + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, NULL, + &old_repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_dir_relpath, + scratch_pool, scratch_pool)); + } + + SVN_ERR(svn_wc__db_fetch_repos_info(NULL, &repos_uuid, wcroot->sdb, + old_repos_id, scratch_pool)); + SVN_ERR_ASSERT(repos_uuid); + + SVN_WC__DB_WITH_TXN( + relocate_txn(wcroot, local_relpath, repos_root_url, repos_uuid, + have_base_node, old_repos_id, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + + +/* Set *REPOS_ID and *REPOS_RELPATH to the BASE repository location of + (WCROOT, LOCAL_RELPATH), directly if its BASE row exists or implied from + its parent's BASE row if not. In the latter case, error if the parent + BASE row does not exist. */ +static svn_error_t * +determine_repos_info(apr_int64_t *repos_id, + const char **repos_relpath, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + const char *repos_parent_relpath; + const char *local_parent_relpath, *name; + + /* ### is it faster to fetch fewer columns? */ + + /* Prefer the current node's repository information. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_BASE_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 0)); + SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 1)); + + *repos_id = svn_sqlite__column_int64(stmt, 0); + *repos_relpath = svn_sqlite__column_text(stmt, 1, result_pool); + + return svn_error_trace(svn_sqlite__reset(stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + /* This was a child node within this wcroot. We want to look at the + BASE node of the directory. */ + svn_relpath_split(&local_parent_relpath, &name, local_relpath, scratch_pool); + + /* The REPOS_ID will be the same (### until we support mixed-repos) */ + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_parent_relpath, repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_parent_relpath, + scratch_pool, scratch_pool)); + + *repos_relpath = svn_relpath_join(repos_parent_relpath, name, result_pool); + + return SVN_NO_ERROR; +} + +/* Helper for svn_wc__db_global_commit() + + Makes local_relpath and all its descendants at the same op-depth represent + the copy origin repos_id:repos_relpath@revision. + + This code is only valid to fix-up a move from an old location, to a new + location during a commit. + + Assumptions: + * local_relpath is not the working copy root (can't be moved) + * repos_relpath is not the repository root (can't be moved) + */ +static svn_error_t * +moved_descendant_commit(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_int64_t repos_id, + const char *repos_relpath, + svn_revnum_t revision, + apr_pool_t *scratch_pool) +{ + apr_hash_t *children; + apr_pool_t *iterpool; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_hash_index_t *hi; + + SVN_ERR_ASSERT(*local_relpath != '\0' + && *repos_relpath != '\0'); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, + op_depth)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (! have_row) + return svn_error_trace(svn_sqlite__reset(stmt)); + + children = apr_hash_make(scratch_pool); + + /* First, obtain all moved children */ + /* To keep error handling simple, first cache them in a hashtable */ + while (have_row) + { + const char *src_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + const char *to_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool); + + svn_hash_sets(children, src_relpath, to_relpath); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Then update them */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_COMMIT_UPDATE_ORIGIN)); + + iterpool = svn_pool_create(scratch_pool); + for (hi = apr_hash_first(scratch_pool, children); hi; hi = apr_hash_next(hi)) + { + const char *src_relpath = svn__apr_hash_index_key(hi); + const char *to_relpath = svn__apr_hash_index_val(hi); + const char *new_repos_relpath; + int to_op_depth = relpath_depth(to_relpath); + int affected; + + svn_pool_clear(iterpool); + + SVN_ERR_ASSERT(to_op_depth > 0); + + new_repos_relpath = svn_relpath_join( + repos_relpath, + svn_relpath_skip_ancestor(local_relpath, + src_relpath), + iterpool); + + SVN_ERR(svn_sqlite__bindf(stmt, "isdisr", wcroot->wc_id, + to_relpath, + to_op_depth, + repos_id, + new_repos_relpath, + revision)); + SVN_ERR(svn_sqlite__update(&affected, stmt)); + +#ifdef SVN_DEBUG + /* Enable in release code? + Broken moves are not fatal yet, but this assertion would break + committing them */ + SVN_ERR_ASSERT(affected >= 1); /* If this fails there is no move dest */ +#endif + + SVN_ERR(moved_descendant_commit(wcroot, to_relpath, to_op_depth, + repos_id, new_repos_relpath, revision, + iterpool)); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +/* Helper for svn_wc__db_global_commit() + + Moves all nodes below LOCAL_RELPATH from op-depth OP_DEPTH to op-depth 0 + (BASE), setting their presence to 'not-present' if their presence wasn't + 'normal'. + + Makes all nodes below LOCAL_RELPATH represent the descendants of repository + location repos_id:repos_relpath@revision. + + Assumptions: + * local_relpath is not the working copy root (can't be replaced) + * repos_relpath is not the repository root (can't be replaced) + */ +static svn_error_t * +descendant_commit(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_int64_t repos_id, + const char *repos_relpath, + svn_revnum_t revision, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR_ASSERT(*local_relpath != '\0' + && *repos_relpath != '\0'); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_COMMIT_DESCENDANTS_TO_BASE)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isdisr", wcroot->wc_id, + local_relpath, + op_depth, + repos_id, + repos_relpath, + revision)); + + SVN_ERR(svn_sqlite__update(NULL, stmt)); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_global_commit(). + */ +static svn_error_t * +commit_node(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_revnum_t new_revision, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const svn_checksum_t *new_checksum, + const apr_array_header_t *new_children, + apr_hash_t *new_dav_cache, + svn_boolean_t keep_changelist, + svn_boolean_t no_unlock, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt_info; + svn_sqlite__stmt_t *stmt_act; + svn_boolean_t have_act; + svn_string_t prop_blob = { 0 }; + svn_string_t inherited_prop_blob = { 0 }; + const char *changelist = NULL; + const char *parent_relpath; + svn_wc__db_status_t new_presence; + svn_node_kind_t new_kind; + const char *new_depth_str = NULL; + svn_sqlite__stmt_t *stmt; + apr_int64_t repos_id; + const char *repos_relpath; + int op_depth; + svn_wc__db_status_t old_presence; + + /* If we are adding a file or directory, then we need to get + repository information from the parent node since "this node" does + not have a BASE). + + For existing nodes, we should retain the (potentially-switched) + repository information. */ + SVN_ERR(determine_repos_info(&repos_id, &repos_relpath, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + /* ### is it better to select only the data needed? */ + SVN_ERR(svn_sqlite__get_statement(&stmt_info, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt_info, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_row(stmt_info)); + + SVN_ERR(svn_sqlite__get_statement(&stmt_act, wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt_act, "is", + wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_act, stmt_act)); + + /* There should be something to commit! */ + + op_depth = svn_sqlite__column_int(stmt_info, 0); + + /* Figure out the new node's kind. It will be whatever is in WORKING_NODE, + or there will be a BASE_NODE that has it. */ + new_kind = svn_sqlite__column_token(stmt_info, 4, kind_map); + + /* What will the new depth be? */ + if (new_kind == svn_node_dir) + new_depth_str = svn_sqlite__column_text(stmt_info, 11, scratch_pool); + + /* Check that the repository information is not being changed. */ + if (op_depth == 0) + { + SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt_info, 1)); + SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt_info, 2)); + + /* A commit cannot change these values. */ + SVN_ERR_ASSERT(repos_id == svn_sqlite__column_int64(stmt_info, 1)); + SVN_ERR_ASSERT(strcmp(repos_relpath, + svn_sqlite__column_text(stmt_info, 2, NULL)) == 0); + } + + /* Find the appropriate new properties -- ACTUAL overrides any properties + in WORKING that arrived as part of a copy/move. + + Note: we'll keep them as a big blob of data, rather than + deserialize/serialize them. */ + if (have_act) + prop_blob.data = svn_sqlite__column_blob(stmt_act, 1, &prop_blob.len, + scratch_pool); + if (prop_blob.data == NULL) + prop_blob.data = svn_sqlite__column_blob(stmt_info, 14, &prop_blob.len, + scratch_pool); + + inherited_prop_blob.data = svn_sqlite__column_blob(stmt_info, 16, + &inherited_prop_blob.len, + scratch_pool); + + if (keep_changelist && have_act) + changelist = svn_sqlite__column_text(stmt_act, 0, scratch_pool); + + old_presence = svn_sqlite__column_token(stmt_info, 3, presence_map); + + /* ### other stuff? */ + + SVN_ERR(svn_sqlite__reset(stmt_info)); + SVN_ERR(svn_sqlite__reset(stmt_act)); + + if (op_depth > 0) + { + int affected_rows; + + /* This removes all layers of this node and at the same time determines + if we need to remove shadowed layers below our descendants. */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ALL_LAYERS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + if (affected_rows > 1) + { + /* We commit a shadowing operation + + 1) Remove all shadowed nodes + 2) And remove all nodes that have a base-deleted as lowest layer, + because 1) removed that layer */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_SHADOWED_RECURSIVE)); + + SVN_ERR(svn_sqlite__bindf(stmt, + "isd", + wcroot->wc_id, + local_relpath, + op_depth)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Note that while these two calls look so similar that they might + be integrated, they really affect a different op-depth and + completely different nodes (via a different recursion pattern). */ + + /* Collapse descendants of the current op_depth in layer 0 */ + SVN_ERR(descendant_commit(wcroot, local_relpath, op_depth, + repos_id, repos_relpath, new_revision, + scratch_pool)); + + /* And make the recorded local moves represent moves of the node we just + committed. */ + SVN_ERR(moved_descendant_commit(wcroot, local_relpath, 0, + repos_id, repos_relpath, new_revision, + scratch_pool)); + + /* This node is no longer modified, so no node was moved here */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_TO_FROM_DEST)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Update or add the BASE_NODE row with all the new information. */ + + if (*local_relpath == '\0') + parent_relpath = NULL; + else + parent_relpath = svn_relpath_dirname(local_relpath, scratch_pool); + + /* Preserve any incomplete status */ + new_presence = (old_presence == svn_wc__db_status_incomplete + ? svn_wc__db_status_incomplete + : svn_wc__db_status_normal); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_APPLY_CHANGES_TO_BASE_NODE)); + /* symlink_target not yet used */ + SVN_ERR(svn_sqlite__bindf(stmt, "issisrtstrisnbn", + wcroot->wc_id, local_relpath, + parent_relpath, + repos_id, + repos_relpath, + new_revision, + presence_map, new_presence, + new_depth_str, + kind_map, new_kind, + changed_rev, + changed_date, + changed_author, + prop_blob.data, prop_blob.len)); + + SVN_ERR(svn_sqlite__bind_checksum(stmt, 13, new_checksum, + scratch_pool)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 15, new_dav_cache, + scratch_pool)); + if (inherited_prop_blob.data != NULL) + { + SVN_ERR(svn_sqlite__bind_blob(stmt, 17, inherited_prop_blob.data, + inherited_prop_blob.len)); + } + + SVN_ERR(svn_sqlite__step_done(stmt)); + + if (have_act) + { + if (keep_changelist && changelist != NULL) + { + /* The user told us to keep the changelist. Replace the row in + ACTUAL_NODE with the basic keys and the changelist. */ + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + STMT_RESET_ACTUAL_WITH_CHANGELIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "isss", + wcroot->wc_id, local_relpath, + svn_relpath_dirname(local_relpath, + scratch_pool), + changelist)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + else + { + /* Toss the ACTUAL_NODE row. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + } + + if (new_kind == svn_node_dir) + { + /* When committing a directory, we should have its new children. */ + /* ### one day. just not today. */ +#if 0 + SVN_ERR_ASSERT(new_children != NULL); +#endif + + /* ### process the children */ + } + + if (!no_unlock) + { + svn_sqlite__stmt_t *lock_stmt; + + SVN_ERR(svn_sqlite__get_statement(&lock_stmt, wcroot->sdb, + STMT_DELETE_LOCK)); + SVN_ERR(svn_sqlite__bindf(lock_stmt, "is", repos_id, repos_relpath)); + SVN_ERR(svn_sqlite__step_done(lock_stmt)); + } + + /* Install any work items into the queue, as part of this transaction. */ + SVN_ERR(add_work_items(wcroot->sdb, work_items, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_global_commit(svn_wc__db_t *db, + const char *local_abspath, + svn_revnum_t new_revision, + svn_revnum_t changed_revision, + apr_time_t changed_date, + const char *changed_author, + const svn_checksum_t *new_checksum, + const apr_array_header_t *new_children, + apr_hash_t *new_dav_cache, + svn_boolean_t keep_changelist, + svn_boolean_t no_unlock, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + svn_wc__db_wcroot_t *wcroot; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_revision)); + SVN_ERR_ASSERT(new_checksum == NULL || new_children == NULL); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + commit_node(wcroot, local_relpath, + new_revision, changed_revision, changed_date, changed_author, + new_checksum, new_children, new_dav_cache, keep_changelist, + no_unlock, work_items, scratch_pool), + wcroot); + + /* We *totally* monkeyed the entries. Toss 'em. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_global_update(svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t new_kind, + const char *new_repos_relpath, + svn_revnum_t new_revision, + const apr_hash_t *new_props, + svn_revnum_t new_changed_rev, + apr_time_t new_changed_date, + const char *new_changed_author, + const apr_array_header_t *new_children, + const svn_checksum_t *new_checksum, + const char *new_target, + const apr_hash_t *new_dav_cache, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + NOT_IMPLEMENTED(); + +#if 0 + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + /* ### allow NULL for NEW_REPOS_RELPATH to indicate "no change"? */ + SVN_ERR_ASSERT(svn_relpath_is_canonical(new_repos_relpath)); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_revision)); + SVN_ERR_ASSERT(new_props != NULL); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_changed_rev)); + SVN_ERR_ASSERT((new_children != NULL + && new_checksum == NULL + && new_target == NULL) + || (new_children == NULL + && new_checksum != NULL + && new_target == NULL) + || (new_children == NULL + && new_checksum == NULL + && new_target != NULL)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + update_node(wcroot, local_relpath, + new_repos_relpath, new_revision, new_props, + new_changed_rev, new_changed_date, new_changed_author, + new_children, new_checksum, new_target, + conflict, work_items, scratch_pool), + wcroot); + + /* We *totally* monkeyed the entries. Toss 'em. */ + SVN_ERR(flush_entries(wcroot, local_abspath, scratch_pool)); + + return SVN_NO_ERROR; +#endif +} + +/* Sets a base nodes revision, repository relative path, and/or inherited + propertis. If LOCAL_ABSPATH's rev (REV) is valid, set its revision. If + SET_REPOS_RELPATH is TRUE set its repository relative path to REPOS_RELPATH + (and make sure its REPOS_ID is still valid). If IPROPS is not NULL set its + inherited properties to IPROPS, if IPROPS is NULL then clear any the iprops + cache for the base node. + */ +static svn_error_t * +db_op_set_rev_repos_relpath_iprops(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_array_header_t *iprops, + svn_revnum_t rev, + svn_boolean_t set_repos_relpath, + const char *repos_relpath, + apr_int64_t repos_id, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(flush_entries(wcroot, + svn_dirent_join(wcroot->abspath, local_relpath, + scratch_pool), + svn_depth_empty, scratch_pool)); + + + if (SVN_IS_VALID_REVNUM(rev)) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_BASE_REVISION)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isr", wcroot->wc_id, local_relpath, + rev)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + if (set_repos_relpath) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_BASE_REPOS)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isis", wcroot->wc_id, local_relpath, + repos_id, repos_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Set or clear iprops. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_IPROP)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__bind_iprops(stmt, 3, iprops, scratch_pool)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +/* The main body of bump_revisions_post_update(). + * + * Tweak the information for LOCAL_RELPATH in WCROOT. If NEW_REPOS_RELPATH is + * non-NULL update the entry to the new url specified by NEW_REPOS_RELPATH, + * NEW_REPOS_ID. If NEW_REV is valid, make this the node's working revision. + * + * If WCROOT_IPROPS is not NULL it is a hash mapping const char * absolute + * working copy paths to depth-first ordered arrays of + * svn_prop_inherited_item_t * structures. If the absolute path equivalent + * of LOCAL_RELPATH exists in WCROOT_IPROPS, then set the hashed value as the + * node's inherited properties. + * + * Unless S_ROOT is TRUE the tweaks might cause the node for LOCAL_ABSPATH to + * be removed from the WC; if IS_ROOT is TRUE this will not happen. + */ +static svn_error_t * +bump_node_revision(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_int64_t new_repos_id, + const char *new_repos_relpath, + svn_revnum_t new_rev, + svn_depth_t depth, + apr_hash_t *exclude_relpaths, + apr_hash_t *wcroot_iprops, + svn_boolean_t is_root, + svn_boolean_t skip_when_dir, + svn_wc__db_t *db, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + const apr_array_header_t *children; + int i; + svn_wc__db_status_t status; + svn_node_kind_t db_kind; + svn_revnum_t revision; + const char *repos_relpath; + apr_int64_t repos_id; + svn_boolean_t set_repos_relpath = FALSE; + svn_boolean_t update_root; + svn_depth_t depth_below_here = depth; + apr_array_header_t *iprops = NULL; + + /* Skip an excluded path and its descendants. */ + if (svn_hash_gets(exclude_relpaths, local_relpath)) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__db_base_get_info_internal(&status, &db_kind, &revision, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, &update_root, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + /* Skip file externals */ + if (update_root + && db_kind == svn_node_file + && !is_root) + return SVN_NO_ERROR; + + if (skip_when_dir && db_kind == svn_node_dir) + return SVN_NO_ERROR; + + /* If the node is still marked 'not-present', then the server did not + re-add it. So it's really gone in this revision, thus we remove the node. + + If the node is still marked 'server-excluded' and yet is not the same + revision as new_rev, then the server did not re-add it, nor + re-server-exclude it, so we can remove the node. */ + if (!is_root + && (status == svn_wc__db_status_not_present + || (status == svn_wc__db_status_server_excluded && + revision != new_rev))) + { + return svn_error_trace(db_base_remove(wcroot, local_relpath, + db, FALSE, FALSE, + SVN_INVALID_REVNUM, + NULL, NULL, scratch_pool)); + } + + if (new_repos_relpath != NULL && strcmp(repos_relpath, new_repos_relpath)) + set_repos_relpath = TRUE; + + if (wcroot_iprops) + iprops = svn_hash_gets(wcroot_iprops, + svn_dirent_join(wcroot->abspath, local_relpath, + scratch_pool)); + + if (iprops + || set_repos_relpath + || (SVN_IS_VALID_REVNUM(new_rev) && new_rev != revision)) + { + SVN_ERR(db_op_set_rev_repos_relpath_iprops(wcroot, local_relpath, + iprops, new_rev, + set_repos_relpath, + new_repos_relpath, + new_repos_id, + scratch_pool)); + } + + /* Early out */ + if (depth <= svn_depth_empty + || db_kind != svn_node_dir + || status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_not_present) + return SVN_NO_ERROR; + + /* And now recurse over the children */ + + depth_below_here = depth; + + if (depth == svn_depth_immediates || depth == svn_depth_files) + depth_below_here = svn_depth_empty; + + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(gather_repo_children(&children, wcroot, local_relpath, 0, + scratch_pool, iterpool)); + for (i = 0; i < children->nelts; i++) + { + const char *child_basename = APR_ARRAY_IDX(children, i, const char *); + const char *child_local_relpath; + const char *child_repos_relpath = NULL; + + svn_pool_clear(iterpool); + + /* Derive the new URL for the current (child) entry */ + if (new_repos_relpath) + child_repos_relpath = svn_relpath_join(new_repos_relpath, + child_basename, iterpool); + + child_local_relpath = svn_relpath_join(local_relpath, child_basename, + iterpool); + + SVN_ERR(bump_node_revision(wcroot, child_local_relpath, new_repos_id, + child_repos_relpath, new_rev, + depth_below_here, + exclude_relpaths, wcroot_iprops, + FALSE /* is_root */, + (depth < svn_depth_immediates), db, + iterpool)); + } + + /* Cleanup */ + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Helper for svn_wc__db_op_bump_revisions_post_update(). + */ +static svn_error_t * +bump_revisions_post_update(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + svn_depth_t depth, + const char *new_repos_relpath, + const char *new_repos_root_url, + const char *new_repos_uuid, + svn_revnum_t new_revision, + apr_hash_t *exclude_relpaths, + apr_hash_t *wcroot_iprops, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_error_t *err; + apr_int64_t new_repos_id = INVALID_REPOS_ID; + + err = svn_wc__db_base_get_info_internal(&status, &kind, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool); + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + switch (status) + { + case svn_wc__db_status_excluded: + case svn_wc__db_status_server_excluded: + case svn_wc__db_status_not_present: + return SVN_NO_ERROR; + + /* Explicitly ignore other statii */ + default: + break; + } + + if (new_repos_root_url != NULL) + SVN_ERR(create_repos_id(&new_repos_id, new_repos_root_url, + new_repos_uuid, + wcroot->sdb, scratch_pool)); + + SVN_ERR(bump_node_revision(wcroot, local_relpath, new_repos_id, + new_repos_relpath, new_revision, + depth, exclude_relpaths, + wcroot_iprops, + TRUE /* is_root */, FALSE, db, + scratch_pool)); + + SVN_ERR(svn_wc__db_bump_moved_away(wcroot, local_relpath, depth, db, + scratch_pool)); + + SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, SVN_INVALID_REVNUM, + SVN_INVALID_REVNUM, notify_func, + notify_baton, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_op_bump_revisions_post_update(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + const char *new_repos_relpath, + const char *new_repos_root_url, + const char *new_repos_uuid, + svn_revnum_t new_revision, + apr_hash_t *exclude_relpaths, + apr_hash_t *wcroot_iprops, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + svn_wc__db_wcroot_t *wcroot; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + + VERIFY_USABLE_WCROOT(wcroot); + + if (svn_hash_gets(exclude_relpaths, local_relpath)) + return SVN_NO_ERROR; + + if (depth == svn_depth_unknown) + depth = svn_depth_infinity; + + SVN_WC__DB_WITH_TXN( + bump_revisions_post_update(wcroot, local_relpath, db, + depth, new_repos_relpath, new_repos_root_url, + new_repos_uuid, new_revision, + exclude_relpaths, wcroot_iprops, + notify_func, notify_baton, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_lock_add(). + */ +static svn_error_t * +lock_add_txn(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const svn_wc__db_lock_t *lock, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + const char *repos_relpath; + apr_int64_t repos_id; + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_LOCK)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", + repos_id, repos_relpath, lock->token)); + + if (lock->owner != NULL) + SVN_ERR(svn_sqlite__bind_text(stmt, 4, lock->owner)); + + if (lock->comment != NULL) + SVN_ERR(svn_sqlite__bind_text(stmt, 5, lock->comment)); + + if (lock->date != 0) + SVN_ERR(svn_sqlite__bind_int64(stmt, 6, lock->date)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_lock_add(svn_wc__db_t *db, + const char *local_abspath, + const svn_wc__db_lock_t *lock, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(lock != NULL); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + lock_add_txn(wcroot, local_relpath, lock, scratch_pool), + wcroot); + + /* There may be some entries, and the lock info is now out of date. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_lock_remove(). + */ +static svn_error_t * +lock_remove_txn(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + const char *repos_relpath; + apr_int64_t repos_id; + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_LOCK)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", repos_id, repos_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_lock_remove(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + lock_remove_txn(wcroot, local_relpath, scratch_pool), + wcroot); + + /* There may be some entries, and the lock info is now out of date. */ + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_scan_base_repos(const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + apr_int64_t repos_id; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + result_pool, scratch_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, wcroot->sdb, + repos_id, result_pool)); + + return SVN_NO_ERROR; +} + + +/* A helper for scan_addition(). + * Compute moved-from information for the node at LOCAL_RELPATH which + * has been determined as having been moved-here. + * If MOVED_FROM_RELPATH is not NULL, set *MOVED_FROM_RELPATH to the + * path of the move-source node in *MOVED_FROM_RELPATH. + * If DELETE_OP_ROOT_RELPATH is not NULL, set *DELETE_OP_ROOT_RELPATH + * to the path of the op-root of the delete-half of the move. + * If moved-from information cannot be derived, set both *MOVED_FROM_RELPATH + * and *DELETE_OP_ROOT_RELPATH to NULL, and return a "copied" status. + * COPY_OPT_ROOT_RELPATH is the relpath of the op-root of the copied-half + * of the move. */ +static svn_error_t * +get_moved_from_info(const char **moved_from_relpath, + const char **moved_from_op_root_relpath, + const char *moved_to_op_root_relpath, + int *op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + /* Run a query to get the moved-from path from the DB. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_FROM_RELPATH)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, moved_to_op_root_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + /* The move was only recorded at the copy-half, possibly because + * the move operation was interrupted mid-way between the copy + * and the delete. Treat this node as a normal copy. */ + if (moved_from_relpath) + *moved_from_relpath = NULL; + if (moved_from_op_root_relpath) + *moved_from_op_root_relpath = NULL; + + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + + if (op_depth) + *op_depth = svn_sqlite__column_int(stmt, 1); + + if (moved_from_relpath || moved_from_op_root_relpath) + { + const char *db_delete_op_root_relpath; + + /* The moved-from path from the DB is the relpath of + * the op_root of the delete-half of the move. */ + db_delete_op_root_relpath = svn_sqlite__column_text(stmt, 0, + result_pool); + if (moved_from_op_root_relpath) + *moved_from_op_root_relpath = db_delete_op_root_relpath; + + if (moved_from_relpath) + { + if (strcmp(moved_to_op_root_relpath, local_relpath) == 0) + { + /* LOCAL_RELPATH is the op_root of the copied-half of the + * move, so the correct MOVED_FROM_ABSPATH is the op-root + * of the delete-half. */ + *moved_from_relpath = db_delete_op_root_relpath; + } + else + { + const char *child_relpath; + + /* LOCAL_RELPATH is a child that was copied along with the + * op_root of the copied-half of the move. Construct the + * corresponding path beneath the op_root of the delete-half. */ + + /* Grab the child path relative to the op_root of the move + * destination. */ + child_relpath = svn_relpath_skip_ancestor( + moved_to_op_root_relpath, local_relpath); + + SVN_ERR_ASSERT(child_relpath && strlen(child_relpath) > 0); + + /* This join is valid because LOCAL_RELPATH has not been moved + * within the copied-half of the move yet -- else, it would + * be its own op_root. */ + *moved_from_relpath = svn_relpath_join(db_delete_op_root_relpath, + child_relpath, + result_pool); + } + } + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +/* The body of scan_addition(). + */ +static svn_error_t * +scan_addition_txn(svn_wc__db_status_t *status, + const char **op_root_relpath_p, + const char **repos_relpath, + apr_int64_t *repos_id, + const char **original_repos_relpath, + apr_int64_t *original_repos_id, + svn_revnum_t *original_revision, + const char **moved_from_relpath, + const char **moved_from_op_root_relpath, + int *moved_from_op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *op_root_relpath; + const char *build_relpath = ""; + + /* Initialize most of the OUT parameters. Generally, we'll only be filling + in a subset of these, so it is easier to init all up front. Note that + the STATUS parameter will be initialized once we read the status of + the specified node. */ + if (op_root_relpath_p) + *op_root_relpath_p = NULL; + if (original_repos_relpath) + *original_repos_relpath = NULL; + if (original_repos_id) + *original_repos_id = INVALID_REPOS_ID; + if (original_revision) + *original_revision = SVN_INVALID_REVNUM; + if (moved_from_relpath) + *moved_from_relpath = NULL; + if (moved_from_op_root_relpath) + *moved_from_op_root_relpath = NULL; + if (moved_from_op_depth) + *moved_from_op_depth = 0; + + { + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_wc__db_status_t presence; + int op_depth; + const char *repos_prefix_path = ""; + int i; + + /* ### is it faster to fetch fewer columns? */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + /* Reset statement before returning */ + SVN_ERR(svn_sqlite__reset(stmt)); + + /* ### maybe we should return a usage error instead? */ + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + presence = svn_sqlite__column_token(stmt, 1, presence_map); + + /* The starting node should exist normally. */ + op_depth = svn_sqlite__column_int(stmt, 0); + if (op_depth == 0 || (presence != svn_wc__db_status_normal + && presence != svn_wc__db_status_incomplete)) + /* reset the statement as part of the error generation process */ + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + svn_sqlite__reset(stmt), + _("Expected node '%s' to be added."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + + if (original_revision) + *original_revision = svn_sqlite__column_revnum(stmt, 12); + + /* Provide the default status; we'll override as appropriate. */ + if (status) + { + if (presence == svn_wc__db_status_normal) + *status = svn_wc__db_status_added; + else + *status = svn_wc__db_status_incomplete; + } + + + /* Calculate the op root local path components */ + op_root_relpath = local_relpath; + + for (i = relpath_depth(local_relpath); i > op_depth; --i) + { + /* Calculate the path of the operation root */ + repos_prefix_path = + svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL), + repos_prefix_path, + scratch_pool); + op_root_relpath = svn_relpath_dirname(op_root_relpath, scratch_pool); + } + + if (op_root_relpath_p) + *op_root_relpath_p = apr_pstrdup(result_pool, op_root_relpath); + + /* ### This if-statement is quite redundant. + * ### We're checking all these values again within the body anyway. + * ### The body should be broken up appropriately and move into the + * ### outer scope. */ + if (original_repos_relpath + || original_repos_id + || (original_revision + && *original_revision == SVN_INVALID_REVNUM) + || status + || moved_from_relpath || moved_from_op_root_relpath) + { + if (local_relpath != op_root_relpath) + /* requery to get the add/copy root */ + { + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, op_root_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + /* Reset statement before returning */ + SVN_ERR(svn_sqlite__reset(stmt)); + + /* ### maybe we should return a usage error instead? */ + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + op_root_relpath, + scratch_pool)); + } + + if (original_revision + && *original_revision == SVN_INVALID_REVNUM) + *original_revision = svn_sqlite__column_revnum(stmt, 12); + } + + if (original_repos_relpath) + *original_repos_relpath = svn_sqlite__column_text(stmt, 11, + result_pool); + + if (!svn_sqlite__column_is_null(stmt, 10) + && (status + || original_repos_id + || moved_from_relpath || moved_from_op_root_relpath)) + /* If column 10 (original_repos_id) is NULL, + this is a plain add, not a copy or a move */ + { + svn_boolean_t moved_here; + if (original_repos_id) + *original_repos_id = svn_sqlite__column_int64(stmt, 10); + + moved_here = svn_sqlite__column_boolean(stmt, 13 /* moved_here */); + if (status) + *status = moved_here ? svn_wc__db_status_moved_here + : svn_wc__db_status_copied; + + if (moved_here + && (moved_from_relpath || moved_from_op_root_relpath)) + { + svn_error_t *err; + + err = get_moved_from_info(moved_from_relpath, + moved_from_op_root_relpath, + op_root_relpath, + moved_from_op_depth, + wcroot, local_relpath, + result_pool, + scratch_pool); + + if (err) + return svn_error_compose_create( + err, svn_sqlite__reset(stmt)); + } + } + } + + + /* ### This loop here is to skip up to the first node which is a BASE node, + because base_get_info() doesn't accommodate the scenario that + we're looking at here; we found the true op_root, which may be inside + further changed trees. */ + if (repos_relpath || repos_id) + { + const char *base_relpath; + + while (TRUE) + { + + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Pointing at op_depth, look at the parent */ + repos_prefix_path = + svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL), + repos_prefix_path, + scratch_pool); + op_root_relpath = svn_relpath_dirname(op_root_relpath, scratch_pool); + + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, op_root_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (! have_row) + break; + + op_depth = svn_sqlite__column_int(stmt, 0); + + /* Skip to op_depth */ + for (i = relpath_depth(op_root_relpath); i > op_depth; i--) + { + /* Calculate the path of the operation root */ + repos_prefix_path = + svn_relpath_join(svn_relpath_basename(op_root_relpath, NULL), + repos_prefix_path, + scratch_pool); + op_root_relpath = + svn_relpath_dirname(op_root_relpath, scratch_pool); + } + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + build_relpath = repos_prefix_path; + + /* If we're here, then we have an added/copied/moved (start) node, and + CURRENT_ABSPATH now points to a BASE node. Figure out the repository + information for the current node, and use that to compute the start + node's repository information. */ + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &base_relpath, repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, op_root_relpath, + scratch_pool, scratch_pool)); + + if (repos_relpath) + *repos_relpath = svn_relpath_join(base_relpath, build_relpath, + result_pool); + } + else + SVN_ERR(svn_sqlite__reset(stmt)); + } + /* Postconditions */ +#ifdef SVN_DEBUG + if (status) + { + SVN_ERR_ASSERT(*status == svn_wc__db_status_added + || *status == svn_wc__db_status_copied + || *status == svn_wc__db_status_incomplete + || *status == svn_wc__db_status_moved_here); + if (*status == svn_wc__db_status_added) + { + SVN_ERR_ASSERT(!original_repos_relpath + || *original_repos_relpath == NULL); + SVN_ERR_ASSERT(!original_revision + || *original_revision == SVN_INVALID_REVNUM); + SVN_ERR_ASSERT(!original_repos_id + || *original_repos_id == INVALID_REPOS_ID); + } + /* An upgrade with a missing directory can leave INCOMPLETE working + op-roots. See upgrade_tests.py 29: upgrade with missing replaced dir + */ + else if (*status != svn_wc__db_status_incomplete) + { + SVN_ERR_ASSERT(!original_repos_relpath + || *original_repos_relpath != NULL); + SVN_ERR_ASSERT(!original_revision + || *original_revision != SVN_INVALID_REVNUM); + SVN_ERR_ASSERT(!original_repos_id + || *original_repos_id != INVALID_REPOS_ID); + } + } + SVN_ERR_ASSERT(!op_root_relpath_p || *op_root_relpath_p != NULL); +#endif + + return SVN_NO_ERROR; +} + + +/* Like svn_wc__db_scan_addition(), but with WCROOT+LOCAL_RELPATH instead of + DB+LOCAL_ABSPATH. + + The output value of *ORIGINAL_REPOS_ID will be INVALID_REPOS_ID if there + is no 'copy-from' repository. */ +static svn_error_t * +scan_addition(svn_wc__db_status_t *status, + const char **op_root_relpath, + const char **repos_relpath, + apr_int64_t *repos_id, + const char **original_repos_relpath, + apr_int64_t *original_repos_id, + svn_revnum_t *original_revision, + const char **moved_from_relpath, + const char **moved_from_op_root_relpath, + int *moved_from_op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_WC__DB_WITH_TXN( + scan_addition_txn(status, op_root_relpath, repos_relpath, repos_id, + original_repos_relpath, original_repos_id, + original_revision, moved_from_relpath, + moved_from_op_root_relpath, moved_from_op_depth, + wcroot, local_relpath, result_pool, scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_scan_addition(svn_wc__db_status_t *status, + const char **op_root_abspath, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + const char **original_repos_relpath, + const char **original_root_url, + const char **original_uuid, + svn_revnum_t *original_revision, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *op_root_relpath = NULL; + apr_int64_t repos_id = INVALID_REPOS_ID; + apr_int64_t original_repos_id = INVALID_REPOS_ID; + apr_int64_t *repos_id_p + = (repos_root_url || repos_uuid) ? &repos_id : NULL; + apr_int64_t *original_repos_id_p + = (original_root_url || original_uuid) ? &original_repos_id : NULL; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(scan_addition(status, + op_root_abspath + ? &op_root_relpath + : NULL, + repos_relpath, repos_id_p, + original_repos_relpath, original_repos_id_p, + original_revision, + NULL, NULL, NULL, + wcroot, local_relpath, result_pool, scratch_pool)); + + if (op_root_abspath) + *op_root_abspath = svn_dirent_join(wcroot->abspath, op_root_relpath, + result_pool); + /* REPOS_ID must be valid if requested; ORIGINAL_REPOS_ID need not be. */ + SVN_ERR_ASSERT(repos_id_p == NULL || repos_id != INVALID_REPOS_ID); + + SVN_ERR(svn_wc__db_fetch_repos_info(repos_root_url, repos_uuid, wcroot->sdb, + repos_id, result_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(original_root_url, original_uuid, + wcroot->sdb, original_repos_id, + result_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_scan_moved(const char **moved_from_abspath, + const char **op_root_abspath, + const char **op_root_moved_from_abspath, + const char **moved_from_delete_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_wc__db_status_t status; + const char *op_root_relpath = NULL; + const char *moved_from_relpath = NULL; + const char *moved_from_op_root_relpath = NULL; + int moved_from_op_depth = -1; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(scan_addition(&status, + op_root_abspath + ? &op_root_relpath + : NULL, + NULL, NULL, + NULL, NULL, NULL, + moved_from_abspath + ? &moved_from_relpath + : NULL, + (op_root_moved_from_abspath + || moved_from_delete_abspath) + ? &moved_from_op_root_relpath + : NULL, + moved_from_delete_abspath + ? &moved_from_op_depth + : NULL, + wcroot, local_relpath, scratch_pool, scratch_pool)); + + if (status != svn_wc__db_status_moved_here || !moved_from_relpath) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Path '%s' was not moved here"), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + + if (op_root_abspath) + *op_root_abspath = svn_dirent_join(wcroot->abspath, op_root_relpath, + result_pool); + + if (moved_from_abspath) + *moved_from_abspath = svn_dirent_join(wcroot->abspath, moved_from_relpath, + result_pool); + + if (op_root_moved_from_abspath) + *op_root_moved_from_abspath = svn_dirent_join(wcroot->abspath, + moved_from_op_root_relpath, + result_pool); + + /* The deleted node is either where we moved from, or one of its ancestors */ + if (moved_from_delete_abspath) + { + const char *tmp = moved_from_op_root_relpath; + + SVN_ERR_ASSERT(moved_from_op_depth >= 0); + + while (relpath_depth(tmp) > moved_from_op_depth) + tmp = svn_relpath_dirname(tmp, scratch_pool); + + *moved_from_delete_abspath = svn_dirent_join(wcroot->abspath, tmp, + scratch_pool); + } + + return SVN_NO_ERROR; +} + +/* ### + */ +static svn_error_t * +follow_moved_to(apr_array_header_t **moved_tos, + int op_depth, + const char *repos_path, + svn_revnum_t revision, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int working_op_depth; + const char *ancestor_relpath, *node_moved_to = NULL; + int i; + + SVN_ERR_ASSERT((!op_depth && !repos_path) || (op_depth && repos_path)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_OP_DEPTH_MOVED_TO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + working_op_depth = svn_sqlite__column_int(stmt, 0); + node_moved_to = svn_sqlite__column_text(stmt, 1, result_pool); + if (!repos_path) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row || svn_sqlite__column_revnum(stmt, 0)) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, + svn_sqlite__reset(stmt), + _("The base node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + repos_path = svn_sqlite__column_text(stmt, 2, scratch_pool); + revision = svn_sqlite__column_revnum(stmt, 3); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + if (node_moved_to) + { + svn_boolean_t have_row2; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_HERE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, node_moved_to, + relpath_depth(node_moved_to))); + SVN_ERR(svn_sqlite__step(&have_row2, stmt)); + if (!have_row2 || !svn_sqlite__column_int(stmt, 0) + || revision != svn_sqlite__column_revnum(stmt, 3) + || strcmp(repos_path, svn_sqlite__column_text(stmt, 2, NULL))) + node_moved_to = NULL; + SVN_ERR(svn_sqlite__reset(stmt)); + } + + if (node_moved_to) + { + struct svn_wc__db_moved_to_t *moved_to; + + moved_to = apr_palloc(result_pool, sizeof(*moved_to)); + moved_to->op_depth = working_op_depth; + moved_to->local_relpath = node_moved_to; + APR_ARRAY_PUSH(*moved_tos, struct svn_wc__db_moved_to_t *) = moved_to; + } + + /* A working row with moved_to, or no working row, and we are done. */ + if (node_moved_to || !have_row) + return SVN_NO_ERROR; + + /* Need to handle being moved via an ancestor. */ + ancestor_relpath = local_relpath; + for (i = relpath_depth(local_relpath); i > working_op_depth; --i) + { + const char *ancestor_moved_to; + + ancestor_relpath = svn_relpath_dirname(ancestor_relpath, scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_TO)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, ancestor_relpath, + working_op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR_ASSERT(have_row); + ancestor_moved_to = svn_sqlite__column_text(stmt, 0, scratch_pool); + SVN_ERR(svn_sqlite__reset(stmt)); + if (ancestor_moved_to) + { + node_moved_to + = svn_relpath_join(ancestor_moved_to, + svn_relpath_skip_ancestor(ancestor_relpath, + local_relpath), + result_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_HERE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, node_moved_to, + relpath_depth(ancestor_moved_to))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + ancestor_moved_to = NULL; + else if (!svn_sqlite__column_int(stmt, 0)) + { + svn_wc__db_status_t presence + = svn_sqlite__column_token(stmt, 1, presence_map); + if (presence != svn_wc__db_status_not_present) + ancestor_moved_to = NULL; + else + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row && !svn_sqlite__column_int(stmt, 0)) + ancestor_moved_to = NULL; + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + if (!ancestor_moved_to) + break; + /* verify repos_path points back? */ + } + if (ancestor_moved_to) + { + struct svn_wc__db_moved_to_t *moved_to; + + moved_to = apr_palloc(result_pool, sizeof(*moved_to)); + moved_to->op_depth = working_op_depth; + moved_to->local_relpath = node_moved_to; + APR_ARRAY_PUSH(*moved_tos, struct svn_wc__db_moved_to_t *) = moved_to; + + SVN_ERR(follow_moved_to(moved_tos, relpath_depth(ancestor_moved_to), + repos_path, revision, wcroot, node_moved_to, + result_pool, scratch_pool)); + break; + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_follow_moved_to(apr_array_header_t **moved_tos, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + *moved_tos = apr_array_make(result_pool, 0, + sizeof(struct svn_wc__db_moved_to_t *)); + + /* ### Wrap in a transaction */ + SVN_ERR(follow_moved_to(moved_tos, 0, NULL, SVN_INVALID_REVNUM, + wcroot, local_relpath, + result_pool, scratch_pool)); + + /* ### Convert moved_to to abspath */ + + return SVN_NO_ERROR; +} + +/* Extract the moved-to information for LOCAL_RELPATH at OP-DEPTH by + examining the lowest working node above OP_DEPTH. The output paths + are NULL if there is no move, otherwise: + + *MOVE_DST_RELPATH: the moved-to destination of LOCAL_RELPATH. + + *MOVE_DST_OP_ROOT_RELPATH: the moved-to destination of the root of + the move of LOCAL_RELPATH. This may be equal to *MOVE_DST_RELPATH + if LOCAL_RELPATH is the root of the move. + + *MOVE_SRC_ROOT_RELPATH: the root of the move source. For moves + inside a delete this will be different from *MOVE_SRC_OP_ROOT_RELPATH. + + *MOVE_SRC_OP_ROOT_RELPATH: the root of the source layer that + contains the move. For moves inside deletes this is the root of + the delete, for other moves this is the root of the move. + + Given a path A/B/C with A/B moved to X then for A/B/C + + MOVE_DST_RELPATH is X/C + MOVE_DST_OP_ROOT_RELPATH is X + MOVE_SRC_ROOT_RELPATH is A/B + MOVE_SRC_OP_ROOT_RELPATH is A/B + + If A is then deleted the MOVE_DST_RELPATH, MOVE_DST_OP_ROOT_RELPATH + and MOVE_SRC_ROOT_RELPATH remain the same but MOVE_SRC_OP_ROOT_RELPATH + changes to A. + + ### Think about combining with scan_deletion? Also with + ### scan_addition to get moved-to for replaces? Do we need to + ### return the op-root of the move source, i.e. A/B in the example + ### above? */ +svn_error_t * +svn_wc__db_op_depth_moved_to(const char **move_dst_relpath, + const char **move_dst_op_root_relpath, + const char **move_src_root_relpath, + const char **move_src_op_root_relpath, + int op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int delete_op_depth; + const char *relpath = local_relpath; + + *move_dst_relpath = *move_dst_op_root_relpath = NULL; + *move_src_root_relpath = *move_src_op_root_relpath = NULL; + + do + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_LOWEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, relpath, op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + delete_op_depth = svn_sqlite__column_int(stmt, 0); + *move_dst_op_root_relpath = svn_sqlite__column_text(stmt, 3, + result_pool); + if (*move_dst_op_root_relpath) + *move_src_root_relpath = apr_pstrdup(result_pool, relpath); + } + SVN_ERR(svn_sqlite__reset(stmt)); + if (!*move_dst_op_root_relpath) + relpath = svn_relpath_dirname(relpath, scratch_pool); + } + while (!*move_dst_op_root_relpath + && have_row && delete_op_depth <= relpath_depth(relpath)); + + if (*move_dst_op_root_relpath) + { + *move_dst_relpath + = svn_relpath_join(*move_dst_op_root_relpath, + svn_relpath_skip_ancestor(relpath, local_relpath), + result_pool); + while (delete_op_depth < relpath_depth(relpath)) + relpath = svn_relpath_dirname(relpath, scratch_pool); + *move_src_op_root_relpath = apr_pstrdup(result_pool, relpath); + } + + return SVN_NO_ERROR; +} + +/* Public (within libsvn_wc) absolute path version of + svn_wc__db_op_depth_moved_to with the op-depth hard-coded to + BASE. */ +svn_error_t * +svn_wc__db_base_moved_to(const char **move_dst_abspath, + const char **move_dst_op_root_abspath, + const char **move_src_root_abspath, + const char **move_src_op_root_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *move_dst_relpath, *move_dst_op_root_relpath; + const char *move_src_root_relpath, *move_src_op_root_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN(svn_wc__db_op_depth_moved_to(&move_dst_relpath, + &move_dst_op_root_relpath, + &move_src_root_relpath, + &move_src_op_root_relpath, + 0 /* BASE op-depth */, + wcroot, local_relpath, + scratch_pool, scratch_pool), + wcroot); + + if (move_dst_abspath) + *move_dst_abspath + = move_dst_relpath + ? svn_dirent_join(wcroot->abspath, move_dst_relpath, result_pool) + : NULL; + + if (move_dst_op_root_abspath) + *move_dst_op_root_abspath + = move_dst_op_root_relpath + ? svn_dirent_join(wcroot->abspath, move_dst_op_root_relpath, result_pool) + : NULL; + + if (move_src_root_abspath) + *move_src_root_abspath + = move_src_root_relpath + ? svn_dirent_join(wcroot->abspath, move_src_root_relpath, result_pool) + : NULL; + + if (move_src_op_root_abspath) + *move_src_op_root_abspath + = move_src_op_root_relpath + ? svn_dirent_join(wcroot->abspath, move_src_op_root_relpath, result_pool) + : NULL; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_upgrade_begin(svn_sqlite__db_t **sdb, + apr_int64_t *repos_id, + apr_int64_t *wc_id, + svn_wc__db_t *wc_db, + const char *dir_abspath, + const char *repos_root_url, + const char *repos_uuid, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + + /* Upgrade is inherently exclusive so specify exclusive locking. */ + SVN_ERR(create_db(sdb, repos_id, wc_id, dir_abspath, + repos_root_url, repos_uuid, + SDB_FILE, + NULL, SVN_INVALID_REVNUM, svn_depth_unknown, + TRUE /* exclusive */, + wc_db->state_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_pdh_create_wcroot(&wcroot, + apr_pstrdup(wc_db->state_pool, + dir_abspath), + *sdb, *wc_id, FORMAT_FROM_SDB, + FALSE /* auto-upgrade */, + FALSE /* enforce_empty_wq */, + wc_db->state_pool, scratch_pool)); + + /* The WCROOT is complete. Stash it into DB. */ + svn_hash_sets(wc_db->dir_data, wcroot->abspath, wcroot); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_upgrade_apply_dav_cache(svn_sqlite__db_t *sdb, + const char *dir_relpath, + apr_hash_t *cache_values, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_int64_t wc_id; + apr_hash_index_t *hi; + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_wc__db_util_fetch_wc_id(&wc_id, sdb, iterpool)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPDATE_BASE_NODE_DAV_CACHE)); + + /* Iterate over all the wcprops, writing each one to the wc_db. */ + for (hi = apr_hash_first(scratch_pool, cache_values); + hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + apr_hash_t *props = svn__apr_hash_index_val(hi); + const char *local_relpath; + + svn_pool_clear(iterpool); + + local_relpath = svn_relpath_join(dir_relpath, name, iterpool); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, iterpool)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_upgrade_apply_props(svn_sqlite__db_t *sdb, + const char *dir_abspath, + const char *local_relpath, + apr_hash_t *base_props, + apr_hash_t *revert_props, + apr_hash_t *working_props, + int original_format, + apr_int64_t wc_id, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int top_op_depth = -1; + int below_op_depth = -1; + svn_wc__db_status_t top_presence; + svn_wc__db_status_t below_presence; + int affected_rows; + + /* ### working_props: use set_props_txn. + ### if working_props == NULL, then skip. what if they equal the + ### pristine props? we should probably do the compare here. + ### + ### base props go into WORKING_NODE if avail, otherwise BASE. + ### + ### revert only goes into BASE. (and WORKING better be there!) + + Prior to 1.4.0 (ORIGINAL_FORMAT < 8), REVERT_PROPS did not exist. If a + file was deleted, then a copy (potentially with props) was disallowed + and could not replace the deletion. An addition *could* be performed, + but that would never bring its own props. + + 1.4.0 through 1.4.5 created the concept of REVERT_PROPS, but had a + bug in svn_wc_add_repos_file2() whereby a copy-with-props did NOT + construct a REVERT_PROPS if the target had no props. Thus, reverting + the delete/copy would see no REVERT_PROPS to restore, leaving the + props from the copy source intact, and appearing as if they are (now) + the base props for the previously-deleted file. (wc corruption) + + 1.4.6 ensured that an empty REVERT_PROPS would be established at all + times. See issue 2530, and r861670 as starting points. + + We will use ORIGINAL_FORMAT and SVN_WC__NO_REVERT_FILES to determine + the handling of our inputs, relative to the state of this node. + */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + top_op_depth = svn_sqlite__column_int(stmt, 0); + top_presence = svn_sqlite__column_token(stmt, 3, presence_map); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + below_op_depth = svn_sqlite__column_int(stmt, 0); + below_presence = svn_sqlite__column_token(stmt, 3, presence_map); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + /* Detect the buggy scenario described above. We cannot upgrade this + working copy if we have no idea where BASE_PROPS should go. */ + if (original_format > SVN_WC__NO_REVERT_FILES + && revert_props == NULL + && top_op_depth != -1 + && top_presence == svn_wc__db_status_normal + && below_op_depth != -1 + && below_presence != svn_wc__db_status_not_present) + { + /* There should be REVERT_PROPS, so it appears that we just ran into + the described bug. Sigh. */ + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("The properties of '%s' are in an " + "indeterminate state and cannot be " + "upgraded. See issue #2530."), + svn_dirent_local_style( + svn_dirent_join(dir_abspath, local_relpath, + scratch_pool), scratch_pool)); + } + + /* Need at least one row, or two rows if there are revert props */ + if (top_op_depth == -1 + || (below_op_depth == -1 && revert_props)) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Insufficient NODES rows for '%s'"), + svn_dirent_local_style( + svn_dirent_join(dir_abspath, local_relpath, + scratch_pool), scratch_pool)); + + /* one row, base props only: upper row gets base props + two rows, base props only: lower row gets base props + two rows, revert props only: lower row gets revert props + two rows, base and revert props: upper row gets base, lower gets revert */ + + + if (revert_props || below_op_depth == -1) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPDATE_NODE_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wc_id, local_relpath, top_op_depth)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 4, base_props, scratch_pool)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + SVN_ERR_ASSERT(affected_rows == 1); + } + + if (below_op_depth != -1) + { + apr_hash_t *props = revert_props ? revert_props : base_props; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_UPDATE_NODE_PROPS)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wc_id, local_relpath, below_op_depth)); + SVN_ERR(svn_sqlite__bind_properties(stmt, 4, props, scratch_pool)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + SVN_ERR_ASSERT(affected_rows == 1); + } + + /* If there are WORKING_PROPS, then they always go into ACTUAL_NODE. */ + if (working_props != NULL + && base_props != NULL) + { + apr_array_header_t *diffs; + + SVN_ERR(svn_prop_diffs(&diffs, working_props, base_props, scratch_pool)); + + if (diffs->nelts == 0) + working_props = NULL; /* No differences */ + } + + if (working_props != NULL) + { + SVN_ERR(set_actual_props(wc_id, local_relpath, working_props, + sdb, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_upgrade_insert_external(svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + const char *parent_abspath, + const char *def_local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t def_peg_revision, + svn_revnum_t def_revision, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *def_local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_int64_t repos_id; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* We know only of DEF_LOCAL_ABSPATH that it definitely belongs to "this" + * WC, i.e. where the svn:externals prop is set. The external target path + * itself may be "hidden behind" other working copies. */ + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &def_local_relpath, + db, def_local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_REPOSITORY)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", repos_root_url)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + repos_id = svn_sqlite__column_int64(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (!have_row) + { + /* Need to set up a new repository row. */ + SVN_ERR(create_repos_id(&repos_id, repos_root_url, repos_uuid, + wcroot->sdb, scratch_pool)); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_EXTERNAL)); + + /* wc_id, local_relpath, parent_relpath, presence, kind, def_local_relpath, + * repos_id, def_repos_relpath, def_operational_revision, def_revision */ + SVN_ERR(svn_sqlite__bindf(stmt, "issstsis", + wcroot->wc_id, + svn_dirent_skip_ancestor(wcroot->abspath, + local_abspath), + svn_dirent_skip_ancestor(wcroot->abspath, + parent_abspath), + "normal", + kind_map, kind, + def_local_relpath, + repos_id, + repos_relpath)); + + if (SVN_IS_VALID_REVNUM(def_peg_revision)) + SVN_ERR(svn_sqlite__bind_revnum(stmt, 9, def_peg_revision)); + + if (SVN_IS_VALID_REVNUM(def_revision)) + SVN_ERR(svn_sqlite__bind_revnum(stmt, 10, def_revision)); + + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_upgrade_get_repos_id(apr_int64_t *repos_id, + svn_sqlite__db_t *sdb, + const char *repos_root_url, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_REPOSITORY)); + SVN_ERR(svn_sqlite__bindf(stmt, "s", repos_root_url)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + return svn_error_createf(SVN_ERR_WC_DB_ERROR, svn_sqlite__reset(stmt), + _("Repository '%s' not found in the database"), + repos_root_url); + + *repos_id = svn_sqlite__column_int64(stmt, 0); + return svn_error_trace(svn_sqlite__reset(stmt)); +} + + +svn_error_t * +svn_wc__db_wq_add(svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *work_item, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + + /* Quick exit, if there are no work items to queue up. */ + if (work_item == NULL) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* Add the work item(s) to the WORK_QUEUE. */ + return svn_error_trace(add_work_items(wcroot->sdb, work_item, + scratch_pool)); +} + +/* The body of svn_wc__db_wq_fetch_next(). + */ +static svn_error_t * +wq_fetch_next(apr_uint64_t *id, + svn_skel_t **work_item, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_uint64_t completed_id, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + if (completed_id != 0) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WORK_ITEM)); + SVN_ERR(svn_sqlite__bind_int64(stmt, 1, completed_id)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORK_ITEM)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (!have_row) + { + *id = 0; + *work_item = NULL; + } + else + { + apr_size_t len; + const void *val; + + *id = svn_sqlite__column_int64(stmt, 0); + + val = svn_sqlite__column_blob(stmt, 1, &len, result_pool); + + *work_item = svn_skel__parse(val, len, result_pool); + } + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_wc__db_wq_fetch_next(apr_uint64_t *id, + svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + apr_uint64_t completed_id, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(id != NULL); + SVN_ERR_ASSERT(work_item != NULL); + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + wq_fetch_next(id, work_item, + wcroot, local_relpath, completed_id, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +/* Records timestamp and date for one or more files in wcroot */ +static svn_error_t * +wq_record(svn_wc__db_wcroot_t *wcroot, + apr_hash_t *record_map, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + for (hi = apr_hash_first(scratch_pool, record_map); hi; + hi = apr_hash_next(hi)) + { + const char *local_abspath = svn__apr_hash_index_key(hi); + const svn_io_dirent2_t *dirent = svn__apr_hash_index_val(hi); + const char *local_relpath = svn_dirent_skip_ancestor(wcroot->abspath, + local_abspath); + + svn_pool_clear(iterpool); + + if (! local_relpath) + continue; + + SVN_ERR(db_record_fileinfo(wcroot, local_relpath, + dirent->filesize, dirent->mtime, + iterpool)); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_wq_record_and_fetch_next(apr_uint64_t *id, + svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + apr_uint64_t completed_id, + apr_hash_t *record_map, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(id != NULL); + SVN_ERR_ASSERT(work_item != NULL); + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + svn_error_compose_create( + wq_fetch_next(id, work_item, + wcroot, local_relpath, completed_id, + result_pool, scratch_pool), + wq_record(wcroot, record_map, scratch_pool)), + wcroot); + + return SVN_NO_ERROR; +} + + + +/* ### temporary API. remove before release. */ +svn_error_t * +svn_wc__db_temp_get_format(int *format, + svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_error_t *err; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath)); + /* ### assert that we were passed a directory? */ + + err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_dir_abspath, scratch_pool, scratch_pool); + + /* If we hit an error examining this directory, then declare this + directory to not be a working copy. */ + if (err) + { + if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) + return svn_error_trace(err); + svn_error_clear(err); + + /* Remap the returned error. */ + *format = 0; + return svn_error_createf(SVN_ERR_WC_MISSING, NULL, + _("'%s' is not a working copy"), + svn_dirent_local_style(local_dir_abspath, + scratch_pool)); + } + + SVN_ERR_ASSERT(wcroot != NULL); + SVN_ERR_ASSERT(wcroot->format >= 1); + + *format = wcroot->format; + + return SVN_NO_ERROR; +} + +/* ### temporary API. remove before release. */ +svn_wc_adm_access_t * +svn_wc__db_temp_get_access(svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + svn_wc__db_wcroot_t *wcroot; + svn_error_t *err; + + SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_dir_abspath)); + + /* ### we really need to assert that we were passed a directory. sometimes + ### adm_retrieve_internal is asked about a file, and then it asks us + ### for an access baton for it. we should definitely return NULL, but + ### ideally: the caller would never ask us about a non-directory. */ + + err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_dir_abspath, scratch_pool, scratch_pool); + if (err) + { + svn_error_clear(err); + return NULL; + } + + if (!wcroot) + return NULL; + + return svn_hash_gets(wcroot->access_cache, local_dir_abspath); +} + + +/* ### temporary API. remove before release. */ +void +svn_wc__db_temp_set_access(svn_wc__db_t *db, + const char *local_dir_abspath, + svn_wc_adm_access_t *adm_access, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + svn_wc__db_wcroot_t *wcroot; + svn_error_t *err; + + SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_dir_abspath)); + /* ### assert that we were passed a directory? */ + + err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_dir_abspath, scratch_pool, scratch_pool); + if (err) + { + /* We don't even have a wcroot, so just bail. */ + svn_error_clear(err); + return; + } + + /* Better not override something already there. */ + SVN_ERR_ASSERT_NO_RETURN( + svn_hash_gets(wcroot->access_cache, local_dir_abspath) == NULL + ); + svn_hash_sets(wcroot->access_cache, local_dir_abspath, adm_access); +} + + +/* ### temporary API. remove before release. */ +svn_error_t * +svn_wc__db_temp_close_access(svn_wc__db_t *db, + const char *local_dir_abspath, + svn_wc_adm_access_t *adm_access, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + svn_wc__db_wcroot_t *wcroot; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath)); + /* ### assert that we were passed a directory? */ + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_dir_abspath, scratch_pool, scratch_pool)); + svn_hash_sets(wcroot->access_cache, local_dir_abspath, NULL); + + return SVN_NO_ERROR; +} + + +/* ### temporary API. remove before release. */ +void +svn_wc__db_temp_clear_access(svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + svn_wc__db_wcroot_t *wcroot; + svn_error_t *err; + + SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_dir_abspath)); + /* ### assert that we were passed a directory? */ + + err = svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_dir_abspath, scratch_pool, scratch_pool); + if (err) + { + svn_error_clear(err); + return; + } + + svn_hash_sets(wcroot->access_cache, local_dir_abspath, NULL); +} + + +apr_hash_t * +svn_wc__db_temp_get_all_access(svn_wc__db_t *db, + apr_pool_t *result_pool) +{ + apr_hash_t *result = apr_hash_make(result_pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(result_pool, db->dir_data); + hi; + hi = apr_hash_next(hi)) + { + const svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi); + + /* This is highly redundant, 'cause the same WCROOT will appear many + times in dir_data. */ + result = apr_hash_overlay(result_pool, result, wcroot->access_cache); + } + + return result; +} + + +svn_error_t * +svn_wc__db_temp_borrow_sdb(svn_sqlite__db_t **sdb, + svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + *sdb = wcroot->sdb; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_read_conflict_victims(const apr_array_header_t **victims, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_array_header_t *new_victims; + + /* The parent should be a working copy directory. */ + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* ### This will be much easier once we have all conflicts in one + field of actual*/ + + /* Look for text, tree and property conflicts in ACTUAL */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_CONFLICT_VICTIMS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + new_victims = apr_array_make(result_pool, 0, sizeof(const char *)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); + + APR_ARRAY_PUSH(new_victims, const char *) = + svn_relpath_basename(child_relpath, result_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + *victims = new_victims; + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_get_conflict_marker_files(). + */ +static svn_error_t * +get_conflict_marker_files(apr_hash_t **marker_files_p, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_hash_t *marker_files = apr_hash_make(result_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row && !svn_sqlite__column_is_null(stmt, 2)) + { + apr_size_t len; + const void *data = svn_sqlite__column_blob(stmt, 2, &len, NULL); + svn_skel_t *conflicts; + const apr_array_header_t *markers; + int i; + + conflicts = svn_skel__parse(data, len, scratch_pool); + + /* ### ADD markers to *marker_files */ + SVN_ERR(svn_wc__conflict_read_markers(&markers, db, wcroot->abspath, + conflicts, + result_pool, scratch_pool)); + + for (i = 0; markers && (i < markers->nelts); i++) + { + const char *marker_abspath = APR_ARRAY_IDX(markers, i, const char*); + + svn_hash_sets(marker_files, marker_abspath, ""); + } + } + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_CONFLICT_VICTIMS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + apr_size_t len; + const void *data = svn_sqlite__column_blob(stmt, 1, &len, NULL); + + const apr_array_header_t *markers; + int i; + + if (data) + { + svn_skel_t *conflicts; + conflicts = svn_skel__parse(data, len, scratch_pool); + + SVN_ERR(svn_wc__conflict_read_markers(&markers, db, wcroot->abspath, + conflicts, + result_pool, scratch_pool)); + + for (i = 0; markers && (i < markers->nelts); i++) + { + const char *marker_abspath = APR_ARRAY_IDX(markers, i, const char*); + + svn_hash_sets(marker_files, marker_abspath, ""); + } + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + if (apr_hash_count(marker_files)) + *marker_files_p = marker_files; + else + *marker_files_p = NULL; + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_wc__db_get_conflict_marker_files(apr_hash_t **marker_files, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + /* The parent should be a working copy directory. */ + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + get_conflict_marker_files(marker_files, wcroot, local_relpath, db, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_read_conflict(svn_skel_t **conflict, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + /* The parent should be a working copy directory. */ + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + return svn_error_trace(svn_wc__db_read_conflict_internal(conflict, wcroot, + local_relpath, + result_pool, + scratch_pool)); +} + +svn_error_t * +svn_wc__db_read_conflict_internal(svn_skel_t **conflict, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + /* Check if we have a conflict in ACTUAL */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ACTUAL_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (! have_row) + { + /* Do this while stmt is still open to avoid closing the sqlite + transaction and then reopening. */ + svn_sqlite__stmt_t *stmt_node; + svn_error_t *err; + + err = svn_sqlite__get_statement(&stmt_node, wcroot->sdb, + STMT_SELECT_NODE_INFO); + + if (err) + stmt_node = NULL; + else + err = svn_sqlite__bindf(stmt_node, "is", wcroot->wc_id, + local_relpath); + + if (!err) + err = svn_sqlite__step(&have_row, stmt_node); + + if (stmt_node) + err = svn_error_compose_create(err, + svn_sqlite__reset(stmt_node)); + + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + + if (have_row) + { + *conflict = NULL; + return SVN_NO_ERROR; + } + + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + { + apr_size_t cfl_len; + const void *cfl_data; + + /* svn_skel__parse doesn't copy data, so store in result_pool */ + cfl_data = svn_sqlite__column_blob(stmt, 2, &cfl_len, result_pool); + + if (cfl_data) + *conflict = svn_skel__parse(cfl_data, cfl_len, result_pool); + else + *conflict = NULL; + + return svn_error_trace(svn_sqlite__reset(stmt)); + } +} + + +svn_error_t * +svn_wc__db_read_kind(svn_node_kind_t *kind, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t allow_missing, + svn_boolean_t show_deleted, + svn_boolean_t show_hidden, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt_info; + svn_boolean_t have_info; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt_info, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt_info, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_info, stmt_info)); + + if (!have_info) + { + if (allow_missing) + { + *kind = svn_node_unknown; + SVN_ERR(svn_sqlite__reset(stmt_info)); + return SVN_NO_ERROR; + } + else + { + SVN_ERR(svn_sqlite__reset(stmt_info)); + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + } + + if (!(show_deleted && show_hidden)) + { + int op_depth = svn_sqlite__column_int(stmt_info, 0); + svn_boolean_t report_none = FALSE; + svn_wc__db_status_t status = svn_sqlite__column_token(stmt_info, 3, + presence_map); + + if (op_depth > 0) + SVN_ERR(convert_to_working_status(&status, status)); + + switch (status) + { + case svn_wc__db_status_not_present: + if (! (show_hidden && show_deleted)) + report_none = TRUE; + break; + case svn_wc__db_status_excluded: + case svn_wc__db_status_server_excluded: + if (! show_hidden) + report_none = TRUE; + break; + case svn_wc__db_status_deleted: + if (! show_deleted) + report_none = TRUE; + break; + default: + break; + } + + if (report_none) + { + *kind = svn_node_none; + return svn_error_trace(svn_sqlite__reset(stmt_info)); + } + } + + *kind = svn_sqlite__column_token(stmt_info, 4, kind_map); + + return svn_error_trace(svn_sqlite__reset(stmt_info)); +} + + +svn_error_t * +svn_wc__db_node_hidden(svn_boolean_t *hidden, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_wc__db_status_t status; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(read_info(&status, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + *hidden = (status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_not_present + || status == svn_wc__db_status_excluded); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_is_wcroot(svn_boolean_t *is_wcroot, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (*local_relpath != '\0') + { + *is_wcroot = FALSE; /* Node is a file, or has a parent directory within + the same wcroot */ + return SVN_NO_ERROR; + } + + *is_wcroot = TRUE; + + return SVN_NO_ERROR; +} + +/* Find a node's kind and whether it is switched, putting the outputs in + * *IS_SWITCHED and *KIND. Either of the outputs may be NULL if not wanted. + */ +static svn_error_t * +db_is_switched(svn_boolean_t *is_switched, + svn_node_kind_t *kind, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_status_t status; + apr_int64_t repos_id; + const char *repos_relpath; + const char *name; + const char *parent_local_relpath; + apr_int64_t parent_repos_id; + const char *parent_repos_relpath; + + SVN_ERR_ASSERT(*local_relpath != '\0'); /* Handled in wrapper */ + + SVN_ERR(read_info(&status, kind, NULL, &repos_relpath, &repos_id, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, scratch_pool, scratch_pool)); + + if (status == svn_wc__db_status_server_excluded + || status == svn_wc__db_status_excluded + || status == svn_wc__db_status_not_present) + { + return svn_error_createf( + SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + else if (! repos_relpath) + { + /* Node is shadowed; easy out */ + if (is_switched) + *is_switched = FALSE; + + return SVN_NO_ERROR; + } + + if (! is_switched) + return SVN_NO_ERROR; + + svn_relpath_split(&parent_local_relpath, &name, local_relpath, scratch_pool); + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &parent_repos_relpath, + &parent_repos_id, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, + wcroot, parent_local_relpath, + scratch_pool, scratch_pool)); + + if (repos_id != parent_repos_id) + *is_switched = TRUE; + else + { + const char *expected_relpath; + + expected_relpath = svn_relpath_join(parent_repos_relpath, name, + scratch_pool); + + *is_switched = (strcmp(expected_relpath, repos_relpath) != 0); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_is_switched(svn_boolean_t *is_wcroot, + svn_boolean_t *is_switched, + svn_node_kind_t *kind, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (is_switched) + *is_switched = FALSE; + + if (*local_relpath == '\0') + { + /* Easy out */ + if (is_wcroot) + *is_wcroot = TRUE; + + if (kind) + *kind = svn_node_dir; + return SVN_NO_ERROR; + } + + if (is_wcroot) + *is_wcroot = FALSE; + + if (! is_switched && ! kind) + return SVN_NO_ERROR; + + SVN_WC__DB_WITH_TXN( + db_is_switched(is_switched, kind, wcroot, local_relpath, scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_temp_wcroot_tempdir(const char **temp_dir_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(temp_dir_abspath != NULL); + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + *temp_dir_abspath = svn_dirent_join_many(result_pool, + wcroot->abspath, + svn_wc_get_adm_dir(scratch_pool), + WCROOT_TEMPDIR_RELPATH, + NULL); + return SVN_NO_ERROR; +} + + +/* Helper for wclock_obtain_cb() to steal an existing lock */ +static svn_error_t * +wclock_steal(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_DELETE_WC_LOCK)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_wclock_obtain(). + */ +static svn_error_t * +wclock_obtain_cb(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int levels_to_lock, + svn_boolean_t steal_lock, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_error_t *err; + const char *lock_relpath; + int max_depth; + int lock_depth; + svn_boolean_t got_row; + + svn_wc__db_wclock_t lock; + + /* Upgrade locks the root before the node exists. Apart from that + the root node always exists so we will just skip the check. + + ### Perhaps the lock for upgrade should be created when the db is + created? 1.6 used to lock .svn on creation. */ + if (local_relpath[0]) + { + svn_boolean_t exists; + + SVN_ERR(does_node_exist(&exists, wcroot, local_relpath)); + if (!exists) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + /* Check if there are nodes locked below the new lock root */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_FIND_WC_LOCK)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + lock_depth = relpath_depth(local_relpath); + max_depth = lock_depth + levels_to_lock; + + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + + while (got_row) + { + svn_boolean_t own_lock; + + lock_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool); + + /* If we are not locking with depth infinity, check if this lock + voids our lock request */ + if (levels_to_lock >= 0 + && relpath_depth(lock_relpath) > max_depth) + { + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + continue; + } + + /* Check if we are the lock owner, because we should be able to + extend our lock. */ + err = wclock_owns_lock(&own_lock, wcroot, lock_relpath, + TRUE, scratch_pool); + + if (err) + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + + if (!own_lock && !steal_lock) + { + SVN_ERR(svn_sqlite__reset(stmt)); + err = svn_error_createf(SVN_ERR_WC_LOCKED, NULL, + _("'%s' is already locked."), + path_for_error_message(wcroot, + lock_relpath, + scratch_pool)); + return svn_error_createf(SVN_ERR_WC_LOCKED, err, + _("Working copy '%s' locked."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + else if (!own_lock) + { + err = wclock_steal(wcroot, lock_relpath, scratch_pool); + + if (err) + SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt))); + } + + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + + if (steal_lock) + SVN_ERR(wclock_steal(wcroot, local_relpath, scratch_pool)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_WC_LOCK)); + lock_relpath = local_relpath; + + while (TRUE) + { + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, lock_relpath)); + + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + + if (got_row) + { + int levels = svn_sqlite__column_int(stmt, 0); + if (levels >= 0) + levels += relpath_depth(lock_relpath); + + SVN_ERR(svn_sqlite__reset(stmt)); + + if (levels == -1 || levels >= lock_depth) + { + + err = svn_error_createf( + SVN_ERR_WC_LOCKED, NULL, + _("'%s' is already locked."), + svn_dirent_local_style( + svn_dirent_join(wcroot->abspath, + lock_relpath, + scratch_pool), + scratch_pool)); + return svn_error_createf( + SVN_ERR_WC_LOCKED, err, + _("Working copy '%s' locked."), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + } + + break; /* There can't be interesting locks on higher nodes */ + } + else + SVN_ERR(svn_sqlite__reset(stmt)); + + if (!*lock_relpath) + break; + + lock_relpath = svn_relpath_dirname(lock_relpath, scratch_pool); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_WC_LOCK)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + levels_to_lock)); + err = svn_sqlite__insert(NULL, stmt); + if (err) + return svn_error_createf(SVN_ERR_WC_LOCKED, err, + _("Working copy '%s' locked"), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + + /* And finally store that we obtained the lock */ + lock.local_relpath = apr_pstrdup(wcroot->owned_locks->pool, local_relpath); + lock.levels = levels_to_lock; + APR_ARRAY_PUSH(wcroot->owned_locks, svn_wc__db_wclock_t) = lock; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_wclock_obtain(svn_wc__db_t *db, + const char *local_abspath, + int levels_to_lock, + svn_boolean_t steal_lock, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(levels_to_lock >= -1); + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + if (!steal_lock) + { + int i; + int depth = relpath_depth(local_relpath); + + for (i = 0; i < wcroot->owned_locks->nelts; i++) + { + svn_wc__db_wclock_t* lock = &APR_ARRAY_IDX(wcroot->owned_locks, + i, svn_wc__db_wclock_t); + + if (svn_relpath_skip_ancestor(lock->local_relpath, local_relpath) + && (lock->levels == -1 + || (lock->levels + relpath_depth(lock->local_relpath)) + >= depth)) + { + return svn_error_createf( + SVN_ERR_WC_LOCKED, NULL, + _("'%s' is already locked via '%s'."), + svn_dirent_local_style(local_abspath, scratch_pool), + path_for_error_message(wcroot, lock->local_relpath, + scratch_pool)); + } + } + } + + SVN_WC__DB_WITH_TXN( + wclock_obtain_cb(wcroot, local_relpath, levels_to_lock, steal_lock, + scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_wclock_find_root() and svn_wc__db_wclocked(). */ +static svn_error_t * +find_wclock(const char **lock_relpath, + svn_wc__db_wcroot_t *wcroot, + const char *dir_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int dir_depth = relpath_depth(dir_relpath); + const char *first_relpath; + + /* Check for locks on all directories that might be ancestors. + As our new apis only use recursive locks the number of locks stored + in the DB will be very low */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ANCESTOR_WCLOCKS)); + + /* Get the top level relpath to reduce the worst case number of results + to the number of directories below this node plus two. + (1: the node itself and 2: the wcroot). */ + first_relpath = strchr(dir_relpath, '/'); + + if (first_relpath != NULL) + first_relpath = apr_pstrndup(scratch_pool, dir_relpath, + first_relpath - dir_relpath); + else + first_relpath = dir_relpath; + + SVN_ERR(svn_sqlite__bindf(stmt, "iss", + wcroot->wc_id, + dir_relpath, + first_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + const char *relpath = svn_sqlite__column_text(stmt, 0, NULL); + + if (svn_relpath_skip_ancestor(relpath, dir_relpath)) + { + int locked_levels = svn_sqlite__column_int(stmt, 1); + int row_depth = relpath_depth(relpath); + + if (locked_levels == -1 + || locked_levels + row_depth >= dir_depth) + { + *lock_relpath = apr_pstrdup(result_pool, relpath); + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; + } + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + *lock_relpath = NULL; + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +static svn_error_t * +is_wclocked(svn_boolean_t *locked, + svn_wc__db_wcroot_t *wcroot, + const char *dir_relpath, + apr_pool_t *scratch_pool) +{ + const char *lock_relpath; + + SVN_ERR(find_wclock(&lock_relpath, wcroot, dir_relpath, + scratch_pool, scratch_pool)); + *locked = (lock_relpath != NULL); + return SVN_NO_ERROR; +} + + +svn_error_t* +svn_wc__db_wclock_find_root(const char **lock_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *lock_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + find_wclock(&lock_relpath, wcroot, local_relpath, + scratch_pool, scratch_pool), + wcroot); + + if (!lock_relpath) + *lock_abspath = NULL; + else + SVN_ERR(svn_wc__db_from_relpath(lock_abspath, db, wcroot->abspath, + lock_relpath, result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_wclocked(svn_boolean_t *locked, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + is_wclocked(locked, wcroot, local_relpath, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_wclock_release(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + int i; + apr_array_header_t *owned_locks; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + + VERIFY_USABLE_WCROOT(wcroot); + + /* First check and remove the owns-lock information as failure in + removing the db record implies that we have to steal the lock later. */ + owned_locks = wcroot->owned_locks; + for (i = 0; i < owned_locks->nelts; i++) + { + svn_wc__db_wclock_t *lock = &APR_ARRAY_IDX(owned_locks, i, + svn_wc__db_wclock_t); + + if (strcmp(lock->local_relpath, local_relpath) == 0) + break; + } + + if (i >= owned_locks->nelts) + return svn_error_createf(SVN_ERR_WC_NOT_LOCKED, NULL, + _("Working copy not locked at '%s'."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + if (i < owned_locks->nelts) + { + owned_locks->nelts--; + + /* Move the last item in the array to the deleted place */ + if (owned_locks->nelts > 0) + APR_ARRAY_IDX(owned_locks, i, svn_wc__db_wclock_t) = + APR_ARRAY_IDX(owned_locks, owned_locks->nelts, svn_wc__db_wclock_t); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_WC_LOCK)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + + +/* Like svn_wc__db_wclock_owns_lock() but taking WCROOT+LOCAL_RELPATH instead + of DB+LOCAL_ABSPATH. */ +static svn_error_t * +wclock_owns_lock(svn_boolean_t *own_lock, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_boolean_t exact, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *owned_locks; + int lock_level; + int i; + + *own_lock = FALSE; + owned_locks = wcroot->owned_locks; + lock_level = relpath_depth(local_relpath); + + if (exact) + { + for (i = 0; i < owned_locks->nelts; i++) + { + svn_wc__db_wclock_t *lock = &APR_ARRAY_IDX(owned_locks, i, + svn_wc__db_wclock_t); + + if (strcmp(lock->local_relpath, local_relpath) == 0) + { + *own_lock = TRUE; + return SVN_NO_ERROR; + } + } + } + else + { + for (i = 0; i < owned_locks->nelts; i++) + { + svn_wc__db_wclock_t *lock = &APR_ARRAY_IDX(owned_locks, i, + svn_wc__db_wclock_t); + + if (svn_relpath_skip_ancestor(lock->local_relpath, local_relpath) + && (lock->levels == -1 + || ((relpath_depth(lock->local_relpath) + lock->levels) + >= lock_level))) + { + *own_lock = TRUE; + return SVN_NO_ERROR; + } + } + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_wclock_owns_lock(svn_boolean_t *own_lock, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t exact, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + + if (!wcroot) + return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, + _("The node '%s' was not found."), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(wclock_owns_lock(own_lock, wcroot, local_relpath, exact, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_temp_op_end_directory_update(). + */ +static svn_error_t * +end_directory_update(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_wc__db_status_t base_status; + + SVN_ERR(svn_wc__db_base_get_info_internal(&base_status, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (base_status == svn_wc__db_status_normal) + return SVN_NO_ERROR; + + SVN_ERR_ASSERT(base_status == svn_wc__db_status_incomplete); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_NODE_BASE_PRESENCE)); + SVN_ERR(svn_sqlite__bindf(stmt, "ist", wcroot->wc_id, local_relpath, + presence_map, svn_wc__db_status_normal)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_temp_op_end_directory_update(svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_dir_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + end_directory_update(wcroot, local_relpath, scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_dir_abspath, svn_depth_empty, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_temp_op_start_directory_update(). + */ +static svn_error_t * +start_directory_update_txn(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const char *new_repos_relpath, + svn_revnum_t new_rev, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + /* Note: In the majority of calls, the repos_relpath is unchanged. */ + /* ### TODO: Maybe check if we can make repos_relpath NULL. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_BASE_NODE_PRESENCE_REVNUM_AND_REPOS_PATH)); + + SVN_ERR(svn_sqlite__bindf(stmt, "istrs", + wcroot->wc_id, + local_relpath, + presence_map, svn_wc__db_status_incomplete, + new_rev, + new_repos_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; + +} + +svn_error_t * +svn_wc__db_temp_op_start_directory_update(svn_wc__db_t *db, + const char *local_abspath, + const char *new_repos_relpath, + svn_revnum_t new_rev, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_rev)); + SVN_ERR_ASSERT(svn_relpath_is_canonical(new_repos_relpath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + start_directory_update_txn(wcroot, local_relpath, + new_repos_relpath, new_rev, scratch_pool), + wcroot); + + SVN_ERR(flush_entries(wcroot, local_abspath, svn_depth_empty, scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* The body of svn_wc__db_temp_op_make_copy(). This is + used by the update editor when deleting a base node tree would be a + tree-conflict because there are changes to subtrees. This function + inserts a copy of the base node tree below any existing working + subtrees. Given a tree: + + 0 1 2 3 + / normal - + A normal - + A/B normal - normal + A/B/C normal - base-del normal + A/F normal - normal + A/F/G normal - normal + A/F/H normal - base-deleted normal + A/F/E normal - not-present + A/X normal - + A/X/Y incomplete - + + This function adds layers to A and some of its descendants in an attempt + to make the working copy look like as if it were a copy of the BASE nodes. + + 0 1 2 3 + / normal - + A normal norm + A/B normal norm norm + A/B/C normal norm base-del normal + A/F normal norm norm + A/F/G normal norm norm + A/F/H normal norm not-pres + A/F/E normal norm base-del + A/X normal norm + A/X/Y incomplete incomplete + */ +static svn_error_t * +make_copy_txn(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + const svn_skel_t *conflicts, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_boolean_t add_working_base_deleted = FALSE; + svn_boolean_t remove_working = FALSE; + const apr_array_header_t *children; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_LOWEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, 0)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + svn_wc__db_status_t working_status; + int working_op_depth; + + working_status = svn_sqlite__column_token(stmt, 1, presence_map); + working_op_depth = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR_ASSERT(working_status == svn_wc__db_status_normal + || working_status == svn_wc__db_status_base_deleted + || working_status == svn_wc__db_status_not_present + || working_status == svn_wc__db_status_incomplete); + + /* Only change nodes in the layers where we are creating the copy. + Deletes in higher layers will just apply to the copy */ + if (working_op_depth <= op_depth) + { + add_working_base_deleted = TRUE; + + if (working_status == svn_wc__db_status_base_deleted) + remove_working = TRUE; + } + } + else + SVN_ERR(svn_sqlite__reset(stmt)); + + if (remove_working) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_DELETE_LOWEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + if (add_working_base_deleted) + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_DELETE_FROM_BASE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + else + { + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_WORKING_NODE_FROM_BASE_COPY)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Get the BASE children, as WORKING children don't need modifications */ + SVN_ERR(gather_repo_children(&children, wcroot, local_relpath, + 0, scratch_pool, iterpool)); + + for (i = 0; i < children->nelts; i++) + { + const char *name = APR_ARRAY_IDX(children, i, const char *); + const char *copy_relpath; + + svn_pool_clear(iterpool); + + copy_relpath = svn_relpath_join(local_relpath, name, iterpool); + + SVN_ERR(make_copy_txn(wcroot, copy_relpath, op_depth, NULL, NULL, + iterpool)); + } + + SVN_ERR(flush_entries(wcroot, svn_dirent_join(wcroot->abspath, local_relpath, + iterpool), + svn_depth_empty, iterpool)); + + if (conflicts) + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflicts, iterpool)); + + SVN_ERR(add_work_items(wcroot->sdb, work_items, iterpool)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_op_make_copy(svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflicts, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* The update editor is supposed to call this function when there is + no working node for LOCAL_ABSPATH. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, + _("Modification of '%s' already exists"), + path_for_error_message(wcroot, + local_relpath, + scratch_pool)); + + /* We don't allow copies to contain server-excluded nodes; + the update editor is going to have to bail out. */ + SVN_ERR(catch_copy_of_server_excluded(wcroot, local_relpath, scratch_pool)); + + SVN_WC__DB_WITH_TXN( + make_copy_txn(wcroot, local_relpath, + relpath_depth(local_relpath), conflicts, work_items, + scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_info_below_working(svn_boolean_t *have_base, + svn_boolean_t *have_work, + svn_wc__db_status_t *status, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + SVN_ERR(info_below_working(have_base, have_work, status, + wcroot, local_relpath, -1, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_get_not_present_descendants(const apr_array_header_t **descendants, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_NOT_PRESENT_DESCENDANTS)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isd", + wcroot->wc_id, + local_relpath, + relpath_depth(local_relpath))); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + { + apr_array_header_t *paths; + + paths = apr_array_make(result_pool, 4, sizeof(const char*)); + while (have_row) + { + const char *found_relpath = svn_sqlite__column_text(stmt, 0, NULL); + + APR_ARRAY_PUSH(paths, const char *) + = apr_pstrdup(result_pool, svn_relpath_skip_ancestor( + local_relpath, found_relpath)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + *descendants = paths; + } + else + *descendants = apr_array_make(result_pool, 0, sizeof(const char*)); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + + +/* Like svn_wc__db_min_max_revisions(), + * but accepts a WCROOT/LOCAL_RELPATH pair. */ +static svn_error_t * +get_min_max_revisions(svn_revnum_t *min_revision, + svn_revnum_t *max_revision, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_boolean_t committed, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_revnum_t min_rev, max_rev; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MIN_MAX_REVISIONS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step_row(stmt)); + + if (committed) + { + min_rev = svn_sqlite__column_revnum(stmt, 2); + max_rev = svn_sqlite__column_revnum(stmt, 3); + } + else + { + min_rev = svn_sqlite__column_revnum(stmt, 0); + max_rev = svn_sqlite__column_revnum(stmt, 1); + } + + /* The statement returns exactly one row. */ + SVN_ERR(svn_sqlite__reset(stmt)); + + if (min_revision) + *min_revision = min_rev; + if (max_revision) + *max_revision = max_rev; + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_min_max_revisions(svn_revnum_t *min_revision, + svn_revnum_t *max_revision, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t committed, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + return svn_error_trace(get_min_max_revisions(min_revision, max_revision, + wcroot, local_relpath, + committed, scratch_pool)); +} + + +/* Set *IS_SPARSE_CHECKOUT TRUE if LOCAL_RELPATH or any of the nodes + * within LOCAL_RELPATH is sparse, FALSE otherwise. */ +static svn_error_t * +is_sparse_checkout_internal(svn_boolean_t *is_sparse_checkout, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_HAS_SPARSE_NODES)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, + local_relpath)); + /* If this query returns a row, the working copy is sparse. */ + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + *is_sparse_checkout = have_row; + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + + +/* Like svn_wc__db_has_switched_subtrees(), + * but accepts a WCROOT/LOCAL_RELPATH pair. */ +static svn_error_t * +has_switched_subtrees(svn_boolean_t *is_switched, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const char *trail_url, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_int64_t repos_id; + const char *repos_relpath; + + /* Optional argument handling for caller */ + if (!is_switched) + return SVN_NO_ERROR; + + *is_switched = FALSE; + + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, NULL, NULL, + &repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + /* First do the cheap check where we only need info on the origin itself */ + if (trail_url != NULL) + { + const char *repos_root_url; + const char *url; + apr_size_t len1, len2; + + /* If the trailing part of the URL of the working copy directory + does not match the given trailing URL then the whole working + copy is switched. */ + + SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, NULL, wcroot->sdb, + repos_id, scratch_pool)); + url = svn_path_url_add_component2(repos_root_url, repos_relpath, + scratch_pool); + + len1 = strlen(trail_url); + len2 = strlen(url); + if ((len1 > len2) || strcmp(url + len2 - len1, trail_url)) + { + *is_switched = TRUE; + return SVN_NO_ERROR; + } + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_HAS_SWITCHED)); + SVN_ERR(svn_sqlite__bindf(stmt, "iss", wcroot->wc_id, local_relpath, repos_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + *is_switched = TRUE; + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_has_switched_subtrees(svn_boolean_t *is_switched, + svn_wc__db_t *db, + const char *local_abspath, + const char *trail_url, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + return svn_error_trace(has_switched_subtrees(is_switched, wcroot, + local_relpath, trail_url, + scratch_pool)); +} + +svn_error_t * +svn_wc__db_get_excluded_subtrees(apr_hash_t **excluded_subtrees, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ALL_EXCLUDED_DESCENDANTS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", + wcroot->wc_id, + local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + *excluded_subtrees = apr_hash_make(result_pool); + else + *excluded_subtrees = NULL; + + while (have_row) + { + const char *abs_path = + svn_dirent_join(wcroot->abspath, + svn_sqlite__column_text(stmt, 0, NULL), + result_pool); + svn_hash_sets(*excluded_subtrees, abs_path, abs_path); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + SVN_ERR(svn_sqlite__reset(stmt)); + return SVN_NO_ERROR; +} + +/* Like svn_wc__db_has_local_mods(), + * but accepts a WCROOT/LOCAL_RELPATH pair. + * ### This needs a DB as well as a WCROOT/RELPATH pair... */ +static svn_error_t * +has_local_mods(svn_boolean_t *is_modified, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + /* Check for additions or deletions. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SUBTREE_HAS_TREE_MODIFICATIONS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + /* If this query returns a row, the working copy is modified. */ + SVN_ERR(svn_sqlite__step(is_modified, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + if (! *is_modified) + { + /* Check for property modifications. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SUBTREE_HAS_PROP_MODIFICATIONS)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + /* If this query returns a row, the working copy is modified. */ + SVN_ERR(svn_sqlite__step(is_modified, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + } + + if (! *is_modified) + { + apr_pool_t *iterpool = NULL; + svn_boolean_t have_row; + + /* Check for text modifications. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_BASE_FILES_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + const char *node_abspath; + svn_filesize_t recorded_size; + apr_time_t recorded_time; + svn_boolean_t skip_check = FALSE; + svn_error_t *err; + + if (cancel_func) + { + err = cancel_func(cancel_baton); + if (err) + return svn_error_trace(svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + } + + svn_pool_clear(iterpool); + + node_abspath = svn_dirent_join(wcroot->abspath, + svn_sqlite__column_text(stmt, 0, + iterpool), + iterpool); + + recorded_size = get_recorded_size(stmt, 1); + recorded_time = svn_sqlite__column_int64(stmt, 2); + + if (recorded_size != SVN_INVALID_FILESIZE + && recorded_time != 0) + { + const svn_io_dirent2_t *dirent; + + err = svn_io_stat_dirent2(&dirent, node_abspath, FALSE, TRUE, + iterpool, iterpool); + if (err) + return svn_error_trace(svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + + if (dirent->kind != svn_node_file) + { + *is_modified = TRUE; /* Missing or obstruction */ + break; + } + else if (dirent->filesize == recorded_size + && dirent->mtime == recorded_time) + { + /* The file is not modified */ + skip_check = TRUE; + } + } + + if (! skip_check) + { + err = svn_wc__internal_file_modified_p(is_modified, + db, node_abspath, + FALSE, iterpool); + + if (err) + return svn_error_trace(svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + + if (*is_modified) + break; + } + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + if (iterpool) + svn_pool_destroy(iterpool); + + SVN_ERR(svn_sqlite__reset(stmt)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_has_local_mods(svn_boolean_t *is_modified, + svn_wc__db_t *db, + const char *local_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + return svn_error_trace(has_local_mods(is_modified, wcroot, local_relpath, + db, cancel_func, cancel_baton, + scratch_pool)); +} + + +/* The body of svn_wc__db_revision_status(). + */ +static svn_error_t * +revision_status_txn(svn_revnum_t *min_revision, + svn_revnum_t *max_revision, + svn_boolean_t *is_sparse_checkout, + svn_boolean_t *is_modified, + svn_boolean_t *is_switched, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + const char *trail_url, + svn_boolean_t committed, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_boolean_t exists; + + SVN_ERR(does_node_exist(&exists, wcroot, local_relpath)); + + if (!exists) + { + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("The node '%s' was not found."), + path_for_error_message(wcroot, local_relpath, + scratch_pool)); + } + + /* Determine mixed-revisionness. */ + SVN_ERR(get_min_max_revisions(min_revision, max_revision, wcroot, + local_relpath, committed, scratch_pool)); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + /* Determine sparseness. */ + SVN_ERR(is_sparse_checkout_internal(is_sparse_checkout, wcroot, + local_relpath, scratch_pool)); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + /* Check for switched nodes. */ + { + err = has_switched_subtrees(is_switched, wcroot, local_relpath, + trail_url, scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + + svn_error_clear(err); /* No Base node, but no fatal error */ + *is_switched = FALSE; + } + } + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + /* Check for local mods. */ + SVN_ERR(has_local_mods(is_modified, wcroot, local_relpath, db, + cancel_func, cancel_baton, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_revision_status(svn_revnum_t *min_revision, + svn_revnum_t *max_revision, + svn_boolean_t *is_sparse_checkout, + svn_boolean_t *is_modified, + svn_boolean_t *is_switched, + svn_wc__db_t *db, + const char *local_abspath, + const char *trail_url, + svn_boolean_t committed, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + revision_status_txn(min_revision, max_revision, + is_sparse_checkout, is_modified, is_switched, + wcroot, local_relpath, db, + trail_url, committed, cancel_func, cancel_baton, + scratch_pool), + wcroot); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_base_get_lock_tokens_recursive(apr_hash_t **lock_tokens, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_int64_t last_repos_id = INVALID_REPOS_ID; + const char *last_repos_root_url = NULL; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + *lock_tokens = apr_hash_make(result_pool); + + /* Fetch all the lock tokens in and under LOCAL_RELPATH. */ + SVN_ERR(svn_sqlite__get_statement( + &stmt, wcroot->sdb, + STMT_SELECT_BASE_NODE_LOCK_TOKENS_RECURSIVE)); + + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + apr_int64_t child_repos_id = svn_sqlite__column_int64(stmt, 0); + const char *child_relpath = svn_sqlite__column_text(stmt, 1, NULL); + const char *lock_token = svn_sqlite__column_text(stmt, 2, result_pool); + + if (child_repos_id != last_repos_id) + { + svn_error_t *err = svn_wc__db_fetch_repos_info(&last_repos_root_url, + NULL, wcroot->sdb, + child_repos_id, + scratch_pool); + + if (err) + { + return svn_error_trace( + svn_error_compose_create(err, + svn_sqlite__reset(stmt))); + } + + last_repos_id = child_repos_id; + } + + SVN_ERR_ASSERT(last_repos_root_url != NULL); + svn_hash_sets(*lock_tokens, + svn_path_url_add_component2(last_repos_root_url, + child_relpath, result_pool), + lock_token); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + return svn_sqlite__reset(stmt); +} + + +/* If EXPRESSION is false, cause the caller to return an SVN_ERR_WC_CORRUPT + * error, showing EXPRESSION and the caller's LOCAL_RELPATH in the message. */ +#define VERIFY(expression) \ + do { \ + if (! (expression)) \ + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, \ + _("database inconsistency at local_relpath='%s' verifying " \ + "expression '%s'"), local_relpath, #expression); \ + } while (0) + + +/* Verify consistency of the metadata concerning WCROOT. This is intended + * for use only during testing and debugging, so is not intended to be + * blazingly fast. + * + * This code is a complement to any verification that we can do in SQLite + * triggers. See, for example, 'wc-checks.sql'. + * + * Some more verification steps we might want to add are: + * + * * on every ACTUAL row (except root): a NODES row exists at its parent path + * * the op-depth root must always exist and every intermediate too + */ +static svn_error_t * +verify_wcroot(svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_ALL_NODES)); + SVN_ERR(svn_sqlite__bindf(stmt, "i", wcroot->wc_id)); + while (TRUE) + { + svn_boolean_t have_row; + const char *local_relpath, *parent_relpath; + int op_depth; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + break; + + op_depth = svn_sqlite__column_int(stmt, 0); + local_relpath = svn_sqlite__column_text(stmt, 1, iterpool); + parent_relpath = svn_sqlite__column_text(stmt, 2, iterpool); + + /* Verify parent_relpath is the parent path of local_relpath */ + VERIFY((parent_relpath == NULL) + ? (local_relpath[0] == '\0') + : (strcmp(svn_relpath_dirname(local_relpath, iterpool), + parent_relpath) == 0)); + + /* Verify op_depth <= the tree depth of local_relpath */ + VERIFY(op_depth <= relpath_depth(local_relpath)); + + /* Verify parent_relpath refers to a row that exists */ + /* TODO: Verify there is a suitable parent row - e.g. has op_depth <= + * the child's and a suitable presence */ + if (parent_relpath && svn_sqlite__column_is_null(stmt, 3)) + { + svn_sqlite__stmt_t *stmt2; + svn_boolean_t have_a_parent_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt2, wcroot->sdb, + STMT_SELECT_NODE_INFO)); + SVN_ERR(svn_sqlite__bindf(stmt2, "is", wcroot->wc_id, + parent_relpath)); + SVN_ERR(svn_sqlite__step(&have_a_parent_row, stmt2)); + VERIFY(have_a_parent_row); + SVN_ERR(svn_sqlite__reset(stmt2)); + } + } + svn_pool_destroy(iterpool); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * +svn_wc__db_verify(svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, wri_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(verify_wcroot(wcroot, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_bump_format(int *result_format, + const char *wcroot_abspath, + svn_wc__db_t *db, + apr_pool_t *scratch_pool) +{ + svn_sqlite__db_t *sdb; + svn_error_t *err; + int format; + + /* Do not scan upwards for a working copy root here to prevent accidental + * upgrades of any working copies the WCROOT might be nested in. + * Just try to open a DB at the specified path instead. */ + err = svn_wc__db_util_open_db(&sdb, wcroot_abspath, SDB_FILE, + svn_sqlite__mode_readwrite, + TRUE, /* exclusive */ + NULL, /* my statements */ + scratch_pool, scratch_pool); + if (err) + { + svn_error_t *err2; + apr_hash_t *entries; + + /* Could not open an sdb. Check for an entries file instead. */ + err2 = svn_wc__read_entries_old(&entries, wcroot_abspath, + scratch_pool, scratch_pool); + if (err2 || apr_hash_count(entries) == 0) + return svn_error_createf(SVN_ERR_WC_INVALID_OP_ON_CWD, + svn_error_compose_create(err, err2), + _("Can't upgrade '%s' as it is not a working copy root"), + svn_dirent_local_style(wcroot_abspath, scratch_pool)); + + /* An entries file was found. This is a pre-wc-ng working copy + * so suggest an upgrade. */ + return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, err, + _("Working copy '%s' is too old and must be upgraded to " + "at least format %d, as created by Subversion %s"), + svn_dirent_local_style(wcroot_abspath, scratch_pool), + SVN_WC__WC_NG_VERSION, + svn_wc__version_string_from_format(SVN_WC__WC_NG_VERSION)); + } + + SVN_ERR(svn_sqlite__read_schema_version(&format, sdb, scratch_pool)); + err = svn_wc__upgrade_sdb(result_format, wcroot_abspath, + sdb, format, scratch_pool); + + /* Make sure we return a different error than expected for upgrades from + entries */ + if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) + err = svn_error_create(SVN_ERR_WC_UNSUPPORTED_FORMAT, err, + _("Working copy upgrade failed")); + + err = svn_error_compose_create(err, svn_sqlite__close(sdb)); + + return svn_error_trace(err); +} + +svn_error_t * +svn_wc__db_vacuum(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, STMT_VACUUM)); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/wc_db.h b/subversion/libsvn_wc/wc_db.h new file mode 100644 index 000000000000..154262d7d21a --- /dev/null +++ b/subversion/libsvn_wc/wc_db.h @@ -0,0 +1,3413 @@ +/** + * @copyright + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * @endcopyright + * + * @file svn_wc_db.h + * @brief The Subversion Working Copy Library - Metadata/Base-Text Support + * + * Requires: + * - A working copy + * + * Provides: + * - Ability to manipulate working copy's administrative files. + * + * Used By: + * - The main working copy library + */ + +#ifndef SVN_WC_DB_H +#define SVN_WC_DB_H + +#include "svn_wc.h" + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_config.h" +#include "svn_io.h" + +#include "private/svn_skel.h" +#include "private/svn_sqlite.h" +#include "private/svn_wc_private.h" + +#include "svn_private_config.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* INTERFACE CONVENTIONS + + "OUT" PARAMETERS + + There are numerous functions within this API which take a (large) number + of "out" parameters. These are listed individually, rather than combined + into a struct, so that a caller can be fine-grained about the which + pieces of information are being requested. In many cases, only a subset + is required, so the implementation can perform various optimizations + to fulfill the limited request for information. + + + POOLS + + wc_db uses the dual-pool paradigm for all of its functions. Any OUT + parameter will be allocated within the result pool, and all temporary + allocations will be performed within the scratch pool. + + The pool that DB is allocated within (the "state" pool) is only used + for a few, limited allocations to track each of the working copy roots + that the DB is asked to operate upon. The memory usage on this pool + is O(# wcroots), which should normally be one or a few. Custom clients + which hold open structures over a significant period of time should + pay particular attention to the number of roots touched, and the + resulting impact on memory consumption (which should still be minimal). + + + PARAMETER CONVENTIONS + + * Parameter Order + - any output arguments + - DB + - LOCAL_ABSPATH + - any other input arguments + - RESULT_POOL + - SCRATCH_POOL + + * DB + This parameter is the primary context for all operations on the + metadata for working copies. This parameter is passed to almost every + function, and maintains information and state about every working + copy "touched" by any of the APIs in this interface. + + * *_ABSPATH + All *_ABSPATH parameters in this API are absolute paths in the local + filesystem, represented in Subversion internal canonical form. + + * LOCAL_ABSPATH + This parameter specifies a particular *versioned* node in the local + filesystem. From this node, a working copy root is implied, and will + be used for the given API operation. + + * LOCAL_DIR_ABSPATH + This parameter is similar to LOCAL_ABSPATH, but the semantics of the + parameter and operation require the node to be a directory within + the working copy. + + * WRI_ABSPATH + This is a "Working copy Root Indicator" path. This refers to a location + in the local filesystem that is anywhere inside a working copy. The given + operation will be performed within the context of the root of that + working copy. This does not necessarily need to refer to a specific + versioned node or the root of a working copy (although it can) -- any + location, existing or not, is sufficient, as long as it is inside a + working copy. + ### TODO: Define behaviour for switches and externals. + ### Preference has been stated that WRI_ABSPATH should imply the root + ### of the parent WC of all switches and externals, but that may + ### not play out well, especially with multiple repositories involved. +*/ + +/* Context data structure for interacting with the administrative data. */ +typedef struct svn_wc__db_t svn_wc__db_t; + + +/* Enumerated values describing the state of a node. */ +typedef enum svn_wc__db_status_t { + /* The node is present and has no known modifications applied to it. */ + svn_wc__db_status_normal, + + /* The node has been added (potentially obscuring a delete or move of + the BASE node; see HAVE_BASE param [### What param? This is an enum + not a function.] ). The text will be marked as + modified, and if properties exist, they will be marked as modified. + + In many cases svn_wc__db_status_added means any of added, moved-here + or copied-here. See individual functions for clarification and + svn_wc__db_scan_addition() to get more details. */ + svn_wc__db_status_added, + + /* This node has been added with history, based on the move source. + Text and property modifications are based on whether changes have + been made against their pristine versions. */ + svn_wc__db_status_moved_here, + + /* This node has been added with history, based on the copy source. + Text and property modifications are based on whether changes have + been made against their pristine versions. */ + svn_wc__db_status_copied, + + /* This node has been deleted. No text or property modifications + will be present. */ + svn_wc__db_status_deleted, + + /* This node was named by the server, but no information was provided. */ + svn_wc__db_status_server_excluded, + + /* This node has been administratively excluded. */ + svn_wc__db_status_excluded, + + /* This node is not present in this revision. This typically happens + when a node is deleted and committed without updating its parent. + The parent revision indicates it should be present, but this node's + revision states otherwise. */ + svn_wc__db_status_not_present, + + /* This node is known, but its information is incomplete. Generally, + it should be treated similar to the other missing status values + until some (later) process updates the node with its data. + + When the incomplete status applies to a directory, the list of + children and the list of its base properties as recorded in the + working copy do not match their working copy versions. + The update editor can complete a directory by using a different + update algorithm. */ + svn_wc__db_status_incomplete, + + /* The BASE node has been marked as deleted. Only used as an internal + status in wc_db.c and entries.c. */ + svn_wc__db_status_base_deleted + +} svn_wc__db_status_t; + +/* Lock information. We write/read it all as one, so let's use a struct + for convenience. */ +typedef struct svn_wc__db_lock_t { + /* The lock token */ + const char *token; + + /* The owner of the lock, possibly NULL */ + const char *owner; + + /* A comment about the lock, possibly NULL */ + const char *comment; + + /* The date the lock was created */ + apr_time_t date; +} svn_wc__db_lock_t; + + +/* ### NOTE: I have not provided docstrings for most of this file at this + ### point in time. The shape and extent of this API is still in massive + ### flux. I'm iterating in public, but do not want to doc until it feels + ### like it is "Right". +*/ + +/* ### where/how to handle: text_time, locks, working_size */ + + +/* + @defgroup svn_wc__db_admin General administrative functions + @{ +*/ + +/* Open a working copy administrative database context. + + This context is (initially) not associated with any particular working + copy directory or working copy root (wcroot). As operations are performed, + this context will load the appropriate wcroot information. + + The context is returned in DB. + + CONFIG should hold the various configuration options that may apply to + the administrative operation. It should live at least as long as the + RESULT_POOL parameter. + + When OPEN_WITHOUT_UPGRADE is TRUE, then the working copy databases will + be opened even when an old database format is found/detected during + the operation of a wc_db API). If open_without_upgrade is FALSE and an + upgrade is required, then SVN_ERR_WC_UPGRADE_REQUIRED will be returned + from that API. + Passing TRUE will allow a bare minimum of APIs to function (most notably, + the temp_get_format() function will always return a value) since most of + these APIs expect a current-format database to be present. + + If ENFORCE_EMPTY_WQ is TRUE, then any databases with stale work items in + their work queue will raise an error when they are opened. The operation + will raise SVN_ERR_WC_CLEANUP_REQUIRED. Passing FALSE for this routine + means that the work queue is being processed (via 'svn cleanup') and all + operations should be allowed. + + The DB will be closed when RESULT_POOL is cleared. It may also be closed + manually using svn_wc__db_close(). In particular, this will close any + SQLite databases that have been opened and cached. + + The context is allocated in RESULT_POOL. This pool is *retained* and used + for future allocations within the DB. Be forewarned about unbounded + memory growth if this DB is used across an unbounded number of wcroots + and versioned directories. + + Temporary allocations will be made in SCRATCH_POOL. +*/ +svn_error_t * +svn_wc__db_open(svn_wc__db_t **db, + svn_config_t *config, + svn_boolean_t open_without_upgrade, + svn_boolean_t enforce_empty_wq, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Close DB. */ +svn_error_t * +svn_wc__db_close(svn_wc__db_t *db); + + +/* Initialize the SDB for LOCAL_ABSPATH, which should be a working copy path. + + A REPOSITORY row will be constructed for the repository identified by + REPOS_ROOT_URL and REPOS_UUID. Neither of these may be NULL. + + A BASE_NODE row will be created for the directory at REPOS_RELPATH at + revision INITIAL_REV. + If INITIAL_REV is greater than zero, then the node will be marked as + "incomplete" because we don't know its children. Contrary, if the + INITIAL_REV is zero, then this directory should represent the root and + we know it has no children, so the node is complete. + + ### Is there any benefit to marking it 'complete' if rev==0? Seems like + ### an unnecessary special case. + + DEPTH is the initial depth of the working copy; it must be a definite + depth, not svn_depth_unknown. + + Use SCRATCH_POOL for temporary allocations. +*/ +svn_error_t * +svn_wc__db_init(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t initial_rev, + svn_depth_t depth, + apr_pool_t *scratch_pool); + + +/* Compute the LOCAL_RELPATH for the given LOCAL_ABSPATH, relative + from wri_abspath. + + The LOCAL_RELPATH is a relative path to the working copy's root. That + root will be located by this function, and the path will be relative to + that location. If LOCAL_ABSPATH is the wcroot directory, then "" will + be returned. + + The LOCAL_RELPATH should ONLY be used for persisting paths to disk. + Those paths should not be abspaths, otherwise the working copy cannot + be moved. The working copy library should not make these paths visible + in its API (which should all be abspaths), and it should not be using + relpaths for other processing. + + LOCAL_RELPATH will be allocated in RESULT_POOL. All other (temporary) + allocations will be made in SCRATCH_POOL. + + This function is available when DB is opened with the OPEN_WITHOUT_UPGRADE + option. +*/ +svn_error_t * +svn_wc__db_to_relpath(const char **local_relpath, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Compute the LOCAL_ABSPATH for a LOCAL_RELPATH located within the working + copy identified by WRI_ABSPATH. + + This is the reverse of svn_wc__db_to_relpath. It should be used for + returning a persisted relpath back into an abspath. + + LOCAL_ABSPATH will be allocated in RESULT_POOL. All other (temporary) + allocations will be made in SCRATCH_POOL. + + This function is available when DB is opened with the OPEN_WITHOUT_UPGRADE + option. + */ +svn_error_t * +svn_wc__db_from_relpath(const char **local_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Compute the working copy root WCROOT_ABSPATH for WRI_ABSPATH using DB. + + This function is available when DB is opened with the OPEN_WITHOUT_UPGRADE + option. + */ +svn_error_t * +svn_wc__db_get_wcroot(const char **wcroot_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* @} */ + +/* Different kinds of trees + + The design doc mentions three different kinds of trees, BASE, WORKING and + ACTUAL: http://svn.apache.org/repos/asf/subversion/trunk/notes/wc-ng-design + We have different APIs to handle each tree, enumerated below, along with + a blurb to explain what that tree represents. +*/ + +/* @defgroup svn_wc__db_base BASE tree management + + BASE is what we get from the server. It is the *absolute* pristine copy. + You need to use checkout, update, switch, or commit to alter your view of + the repository. + + In the BASE tree, each node corresponds to a particular node-rev in the + repository. It can be a mixed-revision tree. Each node holds either a + copy of the node-rev as it exists in the repository (if presence = + 'normal'), or a place-holder (if presence = 'server-excluded' or 'excluded' or + 'not-present'). + + @{ +*/ + +/* Add or replace a directory in the BASE tree. + + The directory is located at LOCAL_ABSPATH on the local filesystem, and + corresponds to <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID> in the + repository, at revision REVISION. + + The directory properties are given by the PROPS hash (which is + const char *name => const svn_string_t *). + + The last-change information is given by <CHANGED_REV, CHANGED_DATE, + CHANGED_AUTHOR>. + + The directory's children are listed in CHILDREN, as an array of + const char *. The child nodes do NOT have to exist when this API + is called. For each child node which does not exists, an "incomplete" + node will be added. These child nodes will be added regardless of + the DEPTH value. The caller must sort out which must be recorded, + and which must be omitted. + + This subsystem does not use DEPTH, but it can be recorded here in + the BASE tree for higher-level code to use. + + If DAV_CACHE is not NULL, sets LOCAL_ABSPATH's dav cache to the specified + data. + + If CONFLICT is not NULL, then it describes a conflict for this node. The + node will be record as conflicted (in ACTUAL). + + If UPDATE_ACTUAL_PROPS is TRUE, set the properties store NEW_ACTUAL_PROPS + as the new set of properties in ACTUAL. If NEW_ACTUAL_PROPS is NULL or + when the value of NEW_ACTUAL_PROPS matches NEW_PROPS, store NULL in + ACTUAL, to mark the properties unmodified. + + If NEW_IPROPS is not NULL, then it is a depth-first ordered array of + svn_prop_inherited_item_t * structures that is set as the base node's + inherited_properties. + + Any work items that are necessary as part of this node construction may + be passed in WORK_ITEMS. + + All temporary allocations will be made in SCRATCH_POOL. +*/ +svn_error_t * +svn_wc__db_base_add_directory(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const apr_array_header_t *children, + svn_depth_t depth, + apr_hash_t *dav_cache, + const svn_skel_t *conflict, + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + +/* Add a new directory in BASE, whether WORKING nodes exist or not. Mark it + as incomplete and with revision REVISION. If REPOS_RELPATH is not NULL, + apply REPOS_RELPATH, REPOS_ROOT_URL and REPOS_UUID. + Perform all temporary allocations in SCRATCH_POOL. + */ +svn_error_t * +svn_wc__db_base_add_incomplete_directory(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_depth_t depth, + svn_boolean_t insert_base_deleted, + svn_boolean_t delete_working, + svn_skel_t *conflict, + svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* Add or replace a file in the BASE tree. + + The file is located at LOCAL_ABSPATH on the local filesystem, and + corresponds to <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID> in the + repository, at revision REVISION. + + The file properties are given by the PROPS hash (which is + const char *name => const svn_string_t *). + + The last-change information is given by <CHANGED_REV, CHANGED_DATE, + CHANGED_AUTHOR>. + + The checksum of the file contents is given in CHECKSUM. An entry in + the pristine text base is NOT required when this API is called. + + If DAV_CACHE is not NULL, sets LOCAL_ABSPATH's dav cache to the specified + data. + + If CONFLICT is not NULL, then it describes a conflict for this node. The + node will be record as conflicted (in ACTUAL). + + If UPDATE_ACTUAL_PROPS is TRUE, set the properties store NEW_ACTUAL_PROPS + as the new set of properties in ACTUAL. If NEW_ACTUAL_PROPS is NULL or + when the value of NEW_ACTUAL_PROPS matches NEW_PROPS, store NULL in + ACTUAL, to mark the properties unmodified. + + Any work items that are necessary as part of this node construction may + be passed in WORK_ITEMS. + + Unless KEEP_RECORDED_INFO is set to TRUE, recorded size and timestamp values + will be cleared. + + All temporary allocations will be made in SCRATCH_POOL. +*/ +svn_error_t * +svn_wc__db_base_add_file(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const svn_checksum_t *checksum, + apr_hash_t *dav_cache, + svn_boolean_t delete_working, + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, + svn_boolean_t keep_recorded_info, + svn_boolean_t insert_base_deleted, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* Add or replace a symlink in the BASE tree. + + The symlink is located at LOCAL_ABSPATH on the local filesystem, and + corresponds to <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID> in the + repository, at revision REVISION. + + The symlink's properties are given by the PROPS hash (which is + const char *name => const svn_string_t *). + + The last-change information is given by <CHANGED_REV, CHANGED_DATE, + CHANGED_AUTHOR>. + + The target of the symlink is specified by TARGET. + + If DAV_CACHE is not NULL, sets LOCAL_ABSPATH's dav cache to the specified + data. + + If CONFLICT is not NULL, then it describes a conflict for this node. The + node will be record as conflicted (in ACTUAL). + + If UPDATE_ACTUAL_PROPS is TRUE, set the properties store NEW_ACTUAL_PROPS + as the new set of properties in ACTUAL. If NEW_ACTUAL_PROPS is NULL or + when the value of NEW_ACTUAL_PROPS matches NEW_PROPS, store NULL in + ACTUAL, to mark the properties unmodified. + + Any work items that are necessary as part of this node construction may + be passed in WORK_ITEMS. + + All temporary allocations will be made in SCRATCH_POOL. +*/ +/* ### KFF: This is an interesting question, because currently + ### symlinks are versioned as regular files with the svn:special + ### property; then the file's text contents indicate that it is a + ### symlink and where that symlink points. That's for portability: + ### you can check 'em out onto a platform that doesn't support + ### symlinks, and even modify the link and check it back in. It's + ### a great solution; but then the question for wc-ng is: + ### + ### Suppose you check out a symlink on platform X and platform Y. + ### X supports symlinks; Y does not. Should the wc-ng storage for + ### those two be the same? I mean, on platform Y, the file is just + ### going to look and behave like a regular file. It would be sort + ### of odd for the wc-ng storage for that file to be of a different + ### type from all the other files. (On the other hand, maybe it's + ### weird today that the wc-1 storage for a working symlink is to + ### be like a regular file (i.e., regular text-base and whatnot). + ### + ### I'm still feeling my way around this problem; just pointing out + ### the issues. + + ### gjs: symlinks are stored in the database as first-class objects, + ### rather than in the filesystem as "special" regular files. thus, + ### all portability concerns are moot. higher-levels can figure out + ### how to represent the link in ACTUAL. higher-levels can also + ### deal with translating to/from the svn:special property and + ### the plain-text file contents. + ### dlr: What about hard links? At minimum, mention in doc string. +*/ +svn_error_t * +svn_wc__db_base_add_symlink(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const char *target, + apr_hash_t *dav_cache, + svn_boolean_t delete_working, + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + apr_array_header_t *new_iprops, + svn_boolean_t keep_recorded_info, + svn_boolean_t insert_base_deleted, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* Create a node in the BASE tree that is present in name only. + + The new node will be located at LOCAL_ABSPATH, and correspond to the + repository node described by <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID> + at revision REVISION. + + The node's kind is described by KIND, and the reason for its absence + is specified by STATUS. Only these values are allowed for STATUS: + + svn_wc__db_status_server_excluded + svn_wc__db_status_excluded + + If CONFLICT is not NULL, then it describes a conflict for this node. The + node will be record as conflicted (in ACTUAL). + + Any work items that are necessary as part of this node construction may + be passed in WORK_ITEMS. + + All temporary allocations will be made in SCRATCH_POOL. +*/ +svn_error_t * +svn_wc__db_base_add_excluded_node(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_node_kind_t kind, + svn_wc__db_status_t status, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* Create a node in the BASE tree that is present in name only. + + The new node will be located at LOCAL_ABSPATH, and correspond to the + repository node described by <REPOS_RELPATH, REPOS_ROOT_URL, REPOS_UUID> + at revision REVISION. + + The node's kind is described by KIND, and the reason for its absence + is 'svn_wc__db_status_not_present'. + + If CONFLICT is not NULL, then it describes a conflict for this node. The + node will be record as conflicted (in ACTUAL). + + Any work items that are necessary as part of this node construction may + be passed in WORK_ITEMS. + + All temporary allocations will be made in SCRATCH_POOL. +*/ +svn_error_t * +svn_wc__db_base_add_not_present_node(svn_wc__db_t *db, + const char *local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + svn_node_kind_t kind, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* Remove a node and all its descendants from the BASE tree. This handles + the deletion of a tree from the update editor and some file external + scenarios. + + The node to remove is indicated by LOCAL_ABSPATH from the local + filesystem. + + This operation *installs* workqueue operations to update the local + filesystem after the database operation. + + To maintain a consistent database this function will also remove + any working node that marks LOCAL_ABSPATH as base-deleted. If this + results in there being no working node for LOCAL_ABSPATH then any + actual node will be removed if the actual node does not mark a + conflict. + + If KEEP_AS_WORKING is TRUE, then the base tree is copied to higher + layers as a copy of itself before deleting the BASE nodes. + + If KEEP_AS_WORKING is FALSE, and QUEUE_DELETES is TRUE, also queue + workqueue items to delete all in-wc representations that aren't + shadowed by higher layers. + (With KEEP_AS_WORKING TRUE, this is a no-op, as everything is + automatically shadowed by the created copy) + + If NOT_PRESENT_REVISION specifies a valid revision a not-present + node is installed in BASE node with kind NOT_PRESENT_KIND after + deleting. + + If CONFLICT and/or WORK_ITEMS are passed they are installed as part + of the operation, after the work items inserted by the operation + itself. +*/ +svn_error_t * +svn_wc__db_base_remove(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t keep_as_working, + svn_boolean_t queue_deletes, + svn_revnum_t not_present_revision, + svn_skel_t *conflict, + svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* Retrieve information about a node in the BASE tree. + + For the BASE node implied by LOCAL_ABSPATH from the local filesystem, + return information in the provided OUT parameters. Each OUT parameter + may be NULL, indicating that specific item is not requested. + + If there is no information about this node, then SVN_ERR_WC_PATH_NOT_FOUND + will be returned. + + The OUT parameters, and their "not available" values are: + STATUS n/a (always available) + KIND n/a (always available) + REVISION SVN_INVALID_REVNUM + REPOS_RELPATH NULL (caller should scan up) + REPOS_ROOT_URL NULL (caller should scan up) + REPOS_UUID NULL (caller should scan up) + CHANGED_REV SVN_INVALID_REVNUM + CHANGED_DATE 0 + CHANGED_AUTHOR NULL + DEPTH svn_depth_unknown + CHECKSUM NULL + TARGET NULL + LOCK NULL + + HAD_PROPS FALSE + PROPS NULL + + UPDATE_ROOT FALSE + + If the STATUS is normal, the REPOS_* values will be non-NULL. + + If DEPTH is requested, and the node is NOT a directory, then the + value will be set to svn_depth_unknown. If LOCAL_ABSPATH is a link, + it's up to the caller to resolve depth for the link's target. + + If CHECKSUM is requested, and the node is NOT a file, then it will + be set to NULL. + + If TARGET is requested, and the node is NOT a symlink, then it will + be set to NULL. + + *PROPS maps "const char *" names to "const svn_string_t *" values. If + the base node is capable of having properties but has none, set + *PROPS to an empty hash. If its status is such that it cannot have + properties, set *PROPS to NULL. + + If UPDATE_ROOT is requested, set it to TRUE if the node should only + be updated when it is the root of an update (e.g. file externals). + + All returned data will be allocated in RESULT_POOL. All temporary + allocations will be made in SCRATCH_POOL. +*/ +svn_error_t * +svn_wc__db_base_get_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + svn_wc__db_lock_t **lock, + svn_boolean_t *had_props, + apr_hash_t **props, + svn_boolean_t *update_root, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Structure returned by svn_wc__db_base_get_children_info. Only has the + fields needed by the adm crawler. */ +struct svn_wc__db_base_info_t { + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_revnum_t revnum; + const char *repos_relpath; + const char *repos_root_url; + svn_depth_t depth; + svn_boolean_t update_root; + svn_wc__db_lock_t *lock; +}; + +/* Return in *NODES a hash mapping name->struct svn_wc__db_base_info_t for + the children of DIR_ABSPATH at op_depth 0. + */ +svn_error_t * +svn_wc__db_base_get_children_info(apr_hash_t **nodes, + svn_wc__db_t *db, + const char *dir_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Set *PROPS to the properties of the node LOCAL_ABSPATH in the BASE tree. + + *PROPS maps "const char *" names to "const svn_string_t *" values. + If the node has no properties, set *PROPS to an empty hash. + *PROPS will never be set to NULL. + If the node is not present in the BASE tree (with presence 'normal' + or 'incomplete'), return an error. + Allocate *PROPS and its keys and values in RESULT_POOL. +*/ +svn_error_t * +svn_wc__db_base_get_props(apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Return a list of the BASE tree node's children's names. + + For the node indicated by LOCAL_ABSPATH, this function will return + the names of all of its children in the array CHILDREN. The array + elements are const char * values. + + If the node is not a directory, then SVN_ERR_WC_NOT_WORKING_COPY will + be returned. + + All returned data will be allocated in RESULT_POOL. All temporary + allocations will be made in SCRATCH_POOL. +*/ +svn_error_t * +svn_wc__db_base_get_children(const apr_array_header_t **children, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Set the dav cache for LOCAL_ABSPATH to PROPS. Use SCRATCH_POOL for + temporary allocations. */ +svn_error_t * +svn_wc__db_base_set_dav_cache(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + apr_pool_t *scratch_pool); + + +/* Retrieve the dav cache for LOCAL_ABSPATH into *PROPS, allocated in + RESULT_POOL. Use SCRATCH_POOL for temporary allocations. Return + SVN_ERR_WC_PATH_NOT_FOUND if no dav cache can be located for + LOCAL_ABSPATH in DB. */ +svn_error_t * +svn_wc__db_base_get_dav_cache(apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Recursively clear the dav cache for LOCAL_ABSPATH. Use + SCRATCH_POOL for temporary allocations. */ +svn_error_t * +svn_wc__db_base_clear_dav_cache_recursive(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* Set LOCK_TOKENS to a hash mapping const char * full URLs to const char * + * lock tokens for every base node at or under LOCAL_ABSPATH in DB which has + * such a lock token set on it. + * Allocate the hash and all items therein from RESULT_POOL. */ +svn_error_t * +svn_wc__db_base_get_lock_tokens_recursive(apr_hash_t **lock_tokens, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* ### anything else needed for maintaining the BASE tree? */ + + +/* @} */ + +/* @defgroup svn_wc__db_pristine Pristine ("text base") management + @{ +*/ + +/* Set *PRISTINE_ABSPATH to the path to the pristine text file + identified by SHA1_CHECKSUM. Error if it does not exist. + + ### This is temporary - callers should not be looking at the file + directly. + + Allocate the path in RESULT_POOL. */ +svn_error_t * +svn_wc__db_pristine_get_path(const char **pristine_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_checksum_t *checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Set *PRISTINE_ABSPATH to the path under WCROOT_ABSPATH that will be + used by the pristine text identified by SHA1_CHECKSUM. The file + need not exist. + */ +svn_error_t * +svn_wc__db_pristine_get_future_path(const char **pristine_abspath, + const char *wcroot_abspath, + const svn_checksum_t *sha1_checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* If requested set *CONTENTS to a readable stream that will yield the pristine + text identified by SHA1_CHECKSUM (must be a SHA-1 checksum) within the WC + identified by WRI_ABSPATH in DB. + + If requested set *SIZE to the size of the pristine stream in bytes, + + Even if the pristine text is removed from the store while it is being + read, the stream will remain valid and readable until it is closed. + + Allocate the stream in RESULT_POOL. */ +svn_error_t * +svn_wc__db_pristine_read(svn_stream_t **contents, + svn_filesize_t *size, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_checksum_t *sha1_checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Set *TEMP_DIR_ABSPATH to a directory in which the caller should create + a uniquely named file for later installation as a pristine text file. + + The directory is guaranteed to be one that svn_wc__db_pristine_install() + can use: specifically, one from which it can atomically move the file. + + Allocate *TEMP_DIR_ABSPATH in RESULT_POOL. */ +svn_error_t * +svn_wc__db_pristine_get_tempdir(const char **temp_dir_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Install the file TEMPFILE_ABSPATH (which is sitting in a directory given by + svn_wc__db_pristine_get_tempdir()) into the pristine data store, to be + identified by the SHA-1 checksum of its contents, SHA1_CHECKSUM, and whose + MD-5 checksum is MD5_CHECKSUM. */ +svn_error_t * +svn_wc__db_pristine_install(svn_wc__db_t *db, + const char *tempfile_abspath, + const svn_checksum_t *sha1_checksum, + const svn_checksum_t *md5_checksum, + apr_pool_t *scratch_pool); + + +/* Set *MD5_CHECKSUM to the MD-5 checksum of a pristine text + identified by its SHA-1 checksum SHA1_CHECKSUM. Return an error + if the pristine text does not exist or its MD5 checksum is not found. + + Allocate *MD5_CHECKSUM in RESULT_POOL. */ +svn_error_t * +svn_wc__db_pristine_get_md5(const svn_checksum_t **md5_checksum, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_checksum_t *sha1_checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Set *SHA1_CHECKSUM to the SHA-1 checksum of a pristine text + identified by its MD-5 checksum MD5_CHECKSUM. Return an error + if the pristine text does not exist or its SHA-1 checksum is not found. + + Note: The MD-5 checksum is not strictly guaranteed to be unique in the + database table, although duplicates are expected to be extremely rare. + ### TODO: The behaviour is currently unspecified if the MD-5 checksum is + not unique. Need to see whether this function is going to stay in use, + and, if so, address this somehow. + + Allocate *SHA1_CHECKSUM in RESULT_POOL. */ +svn_error_t * +svn_wc__db_pristine_get_sha1(const svn_checksum_t **sha1_checksum, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_checksum_t *md5_checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* If necessary transfers the PRISTINE files of the tree rooted at + SRC_LOCAL_ABSPATH to the working copy identified by DST_WRI_ABSPATH. */ +svn_error_t * +svn_wc__db_pristine_transfer(svn_wc__db_t *db, + const char *src_local_abspath, + const char *dst_wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* Remove the pristine text with SHA-1 checksum SHA1_CHECKSUM from the + * pristine store, iff it is not referenced by any of the (other) WC DB + * tables. */ +svn_error_t * +svn_wc__db_pristine_remove(svn_wc__db_t *db, + const char *wri_abspath, + const svn_checksum_t *sha1_checksum, + apr_pool_t *scratch_pool); + + +/* Remove all unreferenced pristines in the WC of WRI_ABSPATH in DB. */ +svn_error_t * +svn_wc__db_pristine_cleanup(svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *scratch_pool); + + +/* Set *PRESENT to true if the pristine store for WRI_ABSPATH in DB contains + a pristine text with SHA-1 checksum SHA1_CHECKSUM, and to false otherwise. +*/ +svn_error_t * +svn_wc__db_pristine_check(svn_boolean_t *present, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_checksum_t *sha1_checksum, + apr_pool_t *scratch_pool); + +/* @defgroup svn_wc__db_external External management + @{ */ + +/* Adds (or overwrites) a file external LOCAL_ABSPATH to the working copy + identified by WRI_ABSPATH. + + It updates both EXTERNALS and NODES in one atomic step. + */ +svn_error_t * +svn_wc__db_external_add_file(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + + const apr_hash_t *props, + apr_array_header_t *iprops, + + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + + const svn_checksum_t *checksum, + + const apr_hash_t *dav_cache, + + const char *record_ancestor_abspath, + const char *recorded_repos_relpath, + svn_revnum_t recorded_peg_revision, + svn_revnum_t recorded_revision, + + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + + svn_boolean_t keep_recorded_info, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + +/* Adds (or overwrites) a symlink external LOCAL_ABSPATH to the working copy + identified by WRI_ABSPATH. + */ +svn_error_t * +svn_wc__db_external_add_symlink(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t revision, + + const apr_hash_t *props, + + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + + const char *target, + + const apr_hash_t *dav_cache, + + const char *record_ancestor_abspath, + const char *recorded_repos_relpath, + svn_revnum_t recorded_peg_revision, + svn_revnum_t recorded_revision, + + svn_boolean_t update_actual_props, + apr_hash_t *new_actual_props, + + svn_boolean_t keep_recorded_info, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + +/* Adds (or overwrites) a directory external LOCAL_ABSPATH to the working copy + identified by WRI_ABSPATH. + + Directory externals are stored in their own working copy, so one should use + the normal svn_wc__db functions to access the normal working copy + information. + */ +svn_error_t * +svn_wc__db_external_add_dir(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + + const char *repos_root_url, + const char *repos_uuid, + + const char *record_ancestor_abspath, + const char *recorded_repos_relpath, + svn_revnum_t recorded_peg_revision, + svn_revnum_t recorded_revision, + + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + +/* Remove a registered external LOCAL_ABSPATH from the working copy identified + by WRI_ABSPATH. + */ +svn_error_t * +svn_wc__db_external_remove(svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* Reads information on the external LOCAL_ABSPATH as stored in the working + copy identified with WRI_ABSPATH (If NULL the parent directory of + LOCAL_ABSPATH is taken as WRI_ABSPATH). + + Return SVN_ERR_WC_PATH_NOT_FOUND if LOCAL_ABSPATH is not an external in + this working copy. + + When STATUS is requested it has one of these values + svn_wc__db_status_normal The external is available + svn_wc__db_status_excluded The external is user excluded + + When KIND is requested then the value will be set to the kind of external. + + If DEFINING_ABSPATH is requested, then the value will be set to the + absolute path of the directory which originally defined the external. + (The path with the svn:externals property) + + If REPOS_ROOT_URL is requested, then the value will be set to the + repository root of the external. + + If REPOS_UUID is requested, then the value will be set to the + repository uuid of the external. + + If RECORDED_REPOS_RELPATH is requested, then the value will be set to the + original repository relative path inside REPOS_ROOT_URL of the external. + + If RECORDED_PEG_REVISION is requested, then the value will be set to the + original recorded operational (peg) revision of the external. + + If RECORDED_REVISION is requested, then the value will be set to the + original recorded revision of the external. + + Allocate the result in RESULT_POOL and perform temporary allocations in + SCRATCH_POOL. + */ +svn_error_t * +svn_wc__db_external_read(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + const char **defining_abspath, + + const char **repos_root_url, + const char **repos_uuid, + + const char **recorded_repos_relpath, + svn_revnum_t *recorded_peg_revision, + svn_revnum_t *recorded_revision, + + svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Return in *EXTERNALS a list of svn_wc__committable_external_info_t * + * containing info on externals defined to be checked out below LOCAL_ABSPATH, + * returning only those externals that are not fixed to a specific revision. + * + * If IMMEDIATES_ONLY is TRUE, only those externals defined to be checked out + * as immediate children of LOCAL_ABSPATH are returned (this is useful for + * treating user requested depth < infinity). + * + * If there are no externals to be returned, set *EXTERNALS to NULL. Otherwise + * set *EXTERNALS to an APR array newly cleated in RESULT_POOL. + * + * NOTE: This only returns the externals known by the immediate WC root for + * LOCAL_ABSPATH; i.e.: + * - If there is a further parent WC "above" the immediate WC root, and if + * that parent WC defines externals to live somewhere within this WC, these + * externals will appear to be foreign/unversioned and won't be picked up. + * - Likewise, only the topmost level of externals nestings (externals + * defined within a checked out external dir) is picked up by this function. + * (For recursion, see svn_wc__committable_externals_below().) + * + * ###TODO: Add a WRI_ABSPATH (wc root indicator) separate from LOCAL_ABSPATH, + * to allow searching any wc-root for externals under LOCAL_ABSPATH, not only + * LOCAL_ABSPATH's most immediate wc-root. */ +svn_error_t * +svn_wc__db_committable_externals_below(apr_array_header_t **externals, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t immediates_only, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Gets a mapping from const char * local abspaths of externals to the const + char * local abspath of where they are defined for all externals defined + at or below LOCAL_ABSPATH. + + ### Returns NULL in *EXTERNALS until we bumped to format 29. + + Allocate the result in RESULT_POOL and perform temporary allocations in + SCRATCH_POOL. */ +svn_error_t * +svn_wc__db_externals_defined_below(apr_hash_t **externals, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Gather all svn:externals property values from the actual properties on + directories below LOCAL_ABSPATH as a mapping of const char *local_abspath + to const char * property values. + + If DEPTHS is not NULL, set *depths to an apr_hash_t* mapping the same + local_abspaths to the const char * ambient depth of the node. + + Allocate the result in RESULT_POOL and perform temporary allocations in + SCRATCH_POOL. */ +svn_error_t * +svn_wc__db_externals_gather_definitions(apr_hash_t **externals, + apr_hash_t **depths, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* @} */ + +/* @defgroup svn_wc__db_op Operations on WORKING tree + @{ +*/ + +/* Copy the node at SRC_ABSPATH (in NODES and ACTUAL_NODE tables) to + * DST_ABSPATH, both in DB but not necessarily in the same WC. The parent + * of DST_ABSPATH must be a versioned directory. + * + * This copy is NOT recursive. It simply establishes this one node, plus + * incomplete nodes for the children. + * + * If IS_MOVE is TRUE, mark this copy operation as the copy-half of + * a move. The delete-half of the move needs to be created separately + * with svn_wc__db_op_delete(). + * + * Add WORK_ITEMS to the work queue. */ +svn_error_t * +svn_wc__db_op_copy(svn_wc__db_t *db, + const char *src_abspath, + const char *dst_abspath, + const char *dst_op_root_abspath, + svn_boolean_t is_move, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + +/* Checks if LOCAL_ABSPATH represents a move back to its original location, + * and if it is reverts the move while keeping local changes after it has been + * moved from MOVED_FROM_ABSPATH. + * + * If MOVED_BACK is not NULL, set *MOVED_BACK to TRUE when a move was reverted, + * otherwise to FALSE. + */ +svn_error_t * +svn_wc__db_op_handle_move_back(svn_boolean_t *moved_back, + svn_wc__db_t *db, + const char *local_abspath, + const char *moved_from_abspath, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* Copy the leaves of the op_depth layer directly shadowed by the operation + * of SRC_ABSPATH (so SRC_ABSPATH must be an op_root) to dst_abspaths + * parents layer. + * + * This operation is recursive. It copies all the descendants at the lower + * layer and adds base-deleted nodes on dst_abspath layer to mark these nodes + * properly deleted. + * + * Usually this operation is directly followed by a call to svn_wc__db_op_copy + * which performs the real copy from src_abspath to dst_abspath. + */ +svn_error_t * +svn_wc__db_op_copy_shadowed_layer(svn_wc__db_t *db, + const char *src_abspath, + const char *dst_abspath, + svn_boolean_t is_move, + apr_pool_t *scratch_pool); + + +/* Record a copy at LOCAL_ABSPATH from a repository directory. + + This copy is NOT recursive. It simply establishes this one node. + CHILDREN must be provided, and incomplete nodes will be constructed + for them. + + ### arguments docco. */ +svn_error_t * +svn_wc__db_op_copy_dir(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const char *original_repos_relpath, + const char *original_root_url, + const char *original_uuid, + svn_revnum_t original_revision, + const apr_array_header_t *children, + svn_boolean_t is_move, + svn_depth_t depth, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* Record a copy at LOCAL_ABSPATH from a repository file. + + ### arguments docco. */ +svn_error_t * +svn_wc__db_op_copy_file(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const char *original_repos_relpath, + const char *original_root_url, + const char *original_uuid, + svn_revnum_t original_revision, + const svn_checksum_t *checksum, + svn_boolean_t update_actual_props, + const apr_hash_t *new_actual_props, + svn_boolean_t is_move, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +svn_error_t * +svn_wc__db_op_copy_symlink(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + svn_revnum_t changed_rev, + apr_time_t changed_date, + const char *changed_author, + const char *original_repos_relpath, + const char *original_root_url, + const char *original_uuid, + svn_revnum_t original_revision, + const char *target, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* ### do we need svn_wc__db_op_copy_server_excluded() ?? */ + + +/* ### add a new versioned directory. a list of children is NOT passed + ### since they are added in future, distinct calls to db_op_add_*. + PROPS gives the properties; empty or NULL means none. */ +/* ### do we need a CONFLICTS param? */ +svn_error_t * +svn_wc__db_op_add_directory(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* Add a file. + PROPS gives the properties; empty or NULL means none. + ### this file has no "pristine" + ### contents, so a checksum [reference] is not required. */ +/* ### do we need a CONFLICTS param? */ +svn_error_t * +svn_wc__db_op_add_file(svn_wc__db_t *db, + const char *local_abspath, + const apr_hash_t *props, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* Add a symlink. + PROPS gives the properties; empty or NULL means none. */ +/* ### do we need a CONFLICTS param? */ +svn_error_t * +svn_wc__db_op_add_symlink(svn_wc__db_t *db, + const char *local_abspath, + const char *target, + const apr_hash_t *props, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* Set the properties of the node LOCAL_ABSPATH in the ACTUAL tree to + PROPS. + + PROPS maps "const char *" names to "const svn_string_t *" values. + To specify no properties, PROPS must be an empty hash, not NULL. + If the node is not present, return an error. + + If PROPS is NULL, set the properties to be the same as the pristine + properties. + + If CONFLICT is not NULL, it is used to register a conflict on this + node at the same time the properties are changed. + + WORK_ITEMS are inserted into the work queue, as additional things that + need to be completed before the working copy is stable. + + + If CLEAR_RECORDED_INFO is true, the recorded information for the node + is cleared. (commonly used when updating svn:* magic properties). + + NOTE: This will overwrite ALL working properties the node currently + has. There is no db_op_set_prop() function. Callers must read all the + properties, change one, and write all the properties. + ### ugh. this has poor transaction semantics... + + + NOTE: This will create an entry in the ACTUAL table for the node if it + does not yet have one. +*/ +svn_error_t * +svn_wc__db_op_set_props(svn_wc__db_t *db, + const char *local_abspath, + apr_hash_t *props, + svn_boolean_t clear_recorded_info, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + +/* Mark LOCAL_ABSPATH, and all children, for deletion. + * + * This function removes the file externals (and if DELETE_DIR_EXTERNALS is + * TRUE also the directory externals) registered below LOCAL_ABSPATH. + * (DELETE_DIR_EXTERNALS should be true if also removing unversioned nodes) + * + * If MOVED_TO_ABSPATH is not NULL, mark the deletion of LOCAL_ABSPATH + * as the delete-half of a move from LOCAL_ABSPATH to MOVED_TO_ABSPATH. + * + * If NOTIFY_FUNC is not NULL, then it will be called (with NOTIFY_BATON) + * for each node deleted. While this processing occurs, if CANCEL_FUNC is + * not NULL, then it will be called (with CANCEL_BATON) to detect cancellation + * during the processing. + * + * Note: the notification (and cancellation) occur outside of a SQLite + * transaction. + */ +svn_error_t * +svn_wc__db_op_delete(svn_wc__db_t *db, + const char *local_abspath, + const char *moved_to_abspath, + svn_boolean_t delete_dir_externals, + svn_skel_t *conflict, + svn_skel_t *work_items, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); + + +/* Mark all LOCAL_ABSPATH in the TARGETS array, and all of their children, + * for deletion. + * + * This function is more efficient than svn_wc__db_op_delete() because + * only one sqlite transaction is used for all targets. + * It currently lacks support for moves (though this could be changed, + * at which point svn_wc__db_op_delete() becomes redundant). + * + * This function removes the file externals (and if DELETE_DIR_EXTERNALS is + * TRUE also the directory externals) registered below the targets. + * (DELETE_DIR_EXTERNALS should be true if also removing unversioned nodes) + * + * If NOTIFY_FUNC is not NULL, then it will be called (with NOTIFY_BATON) + * for each node deleted. While this processing occurs, if CANCEL_FUNC is + * not NULL, then it will be called (with CANCEL_BATON) to detect cancellation + * during the processing. + * + * Note: the notification (and cancellation) occur outside of a SQLite + * transaction. + */ +svn_error_t * +svn_wc__db_op_delete_many(svn_wc__db_t *db, + apr_array_header_t *targets, + svn_boolean_t delete_dir_externals, + const svn_skel_t *conflict, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); + + +/* ### mark PATH as (possibly) modified. "svn edit" ... right API here? */ +svn_error_t * +svn_wc__db_op_modified(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + + +/* ### use NULL to remove from a changelist. + + ### NOTE: only depth=svn_depth_empty is supported right now. + */ +svn_error_t * +svn_wc__db_op_set_changelist(svn_wc__db_t *db, + const char *local_abspath, + const char *new_changelist, + const apr_array_header_t *changelist_filter, + svn_depth_t depth, + /* ### flip to CANCEL, then NOTIFY. precedent. */ + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* Record CONFLICT on LOCAL_ABSPATH, potentially replacing other conflicts + recorded on LOCAL_ABSPATH. + + Users should in most cases pass CONFLICT to another WC_DB call instead of + calling svn_wc__db_op_mark_conflict() directly outside a transaction, to + allow recording atomically with the operation involved. + + Any work items that are necessary as part of marking this node conflicted + can be passed in WORK_ITEMS. + */ +svn_error_t * +svn_wc__db_op_mark_conflict(svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* ### caller maintains ACTUAL, and how the resolution occurred. we're just + ### recording state. + ### + ### I'm not sure that these three values are the best way to do this, + ### but they're handy for now. */ +svn_error_t * +svn_wc__db_op_mark_resolved(svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t resolved_text, + svn_boolean_t resolved_props, + svn_boolean_t resolved_tree, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* Revert all local changes which are being maintained in the database, + * including conflict storage, properties and text modification status. + * + * Returns SVN_ERR_WC_INVALID_OPERATION_DEPTH if the revert is not + * possible, e.g. copy/delete but not a root, or a copy root with + * children. + * + * At present only depth=empty and depth=infinity are supported. + * + * This function populates the revert list that can be queried to + * determine what was reverted. + */ +svn_error_t * +svn_wc__db_op_revert(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Query the revert list for LOCAL_ABSPATH and set *REVERTED if the + * path was reverted. Set *MARKER_FILES to a const char *list of + * marker files if any were recorded on LOCAL_ABSPATH. + * + * Set *COPIED_HERE if the reverted node was copied here and is the + * operation root of the copy. + * Set *KIND to the node kind of the reverted node. + * + * Removes the row for LOCAL_ABSPATH from the revert list. + */ +svn_error_t * +svn_wc__db_revert_list_read(svn_boolean_t *reverted, + const apr_array_header_t **marker_files, + svn_boolean_t *copied_here, + svn_node_kind_t *kind, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* The type of elements in the array returned by + * svn_wc__db_revert_list_read_copied_children(). */ +typedef struct svn_wc__db_revert_list_copied_child_info_t { + const char *abspath; + svn_node_kind_t kind; +} svn_wc__db_revert_list_copied_child_info_t ; + +/* Return in *CHILDREN a list of reverted copied nodes at or within + * LOCAL_ABSPATH (which is a reverted file or a reverted directory). + * Allocate *COPIED_CHILDREN and its elements in RESULT_POOL. + * The elements are of type svn_wc__db_revert_list_copied_child_info_t. */ +svn_error_t * +svn_wc__db_revert_list_read_copied_children(const apr_array_header_t **children, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Make revert notifications for all paths in the revert list that are + * equal to LOCAL_ABSPATH or below LOCAL_ABSPATH. + * + * Removes all the corresponding rows from the revert list. + * + * ### Pass in cancel_func? + */ +svn_error_t * +svn_wc__db_revert_list_notify(svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* Clean up after svn_wc__db_op_revert by removing the revert list. + */ +svn_error_t * +svn_wc__db_revert_list_done(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* ### status */ + + +/* @} */ + +/* @defgroup svn_wc__db_read Read operations on the BASE/WORKING tree + @{ + + These functions query information about nodes in ACTUAL, and returns + the requested information from the appropriate ACTUAL, WORKING, or + BASE tree. + + For example, asking for the checksum of the pristine version will + return the one recorded in WORKING, or if no WORKING node exists, then + the checksum comes from BASE. +*/ + +/* Retrieve information about a node. + + For the node implied by LOCAL_ABSPATH from the local filesystem, return + information in the provided OUT parameters. Each OUT parameter may be + NULL, indicating that specific item is not requested. + + The information returned comes from the BASE tree, as possibly modified + by the WORKING and ACTUAL trees. + + If there is no information about the node, then SVN_ERR_WC_PATH_NOT_FOUND + will be returned. + + The OUT parameters, and their "not available" values are: + STATUS n/a (always available) + KIND svn_node_unknown (For ACTUAL only nodes) + REVISION SVN_INVALID_REVNUM + REPOS_RELPATH NULL + REPOS_ROOT_URL NULL + REPOS_UUID NULL + CHANGED_REV SVN_INVALID_REVNUM + CHANGED_DATE 0 + CHANGED_AUTHOR NULL + DEPTH svn_depth_unknown + CHECKSUM NULL + TARGET NULL + + ORIGINAL_REPOS_RELPATH NULL + ORIGINAL_ROOT_URL NULL + ORIGINAL_UUID NULL + ORIGINAL_REVISION SVN_INVALID_REVNUM + + LOCK NULL + + RECORDED_SIZE SVN_INVALID_FILESIZE + RECORDED_TIME 0 + + CHANGELIST NULL + CONFLICTED FALSE + + OP_ROOT FALSE + HAD_PROPS FALSE + PROPS_MOD FALSE + + HAVE_BASE FALSE + HAVE_MORE_WORK FALSE + HAVE_WORK FALSE + + When STATUS is requested, then it will be one of these values: + + svn_wc__db_status_normal + A plain BASE node, with no local changes. + + svn_wc__db_status_added + A node has been added/copied/moved to here. See HAVE_BASE to see + if this change overwrites a BASE node. Use scan_addition() to resolve + whether this has been added, copied, or moved, and the details of the + operation (this function only looks at LOCAL_ABSPATH, but resolving + the details requires scanning one or more ancestor nodes). + + svn_wc__db_status_deleted + This node has been deleted or moved away. It may be a delete/move of + a BASE node, or a child node of a subtree that was copied/moved to + an ancestor location. Call scan_deletion() to determine the full + details of the operations upon this node. + + svn_wc__db_status_server_excluded + The node is versioned/known by the server, but the server has + decided not to provide further information about the node. This + is a BASE node (since changes are not allowed to this node). + + svn_wc__db_status_excluded + The node has been excluded from the working copy tree. This may + be an exclusion from the BASE tree, or an exclusion in the + WORKING tree for a child node of a copied/moved parent. + + svn_wc__db_status_not_present + This is a node from the BASE tree, has been marked as "not-present" + within this mixed-revision working copy. This node is at a revision + that is not in the tree, contrary to its inclusion in the parent + node's revision. + + svn_wc__db_status_incomplete + The BASE is incomplete due to an interrupted operation. An + incomplete WORKING node will be svn_wc__db_status_added. + + If REVISION is requested, it will be set to the revision of the + unmodified (BASE) node, or to SVN_INVALID_REVNUM if any structural + changes have been made to that node (that is, if the node has a row in + the WORKING table). + + If DEPTH is requested, and the node is NOT a directory, then + the value will be set to svn_depth_unknown. + + If CHECKSUM is requested, and the node is NOT a file, then it will + be set to NULL. + + If TARGET is requested, and the node is NOT a symlink, then it will + be set to NULL. + + If TRANSLATED_SIZE is requested, and the node is NOT a file, then + it will be set to SVN_INVALID_FILESIZE. + + If HAVE_WORK is TRUE, the returned information is from the highest WORKING + layer. In that case HAVE_MORE_WORK and HAVE_BASE provide information about + what other layers exist for this node. + + If HAVE_WORK is FALSE and HAVE_BASE is TRUE then the information is from + the BASE tree. + + If HAVE_WORK and HAVE_BASE are both FALSE and when retrieving CONFLICTED, + then the node doesn't exist at all. + + If OP_ROOT is requested and the node has a WORKING layer, OP_ROOT will be + set to true if this node is the op_root for this layer. + + If HAD_PROPS is requested and the node has pristine props, the value will + be set to TRUE. + + If PROPS_MOD is requested and the node has property modification the value + will be set to TRUE. + + ### add information about the need to scan upwards to get a complete + ### picture of the state of this node. + + ### add some documentation about OUT parameter values based on STATUS ?? + + ### the TEXT_MOD may become an enumerated value at some point to + ### indicate different states of knowledge about text modifications. + ### for example, an "svn edit" command in the future might set a + ### flag indicating administratively-defined modification. and/or we + ### might have a status indicating that we saw it was modified while + ### performing a filesystem traversal. + + All returned data will be allocated in RESULT_POOL. All temporary + allocations will be made in SCRATCH_POOL. +*/ +/* ### old docco. needs to be incorporated as appropriate. there is + ### some pending, potential changes to the definition of this API, + ### so not worrying about it just yet. + + ### if the node has not been committed (after adding): + ### revision will be SVN_INVALID_REVNUM + ### repos_* will be NULL + ### changed_rev will be SVN_INVALID_REVNUM + ### changed_date will be 0 + ### changed_author will be NULL + ### status will be svn_wc__db_status_added + ### text_mod will be TRUE + ### prop_mod will be TRUE if any props have been set + ### base_shadowed will be FALSE + + ### if the node is not a copy, or a move destination: + ### original_repos_path will be NULL + ### original_root_url will be NULL + ### original_uuid will be NULL + ### original_revision will be SVN_INVALID_REVNUM + + ### note that @a base_shadowed can be derived. if the status specifies + ### an add/copy/move *and* there is a corresponding node in BASE, then + ### the BASE has been deleted to open the way for this node. +*/ +svn_error_t * +svn_wc__db_read_info(svn_wc__db_status_t *status, /* ### derived */ + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, /* dirs only */ + const svn_checksum_t **checksum, /* files only */ + const char **target, /* symlinks only */ + + /* ### the following fields if copied/moved (history) */ + const char **original_repos_relpath, + const char **original_root_url, + const char **original_uuid, + svn_revnum_t *original_revision, + + /* For BASE nodes */ + svn_wc__db_lock_t **lock, + + /* Recorded for files present in the working copy */ + svn_filesize_t *recorded_size, + apr_time_t *recorded_time, + + /* From ACTUAL */ + const char **changelist, + svn_boolean_t *conflicted, + + /* ### the followed are derived fields */ + svn_boolean_t *op_root, + + svn_boolean_t *had_props, + svn_boolean_t *props_mod, + + svn_boolean_t *have_base, + svn_boolean_t *have_more_work, + svn_boolean_t *have_work, + + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Structure returned by svn_wc__db_read_children_info. Only has the + fields needed by status. */ +struct svn_wc__db_info_t { + svn_wc__db_status_t status; + svn_node_kind_t kind; + svn_revnum_t revnum; + const char *repos_relpath; + const char *repos_root_url; + const char *repos_uuid; + svn_revnum_t changed_rev; + const char *changed_author; + apr_time_t changed_date; + svn_depth_t depth; + + svn_filesize_t recorded_size; + apr_time_t recorded_time; + + const char *changelist; + svn_boolean_t conflicted; +#ifdef HAVE_SYMLINK + svn_boolean_t special; +#endif + svn_boolean_t op_root; + + svn_boolean_t has_checksum; + svn_boolean_t copied; + svn_boolean_t had_props; + svn_boolean_t props_mod; + + svn_boolean_t have_base; + svn_boolean_t have_more_work; + + svn_boolean_t locked; /* WC directory lock */ + svn_wc__db_lock_t *lock; /* Repository file lock */ + svn_boolean_t incomplete; /* TRUE if a working node is incomplete */ + + const char *moved_to_abspath; /* Only on op-roots. See svn_wc_status3_t. */ + svn_boolean_t moved_here; /* Only on op-roots. */ + + svn_boolean_t file_external; +}; + +/* Return in *NODES a hash mapping name->struct svn_wc__db_info_t for + the children of DIR_ABSPATH, and in *CONFLICTS a hash of names in + conflict. + + The results include any path that was a child of a deleted directory that + existed at LOCAL_ABSPATH, even if that directory is now scheduled to be + replaced by the working node at LOCAL_ABSPATH. + */ +svn_error_t * +svn_wc__db_read_children_info(apr_hash_t **nodes, + apr_hash_t **conflicts, + svn_wc__db_t *db, + const char *dir_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Structure returned by svn_wc__db_read_walker_info. Only has the + fields needed by svn_wc__internal_walk_children(). */ +struct svn_wc__db_walker_info_t { + svn_wc__db_status_t status; + svn_node_kind_t kind; +}; + +/* When a node is deleted in WORKING, some of its information is no longer + available. But in some cases it might still be relevant to obtain this + information even when the information isn't stored in the BASE tree. + + This function allows access to that specific information. + + When a node is not deleted, this node returns the same information + as svn_wc__db_read_info(). + + All output arguments are optional and behave in the same way as when + calling svn_wc__db_read_info(). + + (All other information (like original_*) can be obtained via other apis). + + *PROPS maps "const char *" names to "const svn_string_t *" values. If + the pristine node is capable of having properties but has none, set + *PROPS to an empty hash. If its status is such that it cannot have + properties, set *PROPS to NULL. + */ +svn_error_t * +svn_wc__db_read_pristine_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, /* dirs only */ + const svn_checksum_t **checksum, /* files only */ + const char **target, /* symlinks only */ + svn_boolean_t *had_props, + apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Gets the information required to install a pristine file to the working copy + + Set WCROOT_ABSPATH to the working copy root, SHA1_CHECKSUM to the + checksum of the node (a valid reference into the pristine store) + and PRISTINE_PROPS to the node's pristine properties (to use for + installing the file). + + If WRI_ABSPATH is not NULL, check for information in the working copy + identified by WRI_ABSPATH. + */ +svn_error_t * +svn_wc__db_read_node_install_info(const char **wcroot_abspath, + const svn_checksum_t **sha1_checksum, + apr_hash_t **pristine_props, + apr_time_t *changed_date, + svn_wc__db_t *db, + const char *local_abspath, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Return in *NODES a hash mapping name->struct svn_wc__db_walker_info_t for + the children of DIR_ABSPATH. "name" is the child's name relative to + DIR_ABSPATH, not an absolute path. */ +svn_error_t * +svn_wc__db_read_children_walker_info(apr_hash_t **nodes, + svn_wc__db_t *db, + const char *dir_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/** + * Set *URL to the corresponding url for LOCAL_ABSPATH. + * If the node is added, return the url it will have in the repository. + */ +svn_error_t * +svn_wc__db_read_url(const char **url, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Set *PROPS to the properties of the node LOCAL_ABSPATH in the ACTUAL + tree (looking through to the WORKING or BASE tree as required). + + ### *PROPS will be set to NULL in the following situations: + ### ... tbd + + PROPS maps "const char *" names to "const svn_string_t *" values. + If the node has no properties, set *PROPS to an empty hash. + If the node is not present, return an error. + Allocate *PROPS and its keys and values in RESULT_POOL. +*/ +svn_error_t * +svn_wc__db_read_props(apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Call RECEIVER_FUNC, passing RECEIVER_BATON, an absolute path, and + * a hash table mapping <tt>char *</tt> names onto svn_string_t * + * values for any properties of child nodes of LOCAL_ABSPATH (up to DEPTH). + * + * If PRISTINE is FALSE, read the properties from the WORKING layer (highest + * op_depth); if PRISTINE is FALSE, local modifications will be visible. + */ +svn_error_t * +svn_wc__db_read_props_streamily(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t pristine, + const apr_array_header_t *changelists, + svn_wc__proplist_receiver_t receiver_func, + void *receiver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + + +/* Set *PROPS to the properties of the node LOCAL_ABSPATH in the WORKING + tree (looking through to the BASE tree as required). + + ### *PROPS will set set to NULL in the following situations: + ### ... tbd. see props.c:svn_wc__get_pristine_props() + + *PROPS maps "const char *" names to "const svn_string_t *" values. + If the node has no properties, set *PROPS to an empty hash. + If the node is not present, return an error. + Allocate *PROPS and its keys and values in RESULT_POOL. +*/ +svn_error_t * +svn_wc__db_read_pristine_props(apr_hash_t **props, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/** + * Set @a *iprops to a depth-first ordered array of + * #svn_prop_inherited_item_t * structures representing the properties + * inherited by @a local_abspath from the ACTUAL tree above + * @a local_abspath (looking through to the WORKING or BASE tree as + * required), up to and including the root of the working copy and + * any cached inherited properties inherited by the root. + * + * The #svn_prop_inherited_item_t->path_or_url members of the + * #svn_prop_inherited_item_t * structures in @a *iprops are + * paths relative to the repository root URL for cached inherited + * properties and absolute working copy paths otherwise. + * + * If ACTUAL_PROPS is not NULL, then set *ACTUAL_PROPS to the actual + * properties stored on LOCAL_ABSPATH. + * + * Allocate @a *iprops in @a result_pool. Use @a scratch_pool + * for temporary allocations. + */ +svn_error_t * +svn_wc__db_read_inherited_props(apr_array_header_t **iprops, + apr_hash_t **actual_props, + svn_wc__db_t *db, + const char *local_abspath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Read a BASE node's inherited property information. + + Set *IPROPS to to a depth-first ordered array of + svn_prop_inherited_item_t * structures representing the cached + inherited properties for the BASE node at LOCAL_ABSPATH. + + If no cached properties are found, then set *IPROPS to NULL. + If LOCAL_ABSPATH represents the root of the repository, then set + *IPROPS to an empty array. + + Allocate *IPROPS in RESULT_POOL, use SCRATCH_POOL for temporary + allocations. */ +svn_error_t * +svn_wc__db_read_cached_iprops(apr_array_header_t **iprops, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Find BASE nodes with cached inherited properties. + + Set *IPROPS_PATHS to a hash mapping const char * absolute working copy + paths to the repos_relpath of the path for each path in the working copy + at or below LOCAL_ABSPATH, limited by DEPTH, that has cached inherited + properties for the BASE node of the path. + + Allocate *IPROP_PATHS in RESULT_POOL. + Use SCRATCH_POOL for temporary allocations. */ +svn_error_t * +svn_wc__db_get_children_with_cached_iprops(apr_hash_t **iprop_paths, + svn_depth_t depth, + const char *local_abspath, + svn_wc__db_t *db, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/** Obtain a mapping of const char * local_abspaths to const svn_string_t* + * property values in *VALUES, of all PROPNAME properties on LOCAL_ABSPATH + * and its descendants. + * + * Allocate the result in RESULT_POOL, and perform temporary allocations in + * SCRATCH_POOL. + */ +svn_error_t * +svn_wc__db_prop_retrieve_recursive(apr_hash_t **values, + svn_wc__db_t *db, + const char *local_abspath, + const char *propname, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Set *CHILDREN to a new array of the (const char *) basenames of the + immediate children of the working node at LOCAL_ABSPATH in DB. + + Return every path that refers to a child of the working node at + LOCAL_ABSPATH. Do not include a path just because it was a child of a + deleted directory that existed at LOCAL_ABSPATH if that directory is now + scheduled to be replaced by the working node at LOCAL_ABSPATH. + + Allocate *CHILDREN in RESULT_POOL and do temporary allocations in + SCRATCH_POOL. + + ### return some basic info for each child? e.g. kind. + ### maybe the data in _read_get_info should be a structure, and this + ### can return a struct for each one. + ### however: _read_get_info can say "not interested", which isn't the + ### case with a struct. thus, a struct requires fetching and/or + ### computing all info. +*/ +svn_error_t * +svn_wc__db_read_children_of_working_node(const apr_array_header_t **children, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Like svn_wc__db_read_children_of_working_node(), except also include any + path that was a child of a deleted directory that existed at + LOCAL_ABSPATH, even if that directory is now scheduled to be replaced by + the working node at LOCAL_ABSPATH. +*/ +svn_error_t * +svn_wc__db_read_children(const apr_array_header_t **children, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Read into *VICTIMS the basenames of the immediate children of + LOCAL_ABSPATH in DB that are conflicted. + + In case of tree conflicts a victim doesn't have to be in the + working copy. + + Allocate *VICTIMS in RESULT_POOL and do temporary allocations in + SCRATCH_POOL */ +/* ### This function will probably be removed. */ +svn_error_t * +svn_wc__db_read_conflict_victims(const apr_array_header_t **victims, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Read into *MARKER_FILES the absolute paths of the marker files + of conflicts stored on LOCAL_ABSPATH and its immediate children in DB. + The on-disk files may have been deleted by the user. + + Allocate *MARKER_FILES in RESULT_POOL and do temporary allocations + in SCRATCH_POOL */ +svn_error_t * +svn_wc__db_get_conflict_marker_files(apr_hash_t **markers, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Read the conflict information recorded on LOCAL_ABSPATH in *CONFLICT, + an editable conflict skel. + + If the node exists, but does not have a conflict set *CONFLICT to NULL, + otherwise return a SVN_ERR_WC_PATH_NOT_FOUND error. + + Allocate *CONFLICTS in RESULT_POOL and do temporary allocations in + SCRATCH_POOL */ +svn_error_t * +svn_wc__db_read_conflict(svn_skel_t **conflict, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Return the kind of the node in DB at LOCAL_ABSPATH. The WORKING tree will + be examined first, then the BASE tree. If the node is not present in either + tree and ALLOW_MISSING is TRUE, then svn_node_unknown is returned. + If the node is missing and ALLOW_MISSING is FALSE, then it will return + SVN_ERR_WC_PATH_NOT_FOUND. + + The SHOW_HIDDEN and SHOW_DELETED flags report certain states as kind none. + + When nodes have certain statee they are only reported when: + svn_wc__db_status_not_present when show_hidden && show_deleted + + svn_wc__db_status_excluded when show_hidden + svn_wc__db_status_server_excluded when show_hidden + + svn_wc__db_status_deleted when show_deleted + + In other cases these nodes are reported with *KIND as svn_node_none. + (See also svn_wc_read_kind2()'s documentation) + + Uses SCRATCH_POOL for temporary allocations. */ +svn_error_t * +svn_wc__db_read_kind(svn_node_kind_t *kind, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t allow_missing, + svn_boolean_t show_deleted, + svn_boolean_t show_hidden, + apr_pool_t *scratch_pool); + + +/* An analog to svn_wc__entry_is_hidden(). Set *HIDDEN to TRUE if + LOCAL_ABSPATH in DB "is not present, and I haven't scheduled something + over the top of it." */ +svn_error_t * +svn_wc__db_node_hidden(svn_boolean_t *hidden, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* Checks if a node replaces a node in a different layer. Also check if it + replaces a BASE (op_depth 0) node or just a node in a higher layer (a copy). + Finally check if this is the root of the replacement, or if the replacement + is initiated by the parent node. + + IS_REPLACE_ROOT (if not NULL) is set to TRUE if the node is the root of a + replacement; otherwise to FALSE. + + BASE_REPLACE (if not NULL) is set to TRUE if the node directly or indirectly + replaces a node in the BASE tree; otherwise to FALSE. + + IS_REPLACE (if not NULL) is set to TRUE if the node directly replaces a node + in a lower layer; otherwise to FALSE. + */ +svn_error_t * +svn_wc__db_node_check_replace(svn_boolean_t *is_replace_root, + svn_boolean_t *base_replace, + svn_boolean_t *is_replace, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* ### changelists. return an array, or an iterator interface? how big + ### are these things? are we okay with an in-memory array? examine other + ### changelist usage -- we may already assume the list fits in memory. +*/ + +/* The DB-private version of svn_wc__is_wcroot(), which see. + */ +svn_error_t * +svn_wc__db_is_wcroot(svn_boolean_t *is_wcroot, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* Check whether a node is a working copy root and/or switched. + + If LOCAL_ABSPATH is the root of a working copy, set *IS_WC_ROOT to TRUE, + otherwise to FALSE. + + If LOCAL_ABSPATH is switched against its parent in the same working copy + set *IS_SWITCHED to TRUE, otherwise to FALSE. + + If KIND is not null, set *KIND to the node type of LOCAL_ABSPATH. + + Any of the output arguments can be null to specify that the result is not + interesting to the caller. + + Use SCRATCH_POOL for temporary allocations. + */ +svn_error_t * +svn_wc__db_is_switched(svn_boolean_t *is_wcroot, + svn_boolean_t *is_switched, + svn_node_kind_t *kind, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + + +/* @} */ + + +/* @defgroup svn_wc__db_global Operations that alter multiple trees + @{ +*/ + +/* Associate LOCAL_DIR_ABSPATH, and all its children with the repository at + at REPOS_ROOT_URL. The relative path to the repos root will not change, + just the repository root. The repos uuid will also remain the same. + This also updates any locks which may exist for the node, as well as any + copyfrom repository information. Finally, the DAV cache (aka + "wcprops") will be reset for affected entries. + + Use SCRATCH_POOL for any temporary allocations. + + ### local_dir_abspath "should be" the wcroot or a switch root. all URLs + ### under this directory (depth=infinity) will be rewritten. + + ### This API had a depth parameter, which was removed, should it be + ### resurrected? What's the purpose if we claim relocate is infinitely + ### recursive? + + ### Assuming the future ability to copy across repositories, should we + ### refrain from resetting the copyfrom information in this operation? +*/ +svn_error_t * +svn_wc__db_global_relocate(svn_wc__db_t *db, + const char *local_dir_abspath, + const char *repos_root_url, + apr_pool_t *scratch_pool); + + +/* ### docco + + ### collapse the WORKING and ACTUAL tree changes down into BASE, called + for each committed node. + + NEW_REVISION must be the revision number of the revision created by + the commit. It will become the BASE node's 'revnum' and 'changed_rev' + values in the BASE_NODE table. + + CHANGED_REVISION is the new 'last changed' revision. If the node is + modified its value is equivalent to NEW_REVISION, but in case of a + descendant of a copy/move it can be an older revision. + + CHANGED_DATE is the (server-side) date of CHANGED_REVISION. It may be 0 if + the revprop is missing on the revision. + + CHANGED_AUTHOR is the (server-side) author of CHANGED_REVISION. It may be + NULL if the revprop is missing on the revision. + + One or both of NEW_CHECKSUM and NEW_CHILDREN should be NULL. For new: + files: NEW_CHILDREN should be NULL + dirs: NEW_CHECKSUM should be NULL + symlinks: both should be NULL + + WORK_ITEMS will be place into the work queue. +*/ +svn_error_t * +svn_wc__db_global_commit(svn_wc__db_t *db, + const char *local_abspath, + svn_revnum_t new_revision, + svn_revnum_t changed_revision, + apr_time_t changed_date, + const char *changed_author, + const svn_checksum_t *new_checksum, + const apr_array_header_t *new_children, + apr_hash_t *new_dav_cache, + svn_boolean_t keep_changelist, + svn_boolean_t no_unlock, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* ### docco + + Perform an "update" operation at this node. It will create/modify a BASE + node, and possibly update the ACTUAL tree's node (e.g put the node into + a conflicted state). + + ### there may be cases where we need to tweak an existing WORKING node + + ### this operations on a single node, but may affect children + + ### the repository cannot be changed with this function, but a "switch" + ### (aka changing repos_relpath) is possible + + ### one of NEW_CHILDREN, NEW_CHECKSUM, or NEW_TARGET must be provided. + ### the other two values must be NULL. + ### should this be broken out into an update_(directory|file|symlink) ? + + ### how does this differ from base_add_*? just the CONFLICT param. + ### the WORK_ITEMS param is new here, but the base_add_* functions + ### should probably grow that. should we instead just (re)use base_add + ### rather than grow a new function? + + ### this does not allow a change of depth + + ### we do not update a file's TRANSLATED_SIZE here. at some future point, + ### when the file is installed, then a TRANSLATED_SIZE will be set. +*/ +svn_error_t * +svn_wc__db_global_update(svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t new_kind, + const char *new_repos_relpath, + svn_revnum_t new_revision, + const apr_hash_t *new_props, + svn_revnum_t new_changed_rev, + apr_time_t new_changed_date, + const char *new_changed_author, + const apr_array_header_t *new_children, + const svn_checksum_t *new_checksum, + const char *new_target, + const apr_hash_t *new_dav_cache, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + + +/* Modify the entry of working copy LOCAL_ABSPATH, presumably after an update + of depth DEPTH completes. If LOCAL_ABSPATH doesn't exist, this routine + does nothing. + + Set the node's repository relpath, repository root, repository uuid and + revision to NEW_REPOS_RELPATH, NEW_REPOS_ROOT and NEW_REPOS_UUID. If + NEW_REPOS_RELPATH is null, the repository location is untouched; if + NEW_REVISION in invalid, the working revision field is untouched. + The modifications are mutually exclusive. If NEW_REPOS_ROOT is non-NULL, + set the repository root of the entry to NEW_REPOS_ROOT. + + If LOCAL_ABSPATH is a directory, then, walk entries below LOCAL_ABSPATH + according to DEPTH thusly: + + If DEPTH is svn_depth_infinity, perform the following actions on + every entry below PATH; if svn_depth_immediates, svn_depth_files, + or svn_depth_empty, perform them only on LOCAL_ABSPATH. + + If NEW_REVISION is valid, then tweak every entry to have this new + working revision (excluding files that are scheduled for addition + or replacement). Likewise, if BASE_URL is non-null, then rewrite + all urls to be "telescoping" children of the base_url. + + EXCLUDE_RELPATHS is a hash containing const char *local_relpath. Nodes + for pathnames contained in EXCLUDE_RELPATHS are not touched by this + function. These pathnames should be paths relative to the wcroot. + + If WCROOT_IPROPS is not NULL it is a hash mapping const char * absolute + working copy paths to depth-first ordered arrays of + svn_prop_inherited_item_t * structures. If LOCAL_ABSPATH exists in + WCROOT_IPROPS, then set the hashed value as the node's inherited + properties. +*/ +svn_error_t * +svn_wc__db_op_bump_revisions_post_update(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + const char *new_repos_relpath, + const char *new_repos_root_url, + const char *new_repos_uuid, + svn_revnum_t new_revision, + apr_hash_t *exclude_relpaths, + apr_hash_t *wcroot_iprops, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); + + +/* Record the RECORDED_SIZE and RECORDED_TIME for a versioned node. + + This function will record the information within the WORKING node, + if present, or within the BASE tree. If neither node is present, then + SVN_ERR_WC_PATH_NOT_FOUND will be returned. + + RECORDED_SIZE may be SVN_INVALID_FILESIZE, which will be recorded + as such, implying "unknown size". + + RECORDED_TIME may be 0, which will be recorded as such, implying + "unknown last mod time". +*/ +svn_error_t * +svn_wc__db_global_record_fileinfo(svn_wc__db_t *db, + const char *local_abspath, + svn_filesize_t recorded_size, + apr_time_t recorded_time, + apr_pool_t *scratch_pool); + + +/* ### post-commit handling. + ### maybe multiple phases? + ### 1) mark a changelist as being-committed + ### 2) collect ACTUAL content, store for future use as TEXTBASE + ### 3) caller performs commit + ### 4) post-commit, integrate changelist into BASE +*/ + + +/* @} */ + + +/* @defgroup svn_wc__db_lock Function to manage the LOCKS table. + @{ +*/ + +/* Add or replace LOCK for LOCAL_ABSPATH to DB. */ +svn_error_t * +svn_wc__db_lock_add(svn_wc__db_t *db, + const char *local_abspath, + const svn_wc__db_lock_t *lock, + apr_pool_t *scratch_pool); + + +/* Remove any lock for LOCAL_ABSPATH in DB. */ +svn_error_t * +svn_wc__db_lock_remove(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + + +/* @} */ + + +/* @defgroup svn_wc__db_scan Functions to scan up a tree for further data. + @{ +*/ + +/* Read a BASE node's repository information. + + For the BASE node implied by LOCAL_ABSPATH, its location in the repository + returned in *REPOS_ROOT_URL and *REPOS_UUID will be returned in + *REPOS_RELPATH. Any of the OUT parameters may be NULL, indicating no + interest in that piece of information. + + All returned data will be allocated in RESULT_POOL. All temporary + allocations will be made in SCRATCH_POOL. + + ### Either delete this function and use _base_get_info instead, or + ### add a 'revision' output to make a complete repository node location + ### and rename to not say 'scan', because it doesn't. +*/ +svn_error_t * +svn_wc__db_scan_base_repos(const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Scan upwards for information about a known addition to the WORKING tree. + + IFF a node's status as returned by svn_wc__db_read_info() is + svn_wc__db_status_added (NOT obstructed_add!), then this function + returns a refined status in *STATUS, which is one of: + + svn_wc__db_status_added -- this NODE is a simple add without history. + OP_ROOT_ABSPATH will be set to the topmost node in the added subtree + (implying its parent will be an unshadowed BASE node). The REPOS_* + values will be implied by that ancestor BASE node and this node's + position in the added subtree. ORIGINAL_* will be set to their + NULL values (and SVN_INVALID_REVNUM for ORIGINAL_REVISION). + + svn_wc__db_status_copied -- this NODE is the root or child of a copy. + The root of the copy will be stored in OP_ROOT_ABSPATH. Note that + the parent of the operation root could be another WORKING node (from + an add, copy, or move). The REPOS_* values will be implied by the + ancestor unshadowed BASE node. ORIGINAL_* will indicate the source + of the copy. + + svn_wc__db_status_incomplete -- this NODE is copied but incomplete. + + svn_wc__db_status_moved_here -- this NODE arrived as a result of a move. + The root of the moved nodes will be stored in OP_ROOT_ABSPATH. + Similar to the copied state, its parent may be a WORKING node or a + BASE node. And again, the REPOS_* values are implied by this node's + position in the subtree under the ancestor unshadowed BASE node. + ORIGINAL_* will indicate the source of the move. + + All OUT parameters may be NULL to indicate a lack of interest in + that piece of information. + + STATUS, OP_ROOT_ABSPATH, and REPOS_* will always be assigned a value + if that information is requested (and assuming a successful return). + + ORIGINAL_REPOS_RELPATH will refer to the *root* of the operation. It + does *not* correspond to the node given by LOCAL_ABSPATH. The caller + can use the suffix on LOCAL_ABSPATH (relative to OP_ROOT_ABSPATH) in + order to compute the source node which corresponds to LOCAL_ABSPATH. + + If the node given by LOCAL_ABSPATH does not have changes recorded in + the WORKING tree, then SVN_ERR_WC_PATH_NOT_FOUND is returned. If it + doesn't have an "added" status, then SVN_ERR_WC_PATH_UNEXPECTED_STATUS + will be returned. + + All returned data will be allocated in RESULT_POOL. All temporary + allocations will be made in SCRATCH_POOL. +*/ +svn_error_t * +svn_wc__db_scan_addition(svn_wc__db_status_t *status, + const char **op_root_abspath, + const char **repos_relpath, + const char **repos_root_url, + const char **repos_uuid, + const char **original_repos_relpath, + const char **original_root_url, + const char **original_uuid, + svn_revnum_t *original_revision, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Scan the working copy for move information of the node LOCAL_ABSPATH. + * If LOCAL_ABSPATH return a SVN_ERR_WC_PATH_UNEXPECTED_STATUS error. + * + * If not NULL *MOVED_FROM_ABSPATH will be set to the previous location + * of LOCAL_ABSPATH, before it or an ancestror was moved. + * + * If not NULL *OP_ROOT_ABSPATH will be set to the new location of the + * path that was actually moved + * + * If not NULL *OP_ROOT_MOVED_FROM_ABSPATH will be set to the old location + * of the path that was actually moved. + * + * If not NULL *MOVED_FROM_DELETE_ABSPATH will be set to the ancestor of the + * moved from location that deletes the original location + * + * Given a working copy + * A/B/C + * svn mv A/B D + * svn rm A + * + * You can call this function on D and D/C. When called on D/C all output + * MOVED_FROM_ABSPATH will be A/B/C + * OP_ROOT_ABSPATH will be D + * OP_ROOT_MOVED_FROM_ABSPATH will be A/B + * MOVED_FROM_DELETE_ABSPATH will be A + */ +svn_error_t * +svn_wc__db_scan_moved(const char **moved_from_abspath, + const char **op_root_abspath, + const char **op_root_moved_from_abspath, + const char **moved_from_delete_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Scan upwards for additional information about a deleted node. + + When a deleted node is discovered in the WORKING tree, the situation + may be quite complex. This function will provide the information to + resolve the circumstances of the deletion. + + For discussion purposes, we will start with the most complex example + and then demonstrate simplified examples. Consider node B/W/D/N has been + found as deleted. B is an unmodified directory (thus, only in BASE). W is + "replacement" content that exists in WORKING, shadowing a similar B/W + directory in BASE. D is a deleted subtree in the WORKING tree, and N is + the deleted node. + + In this example, BASE_DEL_ABSPATH will bet set to B/W. That is the root of + the BASE tree (implicitly) deleted by the replacement. WORK_DEL_ABSPATH + will be set to the subtree deleted within the replacement; in this case, + B/W/D. No move-away took place, so MOVED_TO_ABSPATH is set to NULL. + + In another scenario, B/W was moved-away before W was put into the WORKING + tree through an add/copy/move-here. MOVED_TO_ABSPATH will indicate where + B/W was moved to. Note that further operations may have been performed + post-move, but that is not known or reported by this function. + + If BASE does not have a B/W, then the WORKING B/W is not a replacement, + but a simple add/copy/move-here. BASE_DEL_ABSPATH will be set to NULL. + + If B/W/D does not exist in the WORKING tree (we're only talking about a + deletion of nodes of the BASE tree), then deleting B/W/D would have marked + the subtree for deletion. BASE_DEL_ABSPATH will refer to B/W/D, + MOVED_TO_ABSPATH will be NULL, and WORK_DEL_ABSPATH will be NULL. + + If the BASE node B/W/D was moved instead of deleted, then MOVED_TO_ABSPATH + would indicate the target location (and other OUT values as above). + + When the user deletes B/W/D from the WORKING tree, there are a few + additional considerations. If B/W is a simple addition (not a copy or + a move-here), then the deletion will simply remove the nodes from WORKING + and possibly leave behind "base-delete" markers in the WORKING tree. + If the source is a copy/moved-here, then the nodes are replaced with + deletion markers. + + If the user moves-away B/W/D from the WORKING tree, then behavior is + again dependent upon the origination of B/W. For a plain add, the nodes + simply move to the destination; this means that B/W/D ceases to be a + node and so cannot be scanned. For a copy, a deletion is made at B/W/D, + and a new copy (of a subtree of the original source) is made at the + destination. For a move-here, a deletion is made, and a copy is made at + the destination (we do not track multiple moves; the source is moved to + B/W, then B/W/D is deleted; then a copy is made at the destination; + however, note the double-move could have been performed by moving the + subtree first, then moving the source to B/W). + + There are three further considerations when resolving a deleted node: + + If the BASE B/W/D was deleted explicitly *and* B/W is a replacement, + then the explicit deletion is subsumed by the implicit deletion that + occurred with the B/W replacement. Thus, BASE_DEL_ABSPATH will point + to B/W as the root of the BASE deletion. IOW, we can detect the + explicit move-away, but not an explicit deletion. + + If B/W/D/N refers to a node present in the BASE tree, and B/W was + replaced by a shallow subtree, then it is possible for N to be + reported as deleted (from BASE) yet no deletions occurred in the + WORKING tree above N. Thus, WORK_DEL_ABSPATH will be set to NULL. + + + Summary of OUT parameters: + + BASE_DEL_ABSPATH will specify the nearest ancestor of the explicit or + implicit deletion (if any) that applies to the BASE tree. + + WORK_DEL_ABSPATH will specify the root of a deleted subtree within + the WORKING tree (note there is no concept of layered delete operations + in WORKING, so there is only one deletion root in the ancestry). + + MOVED_TO_ABSPATH will specify the path where this node was moved to + if the node has moved-away. + + If the node was moved-away, MOVED_TO_OP_ROOT_ABSPATH will specify the + target path of the root of the move operation. If LOCAL_ABSPATH itself + is the source path of the root of the move operation, then + MOVED_TO_OP_ROOT_ABSPATH equals MOVED_TO_ABSPATH. + + All OUT parameters may be set to NULL to indicate a lack of interest in + that piece of information. + + If the node given by LOCAL_ABSPATH does not exist, then + SVN_ERR_WC_PATH_NOT_FOUND is returned. If it doesn't have a "deleted" + status, then SVN_ERR_WC_PATH_UNEXPECTED_STATUS will be returned. + + All returned data will be allocated in RESULT_POOL. All temporary + allocations will be made in SCRATCH_POOL. +*/ +svn_error_t * +svn_wc__db_scan_deletion(const char **base_del_abspath, + const char **moved_to_abspath, + const char **work_del_abspath, + const char **moved_to_op_root_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* @} */ + + +/* @defgroup svn_wc__db_upgrade Functions for upgrading a working copy. + @{ +*/ + +/* Create a new wc.db file for LOCAL_DIR_ABSPATH, which is going to be a + working copy for the repository REPOS_ROOT_URL with uuid REPOS_UUID. + Return the raw sqlite handle, repository id and working copy id + and store the database in WC_DB. + + Perform temporary allocations in SCRATCH_POOL. */ +svn_error_t * +svn_wc__db_upgrade_begin(svn_sqlite__db_t **sdb, + apr_int64_t *repos_id, + apr_int64_t *wc_id, + svn_wc__db_t *wc_db, + const char *local_dir_abspath, + const char *repos_root_url, + const char *repos_uuid, + apr_pool_t *scratch_pool); + + +svn_error_t * +svn_wc__db_upgrade_apply_dav_cache(svn_sqlite__db_t *sdb, + const char *dir_relpath, + apr_hash_t *cache_values, + apr_pool_t *scratch_pool); + + +/* ### need much more docco + + ### this function should be called within a sqlite transaction. it makes + ### assumptions around this fact. + + Apply the various sets of properties to the database nodes based on + their existence/presence, the current state of the node, and the original + format of the working copy which provided these property sets. +*/ +svn_error_t * +svn_wc__db_upgrade_apply_props(svn_sqlite__db_t *sdb, + const char *dir_abspath, + const char *local_relpath, + apr_hash_t *base_props, + apr_hash_t *revert_props, + apr_hash_t *working_props, + int original_format, + apr_int64_t wc_id, + apr_pool_t *scratch_pool); + +/* Simply insert (or replace) one row in the EXTERNALS table. */ +svn_error_t * +svn_wc__db_upgrade_insert_external(svn_wc__db_t *db, + const char *local_abspath, + svn_node_kind_t kind, + const char *parent_abspath, + const char *def_local_abspath, + const char *repos_relpath, + const char *repos_root_url, + const char *repos_uuid, + svn_revnum_t def_peg_revision, + svn_revnum_t def_revision, + apr_pool_t *scratch_pool); + +/* Get the repository identifier corresponding to REPOS_ROOT_URL from the + database in SDB. The value is returned in *REPOS_ID. All allocations + are allocated in SCRATCH_POOL. + + NOTE: the row in REPOSITORY must exist. If not, then SVN_ERR_WC_DB_ERROR + is returned. + + ### unclear on whether/how this interface will stay/evolve. */ +svn_error_t * +svn_wc__db_upgrade_get_repos_id(apr_int64_t *repos_id, + svn_sqlite__db_t *sdb, + const char *repos_root_url, + apr_pool_t *scratch_pool); + +/* Upgrade the metadata concerning the WC at WCROOT_ABSPATH, in DB, + * to the SVN_WC__VERSION format. + * + * This function is used for upgrading wc-ng working copies to a newer + * wc-ng format. If a pre-1.7 working copy is found, this function + * returns SVN_ERR_WC_UPGRADE_REQUIRED. + * + * Upgrading subdirectories of a working copy is not supported. + * If WCROOT_ABSPATH is not a working copy root SVN_ERR_WC_INVALID_OP_ON_CWD + * is returned. + */ +svn_error_t * +svn_wc__db_bump_format(int *result_format, + const char *wcroot_abspath, + svn_wc__db_t *db, + apr_pool_t *scratch_pool); + +/* @} */ + + +/* @defgroup svn_wc__db_wq Work queue manipulation. see workqueue.h + @{ +*/ + +/* In the WCROOT associated with DB and WRI_ABSPATH, add WORK_ITEM to the + wcroot's work queue. Use SCRATCH_POOL for all temporary allocations. */ +svn_error_t * +svn_wc__db_wq_add(svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *work_item, + apr_pool_t *scratch_pool); + + +/* In the WCROOT associated with DB and WRI_ABSPATH, fetch a work item that + needs to be completed. Its identifier is returned in ID, and the data in + WORK_ITEM. + + Items are returned in the same order they were queued. This allows for + (say) queueing work on a parent node to be handled before that of its + children. + + If there are no work items to be completed, then ID will be set to zero, + and WORK_ITEM to NULL. + + If COMPLETED_ID is not 0, the wq item COMPLETED_ID will be marked as + completed before returning the next item. + + RESULT_POOL will be used to allocate WORK_ITEM, and SCRATCH_POOL + will be used for all temporary allocations. */ +svn_error_t * +svn_wc__db_wq_fetch_next(apr_uint64_t *id, + svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + apr_uint64_t completed_id, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Special variant of svn_wc__db_wq_fetch_next(), which in the same transaction + also records timestamps and sizes for one or more nodes */ +svn_error_t * +svn_wc__db_wq_record_and_fetch_next(apr_uint64_t *id, + svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + apr_uint64_t completed_id, + apr_hash_t *record_map, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* @} */ + + +/* Note: LEVELS_TO_LOCK is here strictly for backward compat. The access + batons still have the notion of 'levels to lock' and we need to ensure + that they still function correctly, even in the new world. 'levels to + lock' should not be exposed through the wc-ng APIs at all: users either + get to lock the entire tree (rooted at some subdir, of course), or none. + + An infinite depth lock is obtained with LEVELS_TO_LOCK set to -1, but until + we move to a single DB only depth 0 is supported. +*/ +svn_error_t * +svn_wc__db_wclock_obtain(svn_wc__db_t *db, + const char *local_abspath, + int levels_to_lock, + svn_boolean_t steal_lock, + apr_pool_t *scratch_pool); + +/* Set LOCK_ABSPATH to the path of the the directory that owns the + lock on LOCAL_ABSPATH, or NULL, if LOCAL_ABSPATH is not locked. */ +svn_error_t* +svn_wc__db_wclock_find_root(const char **lock_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Check if somebody has a wclock on LOCAL_ABSPATH */ +svn_error_t * +svn_wc__db_wclocked(svn_boolean_t *locked, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* Release the previously obtained lock on LOCAL_ABSPATH */ +svn_error_t * +svn_wc__db_wclock_release(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* Checks whether DB currently owns a lock to operate on LOCAL_ABSPATH. + If EXACT is TRUE only lock roots are checked. */ +svn_error_t * +svn_wc__db_wclock_owns_lock(svn_boolean_t *own_lock, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t exact, + apr_pool_t *scratch_pool); + + + +/* @defgroup svn_wc__db_temp Various temporary functions during transition + + ### These functions SHOULD be completely removed before 1.7 + + @{ +*/ + +/* Removes all references to LOCAL_ABSPATH from DB, while optionally leaving + a not present node. + + This operation always recursively removes all nodes at and below + LOCAL_ABSPATH from NODES and ACTUAL. + + If NOT_PRESENT_REVISION specifies a valid revision, leave a not_present + BASE node at local_abspath of the specified status and kind. + (Requires an existing BASE node before removing) + + If DESTROY_WC is TRUE, this operation *installs* workqueue operations to + update the local filesystem after the database operation. If DESTROY_CHANGES + is FALSE, modified and unversioned files are left after running this + operation (and the WQ). If DESTROY_CHANGES and DESTROY_WC are TRUE, + LOCAL_ABSPATH and everything below it will be removed by the WQ. + + + Note: Unlike many similar functions it is a valid scenario for this + function to be called on a wcroot! In this case it will just leave the root + record in BASE + */ +svn_error_t * +svn_wc__db_op_remove_node(svn_boolean_t *left_changes, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t destroy_wc, + svn_boolean_t destroy_changes, + svn_revnum_t not_present_revision, + svn_wc__db_status_t not_present_status, + svn_node_kind_t not_present_kind, + const svn_skel_t *conflict, + const svn_skel_t *work_items, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* Sets the depth of LOCAL_ABSPATH in its working copy to DEPTH using DB. + + Returns SVN_ERR_WC_PATH_NOT_FOUND if LOCAL_ABSPATH is not a BASE directory + */ +svn_error_t * +svn_wc__db_op_set_base_depth(svn_wc__db_t *db, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *scratch_pool); + +/* ### temp function. return the FORMAT for the directory LOCAL_ABSPATH. */ +svn_error_t * +svn_wc__db_temp_get_format(int *format, + svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool); + +/* ### temp functions to manage/store access batons within the DB. */ +svn_wc_adm_access_t * +svn_wc__db_temp_get_access(svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool); +void +svn_wc__db_temp_set_access(svn_wc__db_t *db, + const char *local_dir_abspath, + svn_wc_adm_access_t *adm_access, + apr_pool_t *scratch_pool); +svn_error_t * +svn_wc__db_temp_close_access(svn_wc__db_t *db, + const char *local_dir_abspath, + svn_wc_adm_access_t *adm_access, + apr_pool_t *scratch_pool); +void +svn_wc__db_temp_clear_access(svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool); + +/* ### shallow hash: abspath -> svn_wc_adm_access_t * */ +apr_hash_t * +svn_wc__db_temp_get_all_access(svn_wc__db_t *db, + apr_pool_t *result_pool); + +/* ### temp function to open the sqlite database to the appropriate location, + ### then borrow it for a bit. + ### The *only* reason for this function is because entries.c still + ### manually hacks the sqlite database. + + ### No matter how tempted you may be DO NOT USE THIS FUNCTION! + ### (if you do, gstein will hunt you down and burn your knee caps off + ### in the middle of the night) + ### "Bet on it." --gstein +*/ +svn_error_t * +svn_wc__db_temp_borrow_sdb(svn_sqlite__db_t **sdb, + svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool); + + +/* Return a directory in *TEMP_DIR_ABSPATH that is suitable for temporary + files which may need to be moved (atomically and same-device) into the + working copy indicated by WRI_ABSPATH. */ +svn_error_t * +svn_wc__db_temp_wcroot_tempdir(const char **temp_dir_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Update the BASE_NODE of directory LOCAL_ABSPATH to be NEW_REPOS_RELPATH + at revision NEW_REV with status incomplete. */ +svn_error_t * +svn_wc__db_temp_op_start_directory_update(svn_wc__db_t *db, + const char *local_abspath, + const char *new_repos_relpath, + svn_revnum_t new_rev, + apr_pool_t *scratch_pool); + +/* Marks a directory update started with + svn_wc__db_temp_op_start_directory_update as completed, by removing + the incomplete status */ +svn_error_t * +svn_wc__db_temp_op_end_directory_update(svn_wc__db_t *db, + const char *local_dir_abspath, + apr_pool_t *scratch_pool); + + +/* Copy the base tree at LOCAL_ABSPATH into the working tree as copy, + leaving any subtree additions and copies as-is. This allows the + base node tree to be removed. */ +svn_error_t * +svn_wc__db_op_make_copy(svn_wc__db_t *db, + const char *local_abspath, + const svn_skel_t *conflicts, + const svn_skel_t *work_items, + apr_pool_t *scratch_pool); + +/* Close the wc root LOCAL_ABSPATH and remove any per-directory + handles associated with it. */ +svn_error_t * +svn_wc__db_drop_root(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* Return the OP_DEPTH for LOCAL_RELPATH. */ +int +svn_wc__db_op_depth_for_upgrade(const char *local_relpath); + +/* Set *HAVE_WORK TRUE if there is a working layer below the top layer and + *HAVE_BASE if there is a base layer. Set *STATUS to the status of the + highest layer below WORKING */ +svn_error_t * +svn_wc__db_info_below_working(svn_boolean_t *have_base, + svn_boolean_t *have_work, + svn_wc__db_status_t *status, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + + +/* Gets an array of const char *local_relpaths of descendants of LOCAL_ABSPATH, + * which itself must be the op root of an addition, copy or move. + * The descendants returned are at the same op_depth, but are to be deleted + * by the commit processing because they are not present in the local copy. + */ +svn_error_t * +svn_wc__db_get_not_present_descendants(const apr_array_header_t **descendants, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Gather revision status information about a working copy using DB. + * + * Set *MIN_REVISION and *MAX_REVISION to the lowest and highest revision + * numbers found within LOCAL_ABSPATH. + * Only nodes with op_depth zero and presence 'normal' or 'incomplete' + * are considered, so that added, deleted or excluded nodes do not affect + * the result. If COMMITTED is TRUE, set *MIN_REVISION and *MAX_REVISION + * to the lowest and highest committed (i.e. "last changed") revision numbers, + * respectively. + * + * Indicate in *IS_SPARSE_CHECKOUT whether any of the nodes within + * LOCAL_ABSPATH is sparse. + * Indicate in *IS_MODIFIED whether the working copy has local modifications. + * + * Indicate in *IS_SWITCHED whether any node beneath LOCAL_ABSPATH + * is switched. If TRAIL_URL is non-NULL, use it to determine if LOCAL_ABSPATH + * itself is switched. It should be any trailing portion of LOCAL_ABSPATH's + * expected URL, long enough to include any parts that the caller considers + * might be changed by a switch. If it does not match the end of WC_PATH's + * actual URL, then report a "switched" status. + * + * See also the functions below which provide a subset of this functionality. + */ +svn_error_t * +svn_wc__db_revision_status(svn_revnum_t *min_revision, + svn_revnum_t *max_revision, + svn_boolean_t *is_sparse_checkout, + svn_boolean_t *is_modified, + svn_boolean_t *is_switched, + svn_wc__db_t *db, + const char *local_abspath, + const char *trail_url, + svn_boolean_t committed, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* Set *MIN_REVISION and *MAX_REVISION to the lowest and highest revision + * numbers found within LOCAL_ABSPATH in the working copy using DB. + * Only nodes with op_depth zero and presence 'normal' or 'incomplete' + * are considered, so that added, deleted or excluded nodes do not affect + * the result. If COMMITTED is TRUE, set *MIN_REVISION and *MAX_REVISION + * to the lowest and highest committed (i.e. "last changed") revision numbers, + * respectively. Use SCRATCH_POOL for temporary allocations. + * + * Either of MIN_REVISION and MAX_REVISION may be passed as NULL if + * the caller doesn't care about that return value. + * + * This function provides a subset of the functionality of + * svn_wc__db_revision_status() and is more efficient if the caller + * doesn't need all information returned by svn_wc__db_revision_status(). */ +svn_error_t * +svn_wc__db_min_max_revisions(svn_revnum_t *min_revision, + svn_revnum_t *max_revision, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t committed, + apr_pool_t *scratch_pool); + +/* Indicate in *IS_SWITCHED whether any node beneath LOCAL_ABSPATH + * is switched, using DB. Use SCRATCH_POOL for temporary allocations. + * + * If TRAIL_URL is non-NULL, use it to determine if LOCAL_ABSPATH itself + * is switched. It should be any trailing portion of LOCAL_ABSPATH's + * expected URL, long enough to include any parts that the caller considers + * might be changed by a switch. If it does not match the end of WC_PATH's + * actual URL, then report a "switched" status. + * + * This function provides a subset of the functionality of + * svn_wc__db_revision_status() and is more efficient if the caller + * doesn't need all information returned by svn_wc__db_revision_status(). */ +svn_error_t * +svn_wc__db_has_switched_subtrees(svn_boolean_t *is_switched, + svn_wc__db_t *db, + const char *local_abspath, + const char *trail_url, + apr_pool_t *scratch_pool); + +/* Set @a *excluded_subtrees to a hash mapping <tt>const char *</tt> + * local absolute paths to <tt>const char *</tt> local absolute paths for + * every path under @a local_abspath in @a db which are excluded by + * the server (e.g. due to authz), or user. If no such paths are found then + * @a *server_excluded_subtrees is set to @c NULL. + * Allocate the hash and all items therein from @a result_pool. + */ +svn_error_t * +svn_wc__db_get_excluded_subtrees(apr_hash_t **server_excluded_subtrees, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Indicate in *IS_MODIFIED whether the working copy has local modifications, + * using DB. Use SCRATCH_POOL for temporary allocations. + * + * This function provides a subset of the functionality of + * svn_wc__db_revision_status() and is more efficient if the caller + * doesn't need all information returned by svn_wc__db_revision_status(). */ +svn_error_t * +svn_wc__db_has_local_mods(svn_boolean_t *is_modified, + svn_wc__db_t *db, + const char *local_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + + +/* Verify the consistency of metadata concerning the WC that contains + * WRI_ABSPATH, in DB. Return an error if any problem is found. */ +svn_error_t * +svn_wc__db_verify(svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *scratch_pool); + + +/* Possibly need two structures, one with relpaths and with abspaths? + * Only exposed for testing at present. */ +struct svn_wc__db_moved_to_t { + const char *local_relpath; /* moved-to destination */ + int op_depth; /* op-root of source */ +}; + +/* Set *FINAL_ABSPATH to an array of svn_wc__db_moved_to_t for + * LOCAL_ABSPATH after following any and all nested moves. + * Only exposed for testing at present. */ +svn_error_t * +svn_wc__db_follow_moved_to(apr_array_header_t **moved_tos, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Update a moved-away tree conflict victim at VICTIM_ABSPATH with changes + * brought in by the update operation which flagged the tree conflict. */ +svn_error_t * +svn_wc__db_update_moved_away_conflict_victim(svn_wc__db_t *db, + const char *victim_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + +/* LOCAL_ABSPATH is moved to MOVE_DST_ABSPATH. MOVE_SRC_ROOT_ABSPATH + * is the root of the move to MOVE_DST_OP_ROOT_ABSPATH. + * MOVE_SRC_OP_ROOT_ABSPATH is the op-root of the move; it's the same + * as MOVE_SRC_ROOT_ABSPATH except for moves inside deletes when it is + * the op-root of the delete. */ +svn_error_t * +svn_wc__db_base_moved_to(const char **move_dst_abspath, + const char **move_dst_op_root_abspath, + const char **move_src_root_abspath, + const char **move_src_op_root_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Recover space from the database file for LOCAL_ABSPATH by running + * the "vacuum" command. */ +svn_error_t * +svn_wc__db_vacuum(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool); + +/* This raises move-edit tree-conflicts on any moves inside the + delete-edit conflict on LOCAL_ABSPATH. This is experimental: see + comment in resolve_conflict_on_node about combining with another + function. */ +svn_error_t * +svn_wc__db_resolve_delete_raise_moved_away(svn_wc__db_t *db, + const char *local_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); + +/* Like svn_wc__db_resolve_delete_raise_moved_away this should be + combined. */ +svn_error_t * +svn_wc__db_resolve_break_moved_away(svn_wc__db_t *db, + const char *local_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); + +/* Break moves for all moved-away children of LOCAL_ABSPATH, within + * a single transaction. + * + * ### Like svn_wc__db_resolve_delete_raise_moved_away this should be + * combined. */ +svn_error_t * +svn_wc__db_resolve_break_moved_away_children(svn_wc__db_t *db, + const char *local_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); + +/* Set *REQUIRED_ABSPATH to the path that should be locked to ensure + * that the lock covers all paths affected by resolving the conflicts + * in the tree LOCAL_ABSPATH. */ +svn_error_t * +svn_wc__required_lock_for_resolve(const char **required_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); +/* @} */ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_WC_DB_H */ diff --git a/subversion/libsvn_wc/wc_db_pristine.c b/subversion/libsvn_wc/wc_db_pristine.c new file mode 100644 index 000000000000..d9dc8f3960be --- /dev/null +++ b/subversion/libsvn_wc/wc_db_pristine.c @@ -0,0 +1,925 @@ +/* + * wc_db_pristine.c : Pristine ("text base") management + * + * See the spec in 'notes/wc-ng/pristine-store'. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#define SVN_WC__I_AM_WC_DB + +#include "svn_pools.h" +#include "svn_dirent_uri.h" + +#include "wc.h" +#include "wc_db.h" +#include "wc-queries.h" +#include "wc_db_private.h" + +#define PRISTINE_STORAGE_EXT ".svn-base" +#define PRISTINE_STORAGE_RELPATH "pristine" +#define PRISTINE_TEMPDIR_RELPATH "tmp" + + + +/* Returns in PRISTINE_ABSPATH a new string allocated from RESULT_POOL, + holding the local absolute path to the file location that is dedicated + to hold CHECKSUM's pristine file, relating to the pristine store + configured for the working copy indicated by PDH. The returned path + does not necessarily currently exist. + + Any other allocations are made in SCRATCH_POOL. */ +static svn_error_t * +get_pristine_fname(const char **pristine_abspath, + const char *wcroot_abspath, + const svn_checksum_t *sha1_checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *base_dir_abspath; + const char *hexdigest = svn_checksum_to_cstring(sha1_checksum, scratch_pool); + char subdir[3]; + + /* ### code is in transition. make sure we have the proper data. */ + SVN_ERR_ASSERT(pristine_abspath != NULL); + SVN_ERR_ASSERT(svn_dirent_is_absolute(wcroot_abspath)); + SVN_ERR_ASSERT(sha1_checksum != NULL); + SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1); + + base_dir_abspath = svn_dirent_join_many(scratch_pool, + wcroot_abspath, + svn_wc_get_adm_dir(scratch_pool), + PRISTINE_STORAGE_RELPATH, + NULL); + + /* We should have a valid checksum and (thus) a valid digest. */ + SVN_ERR_ASSERT(hexdigest != NULL); + + /* Get the first two characters of the digest, for the subdir. */ + subdir[0] = hexdigest[0]; + subdir[1] = hexdigest[1]; + subdir[2] = '\0'; + + hexdigest = apr_pstrcat(scratch_pool, hexdigest, PRISTINE_STORAGE_EXT, + (char *)NULL); + + /* The file is located at DIR/.svn/pristine/XX/XXYYZZ...svn-base */ + *pristine_abspath = svn_dirent_join_many(result_pool, + base_dir_abspath, + subdir, + hexdigest, + NULL); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_pristine_get_path(const char **pristine_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_checksum_t *sha1_checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_boolean_t present; + + SVN_ERR_ASSERT(pristine_abspath != NULL); + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + SVN_ERR_ASSERT(sha1_checksum != NULL); + /* ### Transitional: accept MD-5 and look up the SHA-1. Return an error + * if the pristine text is not in the store. */ + if (sha1_checksum->kind != svn_checksum_sha1) + SVN_ERR(svn_wc__db_pristine_get_sha1(&sha1_checksum, db, wri_abspath, + sha1_checksum, + scratch_pool, scratch_pool)); + SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, wri_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_wc__db_pristine_check(&present, db, wri_abspath, sha1_checksum, + scratch_pool)); + if (! present) + return svn_error_createf(SVN_ERR_WC_DB_ERROR, NULL, + _("The pristine text with checksum '%s' was " + "not found"), + svn_checksum_to_cstring_display(sha1_checksum, + scratch_pool)); + + SVN_ERR(get_pristine_fname(pristine_abspath, wcroot->abspath, + sha1_checksum, + result_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_pristine_get_future_path(const char **pristine_abspath, + const char *wcroot_abspath, + const svn_checksum_t *sha1_checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR(get_pristine_fname(pristine_abspath, wcroot_abspath, + sha1_checksum, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Set *CONTENTS to a readable stream from which the pristine text + * identified by SHA1_CHECKSUM and PRISTINE_ABSPATH can be read from the + * pristine store of WCROOT. If SIZE is not null, set *SIZE to the size + * in bytes of that text. If that text is not in the pristine store, + * return an error. + * + * Even if the pristine text is removed from the store while it is being + * read, the stream will remain valid and readable until it is closed. + * + * Allocate the stream in RESULT_POOL. + * + * This function expects to be executed inside a SQLite txn. + * + * Implements 'notes/wc-ng/pristine-store' section A-3(d). + */ +static svn_error_t * +pristine_read_txn(svn_stream_t **contents, + svn_filesize_t *size, + svn_wc__db_wcroot_t *wcroot, + const svn_checksum_t *sha1_checksum, + const char *pristine_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + /* Check that this pristine text is present in the store. (The presence + * of the file is not sufficient.) */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_PRISTINE_SIZE)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (size) + *size = svn_sqlite__column_int64(stmt, 0); + + SVN_ERR(svn_sqlite__reset(stmt)); + if (! have_row) + { + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("Pristine text '%s' not present"), + svn_checksum_to_cstring_display( + sha1_checksum, scratch_pool)); + } + + /* Open the file as a readable stream. It will remain readable even when + * deleted from disk; APR guarantees that on Windows as well as Unix. */ + if (contents) + SVN_ERR(svn_stream_open_readonly(contents, pristine_abspath, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_pristine_read(svn_stream_t **contents, + svn_filesize_t *size, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_checksum_t *sha1_checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *pristine_abspath; + + SVN_ERR_ASSERT(contents != NULL); + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + + /* Some 1.6-to-1.7 wc upgrades created rows without checksums and + updating such a row passes NULL here. */ + if (!sha1_checksum) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Can't read '%s' from pristine store " + "because no checksum supplied"), + svn_dirent_local_style(wri_abspath, scratch_pool)); + + SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath, + sha1_checksum, + scratch_pool, scratch_pool)); + SVN_WC__DB_WITH_TXN( + pristine_read_txn(contents, size, + wcroot, sha1_checksum, pristine_abspath, + result_pool, scratch_pool), + wcroot); + + return SVN_NO_ERROR; +} + + +/* Return the absolute path to the temporary directory for pristine text + files within WCROOT. */ +static char * +pristine_get_tempdir(svn_wc__db_wcroot_t *wcroot, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_dirent_join_many(result_pool, wcroot->abspath, + svn_wc_get_adm_dir(scratch_pool), + PRISTINE_TEMPDIR_RELPATH, (char *)NULL); +} + +svn_error_t * +svn_wc__db_pristine_get_tempdir(const char **temp_dir_abspath, + svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(temp_dir_abspath != NULL); + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + *temp_dir_abspath = pristine_get_tempdir(wcroot, result_pool, scratch_pool); + return SVN_NO_ERROR; +} + + +/* Install the pristine text described by BATON into the pristine store of + * SDB. If it is already stored then just delete the new file + * BATON->tempfile_abspath. + * + * This function expects to be executed inside a SQLite txn that has already + * acquired a 'RESERVED' lock. + * + * Implements 'notes/wc-ng/pristine-store' section A-3(a). + */ +static svn_error_t * +pristine_install_txn(svn_sqlite__db_t *sdb, + /* The path to the source file that is to be moved into place. */ + const char *tempfile_abspath, + /* The target path for the file (within the pristine store). */ + const char *pristine_abspath, + /* The pristine text's SHA-1 checksum. */ + const svn_checksum_t *sha1_checksum, + /* The pristine text's MD-5 checksum. */ + const svn_checksum_t *md5_checksum, + apr_pool_t *scratch_pool) +{ + apr_finfo_t finfo; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_error_t *err; + + /* If this pristine text is already present in the store, just keep it: + * delete the new one and return. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_PRISTINE)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + { +#ifdef SVN_DEBUG + /* Consistency checks. Verify both files exist and match. + * ### We could check much more. */ + { + apr_finfo_t finfo1, finfo2; + SVN_ERR(svn_io_stat(&finfo1, tempfile_abspath, APR_FINFO_SIZE, + scratch_pool)); + SVN_ERR(svn_io_stat(&finfo2, pristine_abspath, APR_FINFO_SIZE, + scratch_pool)); + if (finfo1.size != finfo2.size) + { + return svn_error_createf( + SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL, + _("New pristine text '%s' has different size: %ld versus %ld"), + svn_checksum_to_cstring_display(sha1_checksum, scratch_pool), + (long int)finfo1.size, (long int)finfo2.size); + } + } +#endif + + /* Remove the temp file: it's already there */ + SVN_ERR(svn_io_remove_file2(tempfile_abspath, + FALSE /* ignore_enoent */, scratch_pool)); + return SVN_NO_ERROR; + } + + /* Move the file to its target location. (If it is already there, it is + * an orphan file and it doesn't matter if we overwrite it.) */ + err = svn_io_file_rename(tempfile_abspath, pristine_abspath, + scratch_pool); + + /* Maybe the directory doesn't exist yet? */ + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_t *err2; + + err2 = svn_io_dir_make(svn_dirent_dirname(pristine_abspath, + scratch_pool), + APR_OS_DEFAULT, scratch_pool); + + if (err2) + /* Creating directory didn't work: Return all errors */ + return svn_error_trace(svn_error_compose_create(err, err2)); + else + /* We could create a directory: retry install */ + svn_error_clear(err); + + SVN_ERR(svn_io_file_rename(tempfile_abspath, pristine_abspath, + scratch_pool)); + } + else + SVN_ERR(err); + + SVN_ERR(svn_io_stat(&finfo, pristine_abspath, APR_FINFO_SIZE, + scratch_pool)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_INSERT_PRISTINE)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__bind_int64(stmt, 3, finfo.size)); + SVN_ERR(svn_sqlite__insert(NULL, stmt)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_pristine_install(svn_wc__db_t *db, + const char *tempfile_abspath, + const svn_checksum_t *sha1_checksum, + const svn_checksum_t *md5_checksum, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *wri_abspath; + const char *pristine_abspath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(tempfile_abspath)); + SVN_ERR_ASSERT(sha1_checksum != NULL); + SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1); + SVN_ERR_ASSERT(md5_checksum != NULL); + SVN_ERR_ASSERT(md5_checksum->kind == svn_checksum_md5); + + /* ### this logic assumes that TEMPFILE_ABSPATH follows this pattern: + ### WCROOT_ABSPATH/COMPONENT/COMPONENT/TEMPFNAME + ### if we change this (see PRISTINE_TEMPDIR_RELPATH), then this + ### logic should change. */ + wri_abspath = svn_dirent_dirname( + svn_dirent_dirname( + svn_dirent_dirname(tempfile_abspath, scratch_pool), + scratch_pool), + scratch_pool); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath, + sha1_checksum, + scratch_pool, scratch_pool)); + + /* Ensure the SQL txn has at least a 'RESERVED' lock before we start looking + * at the disk, to ensure no concurrent pristine install/delete txn. */ + SVN_SQLITE__WITH_IMMEDIATE_TXN( + pristine_install_txn(wcroot->sdb, + tempfile_abspath, pristine_abspath, + sha1_checksum, md5_checksum, + scratch_pool), + wcroot->sdb); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_pristine_get_md5(const svn_checksum_t **md5_checksum, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_checksum_t *sha1_checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + SVN_ERR_ASSERT(sha1_checksum != NULL); + SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_PRISTINE)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + return svn_error_createf(SVN_ERR_WC_DB_ERROR, svn_sqlite__reset(stmt), + _("The pristine text with checksum '%s' was " + "not found"), + svn_checksum_to_cstring_display(sha1_checksum, + scratch_pool)); + + SVN_ERR(svn_sqlite__column_checksum(md5_checksum, stmt, 0, result_pool)); + SVN_ERR_ASSERT((*md5_checksum)->kind == svn_checksum_md5); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + + +svn_error_t * +svn_wc__db_pristine_get_sha1(const svn_checksum_t **sha1_checksum, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_checksum_t *md5_checksum, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + SVN_ERR_ASSERT(sha1_checksum != NULL); + SVN_ERR_ASSERT(md5_checksum->kind == svn_checksum_md5); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_PRISTINE_BY_MD5)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, md5_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (!have_row) + return svn_error_createf(SVN_ERR_WC_DB_ERROR, svn_sqlite__reset(stmt), + _("The pristine text with MD5 checksum '%s' was " + "not found"), + svn_checksum_to_cstring_display(md5_checksum, + scratch_pool)); + + SVN_ERR(svn_sqlite__column_checksum(sha1_checksum, stmt, 0, result_pool)); + SVN_ERR_ASSERT((*sha1_checksum)->kind == svn_checksum_sha1); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +/* Handle the moving of a pristine from SRC_WCROOT to DST_WCROOT. The existing + pristine in SRC_WCROOT is described by CHECKSUM, MD5_CHECKSUM and SIZE */ +static svn_error_t * +maybe_transfer_one_pristine(svn_wc__db_wcroot_t *src_wcroot, + svn_wc__db_wcroot_t *dst_wcroot, + const svn_checksum_t *checksum, + const svn_checksum_t *md5_checksum, + apr_int64_t size, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const char *pristine_abspath; + svn_sqlite__stmt_t *stmt; + svn_stream_t *src_stream; + svn_stream_t *dst_stream; + const char *tmp_abspath; + const char *src_abspath; + int affected_rows; + svn_error_t *err; + + SVN_ERR(svn_sqlite__get_statement(&stmt, dst_wcroot->sdb, + STMT_INSERT_OR_IGNORE_PRISTINE)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, checksum, scratch_pool)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__bind_int64(stmt, 3, size)); + + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + if (affected_rows == 0) + return SVN_NO_ERROR; + + SVN_ERR(svn_stream_open_unique(&dst_stream, &tmp_abspath, + pristine_get_tempdir(dst_wcroot, + scratch_pool, + scratch_pool), + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + + SVN_ERR(get_pristine_fname(&src_abspath, src_wcroot->abspath, checksum, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_stream_open_readonly(&src_stream, src_abspath, + scratch_pool, scratch_pool)); + + /* ### Should we verify the SHA1 or MD5 here, or is that too expensive? */ + SVN_ERR(svn_stream_copy3(src_stream, dst_stream, + cancel_func, cancel_baton, + scratch_pool)); + + SVN_ERR(get_pristine_fname(&pristine_abspath, dst_wcroot->abspath, checksum, + scratch_pool, scratch_pool)); + + /* Move the file to its target location. (If it is already there, it is + * an orphan file and it doesn't matter if we overwrite it.) */ + err = svn_io_file_rename(tmp_abspath, pristine_abspath, scratch_pool); + + /* Maybe the directory doesn't exist yet? */ + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_t *err2; + + err2 = svn_io_dir_make(svn_dirent_dirname(pristine_abspath, + scratch_pool), + APR_OS_DEFAULT, scratch_pool); + + if (err2) + /* Creating directory didn't work: Return all errors */ + return svn_error_trace(svn_error_compose_create(err, err2)); + else + /* We could create a directory: retry install */ + svn_error_clear(err); + + SVN_ERR(svn_io_file_rename(tmp_abspath, pristine_abspath, scratch_pool)); + } + else + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +/* Transaction implementation of svn_wc__db_pristine_transfer(). + We have a lock on DST_WCROOT. + */ +static svn_error_t * +pristine_transfer_txn(svn_wc__db_wcroot_t *src_wcroot, + svn_wc__db_wcroot_t *dst_wcroot, + const char *src_relpath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t got_row; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb, + STMT_SELECT_COPY_PRISTINES)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", src_wcroot->wc_id, src_relpath)); + + /* This obtains an sqlite read lock on src_wcroot */ + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + + while (got_row) + { + const svn_checksum_t *checksum; + const svn_checksum_t *md5_checksum; + apr_int64_t size; + svn_error_t *err; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_sqlite__column_checksum(&checksum, stmt, 0, iterpool)); + SVN_ERR(svn_sqlite__column_checksum(&md5_checksum, stmt, 1, iterpool)); + size = svn_sqlite__column_int64(stmt, 2); + + err = maybe_transfer_one_pristine(src_wcroot, dst_wcroot, + checksum, md5_checksum, size, + cancel_func, cancel_baton, + iterpool); + + if (err) + return svn_error_trace(svn_error_compose_create( + err, + svn_sqlite__reset(stmt))); + + SVN_ERR(svn_sqlite__step(&got_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_pristine_transfer(svn_wc__db_t *db, + const char *src_local_abspath, + const char *dst_wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *src_wcroot, *dst_wcroot; + const char *src_relpath, *dst_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&src_wcroot, &src_relpath, + db, src_local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(src_wcroot); + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&dst_wcroot, &dst_relpath, + db, dst_wri_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(dst_wcroot); + + if (src_wcroot == dst_wcroot + || src_wcroot->sdb == dst_wcroot->sdb) + { + return SVN_NO_ERROR; /* Nothing to transfer */ + } + + SVN_WC__DB_WITH_TXN( + pristine_transfer_txn(src_wcroot, dst_wcroot, src_relpath, + cancel_func, cancel_baton, scratch_pool), + dst_wcroot); + + return SVN_NO_ERROR; +} + + + + +/* Remove the file at FILE_ABSPATH in such a way that we could re-create a + * new file of the same name at any time thereafter. + * + * On Windows, the file will not disappear immediately from the directory if + * it is still being read so the best thing to do is first rename it to a + * unique name. */ +static svn_error_t * +remove_file(const char *file_abspath, + svn_wc__db_wcroot_t *wcroot, + svn_boolean_t ignore_enoent, + apr_pool_t *scratch_pool) +{ +#ifdef WIN32 + svn_error_t *err; + const char *temp_abspath; + const char *temp_dir_abspath + = pristine_get_tempdir(wcroot, scratch_pool, scratch_pool); + + /* To rename the file to a unique name in the temp dir, first create a + * uniquely named file in the temp dir and then overwrite it. */ + SVN_ERR(svn_io_open_unique_file3(NULL, &temp_abspath, temp_dir_abspath, + svn_io_file_del_none, + scratch_pool, scratch_pool)); + err = svn_io_file_rename(file_abspath, temp_abspath, scratch_pool); + if (err && ignore_enoent && APR_STATUS_IS_ENOENT(err->apr_err)) + svn_error_clear(err); + else + SVN_ERR(err); + file_abspath = temp_abspath; +#endif + + SVN_ERR(svn_io_remove_file2(file_abspath, ignore_enoent, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* If the pristine text referenced by SHA1_CHECKSUM in WCROOT/SDB, whose path + * within the pristine store is PRISTINE_ABSPATH, has a reference count of + * zero, delete it (both the database row and the disk file). + * + * This function expects to be executed inside a SQLite txn that has already + * acquired a 'RESERVED' lock. + */ +static svn_error_t * +pristine_remove_if_unreferenced_txn(svn_sqlite__db_t *sdb, + svn_wc__db_wcroot_t *wcroot, + const svn_checksum_t *sha1_checksum, + const char *pristine_abspath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + int affected_rows; + + /* Remove the DB row, if refcount is 0. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, + STMT_DELETE_PRISTINE_IF_UNREFERENCED)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__update(&affected_rows, stmt)); + + /* If we removed the DB row, then remove the file. */ + if (affected_rows > 0) + { + /* If the file is not present, something has gone wrong, but at this + * point it no longer matters. In a debug build, raise an error, but + * in a release build, it is more helpful to ignore it and continue. */ +#ifdef SVN_DEBUG + svn_boolean_t ignore_enoent = FALSE; +#else + svn_boolean_t ignore_enoent = TRUE; +#endif + + SVN_ERR(remove_file(pristine_abspath, wcroot, ignore_enoent, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* If the pristine text referenced by SHA1_CHECKSUM in WCROOT has a + * reference count of zero, delete it (both the database row and the disk + * file). + * + * Implements 'notes/wc-ng/pristine-store' section A-3(b). */ +static svn_error_t * +pristine_remove_if_unreferenced(svn_wc__db_wcroot_t *wcroot, + const svn_checksum_t *sha1_checksum, + apr_pool_t *scratch_pool) +{ + const char *pristine_abspath; + + SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath, + sha1_checksum, scratch_pool, scratch_pool)); + + /* Ensure the SQL txn has at least a 'RESERVED' lock before we start looking + * at the disk, to ensure no concurrent pristine install/delete txn. */ + SVN_SQLITE__WITH_IMMEDIATE_TXN( + pristine_remove_if_unreferenced_txn( + wcroot->sdb, wcroot, sha1_checksum, pristine_abspath, scratch_pool), + wcroot->sdb); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_pristine_remove(svn_wc__db_t *db, + const char *wri_abspath, + const svn_checksum_t *sha1_checksum, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + SVN_ERR_ASSERT(sha1_checksum != NULL); + /* ### Transitional: accept MD-5 and look up the SHA-1. Return an error + * if the pristine text is not in the store. */ + if (sha1_checksum->kind != svn_checksum_sha1) + SVN_ERR(svn_wc__db_pristine_get_sha1(&sha1_checksum, db, wri_abspath, + sha1_checksum, + scratch_pool, scratch_pool)); + SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* If the work queue is not empty, don't delete any pristine text because + * the work queue may contain a reference to it. */ + { + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_LOOK_FOR_WORK)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (have_row) + return SVN_NO_ERROR; + } + + /* If not referenced, remove the PRISTINE table row and the file. */ + SVN_ERR(pristine_remove_if_unreferenced(wcroot, sha1_checksum, scratch_pool)); + + return SVN_NO_ERROR; +} + + +static svn_error_t * +pristine_cleanup_wcroot(svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_error_t *err = NULL; + + /* Find each unreferenced pristine in the DB and remove it. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_UNREFERENCED_PRISTINES)); + while (! err) + { + svn_boolean_t have_row; + const svn_checksum_t *sha1_checksum; + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (! have_row) + break; + + SVN_ERR(svn_sqlite__column_checksum(&sha1_checksum, stmt, 0, + scratch_pool)); + err = pristine_remove_if_unreferenced(wcroot, sha1_checksum, + scratch_pool); + } + + return svn_error_trace( + svn_error_compose_create(err, svn_sqlite__reset(stmt))); +} + +svn_error_t * +svn_wc__db_pristine_cleanup(svn_wc__db_t *db, + const char *wri_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(pristine_cleanup_wcroot(wcroot, scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_pristine_check(svn_boolean_t *present, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_checksum_t *sha1_checksum, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + SVN_ERR_ASSERT(sha1_checksum != NULL); + + if (sha1_checksum->kind != svn_checksum_sha1) + { + *present = FALSE; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + wri_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + /* A filestat is much cheaper than a sqlite transaction especially on NFS, + so first check if there is a pristine file and then if we are allowed + to use it. */ + { + const char *pristine_abspath; + svn_node_kind_t kind_on_disk; + + SVN_ERR(get_pristine_fname(&pristine_abspath, wcroot->abspath, + sha1_checksum, scratch_pool, scratch_pool)); + SVN_ERR(svn_io_check_path(pristine_abspath, &kind_on_disk, scratch_pool)); + if (kind_on_disk != svn_node_file) + { + *present = FALSE; + return SVN_NO_ERROR; + } + } + + /* Check that there is an entry in the PRISTINE table. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_PRISTINE)); + SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + *present = have_row; + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/wc_db_private.h b/subversion/libsvn_wc/wc_db_private.h new file mode 100644 index 000000000000..0679b32e7e3a --- /dev/null +++ b/subversion/libsvn_wc/wc_db_private.h @@ -0,0 +1,458 @@ +/** + * @copyright + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * @endcopyright + */ + +/* This file is not for general consumption; it should only be used by + wc_db.c. */ +#ifndef SVN_WC__I_AM_WC_DB +#error "You should not be using these data structures directly" +#endif /* SVN_WC__I_AM_WC_DB */ + +#ifndef WC_DB_PRIVATE_H +#define WC_DB_PRIVATE_H + +#include "wc_db.h" + + +struct svn_wc__db_t { + /* We need the config whenever we run into a new WC directory, in order + to figure out where we should look for the corresponding datastore. */ + svn_config_t *config; + + /* Should we fail with SVN_ERR_WC_UPGRADE_REQUIRED when it is + opened, and found to be not-current? */ + svn_boolean_t verify_format; + + /* Should we ensure the WORK_QUEUE is empty when a WCROOT is opened? */ + svn_boolean_t enforce_empty_wq; + + /* Should we open Sqlite databases EXCLUSIVE */ + svn_boolean_t exclusive; + + /* Map a given working copy directory to its relevant data. + const char *local_abspath -> svn_wc__db_wcroot_t *wcroot */ + apr_hash_t *dir_data; + + /* A few members to assist with caching of kind values for paths. See + get_path_kind() for use. */ + struct + { + svn_stringbuf_t *abspath; + svn_node_kind_t kind; + } parse_cache; + + /* As we grow the state of this DB, allocate that state here. */ + apr_pool_t *state_pool; +}; + + +/* Hold information about an owned lock */ +typedef struct svn_wc__db_wclock_t +{ + /* Relative path of the lock root */ + const char *local_relpath; + + /* Number of levels locked (0 for infinity) */ + int levels; +} svn_wc__db_wclock_t; + + +/** Hold information about a WCROOT. + * + * This structure is referenced by all per-directory handles underneath it. + */ +typedef struct svn_wc__db_wcroot_t { + /* Location of this wcroot in the filesystem. */ + const char *abspath; + + /* The SQLite database containing the metadata for everything in + this wcroot. */ + svn_sqlite__db_t *sdb; + + /* The WCROOT.id for this directory (and all its children). */ + apr_int64_t wc_id; + + /* The format of this wcroot's metadata storage (see wc.h). If the + format has not (yet) been determined, this will be UNKNOWN_FORMAT. */ + int format; + + /* Array of svn_wc__db_wclock_t structures (not pointers!). + Typically just one or two locks maximum. */ + apr_array_header_t *owned_locks; + + /* Map a working copy directory to a cached adm_access baton. + const char *local_abspath -> svn_wc_adm_access_t *adm_access */ + apr_hash_t *access_cache; + +} svn_wc__db_wcroot_t; + + +/* */ +svn_error_t * +svn_wc__db_close_many_wcroots(apr_hash_t *roots, + apr_pool_t *state_pool, + apr_pool_t *scratch_pool); + + +/* Construct a new svn_wc__db_wcroot_t. The WCROOT_ABSPATH and SDB parameters + must have lifetime of at least RESULT_POOL. */ +svn_error_t * +svn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot, + const char *wcroot_abspath, + svn_sqlite__db_t *sdb, + apr_int64_t wc_id, + int format, + svn_boolean_t verify_format, + svn_boolean_t enforce_empty_wq, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* For a given LOCAL_ABSPATH, figure out what sqlite database (WCROOT) to + use and the RELPATH within that wcroot. + + *LOCAL_RELPATH will be allocated within RESULT_POOL. Temporary allocations + will be made in SCRATCH_POOL. + + *WCROOT will be allocated within DB->STATE_POOL. + + Certain internal structures will be allocated in DB->STATE_POOL. +*/ +svn_error_t * +svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot, + const char **local_relpath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Assert that the given WCROOT is usable. + NOTE: the expression is multiply-evaluated!! */ +#define VERIFY_USABLE_WCROOT(wcroot) SVN_ERR_ASSERT( \ + (wcroot) != NULL && (wcroot)->format == SVN_WC__VERSION) + +/* Check if the WCROOT is usable for light db operations such as path + calculations */ +#define CHECK_MINIMAL_WCROOT(wcroot, abspath, scratch_pool) \ + do \ + { \ + if (wcroot == NULL) \ + return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, \ + _("The node '%s' is not in a working copy."), \ + svn_dirent_local_style(wri_abspath, \ + scratch_pool)); \ + } \ + while (0) + +/* Calculates the depth of the relpath below "" */ +APR_INLINE static int +relpath_depth(const char *relpath) +{ + int n = 1; + if (*relpath == '\0') + return 0; + + do + { + if (*relpath == '/') + n++; + } + while (*(++relpath)); + + return n; +} + + +/* */ +svn_error_t * +svn_wc__db_util_fetch_wc_id(apr_int64_t *wc_id, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool); + +/* Open a connection in *SDB to the WC database found in the WC metadata + * directory inside DIR_ABSPATH, having the filename SDB_FNAME. + * + * SMODE is passed to svn_sqlite__open(). + * + * Register MY_STATEMENTS, or if that is null, the default set of WC DB + * statements, as the set of statements to be prepared now and executed + * later. MY_STATEMENTS (the strings and the array itself) is not duplicated + * internally, and should have a lifetime at least as long as RESULT_POOL. + * See svn_sqlite__open() for details. */ +svn_error_t * +svn_wc__db_util_open_db(svn_sqlite__db_t **sdb, + const char *dir_abspath, + const char *sdb_fname, + svn_sqlite__mode_t smode, + svn_boolean_t exclusive, + const char *const *my_statements, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Like svn_wc__db_read_info(), but taking WCROOT+LOCAL_RELPATH instead of + DB+LOCAL_ABSPATH, and outputting repos ids instead of URL+UUID. */ +svn_error_t * +svn_wc__db_read_info_internal(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + const char **original_repos_relpath, + apr_int64_t *original_repos_id, + svn_revnum_t *original_revision, + svn_wc__db_lock_t **lock, + svn_filesize_t *recorded_size, + apr_time_t *recorded_mod_time, + const char **changelist, + svn_boolean_t *conflicted, + svn_boolean_t *op_root, + svn_boolean_t *had_props, + svn_boolean_t *props_mod, + svn_boolean_t *have_base, + svn_boolean_t *have_more_work, + svn_boolean_t *have_work, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Like svn_wc__db_base_get_info(), but taking WCROOT+LOCAL_RELPATH instead of + DB+LOCAL_ABSPATH and outputting REPOS_ID instead of URL+UUID. */ +svn_error_t * +svn_wc__db_base_get_info_internal(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + svn_wc__db_lock_t **lock, + svn_boolean_t *had_props, + apr_hash_t **props, + svn_boolean_t *update_root, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Similar to svn_wc__db_base_get_info(), but taking WCROOT+LOCAL_RELPATH + * instead of DB+LOCAL_ABSPATH, an explicit op-depth of the node to get + * information about, and outputting REPOS_ID instead of URL+UUID, and + * without the LOCK or UPDATE_ROOT outputs. + * + * OR + * + * Similar to svn_wc__db_base_get_info_internal(), but taking an explicit + * op-depth OP_DEPTH of the node to get information about, and without the + * LOCK or UPDATE_ROOT outputs. + * + * ### [JAF] TODO: Harmonize svn_wc__db_base_get_info[_internal] with + * svn_wc__db_depth_get_info -- common API, common implementation. + */ +svn_error_t * +svn_wc__db_depth_get_info(svn_wc__db_status_t *status, + svn_node_kind_t *kind, + svn_revnum_t *revision, + const char **repos_relpath, + apr_int64_t *repos_id, + svn_revnum_t *changed_rev, + apr_time_t *changed_date, + const char **changed_author, + svn_depth_t *depth, + const svn_checksum_t **checksum, + const char **target, + svn_boolean_t *had_props, + apr_hash_t **props, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Look up REPOS_ID in SDB and set *REPOS_ROOT_URL and/or *REPOS_UUID to + its root URL and UUID respectively. If REPOS_ID is INVALID_REPOS_ID, + use NULL for both URL and UUID. Either or both output parameters may be + NULL if not wanted. */ +svn_error_t * +svn_wc__db_fetch_repos_info(const char **repos_root_url, + const char **repos_uuid, + svn_sqlite__db_t *sdb, + apr_int64_t repos_id, + apr_pool_t *result_pool); + +/* Like svn_wc__db_read_conflict(), but with WCROOT+LOCAL_RELPATH instead of + DB+LOCAL_ABSPATH, and outputting relpaths instead of abspaths. */ +svn_error_t * +svn_wc__db_read_conflict_internal(svn_skel_t **conflict, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Like svn_wc__db_op_mark_conflict(), but with WCROOT+LOCAL_RELPATH instead of + DB+LOCAL_ABSPATH. */ +svn_error_t * +svn_wc__db_mark_conflict_internal(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + const svn_skel_t *conflict_skel, + apr_pool_t *scratch_pool); + + +/* Transaction handling */ + +/* A callback which supplies WCROOTs and LOCAL_RELPATHs. */ +typedef svn_error_t *(*svn_wc__db_txn_callback_t)(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool); + + +/* Run CB_FUNC in a SQLite transaction with CB_BATON, using WCROOT and + LOCAL_RELPATH. If callbacks require additional information, they may + provide it using CB_BATON. */ +svn_error_t * +svn_wc__db_with_txn(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_txn_callback_t cb_func, + void *cb_baton, + apr_pool_t *scratch_pool); + +/* Evaluate the expression EXPR within a transaction. + * + * Begin a transaction in WCROOT's DB; evaluate the expression EXPR, which would + * typically be a function call that does some work in DB; finally commit + * the transaction if EXPR evaluated to SVN_NO_ERROR, otherwise roll back + * the transaction. + */ +#define SVN_WC__DB_WITH_TXN(expr, wcroot) \ + SVN_SQLITE__WITH_LOCK(expr, (wcroot)->sdb) + + +/* Return CHILDREN mapping const char * names to svn_node_kind_t * for the + children of LOCAL_RELPATH at OP_DEPTH. */ +svn_error_t * +svn_wc__db_get_children_op_depth(apr_hash_t **children, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Extend any delete of the parent of LOCAL_RELPATH to LOCAL_RELPATH. + + ### What about KIND and OP_DEPTH? KIND ought to be redundant; I'm + discussing on dev@ whether we can let that be null for presence + == base-deleted. OP_DEPTH is the op-depth of what, and why? + It is used to select the lowest working node higher than OP_DEPTH, + so, in terms of the API, OP_DEPTH means ...? + + Given a wc: + + 0 1 2 3 4 + normal + A normal + A/B normal normal + A/B/C not-pres normal + A/B/C/D normal + + That is checkout, delete A/B, copy a replacement A/B, delete copied + child A/B/C, add replacement A/B/C, add A/B/C/D. + + Now an update that adds base nodes for A/B/C, A/B/C/D and A/B/C/D/E + must extend the A/B deletion: + + 0 1 2 3 4 + normal + A normal + A/B normal normal + A/B/C normal not-pres normal + A/B/C/D normal base-del normal + A/B/C/D/E normal base-del + + When adding a node if the parent has a higher working node then the + parent node is deleted (or replaced) and the delete must be extended + to cover new node. + + In the example above A/B/C/D and A/B/C/D/E are the nodes that get + the extended delete, A/B/C is already deleted. + */ +svn_error_t * +svn_wc__db_extend_parent_delete(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_node_kind_t kind, + int op_depth, + apr_pool_t *scratch_pool); + +svn_error_t * +svn_wc__db_retract_parent_delete(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_pool_t *scratch_pool); + +svn_error_t * +svn_wc__db_op_depth_moved_to(const char **move_dst_relpath, + const char **move_dst_op_root_relpath, + const char **move_src_root_relpath, + const char **move_src_op_root_relpath, + int op_depth, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Do a post-drive revision bump for the moved-away destination for + any move sources under LOCAL_RELPATH. This is called from within + the revision bump transaction after the tree at LOCAL_RELPATH has + been bumped. */ +svn_error_t * +svn_wc__db_bump_moved_away(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_depth_t depth, + svn_wc__db_t *db, + apr_pool_t *scratch_pool); + +svn_error_t * +svn_wc__db_resolve_break_moved_away_internal(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool); + +svn_error_t * +svn_wc__db_update_move_list_notify(svn_wc__db_wcroot_t *wcroot, + svn_revnum_t old_revision, + svn_revnum_t new_revision, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); + +#endif /* WC_DB_PRIVATE_H */ diff --git a/subversion/libsvn_wc/wc_db_update_move.c b/subversion/libsvn_wc/wc_db_update_move.c new file mode 100644 index 000000000000..fa5afe4f70aa --- /dev/null +++ b/subversion/libsvn_wc/wc_db_update_move.c @@ -0,0 +1,2631 @@ +/* + * wc_db_update_move.c : updating moves during tree-conflict resolution + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* This file implements an editor and an edit driver which are used + * to resolve an "incoming edit, local move-away" tree conflict resulting + * from an update (or switch). + * + * Our goal is to be able to resolve this conflict such that the end + * result is just the same as if the user had run the update *before* + * the local move. + * + * When an update (or switch) produces incoming changes for a locally + * moved-away subtree, it updates the base nodes of the moved-away tree + * and flags a tree-conflict on the moved-away root node. + * This editor transfers these changes from the moved-away part of the + * working copy to the corresponding moved-here part of the working copy. + * + * Both the driver and receiver components of the editor are implemented + * in this file. + * + * The driver sees two NODES trees: the move source tree and the move + * destination tree. When the move is initially made these trees are + * equivalent, the destination is a copy of the source. The source is + * a single-op-depth, single-revision, deleted layer [1] and the + * destination has an equivalent single-op-depth, single-revision + * layer. The destination may have additional higher op-depths + * representing adds, deletes, moves within the move destination. [2] + * + * After the intial move an update has modified the NODES in the move + * source and may have introduced a tree-conflict since the source and + * destination trees are no longer equivalent. The source is a + * different revision and may have text, property and tree changes + * compared to the destination. The driver will compare the two NODES + * trees and drive an editor to change the destination tree so that it + * once again matches the source tree. Changes made to the + * destination NODES tree to achieve this match will be merged into + * the working files/directories. + * + * The whole drive occurs as one single wc.db transaction. At the end + * of the transaction the destination NODES table should have a layer + * that is equivalent to the source NODES layer, there should be + * workqueue items to make any required changes to working + * files/directories in the move destination, and there should be + * tree-conflicts in the move destination where it was not possible to + * update the working files/directories. + * + * [1] The move source tree is single-revision because we currently do + * not allow a mixed-rev move, and therefore it is single op-depth + * regardless whether it is a base layer or a nested move. + * + * [2] The source tree also may have additional higher op-depths, + * representing a replacement, but this editor only reads from the + * single-op-depth layer of it, and makes no changes of any kind + * within the source tree. + */ + +#define SVN_WC__I_AM_WC_DB + +#include <assert.h> + +#include "svn_checksum.h" +#include "svn_dirent_uri.h" +#include "svn_error.h" +#include "svn_hash.h" +#include "svn_wc.h" +#include "svn_props.h" +#include "svn_pools.h" +#include "svn_sorts.h" + +#include "private/svn_skel.h" +#include "private/svn_sqlite.h" +#include "private/svn_wc_private.h" +#include "private/svn_editor.h" + +#include "wc.h" +#include "props.h" +#include "wc_db_private.h" +#include "wc-queries.h" +#include "conflicts.h" +#include "workqueue.h" +#include "token-map.h" + +/* + * Receiver code. + * + * The receiver is an editor that, when driven with a certain change, will + * merge the edits into the working/actual state of the move destination + * at MOVE_ROOT_DST_RELPATH (in struct tc_editor_baton), perhaps raising + * conflicts if necessary. + * + * The receiver should not need to refer directly to the move source, as + * the driver should provide all relevant information about the change to + * be made at the move destination. + */ + +struct tc_editor_baton { + svn_wc__db_t *db; + svn_wc__db_wcroot_t *wcroot; + const char *move_root_dst_relpath; + + /* The most recent conflict raised during this drive. We rely on the + non-Ev2, depth-first, drive for this to make sense. */ + const char *conflict_root_relpath; + + svn_wc_operation_t operation; + svn_wc_conflict_version_t *old_version; + svn_wc_conflict_version_t *new_version; + apr_pool_t *result_pool; /* For things that live as long as the baton. */ +}; + +/* + * Notifications are delayed until the entire update-move transaction + * completes. These functions provide the necessary support by storing + * notification information in a temporary db table (the "update_move_list") + * and spooling notifications out of that table after the transaction. + */ + +/* Add an entry to the notification list. */ +static svn_error_t * +update_move_list_add(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc_notify_action_t action, + svn_node_kind_t kind, + svn_wc_notify_state_t content_state, + svn_wc_notify_state_t prop_state) + +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_INSERT_UPDATE_MOVE_LIST)); + SVN_ERR(svn_sqlite__bindf(stmt, "sdddd", local_relpath, + action, kind, content_state, prop_state)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +/* Send all notifications stored in the notification list, and then + * remove the temporary database table. */ +svn_error_t * +svn_wc__db_update_move_list_notify(svn_wc__db_wcroot_t *wcroot, + svn_revnum_t old_revision, + svn_revnum_t new_revision, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + if (notify_func) + { + apr_pool_t *iterpool; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_UPDATE_MOVE_LIST)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + const char *local_relpath; + svn_wc_notify_action_t action; + svn_wc_notify_t *notify; + + svn_pool_clear(iterpool); + + local_relpath = svn_sqlite__column_text(stmt, 0, NULL); + action = svn_sqlite__column_int(stmt, 1); + notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath, + local_relpath, + iterpool), + action, iterpool); + notify->kind = svn_sqlite__column_int(stmt, 2); + notify->content_state = svn_sqlite__column_int(stmt, 3); + notify->prop_state = svn_sqlite__column_int(stmt, 4); + notify->old_revision = old_revision; + notify->revision = new_revision; + notify_func(notify_baton, notify, scratch_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + SVN_ERR(svn_sqlite__reset(stmt)); + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_FINALIZE_UPDATE_MOVE)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +/* Mark a tree-conflict on LOCAL_RELPATH if such a tree-conflict does + not already exist. */ +static svn_error_t * +mark_tree_conflict(const char *local_relpath, + svn_wc__db_wcroot_t *wcroot, + svn_wc__db_t *db, + const svn_wc_conflict_version_t *old_version, + const svn_wc_conflict_version_t *new_version, + const char *move_root_dst_relpath, + svn_wc_operation_t operation, + svn_node_kind_t old_kind, + svn_node_kind_t new_kind, + const char *old_repos_relpath, + svn_wc_conflict_reason_t reason, + svn_wc_conflict_action_t action, + const char *move_src_op_root_relpath, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_skel_t *conflict; + svn_wc_conflict_version_t *conflict_old_version, *conflict_new_version; + const char *move_src_op_root_abspath + = move_src_op_root_relpath + ? svn_dirent_join(wcroot->abspath, + move_src_op_root_relpath, scratch_pool) + : NULL; + const char *old_repos_relpath_part + = old_repos_relpath + ? svn_relpath_skip_ancestor(old_version->path_in_repos, + old_repos_relpath) + : NULL; + const char *new_repos_relpath + = old_repos_relpath_part + ? svn_relpath_join(new_version->path_in_repos, old_repos_relpath_part, + scratch_pool) + : NULL; + + if (!new_repos_relpath) + new_repos_relpath + = svn_relpath_join(new_version->path_in_repos, + svn_relpath_skip_ancestor(move_root_dst_relpath, + local_relpath), + scratch_pool); + + err = svn_wc__db_read_conflict_internal(&conflict, wcroot, local_relpath, + scratch_pool, scratch_pool); + if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) + return svn_error_trace(err); + else if (err) + { + svn_error_clear(err); + conflict = NULL; + } + + if (conflict) + { + svn_wc_operation_t conflict_operation; + svn_boolean_t tree_conflicted; + + SVN_ERR(svn_wc__conflict_read_info(&conflict_operation, NULL, NULL, NULL, + &tree_conflicted, + db, wcroot->abspath, conflict, + scratch_pool, scratch_pool)); + + if (conflict_operation != svn_wc_operation_update + && conflict_operation != svn_wc_operation_switch) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("'%s' already in conflict"), + svn_dirent_local_style(local_relpath, + scratch_pool)); + + if (tree_conflicted) + { + svn_wc_conflict_reason_t existing_reason; + svn_wc_conflict_action_t existing_action; + const char *existing_abspath; + + SVN_ERR(svn_wc__conflict_read_tree_conflict(&existing_reason, + &existing_action, + &existing_abspath, + db, wcroot->abspath, + conflict, + scratch_pool, + scratch_pool)); + if (reason != existing_reason + || action != existing_action + || (reason == svn_wc_conflict_reason_moved_away + && strcmp(move_src_op_root_relpath, + svn_dirent_skip_ancestor(wcroot->abspath, + existing_abspath)))) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("'%s' already in conflict"), + svn_dirent_local_style(local_relpath, + scratch_pool)); + + /* Already a suitable tree-conflict. */ + return SVN_NO_ERROR; + } + } + else + conflict = svn_wc__conflict_skel_create(scratch_pool); + + SVN_ERR(svn_wc__conflict_skel_add_tree_conflict( + conflict, db, + svn_dirent_join(wcroot->abspath, local_relpath, + scratch_pool), + reason, + action, + move_src_op_root_abspath, + scratch_pool, + scratch_pool)); + + if (reason != svn_wc_conflict_reason_unversioned + && old_repos_relpath != NULL /* no local additions */) + { + conflict_old_version = svn_wc_conflict_version_create2( + old_version->repos_url, old_version->repos_uuid, + old_repos_relpath, old_version->peg_rev, + old_kind, scratch_pool); + } + else + conflict_old_version = NULL; + + conflict_new_version = svn_wc_conflict_version_create2( + new_version->repos_url, new_version->repos_uuid, + new_repos_relpath, new_version->peg_rev, + new_kind, scratch_pool); + + if (operation == svn_wc_operation_update) + { + SVN_ERR(svn_wc__conflict_skel_set_op_update( + conflict, conflict_old_version, conflict_new_version, + scratch_pool, scratch_pool)); + } + else + { + assert(operation == svn_wc_operation_switch); + SVN_ERR(svn_wc__conflict_skel_set_op_switch( + conflict, conflict_old_version, conflict_new_version, + scratch_pool, scratch_pool)); + } + + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict, scratch_pool)); + + SVN_ERR(update_move_list_add(wcroot, local_relpath, + svn_wc_notify_tree_conflict, + new_kind, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable)); + return SVN_NO_ERROR; +} + +/* If LOCAL_RELPATH is a child of the most recently raised + tree-conflict or is shadowed then set *IS_CONFLICTED to TRUE and + raise a tree-conflict on the root of the obstruction if such a + tree-conflict does not already exist. KIND is the kind of the + incoming LOCAL_RELPATH. This relies on the non-Ev2, depth-first, + drive. */ +static svn_error_t * +check_tree_conflict(svn_boolean_t *is_conflicted, + struct tc_editor_baton *b, + const char *local_relpath, + svn_node_kind_t old_kind, + svn_node_kind_t new_kind, + const char *old_repos_relpath, + svn_wc_conflict_action_t action, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int dst_op_depth = relpath_depth(b->move_root_dst_relpath); + int op_depth; + const char *conflict_root_relpath = local_relpath; + const char *move_dst_relpath, *dummy1; + const char *dummy2, *move_src_op_root_relpath; + + if (b->conflict_root_relpath) + { + if (svn_relpath_skip_ancestor(b->conflict_root_relpath, local_relpath)) + { + *is_conflicted = TRUE; + return SVN_NO_ERROR; + } + b->conflict_root_relpath = NULL; + } + + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_SELECT_LOWEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, local_relpath, + dst_op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + op_depth = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (!have_row) + { + *is_conflicted = FALSE; + return SVN_NO_ERROR; + } + + *is_conflicted = TRUE; + + while (relpath_depth(conflict_root_relpath) > op_depth) + { + conflict_root_relpath = svn_relpath_dirname(conflict_root_relpath, + scratch_pool); + old_kind = new_kind = svn_node_dir; + if (old_repos_relpath) + old_repos_relpath = svn_relpath_dirname(old_repos_relpath, + scratch_pool); + action = svn_wc_conflict_action_edit; + } + + SVN_ERR(svn_wc__db_op_depth_moved_to(&move_dst_relpath, + &dummy1, + &dummy2, + &move_src_op_root_relpath, + dst_op_depth, + b->wcroot, conflict_root_relpath, + scratch_pool, scratch_pool)); + + SVN_ERR(mark_tree_conflict(conflict_root_relpath, + b->wcroot, b->db, b->old_version, b->new_version, + b->move_root_dst_relpath, b->operation, + old_kind, new_kind, + old_repos_relpath, + (move_dst_relpath + ? svn_wc_conflict_reason_moved_away + : svn_wc_conflict_reason_deleted), + action, move_src_op_root_relpath, + scratch_pool)); + b->conflict_root_relpath = apr_pstrdup(b->result_pool, conflict_root_relpath); + + return SVN_NO_ERROR; +} + +static svn_error_t * +tc_editor_add_directory(void *baton, + const char *relpath, + const apr_array_header_t *children, + apr_hash_t *props, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + struct tc_editor_baton *b = baton; + int op_depth = relpath_depth(b->move_root_dst_relpath); + const char *move_dst_repos_relpath; + svn_node_kind_t move_dst_kind; + svn_boolean_t is_conflicted; + const char *abspath; + svn_node_kind_t old_kind; + svn_skel_t *work_item; + svn_wc_notify_action_t action = svn_wc_notify_update_add; + svn_error_t *err; + + /* Update NODES, only the bits not covered by the later call to + replace_moved_layer. */ + SVN_ERR(svn_wc__db_extend_parent_delete(b->wcroot, relpath, svn_node_dir, + op_depth, scratch_pool)); + + err = svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL, + &move_dst_repos_relpath, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + b->wcroot, relpath, + relpath_depth(b->move_root_dst_relpath), + scratch_pool, scratch_pool); + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + old_kind = svn_node_none; + move_dst_repos_relpath = NULL; + } + else + { + SVN_ERR(err); + old_kind = move_dst_kind; + } + + /* Check for NODES tree-conflict. */ + SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath, + old_kind, svn_node_dir, + move_dst_repos_relpath, + svn_wc_conflict_action_add, + scratch_pool)); + if (is_conflicted) + return SVN_NO_ERROR; + + /* Check for unversioned tree-conflict */ + abspath = svn_dirent_join(b->wcroot->abspath, relpath, scratch_pool); + SVN_ERR(svn_io_check_path(abspath, &old_kind, scratch_pool)); + + switch (old_kind) + { + case svn_node_file: + default: + SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version, + b->new_version, b->move_root_dst_relpath, + b->operation, old_kind, svn_node_dir, + move_dst_repos_relpath, + svn_wc_conflict_reason_unversioned, + svn_wc_conflict_action_add, NULL, + scratch_pool)); + b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath); + action = svn_wc_notify_tree_conflict; + is_conflicted = TRUE; + break; + + case svn_node_none: + SVN_ERR(svn_wc__wq_build_dir_install(&work_item, b->db, abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item, + scratch_pool)); + /* Fall through */ + case svn_node_dir: + break; + } + + if (!is_conflicted) + SVN_ERR(update_move_list_add(b->wcroot, relpath, + action, + svn_node_dir, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable)); + return SVN_NO_ERROR; +} + +static svn_error_t * +tc_editor_add_file(void *baton, + const char *relpath, + const svn_checksum_t *checksum, + svn_stream_t *contents, + apr_hash_t *props, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + struct tc_editor_baton *b = baton; + int op_depth = relpath_depth(b->move_root_dst_relpath); + const char *move_dst_repos_relpath; + svn_node_kind_t move_dst_kind; + svn_node_kind_t old_kind; + svn_boolean_t is_conflicted; + const char *abspath; + svn_skel_t *work_item; + svn_error_t *err; + + /* Update NODES, only the bits not covered by the later call to + replace_moved_layer. */ + SVN_ERR(svn_wc__db_extend_parent_delete(b->wcroot, relpath, svn_node_file, + op_depth, scratch_pool)); + + err = svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL, + &move_dst_repos_relpath, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + b->wcroot, relpath, + relpath_depth(b->move_root_dst_relpath), + scratch_pool, scratch_pool); + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + old_kind = svn_node_none; + move_dst_repos_relpath = NULL; + } + else + { + SVN_ERR(err); + old_kind = move_dst_kind; + } + + /* Check for NODES tree-conflict. */ + SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath, + old_kind, svn_node_file, move_dst_repos_relpath, + svn_wc_conflict_action_add, + scratch_pool)); + if (is_conflicted) + return SVN_NO_ERROR; + + /* Check for unversioned tree-conflict */ + abspath = svn_dirent_join(b->wcroot->abspath, relpath, scratch_pool); + SVN_ERR(svn_io_check_path(abspath, &old_kind, scratch_pool)); + + if (old_kind != svn_node_none) + { + SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version, + b->new_version, b->move_root_dst_relpath, + b->operation, old_kind, svn_node_file, + move_dst_repos_relpath, + svn_wc_conflict_reason_unversioned, + svn_wc_conflict_action_add, NULL, + scratch_pool)); + b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath); + return SVN_NO_ERROR; + } + + /* Update working file. */ + SVN_ERR(svn_wc__wq_build_file_install(&work_item, b->db, + svn_dirent_join(b->wcroot->abspath, + relpath, + scratch_pool), + NULL, + FALSE /* FIXME: use_commit_times? */, + TRUE /* record_file_info */, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item, + scratch_pool)); + + SVN_ERR(update_move_list_add(b->wcroot, relpath, + svn_wc_notify_update_add, + svn_node_file, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable)); + return SVN_NO_ERROR; +} + +static svn_error_t * +tc_editor_add_symlink(void *baton, + const char *relpath, + const char *target, + apr_hash_t *props, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); +} + +static svn_error_t * +tc_editor_add_absent(void *baton, + const char *relpath, + svn_node_kind_t kind, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); +} + +/* All the info we need about one version of a working node. */ +typedef struct working_node_version_t +{ + svn_wc_conflict_version_t *location_and_kind; + apr_hash_t *props; + const svn_checksum_t *checksum; /* for files only */ +} working_node_version_t; + +/* Return *WORK_ITEMS to create a conflict on LOCAL_ABSPATH. */ +static svn_error_t * +create_conflict_markers(svn_skel_t **work_items, + const char *local_abspath, + svn_wc__db_t *db, + const char *repos_relpath, + svn_skel_t *conflict_skel, + svn_wc_operation_t operation, + const working_node_version_t *old_version, + const working_node_version_t *new_version, + svn_node_kind_t kind, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_conflict_version_t *original_version; + svn_wc_conflict_version_t *conflicted_version; + const char *part; + + original_version = svn_wc_conflict_version_dup( + old_version->location_and_kind, scratch_pool); + original_version->node_kind = kind; + conflicted_version = svn_wc_conflict_version_dup( + new_version->location_and_kind, scratch_pool); + conflicted_version->node_kind = kind; + + part = svn_relpath_skip_ancestor(original_version->path_in_repos, + repos_relpath); + conflicted_version->path_in_repos + = svn_relpath_join(conflicted_version->path_in_repos, part, scratch_pool); + original_version->path_in_repos = repos_relpath; + + if (operation == svn_wc_operation_update) + { + SVN_ERR(svn_wc__conflict_skel_set_op_update( + conflict_skel, original_version, + conflicted_version, + scratch_pool, scratch_pool)); + } + else + { + SVN_ERR(svn_wc__conflict_skel_set_op_switch( + conflict_skel, original_version, + conflicted_version, + scratch_pool, scratch_pool)); + } + + /* According to this func's doc string, it is "Currently only used for + * property conflicts as text conflict markers are just in-wc files." */ + SVN_ERR(svn_wc__conflict_create_markers(work_items, db, + local_abspath, + conflict_skel, + result_pool, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +update_working_props(svn_wc_notify_state_t *prop_state, + svn_skel_t **conflict_skel, + apr_array_header_t **propchanges, + apr_hash_t **actual_props, + svn_wc__db_t *db, + const char *local_abspath, + const struct working_node_version_t *old_version, + const struct working_node_version_t *new_version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *new_actual_props; + apr_array_header_t *new_propchanges; + + /* + * Run a 3-way prop merge to update the props, using the pre-update + * props as the merge base, the post-update props as the + * merge-left version, and the current props of the + * moved-here working file as the merge-right version. + */ + SVN_ERR(svn_wc__db_read_props(actual_props, + db, local_abspath, + result_pool, scratch_pool)); + SVN_ERR(svn_prop_diffs(propchanges, new_version->props, old_version->props, + result_pool)); + SVN_ERR(svn_wc__merge_props(conflict_skel, prop_state, + &new_actual_props, + db, local_abspath, + old_version->props, old_version->props, + *actual_props, *propchanges, + result_pool, scratch_pool)); + + /* Setting properties in ACTUAL_NODE with svn_wc__db_op_set_props + relies on NODES row having been updated first which we don't do + at present. So this extra property diff has the same effect. + + ### Perhaps we should update NODES first (but after + ### svn_wc__db_read_props above)? */ + SVN_ERR(svn_prop_diffs(&new_propchanges, new_actual_props, new_version->props, + scratch_pool)); + if (!new_propchanges->nelts) + new_actual_props = NULL; + + /* Install the new actual props. Don't set the conflict_skel yet, because + we might need to add a text conflict to it as well. */ + SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, + new_actual_props, + svn_wc__has_magic_property(*propchanges), + NULL/*conflict_skel*/, NULL/*work_items*/, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +tc_editor_alter_directory(void *baton, + const char *dst_relpath, + svn_revnum_t expected_move_dst_revision, + const apr_array_header_t *children, + apr_hash_t *new_props, + apr_pool_t *scratch_pool) +{ + struct tc_editor_baton *b = baton; + const char *move_dst_repos_relpath; + svn_revnum_t move_dst_revision; + svn_node_kind_t move_dst_kind; + working_node_version_t old_version, new_version; + svn_wc__db_status_t status; + svn_boolean_t is_conflicted; + + SVN_ERR_ASSERT(expected_move_dst_revision == b->old_version->peg_rev); + + SVN_ERR(svn_wc__db_depth_get_info(&status, &move_dst_kind, &move_dst_revision, + &move_dst_repos_relpath, NULL, NULL, NULL, + NULL, NULL, &old_version.checksum, NULL, + NULL, &old_version.props, + b->wcroot, dst_relpath, + relpath_depth(b->move_root_dst_relpath), + scratch_pool, scratch_pool)); + + /* If the node would be recorded as svn_wc__db_status_base_deleted it + wouldn't have a repos_relpath */ + /* ### Can svn_wc__db_depth_get_info() do this for us without this hint? */ + if (status == svn_wc__db_status_deleted && move_dst_repos_relpath) + status = svn_wc__db_status_not_present; + + /* There might be not-present nodes of a different revision as the same + depth as a copy. This is commonly caused by copying/moving mixed revision + directories */ + SVN_ERR_ASSERT(move_dst_revision == expected_move_dst_revision + || status == svn_wc__db_status_not_present); + SVN_ERR_ASSERT(move_dst_kind == svn_node_dir); + + SVN_ERR(check_tree_conflict(&is_conflicted, b, dst_relpath, + move_dst_kind, + svn_node_dir, + move_dst_repos_relpath, + svn_wc_conflict_action_edit, + scratch_pool)); + if (is_conflicted) + return SVN_NO_ERROR; + + old_version.location_and_kind = b->old_version; + new_version.location_and_kind = b->new_version; + + new_version.checksum = NULL; /* not a file */ + new_version.props = new_props ? new_props : old_version.props; + + if (new_props) + { + const char *dst_abspath = svn_dirent_join(b->wcroot->abspath, + dst_relpath, + scratch_pool); + svn_wc_notify_state_t prop_state; + svn_skel_t *conflict_skel = NULL; + apr_hash_t *actual_props; + apr_array_header_t *propchanges; + + SVN_ERR(update_working_props(&prop_state, &conflict_skel, + &propchanges, &actual_props, + b->db, dst_abspath, + &old_version, &new_version, + scratch_pool, scratch_pool)); + + if (conflict_skel) + { + svn_skel_t *work_items; + + SVN_ERR(create_conflict_markers(&work_items, dst_abspath, + b->db, move_dst_repos_relpath, + conflict_skel, b->operation, + &old_version, &new_version, + svn_node_dir, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_mark_conflict_internal(b->wcroot, dst_relpath, + conflict_skel, + scratch_pool)); + SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_items, + scratch_pool)); + } + + SVN_ERR(update_move_list_add(b->wcroot, dst_relpath, + svn_wc_notify_update_update, + svn_node_dir, + svn_wc_notify_state_inapplicable, + prop_state)); + } + + return SVN_NO_ERROR; +} + + +/* Merge the difference between OLD_VERSION and NEW_VERSION into + * the working file at LOCAL_RELPATH. + * + * The term 'old' refers to the pre-update state, which is the state of + * (some layer of) LOCAL_RELPATH while this function runs; and 'new' + * refers to the post-update state, as found at the (base layer of) the + * move source path while this function runs. + * + * LOCAL_RELPATH is a file in the working copy at WCROOT in DB, and + * REPOS_RELPATH is the repository path it would be committed to. + * + * Use NOTIFY_FUNC and NOTIFY_BATON for notifications. + * Set *WORK_ITEMS to any required work items, allocated in RESULT_POOL. + * Use SCRATCH_POOL for temporary allocations. */ +static svn_error_t * +update_working_file(const char *local_relpath, + const char *repos_relpath, + svn_wc_operation_t operation, + const working_node_version_t *old_version, + const working_node_version_t *new_version, + svn_wc__db_wcroot_t *wcroot, + svn_wc__db_t *db, + apr_pool_t *scratch_pool) +{ + const char *local_abspath = svn_dirent_join(wcroot->abspath, + local_relpath, + scratch_pool); + const char *old_pristine_abspath; + const char *new_pristine_abspath; + svn_skel_t *conflict_skel = NULL; + apr_hash_t *actual_props; + apr_array_header_t *propchanges; + enum svn_wc_merge_outcome_t merge_outcome; + svn_wc_notify_state_t prop_state, content_state; + svn_skel_t *work_item, *work_items = NULL; + + SVN_ERR(update_working_props(&prop_state, &conflict_skel, &propchanges, + &actual_props, db, local_abspath, + old_version, new_version, + scratch_pool, scratch_pool)); + + if (!svn_checksum_match(new_version->checksum, old_version->checksum)) + { + svn_boolean_t is_locally_modified; + + SVN_ERR(svn_wc__internal_file_modified_p(&is_locally_modified, + db, local_abspath, + FALSE /* exact_comparison */, + scratch_pool)); + if (!is_locally_modified) + { + SVN_ERR(svn_wc__wq_build_file_install(&work_item, db, + local_abspath, + NULL, + FALSE /* FIXME: use_commit_times? */, + TRUE /* record_file_info */, + scratch_pool, scratch_pool)); + + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + + content_state = svn_wc_notify_state_changed; + } + else + { + /* + * Run a 3-way merge to update the file, using the pre-update + * pristine text as the merge base, the post-update pristine + * text as the merge-left version, and the current content of the + * moved-here working file as the merge-right version. + */ + SVN_ERR(svn_wc__db_pristine_get_path(&old_pristine_abspath, + db, wcroot->abspath, + old_version->checksum, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_pristine_get_path(&new_pristine_abspath, + db, wcroot->abspath, + new_version->checksum, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__internal_merge(&work_item, &conflict_skel, + &merge_outcome, db, + old_pristine_abspath, + new_pristine_abspath, + local_abspath, + local_abspath, + NULL, NULL, NULL, /* diff labels */ + actual_props, + FALSE, /* dry-run */ + NULL, /* diff3-cmd */ + NULL, /* merge options */ + propchanges, + NULL, NULL, /* cancel_func + baton */ + scratch_pool, scratch_pool)); + + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + + if (merge_outcome == svn_wc_merge_conflict) + content_state = svn_wc_notify_state_conflicted; + else + content_state = svn_wc_notify_state_merged; + } + } + else + content_state = svn_wc_notify_state_unchanged; + + /* If there are any conflicts to be stored, convert them into work items + * too. */ + if (conflict_skel) + { + SVN_ERR(create_conflict_markers(&work_item, local_abspath, db, + repos_relpath, conflict_skel, + operation, old_version, new_version, + svn_node_file, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_mark_conflict_internal(wcroot, local_relpath, + conflict_skel, + scratch_pool)); + + work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); + } + + SVN_ERR(svn_wc__db_wq_add(db, wcroot->abspath, work_items, scratch_pool)); + + SVN_ERR(update_move_list_add(wcroot, local_relpath, + svn_wc_notify_update_update, + svn_node_file, + content_state, + prop_state)); + + return SVN_NO_ERROR; +} + + +/* Edit the file found at the move destination, which is initially at + * the old state. Merge the changes into the "working"/"actual" file. + */ +static svn_error_t * +tc_editor_alter_file(void *baton, + const char *dst_relpath, + svn_revnum_t expected_move_dst_revision, + apr_hash_t *new_props, + const svn_checksum_t *new_checksum, + svn_stream_t *new_contents, + apr_pool_t *scratch_pool) +{ + struct tc_editor_baton *b = baton; + const char *move_dst_repos_relpath; + svn_revnum_t move_dst_revision; + svn_node_kind_t move_dst_kind; + working_node_version_t old_version, new_version; + svn_boolean_t is_conflicted; + svn_wc__db_status_t status; + + SVN_ERR(svn_wc__db_depth_get_info(&status, &move_dst_kind, &move_dst_revision, + &move_dst_repos_relpath, NULL, NULL, NULL, + NULL, NULL, &old_version.checksum, NULL, + NULL, &old_version.props, + b->wcroot, dst_relpath, + relpath_depth(b->move_root_dst_relpath), + scratch_pool, scratch_pool)); + + /* If the node would be recorded as svn_wc__db_status_base_deleted it + wouldn't have a repos_relpath */ + /* ### Can svn_wc__db_depth_get_info() do this for us without this hint? */ + if (status == svn_wc__db_status_deleted && move_dst_repos_relpath) + status = svn_wc__db_status_not_present; + + SVN_ERR_ASSERT(move_dst_revision == expected_move_dst_revision + || status == svn_wc__db_status_not_present); + SVN_ERR_ASSERT(move_dst_kind == svn_node_file); + + SVN_ERR(check_tree_conflict(&is_conflicted, b, dst_relpath, + move_dst_kind, + svn_node_file, + move_dst_repos_relpath, + svn_wc_conflict_action_edit, + scratch_pool)); + if (is_conflicted) + return SVN_NO_ERROR; + + old_version.location_and_kind = b->old_version; + new_version.location_and_kind = b->new_version; + + /* If new checksum is null that means no change; similarly props. */ + new_version.checksum = new_checksum ? new_checksum : old_version.checksum; + new_version.props = new_props ? new_props : old_version.props; + + /* Update file and prop contents if the update has changed them. */ + if (!svn_checksum_match(new_checksum, old_version.checksum) || new_props) + { + SVN_ERR(update_working_file(dst_relpath, move_dst_repos_relpath, + b->operation, &old_version, &new_version, + b->wcroot, b->db, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +tc_editor_alter_symlink(void *baton, + const char *relpath, + svn_revnum_t revision, + apr_hash_t *props, + const char *target, + apr_pool_t *scratch_pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); +} + +static svn_error_t * +tc_editor_delete(void *baton, + const char *relpath, + svn_revnum_t revision, + apr_pool_t *scratch_pool) +{ + struct tc_editor_baton *b = baton; + svn_sqlite__stmt_t *stmt; + int op_depth = relpath_depth(b->move_root_dst_relpath); + const char *move_dst_repos_relpath; + svn_node_kind_t move_dst_kind; + svn_boolean_t is_conflicted; + svn_boolean_t must_delete_working_nodes = FALSE; + const char *local_abspath = svn_dirent_join(b->wcroot->abspath, relpath, + scratch_pool); + const char *parent_relpath = svn_relpath_dirname(relpath, scratch_pool); + int op_depth_below; + svn_boolean_t have_row; + + SVN_ERR(svn_wc__db_depth_get_info(NULL, &move_dst_kind, NULL, + &move_dst_repos_relpath, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + b->wcroot, relpath, + relpath_depth(b->move_root_dst_relpath), + scratch_pool, scratch_pool)); + + /* Check before retracting delete to catch delete-delete + conflicts. This catches conflicts on the node itself; deleted + children are caught as local modifications below.*/ + SVN_ERR(check_tree_conflict(&is_conflicted, b, relpath, + move_dst_kind, + svn_node_unknown, + move_dst_repos_relpath, + svn_wc_conflict_action_delete, + scratch_pool)); + + if (!is_conflicted) + { + svn_boolean_t is_modified, is_all_deletes; + + SVN_ERR(svn_wc__node_has_local_mods(&is_modified, &is_all_deletes, b->db, + local_abspath, + NULL, NULL, scratch_pool)); + if (is_modified) + { + svn_wc_conflict_reason_t reason; + + if (!is_all_deletes) + { + /* No conflict means no NODES rows at the relpath op-depth + so it's easy to convert the modified tree into a copy. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_UPDATE_OP_DEPTH_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdd", b->wcroot->wc_id, relpath, + op_depth, relpath_depth(relpath))); + SVN_ERR(svn_sqlite__step_done(stmt)); + + reason = svn_wc_conflict_reason_edited; + } + else + { + + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_DELETE_WORKING_OP_DEPTH_ABOVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath, + op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + reason = svn_wc_conflict_reason_deleted; + must_delete_working_nodes = TRUE; + } + is_conflicted = TRUE; + SVN_ERR(mark_tree_conflict(relpath, b->wcroot, b->db, b->old_version, + b->new_version, b->move_root_dst_relpath, + b->operation, + move_dst_kind, + svn_node_none, + move_dst_repos_relpath, reason, + svn_wc_conflict_action_delete, NULL, + scratch_pool)); + b->conflict_root_relpath = apr_pstrdup(b->result_pool, relpath); + } + } + + if (!is_conflicted || must_delete_working_nodes) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + svn_skel_t *work_item; + svn_node_kind_t del_kind; + const char *del_abspath; + + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_SELECT_CHILDREN_OP_DEPTH)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + svn_error_t *err; + + svn_pool_clear(iterpool); + + del_kind = svn_sqlite__column_token(stmt, 1, kind_map); + del_abspath = svn_dirent_join(b->wcroot->abspath, + svn_sqlite__column_text(stmt, 0, NULL), + iterpool); + if (del_kind == svn_node_dir) + err = svn_wc__wq_build_dir_remove(&work_item, b->db, + b->wcroot->abspath, del_abspath, + FALSE /* recursive */, + iterpool, iterpool); + else + err = svn_wc__wq_build_file_remove(&work_item, b->db, + b->wcroot->abspath, del_abspath, + iterpool, iterpool); + if (!err) + err = svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item, + iterpool); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + SVN_ERR(svn_wc__db_depth_get_info(NULL, &del_kind, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, + b->wcroot, relpath, op_depth, + iterpool, iterpool)); + if (del_kind == svn_node_dir) + SVN_ERR(svn_wc__wq_build_dir_remove(&work_item, b->db, + b->wcroot->abspath, local_abspath, + FALSE /* recursive */, + iterpool, iterpool)); + else + SVN_ERR(svn_wc__wq_build_file_remove(&work_item, b->db, + b->wcroot->abspath, local_abspath, + iterpool, iterpool)); + SVN_ERR(svn_wc__db_wq_add(b->db, b->wcroot->abspath, work_item, + iterpool)); + + if (!is_conflicted) + SVN_ERR(update_move_list_add(b->wcroot, relpath, + svn_wc_notify_update_delete, + del_kind, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable)); + svn_pool_destroy(iterpool); + } + + /* Deleting the ROWS is valid so long as we update the parent before + committing the transaction. The removed rows could have been + replacing a lower layer in which case we need to add base-deleted + rows. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_SELECT_HIGHEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, parent_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + op_depth_below = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + if (have_row) + { + /* Remove non-shadowing nodes. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_DELETE_NO_LOWER_LAYER)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdd", b->wcroot->wc_id, relpath, + op_depth, op_depth_below)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* Convert remaining shadowing nodes to presence='base-deleted'. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_REPLACE_WITH_BASE_DELETED)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath, + op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + else + { + SVN_ERR(svn_sqlite__get_statement(&stmt, b->wcroot->sdb, + STMT_DELETE_WORKING_OP_DEPTH)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", b->wcroot->wc_id, relpath, + op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + } + + /* Retract any base-delete. */ + SVN_ERR(svn_wc__db_retract_parent_delete(b->wcroot, relpath, op_depth, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +tc_editor_copy(void *baton, + const char *src_relpath, + svn_revnum_t src_revision, + const char *dst_relpath, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); +} + +static svn_error_t * +tc_editor_move(void *baton, + const char *src_relpath, + svn_revnum_t src_revision, + const char *dst_relpath, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); +} + +static svn_error_t * +tc_editor_rotate(void *baton, + const apr_array_header_t *relpaths, + const apr_array_header_t *revisions, + apr_pool_t *scratch_pool) +{ + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); +} + +static svn_error_t * +tc_editor_complete(void *baton, + apr_pool_t *scratch_pool) +{ + return SVN_NO_ERROR; +} + +static svn_error_t * +tc_editor_abort(void *baton, + apr_pool_t *scratch_pool) +{ + return SVN_NO_ERROR; +} + +/* The editor callback table implementing the receiver. */ +static const svn_editor_cb_many_t editor_ops = { + tc_editor_add_directory, + tc_editor_add_file, + tc_editor_add_symlink, + tc_editor_add_absent, + tc_editor_alter_directory, + tc_editor_alter_file, + tc_editor_alter_symlink, + tc_editor_delete, + tc_editor_copy, + tc_editor_move, + tc_editor_rotate, + tc_editor_complete, + tc_editor_abort +}; + + +/* + * Driver code. + * + * The scenario is that a subtree has been locally moved, and then the base + * layer on the source side of the move has received an update to a new + * state. The destination subtree has not yet been updated, and still + * matches the pre-update state of the source subtree. + * + * The edit driver drives the receiver with the difference between the + * pre-update state (as found now at the move-destination) and the + * post-update state (found now at the move-source). + * + * We currently assume that both the pre-update and post-update states are + * single-revision. + */ + +/* Set *OPERATION, *LOCAL_CHANGE, *INCOMING_CHANGE, *OLD_VERSION, *NEW_VERSION + * to reflect the tree conflict on the victim SRC_ABSPATH in DB. + * + * If SRC_ABSPATH is not a tree-conflict victim, return an error. + */ +static svn_error_t * +get_tc_info(svn_wc_operation_t *operation, + svn_wc_conflict_reason_t *local_change, + svn_wc_conflict_action_t *incoming_change, + const char **move_src_op_root_abspath, + svn_wc_conflict_version_t **old_version, + svn_wc_conflict_version_t **new_version, + svn_wc__db_t *db, + const char *src_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const apr_array_header_t *locations; + svn_boolean_t tree_conflicted; + svn_skel_t *conflict_skel; + + /* Check for tree conflict on src. */ + SVN_ERR(svn_wc__db_read_conflict(&conflict_skel, db, + src_abspath, + scratch_pool, scratch_pool)); + if (!conflict_skel) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("'%s' is not in conflict"), + svn_dirent_local_style(src_abspath, + scratch_pool)); + + SVN_ERR(svn_wc__conflict_read_info(operation, &locations, + NULL, NULL, &tree_conflicted, + db, src_abspath, + conflict_skel, result_pool, + scratch_pool)); + if ((*operation != svn_wc_operation_update + && *operation != svn_wc_operation_switch) + || !tree_conflicted) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("'%s' is not a tree-conflict victim"), + svn_dirent_local_style(src_abspath, + scratch_pool)); + if (locations) + { + SVN_ERR_ASSERT(locations->nelts >= 2); + *old_version = APR_ARRAY_IDX(locations, 0, + svn_wc_conflict_version_t *); + *new_version = APR_ARRAY_IDX(locations, 1, + svn_wc_conflict_version_t *); + } + + SVN_ERR(svn_wc__conflict_read_tree_conflict(local_change, + incoming_change, + move_src_op_root_abspath, + db, src_abspath, + conflict_skel, scratch_pool, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Return *PROPS, *CHECKSUM, *CHILDREN and *KIND for LOCAL_RELPATH at + OP_DEPTH provided the row exists. Return *KIND of svn_node_none if + the row does not exist. *CHILDREN is a sorted array of basenames of + type 'const char *', rather than a hash, to allow the driver to + process children in a defined order. */ +static svn_error_t * +get_info(apr_hash_t **props, + const svn_checksum_t **checksum, + apr_array_header_t **children, + svn_node_kind_t *kind, + const char *local_relpath, + int op_depth, + svn_wc__db_wcroot_t *wcroot, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *hash_children; + apr_array_header_t *sorted_children; + svn_error_t *err; + int i; + + err = svn_wc__db_depth_get_info(NULL, kind, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, checksum, NULL, NULL, props, + wcroot, local_relpath, op_depth, + result_pool, scratch_pool); + if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + *kind = svn_node_none; + } + else + SVN_ERR(err); + + + SVN_ERR(svn_wc__db_get_children_op_depth(&hash_children, wcroot, + local_relpath, op_depth, + scratch_pool, scratch_pool)); + + sorted_children = svn_sort__hash(hash_children, + svn_sort_compare_items_lexically, + scratch_pool); + + *children = apr_array_make(result_pool, sorted_children->nelts, + sizeof(const char *)); + for (i = 0; i < sorted_children->nelts; ++i) + APR_ARRAY_PUSH(*children, const char *) + = apr_pstrdup(result_pool, APR_ARRAY_IDX(sorted_children, i, + svn_sort__item_t).key); + + return SVN_NO_ERROR; +} + +/* Return TRUE if SRC_CHILDREN and DST_CHILDREN represent the same + children, FALSE otherwise. SRC_CHILDREN and DST_CHILDREN are + sorted arrays of basenames of type 'const char *'. */ +static svn_boolean_t +children_match(apr_array_header_t *src_children, + apr_array_header_t *dst_children) { int i; + + if (src_children->nelts != dst_children->nelts) + return FALSE; + + for(i = 0; i < src_children->nelts; ++i) + { + const char *src_child = + APR_ARRAY_IDX(src_children, i, const char *); + const char *dst_child = + APR_ARRAY_IDX(dst_children, i, const char *); + + if (strcmp(src_child, dst_child)) + return FALSE; + } + + return TRUE; +} + +/* Return TRUE if SRC_PROPS and DST_PROPS contain the same properties, + FALSE otherwise. SRC_PROPS and DST_PROPS are standard property + hashes. */ +static svn_error_t * +props_match(svn_boolean_t *match, + apr_hash_t *src_props, + apr_hash_t *dst_props, + apr_pool_t *scratch_pool) +{ + if (!src_props && !dst_props) + *match = TRUE; + else if (!src_props || ! dst_props) + *match = FALSE; + else + { + apr_array_header_t *propdiffs; + + SVN_ERR(svn_prop_diffs(&propdiffs, src_props, dst_props, scratch_pool)); + *match = propdiffs->nelts ? FALSE : TRUE; + } + return SVN_NO_ERROR; +} + +/* ### Drive TC_EDITOR so as to ... + */ +static svn_error_t * +update_moved_away_node(svn_editor_t *tc_editor, + const char *src_relpath, + const char *dst_relpath, + int src_op_depth, + const char *move_root_dst_relpath, + svn_revnum_t move_root_dst_revision, + svn_wc__db_t *db, + svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t src_kind, dst_kind; + const svn_checksum_t *src_checksum, *dst_checksum; + apr_hash_t *src_props, *dst_props; + apr_array_header_t *src_children, *dst_children; + int dst_op_depth = relpath_depth(move_root_dst_relpath); + + SVN_ERR(get_info(&src_props, &src_checksum, &src_children, &src_kind, + src_relpath, src_op_depth, + wcroot, scratch_pool, scratch_pool)); + + SVN_ERR(get_info(&dst_props, &dst_checksum, &dst_children, &dst_kind, + dst_relpath, dst_op_depth, + wcroot, scratch_pool, scratch_pool)); + + if (src_kind == svn_node_none + || (dst_kind != svn_node_none && src_kind != dst_kind)) + { + SVN_ERR(svn_editor_delete(tc_editor, dst_relpath, + move_root_dst_revision)); + } + + if (src_kind != svn_node_none && src_kind != dst_kind) + { + if (src_kind == svn_node_file || src_kind == svn_node_symlink) + { + svn_stream_t *contents; + + SVN_ERR(svn_wc__db_pristine_read(&contents, NULL, db, + wcroot->abspath, src_checksum, + scratch_pool, scratch_pool)); + SVN_ERR(svn_editor_add_file(tc_editor, dst_relpath, + src_checksum, contents, src_props, + move_root_dst_revision)); + } + else if (src_kind == svn_node_dir) + { + SVN_ERR(svn_editor_add_directory(tc_editor, dst_relpath, + src_children, src_props, + move_root_dst_revision)); + } + } + else if (src_kind != svn_node_none) + { + svn_boolean_t match; + apr_hash_t *props; + + SVN_ERR(props_match(&match, src_props, dst_props, scratch_pool)); + props = match ? NULL: src_props; + + + if (src_kind == svn_node_file || src_kind == svn_node_symlink) + { + svn_stream_t *contents; + + if (svn_checksum_match(src_checksum, dst_checksum)) + src_checksum = NULL; + + if (src_checksum) + SVN_ERR(svn_wc__db_pristine_read(&contents, NULL, db, + wcroot->abspath, src_checksum, + scratch_pool, scratch_pool)); + else + contents = NULL; + + if (props || src_checksum) + SVN_ERR(svn_editor_alter_file(tc_editor, dst_relpath, + move_root_dst_revision, + props, src_checksum, contents)); + } + else if (src_kind == svn_node_dir) + { + apr_array_header_t *children + = children_match(src_children, dst_children) ? NULL : src_children; + + if (props || children) + SVN_ERR(svn_editor_alter_directory(tc_editor, dst_relpath, + move_root_dst_revision, + children, props)); + } + } + + if (src_kind == svn_node_dir) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i = 0, j = 0; + + while (i < src_children->nelts || j < dst_children->nelts) + { + const char *child_name; + const char *src_child_relpath, *dst_child_relpath; + svn_boolean_t src_only = FALSE, dst_only = FALSE; + + svn_pool_clear(iterpool); + if (i >= src_children->nelts) + { + dst_only = TRUE; + child_name = APR_ARRAY_IDX(dst_children, j, const char *); + } + else if (j >= dst_children->nelts) + { + src_only = TRUE; + child_name = APR_ARRAY_IDX(src_children, i, const char *); + } + else + { + const char *src_name = APR_ARRAY_IDX(src_children, i, + const char *); + const char *dst_name = APR_ARRAY_IDX(dst_children, j, + const char *); + int cmp = strcmp(src_name, dst_name); + + if (cmp > 0) + dst_only = TRUE; + else if (cmp < 0) + src_only = TRUE; + + child_name = dst_only ? dst_name : src_name; + } + + src_child_relpath = svn_relpath_join(src_relpath, child_name, + iterpool); + dst_child_relpath = svn_relpath_join(dst_relpath, child_name, + iterpool); + + SVN_ERR(update_moved_away_node(tc_editor, src_child_relpath, + dst_child_relpath, src_op_depth, + move_root_dst_relpath, + move_root_dst_revision, + db, wcroot, scratch_pool)); + + if (!dst_only) + ++i; + if (!src_only) + ++j; + } + } + + return SVN_NO_ERROR; +} + +/* Update the single op-depth layer in the move destination subtree + rooted at DST_RELPATH to make it match the move source subtree + rooted at SRC_RELPATH. */ +static svn_error_t * +replace_moved_layer(const char *src_relpath, + const char *dst_relpath, + int src_op_depth, + svn_wc__db_wcroot_t *wcroot, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int dst_op_depth = relpath_depth(dst_relpath); + + /* Replace entire subtree at one op-depth. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_LOCAL_RELPATH_OP_DEPTH)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + src_relpath, src_op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + svn_error_t *err; + svn_sqlite__stmt_t *stmt2; + const char *src_cp_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *dst_cp_relpath + = svn_relpath_join(dst_relpath, + svn_relpath_skip_ancestor(src_relpath, + src_cp_relpath), + scratch_pool); + + err = svn_sqlite__get_statement(&stmt2, wcroot->sdb, + STMT_COPY_NODE_MOVE); + if (!err) + err = svn_sqlite__bindf(stmt2, "isdsds", wcroot->wc_id, + src_cp_relpath, src_op_depth, + dst_cp_relpath, dst_op_depth, + svn_relpath_dirname(dst_cp_relpath, + scratch_pool)); + if (!err) + err = svn_sqlite__step_done(stmt2); + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +/* Transfer changes from the move source to the move destination. + * + * Drive the editor TC_EDITOR with the difference between DST_RELPATH + * (at its own op-depth) and SRC_RELPATH (at op-depth zero). + * + * Then update the single op-depth layer in the move destination subtree + * rooted at DST_RELPATH to make it match the move source subtree + * rooted at SRC_RELPATH. + * + * ### And the other params? + */ +static svn_error_t * +drive_tree_conflict_editor(svn_editor_t *tc_editor, + const char *src_relpath, + const char *dst_relpath, + int src_op_depth, + svn_wc_operation_t operation, + svn_wc_conflict_reason_t local_change, + svn_wc_conflict_action_t incoming_change, + svn_wc_conflict_version_t *old_version, + svn_wc_conflict_version_t *new_version, + svn_wc__db_t *db, + svn_wc__db_wcroot_t *wcroot, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + /* + * Refuse to auto-resolve unsupported tree conflicts. + */ + /* ### Only handle conflicts created by update/switch operations for now. */ + if (operation != svn_wc_operation_update && + operation != svn_wc_operation_switch) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Cannot auto-resolve tree-conflict on '%s'"), + svn_dirent_local_style( + svn_dirent_join(wcroot->abspath, + src_relpath, scratch_pool), + scratch_pool)); + + /* We walk the move source (i.e. the post-update tree), comparing each node + * with the equivalent node at the move destination and applying the update + * to nodes at the move destination. */ + SVN_ERR(update_moved_away_node(tc_editor, src_relpath, dst_relpath, + src_op_depth, + dst_relpath, old_version->peg_rev, + db, wcroot, scratch_pool)); + + SVN_ERR(replace_moved_layer(src_relpath, dst_relpath, src_op_depth, + wcroot, scratch_pool)); + + SVN_ERR(svn_editor_complete(tc_editor)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +suitable_for_move(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + svn_revnum_t revision; + const char *repos_relpath; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_BASE_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + { + revision = svn_sqlite__column_revnum(stmt, 4); + repos_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool); + } + SVN_ERR(svn_sqlite__reset(stmt)); + if (!have_row) + return SVN_NO_ERROR; /* Return an error? */ + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_REPOS_PATH_REVISION)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while (have_row) + { + svn_revnum_t node_revision = svn_sqlite__column_revnum(stmt, 2); + const char *relpath = svn_sqlite__column_text(stmt, 0, NULL); + + svn_pool_clear(iterpool); + + relpath = svn_relpath_skip_ancestor(local_relpath, relpath); + relpath = svn_relpath_join(repos_relpath, relpath, iterpool); + + if (revision != node_revision) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + svn_sqlite__reset(stmt), + _("Cannot apply update because move source " + "%s' is a mixed-revision working copy"), + svn_dirent_local_style(svn_dirent_join( + wcroot->abspath, + local_relpath, + scratch_pool), + scratch_pool)); + + if (strcmp(relpath, svn_sqlite__column_text(stmt, 1, NULL))) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, + svn_sqlite__reset(stmt), + _("Cannot apply update because move source " + "'%s' is a switched subtree"), + svn_dirent_local_style(svn_dirent_join( + wcroot->abspath, + local_relpath, + scratch_pool), + scratch_pool)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* The body of svn_wc__db_update_moved_away_conflict_victim(), which see. + */ +static svn_error_t * +update_moved_away_conflict_victim(svn_wc__db_t *db, + svn_wc__db_wcroot_t *wcroot, + const char *victim_relpath, + svn_wc_operation_t operation, + svn_wc_conflict_reason_t local_change, + svn_wc_conflict_action_t incoming_change, + const char *move_src_op_root_relpath, + svn_wc_conflict_version_t *old_version, + svn_wc_conflict_version_t *new_version, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_editor_t *tc_editor; + struct tc_editor_baton *tc_editor_baton; + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + const char *dummy1, *dummy2, *dummy3; + int src_op_depth; + const char *move_root_dst_abspath; + + /* ### assumes wc write lock already held */ + + /* Construct editor baton. */ + tc_editor_baton = apr_pcalloc(scratch_pool, sizeof(*tc_editor_baton)); + SVN_ERR(svn_wc__db_op_depth_moved_to( + &dummy1, &tc_editor_baton->move_root_dst_relpath, &dummy2, &dummy3, + relpath_depth(move_src_op_root_relpath) - 1, + wcroot, victim_relpath, scratch_pool, scratch_pool)); + if (tc_editor_baton->move_root_dst_relpath == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("The node '%s' has not been moved away"), + svn_dirent_local_style( + svn_dirent_join(wcroot->abspath, victim_relpath, + scratch_pool), + scratch_pool)); + + move_root_dst_abspath + = svn_dirent_join(wcroot->abspath, tc_editor_baton->move_root_dst_relpath, + scratch_pool); + SVN_ERR(svn_wc__write_check(db, move_root_dst_abspath, scratch_pool)); + + tc_editor_baton->operation = operation; + tc_editor_baton->old_version= old_version; + tc_editor_baton->new_version= new_version; + tc_editor_baton->db = db; + tc_editor_baton->wcroot = wcroot; + tc_editor_baton->result_pool = scratch_pool; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_HIGHEST_WORKING_NODE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + move_src_op_root_relpath, + relpath_depth(move_src_op_root_relpath))); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + if (have_row) + src_op_depth = svn_sqlite__column_int(stmt, 0); + SVN_ERR(svn_sqlite__reset(stmt)); + if (!have_row) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("'%s' is not deleted"), + svn_dirent_local_style( + svn_dirent_join(wcroot->abspath, victim_relpath, + scratch_pool), + scratch_pool)); + + if (src_op_depth == 0) + SVN_ERR(suitable_for_move(wcroot, victim_relpath, scratch_pool)); + + /* Create a new, and empty, list for notification information. */ + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_UPDATE_MOVE_LIST)); + /* Create the editor... */ + SVN_ERR(svn_editor_create(&tc_editor, tc_editor_baton, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + SVN_ERR(svn_editor_setcb_many(tc_editor, &editor_ops, scratch_pool)); + + /* ... and drive it. */ + SVN_ERR(drive_tree_conflict_editor(tc_editor, + victim_relpath, + tc_editor_baton->move_root_dst_relpath, + src_op_depth, + operation, + local_change, incoming_change, + tc_editor_baton->old_version, + tc_editor_baton->new_version, + db, wcroot, + cancel_func, cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_update_moved_away_conflict_victim(svn_wc__db_t *db, + const char *victim_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_wc_operation_t operation; + svn_wc_conflict_reason_t local_change; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_version_t *old_version; + svn_wc_conflict_version_t *new_version; + const char *move_src_op_root_abspath, *move_src_op_root_relpath; + + /* ### Check for mixed-rev src or dst? */ + + SVN_ERR(get_tc_info(&operation, &local_change, &incoming_change, + &move_src_op_root_abspath, + &old_version, &new_version, + db, victim_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__write_check(db, move_src_op_root_abspath, scratch_pool)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, victim_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + move_src_op_root_relpath + = svn_dirent_skip_ancestor(wcroot->abspath, move_src_op_root_abspath); + + SVN_WC__DB_WITH_TXN( + update_moved_away_conflict_victim( + db, wcroot, local_relpath, + operation, local_change, incoming_change, + move_src_op_root_relpath, + old_version, new_version, + cancel_func, cancel_baton, + scratch_pool), + wcroot); + + /* Send all queued up notifications. */ + SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, + old_version->peg_rev, + new_version->peg_rev, + notify_func, notify_baton, + scratch_pool)); + if (notify_func) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath, + local_relpath, + scratch_pool), + svn_wc_notify_update_completed, + scratch_pool); + notify->kind = svn_node_none; + notify->content_state = svn_wc_notify_state_inapplicable; + notify->prop_state = svn_wc_notify_state_inapplicable; + notify->revision = new_version->peg_rev; + notify_func(notify_baton, notify, scratch_pool); + } + + + return SVN_NO_ERROR; +} + +/* Set *CAN_BUMP to TRUE if DEPTH is sufficient to cover the entire + BASE tree at LOCAL_RELPATH, to FALSE otherwise. */ +static svn_error_t * +depth_sufficient_to_bump(svn_boolean_t *can_bump, + const char *local_relpath, + svn_wc__db_wcroot_t *wcroot, + svn_depth_t depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + switch (depth) + { + case svn_depth_infinity: + *can_bump = TRUE; + return SVN_NO_ERROR; + + case svn_depth_empty: + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_OP_DEPTH_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, + local_relpath, 0)); + break; + + case svn_depth_files: + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_HAS_NON_FILE_CHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + break; + + case svn_depth_immediates: + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_HAS_GRANDCHILDREN)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, + local_relpath)); + break; + default: + SVN_ERR_MALFUNCTION(); + } + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + *can_bump = !have_row; + return SVN_NO_ERROR; +} + +/* Mark a move-edit conflict on MOVE_SRC_ROOT_RELPATH. */ +static svn_error_t * +bump_mark_tree_conflict(svn_wc__db_wcroot_t *wcroot, + const char *move_src_root_relpath, + const char *move_src_op_root_relpath, + const char *move_dst_op_root_relpath, + svn_wc__db_t *db, + apr_pool_t *scratch_pool) +{ + apr_int64_t repos_id; + const char *repos_root_url; + const char *repos_uuid; + const char *old_repos_relpath; + const char *new_repos_relpath; + svn_revnum_t old_rev; + svn_revnum_t new_rev; + svn_node_kind_t old_kind; + svn_node_kind_t new_kind; + svn_wc_conflict_version_t *old_version; + svn_wc_conflict_version_t *new_version; + + /* Read new (post-update) information from the new move source BASE node. */ + SVN_ERR(svn_wc__db_base_get_info_internal(NULL, &new_kind, &new_rev, + &new_repos_relpath, &repos_id, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + wcroot, move_src_op_root_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__db_fetch_repos_info(&repos_root_url, &repos_uuid, + wcroot->sdb, repos_id, scratch_pool)); + + /* Read old (pre-update) information from the move destination node. */ + SVN_ERR(svn_wc__db_depth_get_info(NULL, &old_kind, &old_rev, + &old_repos_relpath, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + wcroot, move_dst_op_root_relpath, + relpath_depth(move_dst_op_root_relpath), + scratch_pool, scratch_pool)); + + old_version = svn_wc_conflict_version_create2( + repos_root_url, repos_uuid, old_repos_relpath, old_rev, + old_kind, scratch_pool); + new_version = svn_wc_conflict_version_create2( + repos_root_url, repos_uuid, new_repos_relpath, new_rev, + new_kind, scratch_pool); + + SVN_ERR(mark_tree_conflict(move_src_root_relpath, + wcroot, db, old_version, new_version, + move_dst_op_root_relpath, + svn_wc_operation_update, + old_kind, new_kind, + old_repos_relpath, + svn_wc_conflict_reason_moved_away, + svn_wc_conflict_action_edit, + move_src_op_root_relpath, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Bump LOCAL_RELPATH, and all the children of LOCAL_RELPATH, that are + moved-to at op-depth greater than OP_DEPTH. SRC_DONE is a hash + with keys that are 'const char *' relpaths that have already been + bumped. Any bumped paths are added to SRC_DONE. */ +static svn_error_t * +bump_moved_away(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + int op_depth, + apr_hash_t *src_done, + svn_depth_t depth, + svn_wc__db_t *db, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool; + + iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_PAIR3)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while(have_row) + { + svn_sqlite__stmt_t *stmt2; + const char *src_relpath, *dst_relpath; + int src_op_depth = svn_sqlite__column_int(stmt, 2); + svn_error_t *err; + svn_skel_t *conflict; + svn_depth_t src_depth = depth; + + svn_pool_clear(iterpool); + + src_relpath = svn_sqlite__column_text(stmt, 0, iterpool); + dst_relpath = svn_sqlite__column_text(stmt, 1, iterpool); + + if (depth != svn_depth_infinity) + { + svn_boolean_t skip_this_src = FALSE; + svn_node_kind_t src_kind; + + if (strcmp(src_relpath, local_relpath)) + { + switch (depth) + { + case svn_depth_empty: + skip_this_src = TRUE; + break; + case svn_depth_files: + src_kind = svn_sqlite__column_token(stmt, 3, kind_map); + if (src_kind != svn_node_file) + { + skip_this_src = TRUE; + break; + } + /* Fallthrough */ + case svn_depth_immediates: + if (strcmp(svn_relpath_dirname(src_relpath, scratch_pool), + local_relpath)) + skip_this_src = TRUE; + src_depth = svn_depth_empty; + break; + default: + SVN_ERR_MALFUNCTION(); + } + } + + if (skip_this_src) + { + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + continue; + } + } + + err = svn_sqlite__get_statement(&stmt2, wcroot->sdb, + STMT_HAS_LAYER_BETWEEN); + if (!err) + err = svn_sqlite__bindf(stmt2, "isdd", wcroot->wc_id, local_relpath, + op_depth, src_op_depth); + if (!err) + err = svn_sqlite__step(&have_row, stmt2); + if (!err) + err = svn_sqlite__reset(stmt2); + if (!err && !have_row) + { + svn_boolean_t can_bump; + const char *src_root_relpath = src_relpath; + + if (op_depth == 0) + err = depth_sufficient_to_bump(&can_bump, src_relpath, wcroot, + src_depth, scratch_pool); + else + /* Having chosen to bump an entire BASE tree move we + always have sufficient depth to bump subtree moves. */ + can_bump = TRUE; + + if (!err) + { + if (!can_bump) + { + err = bump_mark_tree_conflict(wcroot, src_relpath, + src_root_relpath, dst_relpath, + db, scratch_pool); + if (err) + return svn_error_compose_create(err, + svn_sqlite__reset(stmt)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + continue; + } + + while (relpath_depth(src_root_relpath) > src_op_depth) + src_root_relpath = svn_relpath_dirname(src_root_relpath, + iterpool); + + if (!svn_hash_gets(src_done, src_relpath)) + { + svn_hash_sets(src_done, + apr_pstrdup(result_pool, src_relpath), ""); + err = svn_wc__db_read_conflict_internal(&conflict, wcroot, + src_root_relpath, + iterpool, iterpool); + /* ### TODO: check this is the right sort of tree-conflict? */ + if (!err && !conflict) + { + /* ### TODO: verify moved_here? */ + err = replace_moved_layer(src_relpath, dst_relpath, + op_depth, wcroot, iterpool); + + if (!err) + err = bump_moved_away(wcroot, dst_relpath, + relpath_depth(dst_relpath), + src_done, depth, db, + result_pool, iterpool); + } + } + } + } + + if (err) + return svn_error_compose_create(err, svn_sqlite__reset(stmt)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_bump_moved_away(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_depth_t depth, + svn_wc__db_t *db, + apr_pool_t *scratch_pool) +{ + const char *dummy1, *move_dst_op_root_relpath; + const char *move_src_root_relpath, *move_src_op_root_relpath; + apr_hash_t *src_done; + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_UPDATE_MOVE_LIST)); + + SVN_ERR(svn_wc__db_op_depth_moved_to(&dummy1, &move_dst_op_root_relpath, + &move_src_root_relpath, + &move_src_op_root_relpath, 0, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + + if (move_src_root_relpath) + { + if (strcmp(move_src_root_relpath, local_relpath)) + { + SVN_ERR(bump_mark_tree_conflict(wcroot, move_src_root_relpath, + move_src_op_root_relpath, + move_dst_op_root_relpath, + db, scratch_pool)); + return SVN_NO_ERROR; + } + + } + + src_done = apr_hash_make(scratch_pool); + SVN_ERR(bump_moved_away(wcroot, local_relpath, 0, src_done, depth, db, + scratch_pool, scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +resolve_delete_raise_moved_away(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_t *db, + svn_wc_operation_t operation, + svn_wc_conflict_action_t action, + svn_wc_conflict_version_t *old_version, + svn_wc_conflict_version_t *new_version, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + int op_depth = relpath_depth(local_relpath); + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_UPDATE_MOVE_LIST)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_OP_DEPTH_MOVED_PAIR)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, + op_depth)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + while(have_row) + { + const char *moved_relpath = svn_sqlite__column_text(stmt, 0, NULL); + const char *move_root_dst_relpath = svn_sqlite__column_text(stmt, 1, + NULL); + const char *moved_dst_repos_relpath = svn_sqlite__column_text(stmt, 2, + NULL); + svn_pool_clear(iterpool); + + SVN_ERR(mark_tree_conflict(moved_relpath, + wcroot, db, old_version, new_version, + move_root_dst_relpath, operation, + svn_node_dir /* ### ? */, + svn_node_dir /* ### ? */, + moved_dst_repos_relpath, + svn_wc_conflict_reason_moved_away, + action, local_relpath, + iterpool)); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_resolve_delete_raise_moved_away(svn_wc__db_t *db, + const char *local_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + svn_wc_operation_t operation; + svn_wc_conflict_reason_t reason; + svn_wc_conflict_action_t action; + svn_wc_conflict_version_t *old_version, *new_version; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(get_tc_info(&operation, &reason, &action, NULL, + &old_version, &new_version, + db, local_abspath, scratch_pool, scratch_pool)); + + SVN_WC__DB_WITH_TXN( + resolve_delete_raise_moved_away(wcroot, local_relpath, + db, operation, action, + old_version, new_version, + scratch_pool), + wcroot); + + SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, + old_version->peg_rev, + (new_version + ? new_version->peg_rev + : SVN_INVALID_REVNUM), + notify_func, notify_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +break_move(svn_wc__db_wcroot_t *wcroot, + const char *src_relpath, + int src_op_depth, + const char *dst_relpath, + int dst_op_depth, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_CLEAR_MOVED_TO_RELPATH)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, src_relpath, + src_op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + /* This statement clears moved_here. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_UPDATE_OP_DEPTH_RECURSIVE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isdd", wcroot->wc_id, + dst_relpath, dst_op_depth, dst_op_depth)); + SVN_ERR(svn_sqlite__step_done(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_resolve_break_moved_away_internal(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + const char *dummy1, *move_dst_op_root_relpath; + const char *dummy2, *move_src_op_root_relpath; + + SVN_ERR(svn_wc__db_op_depth_moved_to(&dummy1, &move_dst_op_root_relpath, + &dummy2, + &move_src_op_root_relpath, + relpath_depth(local_relpath) - 1, + wcroot, local_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(break_move(wcroot, local_relpath, + relpath_depth(move_src_op_root_relpath), + move_dst_op_root_relpath, + relpath_depth(move_dst_op_root_relpath), + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +break_moved_away_children_internal(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + apr_pool_t *iterpool; + + SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb, + STMT_CREATE_UPDATE_MOVE_LIST)); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_PAIR2)); + SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + iterpool = svn_pool_create(scratch_pool); + while (have_row) + { + const char *src_relpath = svn_sqlite__column_text(stmt, 0, iterpool); + const char *dst_relpath = svn_sqlite__column_text(stmt, 1, iterpool); + int src_op_depth = svn_sqlite__column_int(stmt, 2); + + svn_pool_clear(iterpool); + + SVN_ERR(break_move(wcroot, src_relpath, src_op_depth, dst_relpath, + relpath_depth(dst_relpath), iterpool)); + SVN_ERR(update_move_list_add(wcroot, src_relpath, + svn_wc_notify_move_broken, + svn_node_unknown, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + svn_pool_destroy(iterpool); + + SVN_ERR(svn_sqlite__reset(stmt)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_resolve_break_moved_away(svn_wc__db_t *db, + const char *local_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + svn_wc__db_resolve_break_moved_away_internal(wcroot, local_relpath, + scratch_pool), + wcroot); + + if (notify_func) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath, + local_relpath, + scratch_pool), + svn_wc_notify_move_broken, + scratch_pool); + notify->kind = svn_node_unknown; + notify->content_state = svn_wc_notify_state_inapplicable; + notify->prop_state = svn_wc_notify_state_inapplicable; + notify->revision = SVN_INVALID_REVNUM; + notify_func(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_resolve_break_moved_away_children(svn_wc__db_t *db, + const char *local_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + break_moved_away_children_internal(wcroot, local_relpath, scratch_pool), + wcroot); + + SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, + SVN_INVALID_REVNUM, + SVN_INVALID_REVNUM, + notify_func, notify_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +static svn_error_t * +required_lock_for_resolve(const char **required_relpath, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + *required_relpath = local_relpath; + + /* This simply looks for all moves out of the LOCAL_RELPATH tree. We + could attempt to limit it to only those moves that are going to + be resolved but that would require second guessing the resolver. + This simple algorithm is sufficient although it may give a + strictly larger/deeper lock than necessary. */ + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_MOVED_OUTSIDE)); + SVN_ERR(svn_sqlite__bindf(stmt, "isd", wcroot->wc_id, local_relpath, 0)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + while (have_row) + { + const char *move_dst_relpath = svn_sqlite__column_text(stmt, 1, + NULL); + + *required_relpath + = svn_relpath_get_longest_ancestor(*required_relpath, + move_dst_relpath, + scratch_pool); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + SVN_ERR(svn_sqlite__reset(stmt)); + + *required_relpath = apr_pstrdup(result_pool, *required_relpath); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__required_lock_for_resolve(const char **required_abspath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + const char *required_relpath; + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, + db, local_abspath, + scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_WC__DB_WITH_TXN( + required_lock_for_resolve(&required_relpath, wcroot, local_relpath, + scratch_pool, scratch_pool), + wcroot); + + *required_abspath = svn_dirent_join(wcroot->abspath, required_relpath, + result_pool); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/wc_db_util.c b/subversion/libsvn_wc/wc_db_util.c new file mode 100644 index 000000000000..39dd034c5455 --- /dev/null +++ b/subversion/libsvn_wc/wc_db_util.c @@ -0,0 +1,228 @@ +/* + * wc_db_util.c : Various util functions for wc_db(_pdh) + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* About this file: + This file is meant to be a stash of fairly low-level functions used by both + wc_db.c and wc_db_pdh.c. In breaking stuff out of the monolithic wc_db.c, + I have discovered that some utility functions are used by bits in both + files. Rather than shoehorn those functions into one file or the other, or + create circular dependencies between the files, I felt a third file, with + a well-defined scope, would be sensible. History will judge its effect. + + The goal of it file is simple: just execute SQLite statements. That is, + functions in this file should have no knowledge of pdh's or db's, and + should just operate on the raw sdb object. If a function requires more + information than that, it shouldn't be in here. -hkw + */ + +#define SVN_WC__I_AM_WC_DB + +#include "svn_dirent_uri.h" +#include "private/svn_sqlite.h" + +#include "wc.h" +#include "adm_files.h" +#include "wc_db_private.h" +#include "wc-queries.h" + +#include "svn_private_config.h" + +WC_QUERIES_SQL_DECLARE_STATEMENTS(statements); + + + +/* */ +svn_error_t * +svn_wc__db_util_fetch_wc_id(apr_int64_t *wc_id, + svn_sqlite__db_t *sdb, + apr_pool_t *scratch_pool) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + /* ### cheat. we know there is just one WORKING_COPY row, and it has a + ### NULL value for local_abspath. */ + 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_createf(SVN_ERR_WC_CORRUPT, svn_sqlite__reset(stmt), + _("Missing a row in WCROOT.")); + + SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 0)); + *wc_id = svn_sqlite__column_int64(stmt, 0); + + return svn_error_trace(svn_sqlite__reset(stmt)); +} + + + + +/* An SQLite application defined function that allows SQL queries to + use "relpath_depth(local_relpath)". */ +static svn_error_t * +relpath_depth_sqlite(svn_sqlite__context_t *sctx, + int argc, + svn_sqlite__value_t *values[], + apr_pool_t *scratch_pool) +{ + const char *path = NULL; + apr_int64_t depth; + + if (argc == 1 && svn_sqlite__value_type(values[0]) == SVN_SQLITE__TEXT) + path = svn_sqlite__value_text(values[0]); + if (!path) + { + svn_sqlite__result_null(sctx); + return SVN_NO_ERROR; + } + + depth = *path ? 1 : 0; + while (*path) + { + if (*path == '/') + ++depth; + ++path; + } + svn_sqlite__result_int64(sctx, depth); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_util_open_db(svn_sqlite__db_t **sdb, + const char *dir_abspath, + const char *sdb_fname, + svn_sqlite__mode_t smode, + svn_boolean_t exclusive, + const char *const *my_statements, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *sdb_abspath = svn_wc__adm_child(dir_abspath, sdb_fname, + scratch_pool); + + if (smode != svn_sqlite__mode_rwcreate) + { + svn_node_kind_t kind; + + /* A file stat is much cheaper then a failed database open handled + by SQLite. */ + SVN_ERR(svn_io_check_path(sdb_abspath, &kind, scratch_pool)); + + if (kind != svn_node_file) + return svn_error_createf(APR_ENOENT, NULL, + _("Working copy database '%s' not found"), + svn_dirent_local_style(sdb_abspath, + scratch_pool)); + } +#ifndef WIN32 + else + { + apr_file_t *f; + + /* A standard SQLite build creates a DB with mode 644 ^ !umask + which means the file doesn't have group/world write access + even when umask allows it. By ensuring the file exists before + SQLite gets involved we give it the permissions allowed by + umask. */ + SVN_ERR(svn_io_file_open(&f, sdb_abspath, + (APR_READ | APR_WRITE | APR_CREATE), + APR_OS_DEFAULT, scratch_pool)); + SVN_ERR(svn_io_file_close(f, scratch_pool)); + } +#endif + + SVN_ERR(svn_sqlite__open(sdb, sdb_abspath, smode, + my_statements ? my_statements : statements, + 0, NULL, result_pool, scratch_pool)); + + if (exclusive) + SVN_ERR(svn_sqlite__exec_statements(*sdb, STMT_PRAGMA_LOCKING_MODE)); + + SVN_ERR(svn_sqlite__create_scalar_function(*sdb, "relpath_depth", 1, + relpath_depth_sqlite, NULL)); + + return SVN_NO_ERROR; +} + + +/* Some helpful transaction helpers. + + Instead of directly using SQLite transactions, these wrappers + relieve the consumer from the need to wrap the wcroot and + local_relpath, which are almost always used within the transaction. + + This also means if we later want to implement some wc_db-specific txn + handling, we have a convenient place to do it. + */ + +/* A callback which supplies WCROOTs and LOCAL_RELPATHs. */ +typedef svn_error_t *(*db_txn_callback_t)(void *baton, + svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + apr_pool_t *scratch_pool); + +/* Baton for use with run_txn() and with_db_txn(). */ +struct txn_baton_t +{ + svn_wc__db_wcroot_t *wcroot; + const char *local_relpath; + + db_txn_callback_t cb_func; + void *cb_baton; +}; + + +/* Unwrap the sqlite transaction into a wc_db txn. + Implements svn_sqlite__transaction_callback_t. */ +static svn_error_t * +run_txn(void *baton, svn_sqlite__db_t *db, apr_pool_t *scratch_pool) +{ + struct txn_baton_t *tb = baton; + + return svn_error_trace( + tb->cb_func(tb->cb_baton, tb->wcroot, tb->local_relpath, scratch_pool)); +} + + +/* Run CB_FUNC in a SQLite transaction with CB_BATON, using WCROOT and + LOCAL_RELPATH. If callbacks require additional information, they may + provide it using CB_BATON. */ +svn_error_t * +svn_wc__db_with_txn(svn_wc__db_wcroot_t *wcroot, + const char *local_relpath, + svn_wc__db_txn_callback_t cb_func, + void *cb_baton, + apr_pool_t *scratch_pool) +{ + struct txn_baton_t tb; + + tb.wcroot = wcroot; + tb.local_relpath = local_relpath; + tb.cb_func = cb_func; + tb.cb_baton = cb_baton; + + return svn_error_trace( + svn_sqlite__with_lock(wcroot->sdb, run_txn, &tb, scratch_pool)); +} diff --git a/subversion/libsvn_wc/wc_db_wcroot.c b/subversion/libsvn_wc/wc_db_wcroot.c new file mode 100644 index 000000000000..1091f1b5cc03 --- /dev/null +++ b/subversion/libsvn_wc/wc_db_wcroot.c @@ -0,0 +1,900 @@ +/* + * wc_db_wcroot.c : supporting datastructures for the administrative database + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#define SVN_WC__I_AM_WC_DB + +#include <assert.h> + +#include "svn_dirent_uri.h" +#include "svn_hash.h" +#include "svn_path.h" +#include "svn_version.h" + +#include "wc.h" +#include "adm_files.h" +#include "wc_db_private.h" +#include "wc-queries.h" + +#include "svn_private_config.h" + +/* ### Same values as wc_db.c */ +#define SDB_FILE "wc.db" +#define UNKNOWN_WC_ID ((apr_int64_t) -1) +#define FORMAT_FROM_SDB (-1) + + + +/* Get the format version from a wc-1 directory. If it is not a working copy + directory, then it sets VERSION to zero and returns no error. */ +static svn_error_t * +get_old_version(int *version, + const char *abspath, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + const char *format_file_path; + svn_node_kind_t kind; + + /* Try reading the format number from the entries file. */ + format_file_path = svn_wc__adm_child(abspath, SVN_WC__ADM_ENTRIES, + scratch_pool); + + /* Since trying to open a non-existent file is quite expensive, try a + quick stat call first. In wc-ng w/cs, this will be an early exit. */ + SVN_ERR(svn_io_check_path(format_file_path, &kind, scratch_pool)); + if (kind == svn_node_none) + { + *version = 0; + return SVN_NO_ERROR; + } + + err = svn_io_read_version_file(version, format_file_path, scratch_pool); + if (err == NULL) + return SVN_NO_ERROR; + if (err->apr_err != SVN_ERR_BAD_VERSION_FILE_FORMAT + && !APR_STATUS_IS_ENOENT(err->apr_err) + && !APR_STATUS_IS_ENOTDIR(err->apr_err)) + return svn_error_createf(SVN_ERR_WC_MISSING, err, _("'%s' does not exist"), + svn_dirent_local_style(abspath, scratch_pool)); + svn_error_clear(err); + + /* This must be a really old working copy! Fall back to reading the + format file. + + Note that the format file might not exist in newer working copies + (format 7 and higher), but in that case, the entries file should + have contained the format number. */ + format_file_path = svn_wc__adm_child(abspath, SVN_WC__ADM_FORMAT, + scratch_pool); + err = svn_io_read_version_file(version, format_file_path, scratch_pool); + if (err == NULL) + return SVN_NO_ERROR; + + /* Whatever error may have occurred... we can just ignore. This is not + a working copy directory. Signal the caller. */ + svn_error_clear(err); + + *version = 0; + return SVN_NO_ERROR; +} + + +/* A helper function to parse_local_abspath() which returns the on-disk KIND + of LOCAL_ABSPATH, using DB and SCRATCH_POOL as needed. + + This function may do strange things, but at long as it comes up with the + Right Answer, we should be happy. */ +static svn_error_t * +get_path_kind(svn_node_kind_t *kind, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_boolean_t special; + svn_node_kind_t node_kind; + + /* This implements a *really* simple LRU cache, where "simple" is defined + as "only one element". In other words, we remember the most recently + queried path, and nothing else. This gives >80% cache hits. */ + + if (db->parse_cache.abspath + && strcmp(db->parse_cache.abspath->data, local_abspath) == 0) + { + /* Cache hit! */ + *kind = db->parse_cache.kind; + return SVN_NO_ERROR; + } + + if (!db->parse_cache.abspath) + { + db->parse_cache.abspath = svn_stringbuf_create(local_abspath, + db->state_pool); + } + else + { + svn_stringbuf_set(db->parse_cache.abspath, local_abspath); + } + + SVN_ERR(svn_io_check_special_path(local_abspath, &node_kind, + &special, scratch_pool)); + + db->parse_cache.kind = (special ? svn_node_symlink : node_kind); + *kind = db->parse_cache.kind; + + return SVN_NO_ERROR; +} + + +/* Return an error if the work queue in SDB is non-empty. */ +static svn_error_t * +verify_no_work(svn_sqlite__db_t *sdb) +{ + svn_sqlite__stmt_t *stmt; + svn_boolean_t have_row; + + SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_LOOK_FOR_WORK)); + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + SVN_ERR(svn_sqlite__reset(stmt)); + + if (have_row) + return svn_error_create(SVN_ERR_WC_CLEANUP_REQUIRED, NULL, + NULL /* nothing to add. */); + + return SVN_NO_ERROR; +} + + +/* */ +static apr_status_t +close_wcroot(void *data) +{ + svn_wc__db_wcroot_t *wcroot = data; + svn_error_t *err; + + SVN_ERR_ASSERT_NO_RETURN(wcroot->sdb != NULL); + + err = svn_sqlite__close(wcroot->sdb); + wcroot->sdb = NULL; + if (err) + { + apr_status_t result = err->apr_err; + svn_error_clear(err); + return result; + } + + return APR_SUCCESS; +} + + +svn_error_t * +svn_wc__db_open(svn_wc__db_t **db, + svn_config_t *config, + svn_boolean_t open_without_upgrade, + svn_boolean_t enforce_empty_wq, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + *db = apr_pcalloc(result_pool, sizeof(**db)); + (*db)->config = config; + (*db)->verify_format = !open_without_upgrade; + (*db)->enforce_empty_wq = enforce_empty_wq; + (*db)->dir_data = apr_hash_make(result_pool); + + (*db)->state_pool = result_pool; + + /* Don't need to initialize (*db)->parse_cache, due to the calloc above */ + if (config) + { + svn_error_t *err; + svn_boolean_t sqlite_exclusive = FALSE; + + err = svn_config_get_bool(config, &sqlite_exclusive, + SVN_CONFIG_SECTION_WORKING_COPY, + SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE, + FALSE); + if (err) + { + svn_error_clear(err); + } + else + (*db)->exclusive = sqlite_exclusive; + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_close(svn_wc__db_t *db) +{ + apr_pool_t *scratch_pool = db->state_pool; + apr_hash_t *roots = apr_hash_make(scratch_pool); + apr_hash_index_t *hi; + + /* Collect all the unique WCROOT structures, and empty out DIR_DATA. */ + for (hi = apr_hash_first(scratch_pool, db->dir_data); + hi; + hi = apr_hash_next(hi)) + { + svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi); + const char *local_abspath = svn__apr_hash_index_key(hi); + + if (wcroot->sdb) + svn_hash_sets(roots, wcroot->abspath, wcroot); + + svn_hash_sets(db->dir_data, local_abspath, NULL); + } + + /* Run the cleanup for each WCROOT. */ + return svn_error_trace(svn_wc__db_close_many_wcroots(roots, db->state_pool, + scratch_pool)); +} + + +svn_error_t * +svn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot, + const char *wcroot_abspath, + svn_sqlite__db_t *sdb, + apr_int64_t wc_id, + int format, + svn_boolean_t verify_format, + svn_boolean_t enforce_empty_wq, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (sdb != NULL) + SVN_ERR(svn_sqlite__read_schema_version(&format, sdb, scratch_pool)); + + /* If we construct a wcroot, then we better have a format. */ + SVN_ERR_ASSERT(format >= 1); + + /* If this working copy is PRE-1.0, then simply bail out. */ + if (format < 4) + { + return svn_error_createf( + SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL, + _("Working copy format of '%s' is too old (%d); " + "please check out your working copy again"), + svn_dirent_local_style(wcroot_abspath, scratch_pool), format); + } + + /* If this working copy is from a future version, then bail out. */ + if (format > SVN_WC__VERSION) + { + return svn_error_createf( + SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL, + _("This client is too old to work with the working copy at\n" + "'%s' (format %d).\n" + "You need to get a newer Subversion client. For more details, see\n" + " http://subversion.apache.org/faq.html#working-copy-format-change\n" + ), + svn_dirent_local_style(wcroot_abspath, scratch_pool), + format); + } + + /* Verify that no work items exists. If they do, then our integrity is + suspect and, thus, we cannot use this database. */ + if (format >= SVN_WC__HAS_WORK_QUEUE + && (enforce_empty_wq || (format < SVN_WC__VERSION && verify_format))) + { + svn_error_t *err = verify_no_work(sdb); + if (err) + { + /* Special message for attempts to upgrade a 1.7-dev wc with + outstanding workqueue items. */ + if (err->apr_err == SVN_ERR_WC_CLEANUP_REQUIRED + && format < SVN_WC__VERSION && verify_format) + err = svn_error_quick_wrap(err, _("Cleanup with an older 1.7 " + "client before upgrading with " + "this client")); + return svn_error_trace(err); + } + } + + /* Auto-upgrade the SDB if possible. */ + if (format < SVN_WC__VERSION && verify_format) + { + return svn_error_createf(SVN_ERR_WC_UPGRADE_REQUIRED, NULL, + _("The working copy at '%s'\nis too old " + "(format %d) to work with client version " + "'%s' (expects format %d). You need to " + "upgrade the working copy first.\n"), + svn_dirent_local_style(wcroot_abspath, + scratch_pool), + format, SVN_VERSION, SVN_WC__VERSION); + } + + *wcroot = apr_palloc(result_pool, sizeof(**wcroot)); + + (*wcroot)->abspath = wcroot_abspath; + (*wcroot)->sdb = sdb; + (*wcroot)->wc_id = wc_id; + (*wcroot)->format = format; + /* 8 concurrent locks is probably more than a typical wc_ng based svn client + uses. */ + (*wcroot)->owned_locks = apr_array_make(result_pool, 8, + sizeof(svn_wc__db_wclock_t)); + (*wcroot)->access_cache = apr_hash_make(result_pool); + + /* SDB will be NULL for pre-NG working copies. We only need to run a + cleanup when the SDB is present. */ + if (sdb != NULL) + apr_pool_cleanup_register(result_pool, *wcroot, close_wcroot, + apr_pool_cleanup_null); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_close_many_wcroots(apr_hash_t *roots, + apr_pool_t *state_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, roots); hi; hi = apr_hash_next(hi)) + { + svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi); + apr_status_t result; + + result = apr_pool_cleanup_run(state_pool, wcroot, close_wcroot); + if (result != APR_SUCCESS) + return svn_error_wrap_apr(result, NULL); + } + + return SVN_NO_ERROR; +} + + +/* POOL may be NULL if the lifetime of LOCAL_ABSPATH is sufficient. */ +static const char * +compute_relpath(const svn_wc__db_wcroot_t *wcroot, + const char *local_abspath, + apr_pool_t *result_pool) +{ + const char *relpath = svn_dirent_is_child(wcroot->abspath, local_abspath, + result_pool); + if (relpath == NULL) + return ""; + return relpath; +} + + +/* Return in *LINK_TARGET_ABSPATH the absolute path the symlink at + * LOCAL_ABSPATH is pointing to. Perform all allocations in POOL. */ +static svn_error_t * +read_link_target(const char **link_target_abspath, + const char *local_abspath, + apr_pool_t *pool) +{ + svn_string_t *link_target; + const char *canon_link_target; + + SVN_ERR(svn_io_read_link(&link_target, local_abspath, pool)); + if (link_target->len == 0) + return svn_error_createf(SVN_ERR_WC_NOT_SYMLINK, NULL, + _("The symlink at '%s' points nowhere"), + svn_dirent_local_style(local_abspath, pool)); + + canon_link_target = svn_dirent_canonicalize(link_target->data, pool); + + /* Treat relative symlinks as relative to LOCAL_ABSPATH's parent. */ + if (!svn_dirent_is_absolute(canon_link_target)) + canon_link_target = svn_dirent_join(svn_dirent_dirname(local_abspath, + pool), + canon_link_target, pool); + + /* Collapse any .. in the symlink part of the path. */ + if (svn_path_is_backpath_present(canon_link_target)) + SVN_ERR(svn_dirent_get_absolute(link_target_abspath, canon_link_target, + pool)); + else + *link_target_abspath = canon_link_target; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__db_wcroot_parse_local_abspath(svn_wc__db_wcroot_t **wcroot, + const char **local_relpath, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *local_dir_abspath; + const char *original_abspath = local_abspath; + svn_node_kind_t kind; + const char *build_relpath; + svn_wc__db_wcroot_t *probe_wcroot; + svn_wc__db_wcroot_t *found_wcroot = NULL; + const char *scan_abspath; + svn_sqlite__db_t *sdb = NULL; + svn_boolean_t moved_upwards = FALSE; + svn_boolean_t always_check = FALSE; + int wc_format = 0; + const char *adm_relpath; + + /* ### we need more logic for finding the database (if it is located + ### outside of the wcroot) and then managing all of that within DB. + ### for now: play quick & dirty. */ + + probe_wcroot = svn_hash_gets(db->dir_data, local_abspath); + if (probe_wcroot != NULL) + { + *wcroot = probe_wcroot; + + /* We got lucky. Just return the thing BEFORE performing any I/O. */ + /* ### validate SMODE against how we opened wcroot->sdb? and against + ### DB->mode? (will we record per-dir mode?) */ + + /* ### for most callers, we could pass NULL for result_pool. */ + *local_relpath = compute_relpath(probe_wcroot, local_abspath, + result_pool); + + return SVN_NO_ERROR; + } + + /* ### at some point in the future, we may need to find a way to get + ### rid of this stat() call. it is going to happen for EVERY call + ### into wc_db which references a file. calls for directories could + ### get an early-exit in the hash lookup just above. */ + SVN_ERR(get_path_kind(&kind, db, local_abspath, scratch_pool)); + if (kind != svn_node_dir) + { + /* If the node specified by the path is NOT present, then it cannot + possibly be a directory containing ".svn/wc.db". + + If it is a file, then it cannot contain ".svn/wc.db". + + For both of these cases, strip the basename off of the path and + move up one level. Keep record of what we strip, though, since + we'll need it later to construct local_relpath. */ + svn_dirent_split(&local_dir_abspath, &build_relpath, local_abspath, + scratch_pool); + + /* Is this directory in our hash? */ + probe_wcroot = svn_hash_gets(db->dir_data, local_dir_abspath); + if (probe_wcroot != NULL) + { + const char *dir_relpath; + + *wcroot = probe_wcroot; + + /* Stashed directory's local_relpath + basename. */ + dir_relpath = compute_relpath(probe_wcroot, local_dir_abspath, + NULL); + *local_relpath = svn_relpath_join(dir_relpath, + build_relpath, + result_pool); + return SVN_NO_ERROR; + } + + /* If the requested path is not on the disk, then we don't know how + many ancestors need to be scanned until we start hitting content + on the disk. Set ALWAYS_CHECK to keep looking for .svn/entries + rather than bailing out after the first check. */ + if (kind == svn_node_none) + always_check = TRUE; + + /* Start the scanning at LOCAL_DIR_ABSPATH. */ + local_abspath = local_dir_abspath; + } + else + { + /* Start the local_relpath empty. If *this* directory contains the + wc.db, then relpath will be the empty string. */ + build_relpath = ""; + + /* Remember the dir containing LOCAL_ABSPATH (they're the same). */ + local_dir_abspath = local_abspath; + } + + /* LOCAL_ABSPATH refers to a directory at this point. At this point, + we've determined that an associated WCROOT is NOT in the DB's hash + table for this directory. Let's find an existing one in the ancestors, + or create one when we find the actual wcroot. */ + + /* Assume that LOCAL_ABSPATH is a directory, and look for the SQLite + database in the right place. If we find it... great! If not, then + peel off some components, and try again. */ + + adm_relpath = svn_wc_get_adm_dir(scratch_pool); + while (TRUE) + { + svn_error_t *err; + svn_node_kind_t adm_subdir_kind; + + const char *adm_subdir = svn_dirent_join(local_abspath, adm_relpath, + scratch_pool); + + SVN_ERR(svn_io_check_path(adm_subdir, &adm_subdir_kind, scratch_pool)); + + if (adm_subdir_kind == svn_node_dir) + { + /* We always open the database in read/write mode. If the database + isn't writable in the filesystem, SQLite will internally open + it as read-only, and we'll get an error if we try to do a write + operation. + + We could decide what to do on a per-operation basis, but since + we're caching database handles, it make sense to be as permissive + as the filesystem allows. */ + err = svn_wc__db_util_open_db(&sdb, local_abspath, SDB_FILE, + svn_sqlite__mode_readwrite, + db->exclusive, NULL, + db->state_pool, scratch_pool); + if (err == NULL) + { +#ifdef SVN_DEBUG + /* Install self-verification trigger statements. */ + err = svn_sqlite__exec_statements(sdb, + STMT_VERIFICATION_TRIGGERS); + if (err && err->apr_err == SVN_ERR_SQLITE_ERROR) + { + /* Verification triggers can fail to install on old 1.7-dev + * formats which didn't have a NODES table yet. Ignore sqlite + * errors so such working copies can be upgraded. */ + svn_error_clear(err); + } + else + SVN_ERR(err); +#endif + break; + } + if (err->apr_err != SVN_ERR_SQLITE_ERROR + && !APR_STATUS_IS_ENOENT(err->apr_err)) + return svn_error_trace(err); + svn_error_clear(err); + + /* If we have not moved upwards, then check for a wc-1 working copy. + Since wc-1 has a .svn in every directory, and we didn't find one + in the original directory, then we aren't looking at a wc-1. + + If the original path is not present, then we have to check on every + iteration. The content may be the immediate parent, or possibly + five ancetors higher. We don't test for directory presence (just + for the presence of subdirs/files), so we don't know when we can + stop checking ... so just check always. */ + if (!moved_upwards || always_check) + { + SVN_ERR(get_old_version(&wc_format, local_abspath, + scratch_pool)); + if (wc_format != 0) + break; + } + } + + /* We couldn't open the SDB within the specified directory, so + move up one more directory. */ + if (svn_dirent_is_root(local_abspath, strlen(local_abspath))) + { + /* Hit the root without finding a wcroot. */ + + /* The wcroot could be a symlink to a directory. + * (Issue #2557, #3987). If so, try again, this time scanning + * for a db within the directory the symlink points to, + * rather than within the symlink's parent directory. */ + if (kind == svn_node_symlink) + { + svn_node_kind_t resolved_kind; + + local_abspath = original_abspath; + + SVN_ERR(svn_io_check_resolved_path(local_abspath, + &resolved_kind, + scratch_pool)); + if (resolved_kind == svn_node_dir) + { + /* Is this directory recorded in our hash? */ + found_wcroot = svn_hash_gets(db->dir_data, local_abspath); + if (found_wcroot) + break; + + SVN_ERR(read_link_target(&local_abspath, local_abspath, + scratch_pool)); +try_symlink_as_dir: + kind = svn_node_dir; + moved_upwards = FALSE; + local_dir_abspath = local_abspath; + build_relpath = ""; + + continue; + } + } + + return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, + _("'%s' is not a working copy"), + svn_dirent_local_style(original_abspath, + scratch_pool)); + } + + local_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + moved_upwards = TRUE; + + /* Is the parent directory recorded in our hash? */ + found_wcroot = svn_hash_gets(db->dir_data, local_abspath); + if (found_wcroot != NULL) + break; + } + + if (found_wcroot != NULL) + { + /* We found a hash table entry for an ancestor, so we stopped scanning + since all subdirectories use the same WCROOT. */ + *wcroot = found_wcroot; + } + else if (wc_format == 0) + { + /* We finally found the database. Construct a wcroot_t for it. */ + + apr_int64_t wc_id; + svn_error_t *err; + + err = svn_wc__db_util_fetch_wc_id(&wc_id, sdb, scratch_pool); + if (err) + { + if (err->apr_err == SVN_ERR_WC_CORRUPT) + return svn_error_quick_wrap( + err, apr_psprintf(scratch_pool, + _("Missing a row in WCROOT for '%s'."), + svn_dirent_local_style(original_abspath, + scratch_pool))); + return svn_error_trace(err); + } + + /* WCROOT.local_abspath may be NULL when the database is stored + inside the wcroot, but we know the abspath is this directory + (ie. where we found it). */ + + err = svn_wc__db_pdh_create_wcroot(wcroot, + apr_pstrdup(db->state_pool, local_abspath), + sdb, wc_id, FORMAT_FROM_SDB, + db->verify_format, db->enforce_empty_wq, + db->state_pool, scratch_pool); + if (err && (err->apr_err == SVN_ERR_WC_UNSUPPORTED_FORMAT || + err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) && + kind == svn_node_symlink) + { + /* We found an unsupported WC after traversing upwards from a + * symlink. Fall through to code below to check if the symlink + * points at a supported WC. */ + svn_error_clear(err); + *wcroot = NULL; + } + else + SVN_ERR(err); + } + else + { + /* We found something that looks like a wc-1 working copy directory. + However, if the format version is 12 and the .svn/entries file + is only 3 bytes long, then it's a breadcrumb in a wc-ng working + copy that's missing an .svn/wc.db, or its .svn/wc.db is corrupt. */ + if (wc_format == SVN_WC__WC_NG_VERSION /* 12 */) + { + apr_finfo_t info; + + /* Check attributes of .svn/entries */ + const char *admin_abspath = svn_wc__adm_child( + local_abspath, SVN_WC__ADM_ENTRIES, scratch_pool); + svn_error_t *err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE, + scratch_pool); + + /* If the former does not succeed, something is seriously wrong. */ + if (err) + return svn_error_createf( + SVN_ERR_WC_CORRUPT, err, + _("The working copy at '%s' is corrupt."), + svn_dirent_local_style(local_abspath, scratch_pool)); + svn_error_clear(err); + + if (3 == info.size) + { + /* Check existence of .svn/wc.db */ + admin_abspath = svn_wc__adm_child(local_abspath, SDB_FILE, + scratch_pool); + err = svn_io_stat(&info, admin_abspath, APR_FINFO_SIZE, + scratch_pool); + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_clear(err); + return svn_error_createf( + SVN_ERR_WC_CORRUPT, NULL, + _("The working copy database at '%s' is missing."), + svn_dirent_local_style(local_abspath, scratch_pool)); + } + else + /* We should never have reached this point in the code + if .svn/wc.db exists; therefore it's best to assume + it's corrupt. */ + return svn_error_createf( + SVN_ERR_WC_CORRUPT, err, + _("The working copy database at '%s' is corrupt."), + svn_dirent_local_style(local_abspath, scratch_pool)); + } + } + + SVN_ERR(svn_wc__db_pdh_create_wcroot(wcroot, + apr_pstrdup(db->state_pool, local_abspath), + NULL, UNKNOWN_WC_ID, wc_format, + db->verify_format, db->enforce_empty_wq, + db->state_pool, scratch_pool)); + } + + if (*wcroot) + { + const char *dir_relpath; + + /* The subdirectory's relpath is easily computed relative to the + wcroot that we just found. */ + dir_relpath = compute_relpath(*wcroot, local_dir_abspath, NULL); + + /* And the result local_relpath may include a filename. */ + *local_relpath = svn_relpath_join(dir_relpath, build_relpath, result_pool); + } + + if (kind == svn_node_symlink) + { + svn_boolean_t retry_if_dir = FALSE; + svn_wc__db_status_t status; + svn_boolean_t conflicted; + svn_error_t *err; + + /* Check if the symlink is versioned or obstructs a versioned node + * in this DB -- in that case, use this wcroot. Else, if the symlink + * points to a directory, try to find a wcroot in that directory + * instead. */ + + if (*wcroot) + { + err = svn_wc__db_read_info_internal(&status, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, &conflicted, + NULL, NULL, NULL, NULL, NULL, + NULL, *wcroot, *local_relpath, + scratch_pool, scratch_pool); + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND + && !SVN_WC__ERR_IS_NOT_CURRENT_WC(err)) + return svn_error_trace(err); + + svn_error_clear(err); + retry_if_dir = TRUE; /* The symlink is unversioned. */ + } + else + { + /* The symlink is versioned, or obstructs a versioned node. + * Ignore non-conflicted not-present/excluded nodes. + * This allows the symlink to redirect the wcroot query to a + * directory, regardless of 'invisible' nodes in this WC. */ + retry_if_dir = ((status == svn_wc__db_status_not_present || + status == svn_wc__db_status_excluded || + status == svn_wc__db_status_server_excluded) + && !conflicted); + } + } + else + retry_if_dir = TRUE; + + if (retry_if_dir) + { + svn_node_kind_t resolved_kind; + + SVN_ERR(svn_io_check_resolved_path(original_abspath, + &resolved_kind, + scratch_pool)); + if (resolved_kind == svn_node_dir) + { + SVN_ERR(read_link_target(&local_abspath, original_abspath, + scratch_pool)); + /* This handle was opened in this function but is not going + to be used further so close it. */ + if (sdb) + SVN_ERR(svn_sqlite__close(sdb)); + goto try_symlink_as_dir; + } + } + } + + /* We've found the appropriate WCROOT for the requested path. Stash + it into that path's directory. */ + svn_hash_sets(db->dir_data, + apr_pstrdup(db->state_pool, local_dir_abspath), + *wcroot); + + /* Did we traverse up to parent directories? */ + if (!moved_upwards) + { + /* We did NOT move to a parent of the original requested directory. + We've constructed and filled in a WCROOT for the request, so we + are done. */ + return SVN_NO_ERROR; + } + + /* The WCROOT that we just found/built was for the LOCAL_ABSPATH originally + passed into this function. We stepped *at least* one directory above that. + We should now associate the WROOT for each parent directory that does + not (yet) have one. */ + + scan_abspath = local_dir_abspath; + + do + { + const char *parent_dir = svn_dirent_dirname(scan_abspath, scratch_pool); + svn_wc__db_wcroot_t *parent_wcroot; + + parent_wcroot = svn_hash_gets(db->dir_data, parent_dir); + if (parent_wcroot == NULL) + { + svn_hash_sets(db->dir_data, apr_pstrdup(db->state_pool, parent_dir), + *wcroot); + } + + /* Move up a directory, stopping when we reach the directory where + we found/built the WCROOT. */ + scan_abspath = parent_dir; + } + while (strcmp(scan_abspath, local_abspath) != 0); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__db_drop_root(svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *root_wcroot = svn_hash_gets(db->dir_data, local_abspath); + apr_hash_index_t *hi; + apr_status_t result; + + if (!root_wcroot) + return SVN_NO_ERROR; + + if (strcmp(root_wcroot->abspath, local_abspath) != 0) + return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, + _("'%s' is not a working copy root"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + + for (hi = apr_hash_first(scratch_pool, db->dir_data); + hi; + hi = apr_hash_next(hi)) + { + svn_wc__db_wcroot_t *wcroot = svn__apr_hash_index_val(hi); + + if (wcroot == root_wcroot) + svn_hash_sets(db->dir_data, svn__apr_hash_index_key(hi), NULL); + } + + result = apr_pool_cleanup_run(db->state_pool, root_wcroot, close_wcroot); + if (result != APR_SUCCESS) + return svn_error_wrap_apr(result, NULL); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/wcroot_anchor.c b/subversion/libsvn_wc/wcroot_anchor.c new file mode 100644 index 000000000000..913a61b50ad7 --- /dev/null +++ b/subversion/libsvn_wc/wcroot_anchor.c @@ -0,0 +1,227 @@ +/* + * wcroot_anchor.c : wcroot and anchor functions + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <stdlib.h> +#include <string.h> + +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_string.h" +#include "svn_dirent_uri.h" +#include "svn_error.h" +#include "svn_io.h" +#include "svn_private_config.h" + +#include "wc.h" + +#include "private/svn_wc_private.h" + +/* ABOUT ANCHOR AND TARGET, AND svn_wc_get_actual_target2() + + THE GOAL + + Note the following actions, where X is the thing we wish to update, + P is a directory whose repository URL is the parent of + X's repository URL, N is directory whose repository URL is *not* + the parent directory of X (including the case where N is not a + versioned resource at all): + + 1. `svn up .' from inside X. + 2. `svn up ...P/X' from anywhere. + 3. `svn up ...N/X' from anywhere. + + For the purposes of the discussion, in the '...N/X' situation, X is + said to be a "working copy (WC) root" directory. + + Now consider the four cases for X's type (file/dir) in the working + copy vs. the repository: + + A. dir in working copy, dir in repos. + B. dir in working copy, file in repos. + C. file in working copy, dir in repos. + D. file in working copy, file in repos. + + Here are the results we expect for each combination of the above: + + 1A. Successfully update X. + 1B. Error (you don't want to remove your current working + directory out from underneath the application). + 1C. N/A (you can't be "inside X" if X is a file). + 1D. N/A (you can't be "inside X" if X is a file). + + 2A. Successfully update X. + 2B. Successfully update X. + 2C. Successfully update X. + 2D. Successfully update X. + + 3A. Successfully update X. + 3B. Error (you can't create a versioned file X inside a + non-versioned directory). + 3C. N/A (you can't have a versioned file X in directory that is + not its repository parent). + 3D. N/A (you can't have a versioned file X in directory that is + not its repository parent). + + To summarize, case 2 always succeeds, and cases 1 and 3 always fail + (or can't occur) *except* when the target is a dir that remains a + dir after the update. + + ACCOMPLISHING THE GOAL + + Updates are accomplished by driving an editor, and an editor is + "rooted" on a directory. So, in order to update a file, we need to + break off the basename of the file, rooting the editor in that + file's parent directory, and then updating only that file, not the + other stuff in its parent directory. + + Secondly, we look at the case where we wish to update a directory. + This is typically trivial. However, one problematic case, exists + when we wish to update a directory that has been removed from the + repository and replaced with a file of the same name. If we root + our edit at the initial directory, there is no editor mechanism for + deleting that directory and replacing it with a file (this would be + like having an editor now anchored on a file, which is disallowed). + + All that remains is to have a function with the knowledge required + to properly decide where to root our editor, and what to act upon + with that now-rooted editor. Given a path to be updated, this + function should conditionally split that path into an "anchor" and + a "target", where the "anchor" is the directory at which the update + editor is rooted (meaning, editor->open_root() is called with + this directory in mind), and the "target" is the actual intended + subject of the update. + + svn_wc_get_actual_target2() is that function. + + So, what are the conditions? + + Case I: Any time X is '.' (implying it is a directory), we won't + lop off a basename. So we'll root our editor at X, and update all + of X. + + Cases II & III: Any time we are trying to update some path ...N/X, + we again will not lop off a basename. We can't root an editor at + ...N with X as a target, either because ...N isn't a versioned + resource at all (Case II) or because X is X is not a child of ...N + in the repository (Case III). We root at X, and update X. + + Cases IV-???: We lop off a basename when we are updating a + path ...P/X, rooting our editor at ...P and updating X, or when X + is missing from disk. + + These conditions apply whether X is a file or directory. + + --- + + As it turns out, commits need to have a similar check in place, + too, specifically for the case where a single directory is being + committed (we have to anchor at that directory's parent in case the + directory itself needs to be modified). +*/ + + +svn_error_t * +svn_wc_check_root(svn_boolean_t *is_wcroot, + svn_boolean_t *is_switched, + svn_node_kind_t *kind, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + return svn_error_trace(svn_wc__db_is_switched(is_wcroot,is_switched, kind, + wc_ctx->db, local_abspath, + scratch_pool)); +} + +svn_error_t * +svn_wc__is_wcroot(svn_boolean_t *is_wcroot, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + return svn_error_trace(svn_wc__db_is_wcroot(is_wcroot, + wc_ctx->db, + local_abspath, + scratch_pool)); +} + + +svn_error_t * +svn_wc__get_wcroot(const char **wcroot_abspath, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + return svn_wc__db_get_wcroot(wcroot_abspath, wc_ctx->db, + local_abspath, result_pool, scratch_pool); +} + + +svn_error_t * +svn_wc_get_actual_target2(const char **anchor, + const char **target, + svn_wc_context_t *wc_ctx, + const char *path, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_boolean_t is_wc_root, is_switched; + svn_node_kind_t kind; + const char *local_abspath; + svn_error_t *err; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool)); + + err = svn_wc__db_is_switched(&is_wc_root, &is_switched, &kind, + wc_ctx->db, local_abspath, + scratch_pool); + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND && + err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) + return svn_error_trace(err); + + svn_error_clear(err); + is_wc_root = FALSE; + is_switched = FALSE; + } + + /* If PATH is not a WC root, or if it is a file, lop off a basename. */ + if (!(is_wc_root || is_switched) || (kind != svn_node_dir)) + { + svn_dirent_split(anchor, target, path, result_pool); + } + else + { + *anchor = apr_pstrdup(result_pool, path); + *target = ""; + } + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/workqueue.c b/subversion/libsvn_wc/workqueue.c new file mode 100644 index 000000000000..ddbac150dcf9 --- /dev/null +++ b/subversion/libsvn_wc/workqueue.c @@ -0,0 +1,1666 @@ +/* + * workqueue.c : manipulating work queue items + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <apr_pools.h> + +#include "svn_types.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_subst.h" +#include "svn_hash.h" +#include "svn_io.h" + +#include "wc.h" +#include "wc_db.h" +#include "workqueue.h" +#include "adm_files.h" +#include "conflicts.h" +#include "translate.h" + +#include "svn_private_config.h" +#include "private/svn_skel.h" + + +/* Workqueue operation names. */ +#define OP_FILE_COMMIT "file-commit" +#define OP_FILE_INSTALL "file-install" +#define OP_FILE_REMOVE "file-remove" +#define OP_FILE_MOVE "file-move" +#define OP_FILE_COPY_TRANSLATED "file-translate" +#define OP_SYNC_FILE_FLAGS "sync-file-flags" +#define OP_PREJ_INSTALL "prej-install" +#define OP_DIRECTORY_REMOVE "dir-remove" +#define OP_DIRECTORY_INSTALL "dir-install" + +#define OP_POSTUPGRADE "postupgrade" + +/* Legacy items */ +#define OP_BASE_REMOVE "base-remove" +#define OP_RECORD_FILEINFO "record-fileinfo" +#define OP_TMP_SET_TEXT_CONFLICT_MARKERS "tmp-set-text-conflict-markers" +#define OP_TMP_SET_PROPERTY_CONFLICT_MARKER "tmp-set-property-conflict-marker" + +/* For work queue debugging. Generates output about its operation. */ +/* #define SVN_DEBUG_WORK_QUEUE */ + +typedef struct work_item_baton_t work_item_baton_t; + +struct work_item_dispatch { + const char *name; + svn_error_t *(*func)(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); +}; + +/* Forward definition */ +static svn_error_t * +get_and_record_fileinfo(work_item_baton_t *wqb, + const char *local_abspath, + svn_boolean_t ignore_enoent, + apr_pool_t *scratch_pool); + +/* ------------------------------------------------------------------------ */ +/* OP_REMOVE_BASE */ + +/* Removes a BASE_NODE and all it's data, leaving any adds and copies as is. + Do this as a depth first traversal to make sure than any parent still exists + on error conditions. + */ + +/* Process the OP_REMOVE_BASE work item WORK_ITEM. + * See svn_wc__wq_build_remove_base() which generates this work item. + * Implements (struct work_item_dispatch).func. */ +static svn_error_t * +run_base_remove(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg1 = work_item->children->next; + const char *local_relpath; + const char *local_abspath; + svn_revnum_t not_present_rev = SVN_INVALID_REVNUM; + apr_int64_t val; + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); + SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + SVN_ERR(svn_skel__parse_int(&val, arg1->next, scratch_pool)); + + if (arg1->next->next) + { + not_present_rev = (svn_revnum_t)val; + + SVN_ERR(svn_skel__parse_int(&val, arg1->next->next, scratch_pool)); + } + else + { + svn_boolean_t keep_not_present; + + SVN_ERR_ASSERT(SVN_WC__VERSION <= 28); /* Case unused in later versions*/ + + keep_not_present = (val != 0); + + if (keep_not_present) + { + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, + ¬_present_rev, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + } + } + + SVN_ERR(svn_wc__db_base_remove(db, local_abspath, + FALSE /* keep_as_working */, + TRUE /* queue_deletes */, + not_present_rev, + NULL, NULL, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +/* ------------------------------------------------------------------------ */ + +/* OP_FILE_COMMIT */ + + +/* FILE_ABSPATH is the new text base of the newly-committed versioned file, + * in repository-normal form (aka "detranslated" form). Adjust the working + * file accordingly. + * + * If eol and/or keyword translation would cause the working file to + * change, then overwrite the working file with a translated copy of + * the new text base (but only if the translated copy differs from the + * current working file -- if they are the same, do nothing, to avoid + * clobbering timestamps unnecessarily). + * + * Set the working file's executability according to its svn:executable + * property. + * + * Set the working file's read-only attribute according to its properties + * and lock status (see svn_wc__maybe_set_read_only()). + * + * If the working file was re-translated or had its executability or + * read-only state changed, + * then set OVERWROTE_WORKING to TRUE. If the working file isn't + * touched at all, then set to FALSE. + * + * Use SCRATCH_POOL for any temporary allocation. + */ +static svn_error_t * +install_committed_file(svn_boolean_t *overwrote_working, + svn_wc__db_t *db, + const char *file_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_boolean_t same; + const char *tmp_wfile; + svn_boolean_t special; + + /* start off assuming that the working file isn't touched. */ + *overwrote_working = FALSE; + + /* In the commit, newlines and keywords may have been + * canonicalized and/or contracted... Or they may not have + * been. It's kind of hard to know. Here's how we find out: + * + * 1. Make a translated tmp copy of the committed text base, + * translated according to the versioned file's properties. + * Or, if no committed text base exists (the commit must have + * been a propchange only), make a translated tmp copy of the + * working file. + * 2. Compare the translated tmpfile to the working file. + * 3. If different, copy the tmpfile over working file. + * + * This means we only rewrite the working file if we absolutely + * have to, which is good because it avoids changing the file's + * timestamp unless necessary, so editors aren't tempted to + * reread the file if they don't really need to. + */ + + /* Copy and translate the new base-to-be file (if found, else the working + * file) from repository-normal form to working form, writing a new + * temporary file if any translation was actually done. Set TMP_WFILE to + * the translated file's path, which may be the source file's path if no + * translation was done. Set SAME to indicate whether the new working + * text is the same as the old working text (or TRUE if it's a special + * file). */ + { + const char *tmp = file_abspath; + + /* Copy and translate, if necessary. The output file will be deleted at + * scratch_pool cleanup. + * ### That's not quite safe: we might rename the file and then maybe + * its path will get re-used for another temp file before pool clean-up. + * Instead, we should take responsibility for deleting it. */ + SVN_ERR(svn_wc__internal_translated_file(&tmp_wfile, tmp, db, + file_abspath, + SVN_WC_TRANSLATE_FROM_NF, + cancel_func, cancel_baton, + scratch_pool, scratch_pool)); + + /* If the translation is a no-op, the text base and the working copy + * file contain the same content, because we use the same props here + * as were used to detranslate from working file to text base. + * + * In that case: don't replace the working file, but make sure + * it has the right executable and read_write attributes set. + */ + + SVN_ERR(svn_wc__get_translate_info(NULL, NULL, + NULL, + &special, + db, file_abspath, NULL, FALSE, + scratch_pool, scratch_pool)); + /* Translated file returns the exact pointer if not translated. */ + if (! special && tmp != tmp_wfile) + SVN_ERR(svn_io_files_contents_same_p(&same, tmp_wfile, + file_abspath, scratch_pool)); + else + same = TRUE; + } + + if (! same) + { + SVN_ERR(svn_io_file_rename(tmp_wfile, file_abspath, scratch_pool)); + *overwrote_working = TRUE; + } + + /* ### should be using OP_SYNC_FILE_FLAGS, or an internal version of + ### that here. do we need to set *OVERWROTE_WORKING? */ + + /* ### Re: OVERWROTE_WORKING, the following function is rather liberal + ### with setting that flag, so we should probably decide if we really + ### care about it when syncing flags. */ + SVN_ERR(svn_wc__sync_flags_with_props(overwrote_working, db, file_abspath, + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +process_commit_file_install(svn_wc__db_t *db, + const char *local_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + svn_boolean_t overwrote_working; + + /* Install the new file, which may involve expanding keywords. + A copy of this file should have been dropped into our `tmp/text-base' + directory during the commit process. Part of this process + involves recording the textual timestamp for this entry. We'd like + to just use the timestamp of the working file, but it is possible + that at some point during the commit, the real working file might + have changed again. + */ + + SVN_ERR(install_committed_file(&overwrote_working, db, + local_abspath, + cancel_func, cancel_baton, + scratch_pool)); + + /* We will compute and modify the size and timestamp */ + if (overwrote_working) + { + apr_finfo_t finfo; + + SVN_ERR(svn_io_stat(&finfo, local_abspath, + APR_FINFO_MIN | APR_FINFO_LINK, scratch_pool)); + SVN_ERR(svn_wc__db_global_record_fileinfo(db, local_abspath, + finfo.size, finfo.mtime, + scratch_pool)); + } + else + { + svn_boolean_t modified; + + /* The working copy file hasn't been overwritten. We just + removed the recorded size and modification time from the nodes + record by calling svn_wc__db_global_commit(). + + Now we have some file in our working copy that might be what + we just committed, but we are not certain at this point. + + We still have a write lock here, so we check if the file is + what we expect it to be and if it is the right file we update + the recorded information. (If it isn't we keep the null data). + + Instead of reimplementing all this here, we just call a function + that already does implement this when it notices that we have the + right kind of lock (and we ignore the result) + */ + SVN_ERR(svn_wc__internal_file_modified_p(&modified, + db, local_abspath, FALSE, + scratch_pool)); + } + return SVN_NO_ERROR; +} + + +static svn_error_t * +run_file_commit(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg1 = work_item->children->next; + const char *local_relpath; + const char *local_abspath; + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); + SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + /* We don't both parsing the other two values in the skel. */ + + return svn_error_trace( + process_commit_file_install(db, local_abspath, + cancel_func, cancel_baton, + scratch_pool)); +} + +svn_error_t * +svn_wc__wq_build_file_commit(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t props_mod, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + *work_item = svn_skel__make_empty_list(result_pool); + + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, + local_abspath, result_pool, scratch_pool)); + + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + + svn_skel__prepend_str(OP_FILE_COMMIT, *work_item, result_pool); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ +/* OP_POSTUPGRADE */ + +static svn_error_t * +run_postupgrade(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const char *entries_path; + const char *format_path; + const char *wcroot_abspath; + const char *adm_path; + const char *temp_path; + svn_error_t *err; + + err = svn_wc__wipe_postupgrade(wri_abspath, FALSE, + cancel_func, cancel_baton, scratch_pool); + if (err && err->apr_err == SVN_ERR_ENTRY_NOT_FOUND) + /* No entry, this can happen when the wq item is rerun. */ + svn_error_clear(err); + else + SVN_ERR(err); + + SVN_ERR(svn_wc__db_get_wcroot(&wcroot_abspath, db, wri_abspath, + scratch_pool, scratch_pool)); + + adm_path = svn_wc__adm_child(wcroot_abspath, NULL, scratch_pool); + entries_path = svn_wc__adm_child(wcroot_abspath, SVN_WC__ADM_ENTRIES, + scratch_pool); + format_path = svn_wc__adm_child(wcroot_abspath, SVN_WC__ADM_FORMAT, + scratch_pool); + + /* Write the 'format' and 'entries' files. + + ### The order may matter for some sufficiently old clients.. but + ### this code only runs during upgrade after the files had been + ### removed earlier during the upgrade. */ + SVN_ERR(svn_io_write_unique(&temp_path, adm_path, SVN_WC__NON_ENTRIES_STRING, + sizeof(SVN_WC__NON_ENTRIES_STRING) - 1, + svn_io_file_del_none, scratch_pool)); + SVN_ERR(svn_io_file_rename(temp_path, format_path, scratch_pool)); + + SVN_ERR(svn_io_write_unique(&temp_path, adm_path, SVN_WC__NON_ENTRIES_STRING, + sizeof(SVN_WC__NON_ENTRIES_STRING) - 1, + svn_io_file_del_none, scratch_pool)); + SVN_ERR(svn_io_file_rename(temp_path, entries_path, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__wq_build_postupgrade(svn_skel_t **work_item, + apr_pool_t *result_pool) +{ + *work_item = svn_skel__make_empty_list(result_pool); + + svn_skel__prepend_str(OP_POSTUPGRADE, *work_item, result_pool); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +/* OP_FILE_INSTALL */ + +/* Process the OP_FILE_INSTALL work item WORK_ITEM. + * See svn_wc__wq_build_file_install() which generates this work item. + * Implements (struct work_item_dispatch).func. */ +static svn_error_t * +run_file_install(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg1 = work_item->children->next; + const svn_skel_t *arg4 = arg1->next->next->next; + const char *local_relpath; + const char *local_abspath; + svn_boolean_t use_commit_times; + svn_boolean_t record_fileinfo; + svn_boolean_t special; + svn_stream_t *src_stream; + svn_subst_eol_style_t style; + const char *eol; + apr_hash_t *keywords; + const char *temp_dir_abspath; + svn_stream_t *dst_stream; + const char *dst_abspath; + apr_int64_t val; + const char *wcroot_abspath; + const char *source_abspath; + const svn_checksum_t *checksum; + apr_hash_t *props; + apr_time_t changed_date; + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); + SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + SVN_ERR(svn_skel__parse_int(&val, arg1->next, scratch_pool)); + use_commit_times = (val != 0); + SVN_ERR(svn_skel__parse_int(&val, arg1->next->next, scratch_pool)); + record_fileinfo = (val != 0); + + SVN_ERR(svn_wc__db_read_node_install_info(&wcroot_abspath, + &checksum, &props, + &changed_date, + db, local_abspath, wri_abspath, + scratch_pool, scratch_pool)); + + if (arg4 != NULL) + { + /* Use the provided path for the source. */ + local_relpath = apr_pstrmemdup(scratch_pool, arg4->data, arg4->len); + SVN_ERR(svn_wc__db_from_relpath(&source_abspath, db, wri_abspath, + local_relpath, + scratch_pool, scratch_pool)); + } + else if (! checksum) + { + /* This error replaces a previous assertion. Reporting an error from here + leaves the workingqueue operation in place, so the working copy is + still broken! + + But when we report this error the user at least knows what node has + this specific problem, so maybe we can find out why users see this + error */ + return svn_error_createf(SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL, + _("Can't install '%s' from pristine store, " + "because no checksum is recorded for this " + "file"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + else + { + SVN_ERR(svn_wc__db_pristine_get_future_path(&source_abspath, + wcroot_abspath, + checksum, + scratch_pool, scratch_pool)); + } + + SVN_ERR(svn_stream_open_readonly(&src_stream, source_abspath, + scratch_pool, scratch_pool)); + + /* Fetch all the translation bits. */ + SVN_ERR(svn_wc__get_translate_info(&style, &eol, + &keywords, + &special, db, local_abspath, + props, FALSE, + scratch_pool, scratch_pool)); + if (special) + { + /* When this stream is closed, the resulting special file will + atomically be created/moved into place at LOCAL_ABSPATH. */ + SVN_ERR(svn_subst_create_specialfile(&dst_stream, local_abspath, + scratch_pool, scratch_pool)); + + /* Copy the "repository normal" form of the special file into the + special stream. */ + SVN_ERR(svn_stream_copy3(src_stream, dst_stream, + cancel_func, cancel_baton, + scratch_pool)); + + /* No need to set exec or read-only flags on special files. */ + + /* ### Shouldn't this record a timestamp and size, etc.? */ + return SVN_NO_ERROR; + } + + if (svn_subst_translation_required(style, eol, keywords, + FALSE /* special */, + TRUE /* force_eol_check */)) + { + /* Wrap it in a translating (expanding) stream. */ + src_stream = svn_subst_stream_translated(src_stream, eol, + TRUE /* repair */, + keywords, + TRUE /* expand */, + scratch_pool); + } + + /* Where is the Right Place to put a temp file in this working copy? */ + SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, + db, wcroot_abspath, + scratch_pool, scratch_pool)); + + /* Translate to a temporary file. We don't want the user seeing a partial + file, nor let them muck with it while we translate. We may also need to + get its TRANSLATED_SIZE before the user can monkey it. */ + SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_abspath, + temp_dir_abspath, + svn_io_file_del_none, + scratch_pool, scratch_pool)); + + /* Copy from the source to the dest, translating as we go. This will also + close both streams. */ + SVN_ERR(svn_stream_copy3(src_stream, dst_stream, + cancel_func, cancel_baton, + scratch_pool)); + + /* All done. Move the file into place. */ + + { + svn_error_t *err; + + err = svn_io_file_rename(dst_abspath, local_abspath, scratch_pool); + + /* With a single db we might want to install files in a missing directory. + Simply trying this scenario on error won't do any harm and at least + one user reported this problem on IRC. */ + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_t *err2; + + err2 = svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath, + scratch_pool), + scratch_pool); + + if (err2) + /* Creating directory didn't work: Return all errors */ + return svn_error_trace(svn_error_compose_create(err, err2)); + else + /* We could create a directory: retry install */ + svn_error_clear(err); + + SVN_ERR(svn_io_file_rename(dst_abspath, local_abspath, scratch_pool)); + } + else + SVN_ERR(err); + } + + /* Tweak the on-disk file according to its properties. */ +#ifndef WIN32 + if (props && svn_hash_gets(props, SVN_PROP_EXECUTABLE)) + SVN_ERR(svn_io_set_file_executable(local_abspath, TRUE, FALSE, + scratch_pool)); +#endif + + /* Note that this explicitly checks the pristine properties, to make sure + that when the lock is locally set (=modification) it is not read only */ + if (props && svn_hash_gets(props, SVN_PROP_NEEDS_LOCK)) + { + svn_wc__db_status_t status; + svn_wc__db_lock_t *lock; + SVN_ERR(svn_wc__db_read_info(&status, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, &lock, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + if (!lock && status != svn_wc__db_status_added) + SVN_ERR(svn_io_set_file_read_only(local_abspath, FALSE, scratch_pool)); + } + + if (use_commit_times) + { + if (changed_date) + SVN_ERR(svn_io_set_file_affected_time(changed_date, + local_abspath, + scratch_pool)); + } + + /* ### this should happen before we rename the file into place. */ + if (record_fileinfo) + { + SVN_ERR(get_and_record_fileinfo(wqb, local_abspath, + FALSE /* ignore_enoent */, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__wq_build_file_install(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + const char *source_abspath, + svn_boolean_t use_commit_times, + svn_boolean_t record_fileinfo, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + const char *wri_abspath; + *work_item = svn_skel__make_empty_list(result_pool); + + /* Use the directory of the file to install as wri_abspath to avoid + filestats on just obtaining the wc-root */ + wri_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + /* If a SOURCE_ABSPATH was provided, then put it into the skel. If this + value is not provided, then the file's pristine contents will be used. */ + if (source_abspath != NULL) + { + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath, + source_abspath, + result_pool, scratch_pool)); + + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + } + + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath, + local_abspath, result_pool, scratch_pool)); + + svn_skel__prepend_int(record_fileinfo, *work_item, result_pool); + svn_skel__prepend_int(use_commit_times, *work_item, result_pool); + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + svn_skel__prepend_str(OP_FILE_INSTALL, *work_item, result_pool); + + return SVN_NO_ERROR; +} + + +/* ------------------------------------------------------------------------ */ + +/* OP_FILE_REMOVE */ + +/* Process the OP_FILE_REMOVE work item WORK_ITEM. + * See svn_wc__wq_build_file_remove() which generates this work item. + * Implements (struct work_item_dispatch).func. */ +static svn_error_t * +run_file_remove(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg1 = work_item->children->next; + const char *local_relpath; + const char *local_abspath; + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); + SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + /* Remove the path, no worrying if it isn't there. */ + return svn_error_trace(svn_io_remove_file2(local_abspath, TRUE, + scratch_pool)); +} + + +svn_error_t * +svn_wc__wq_build_file_remove(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + *work_item = svn_skel__make_empty_list(result_pool); + + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath, + local_abspath, result_pool, scratch_pool)); + + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + svn_skel__prepend_str(OP_FILE_REMOVE, *work_item, result_pool); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +/* OP_DIRECTORY_REMOVE */ + +/* Process the OP_FILE_REMOVE work item WORK_ITEM. + * See svn_wc__wq_build_file_remove() which generates this work item. + * Implements (struct work_item_dispatch).func. */ +static svn_error_t * +run_dir_remove(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg1 = work_item->children->next; + const char *local_relpath; + const char *local_abspath; + svn_boolean_t recursive; + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); + SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + recursive = FALSE; + if (arg1->next) + { + apr_int64_t val; + SVN_ERR(svn_skel__parse_int(&val, arg1->next, scratch_pool)); + + recursive = (val != 0); + } + + /* Remove the path, no worrying if it isn't there. */ + if (recursive) + return svn_error_trace( + svn_io_remove_dir2(local_abspath, TRUE, + cancel_func, cancel_baton, + scratch_pool)); + else + { + svn_error_t *err; + + err = svn_io_dir_remove_nonrecursive(local_abspath, scratch_pool); + + if (err && (APR_STATUS_IS_ENOENT(err->apr_err) + || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err) + || APR_STATUS_IS_ENOTEMPTY(err->apr_err))) + { + svn_error_clear(err); + err = NULL; + } + + return svn_error_trace(err); + } +} + +svn_error_t * +svn_wc__wq_build_dir_remove(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_abspath, + svn_boolean_t recursive, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + *work_item = svn_skel__make_empty_list(result_pool); + + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath, + local_abspath, result_pool, scratch_pool)); + + if (recursive) + svn_skel__prepend_int(TRUE, *work_item, result_pool); + + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + svn_skel__prepend_str(OP_DIRECTORY_REMOVE, *work_item, result_pool); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +/* OP_FILE_MOVE */ + +/* Process the OP_FILE_MOVE work item WORK_ITEM. + * See svn_wc__wq_build_file_move() which generates this work item. + * Implements (struct work_item_dispatch).func. */ +static svn_error_t * +run_file_move(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg1 = work_item->children->next; + const char *src_abspath, *dst_abspath; + const char *local_relpath; + svn_error_t *err; + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); + SVN_ERR(svn_wc__db_from_relpath(&src_abspath, db, wri_abspath, local_relpath, + scratch_pool, scratch_pool)); + local_relpath = apr_pstrmemdup(scratch_pool, arg1->next->data, + arg1->next->len); + SVN_ERR(svn_wc__db_from_relpath(&dst_abspath, db, wri_abspath, local_relpath, + scratch_pool, scratch_pool)); + + /* Use svn_io_file_move() instead of svn_io_file_rename() to allow cross + device copies. We should not fail in the workqueue. */ + + err = svn_io_file_move(src_abspath, dst_abspath, scratch_pool); + + /* If the source is not found, we assume the wq op is already handled */ + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + svn_error_clear(err); + else + SVN_ERR(err); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__wq_build_file_move(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + const char *src_abspath, + const char *dst_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t kind; + const char *local_relpath; + *work_item = svn_skel__make_empty_list(result_pool); + + SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); + + /* File must exist */ + SVN_ERR(svn_io_check_path(src_abspath, &kind, result_pool)); + + if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("'%s' not found"), + svn_dirent_local_style(src_abspath, + scratch_pool)); + + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath, dst_abspath, + result_pool, scratch_pool)); + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, wri_abspath, src_abspath, + result_pool, scratch_pool)); + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + + svn_skel__prepend_str(OP_FILE_MOVE, *work_item, result_pool); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +/* OP_FILE_COPY_TRANSLATED */ + +/* Process the OP_FILE_COPY_TRANSLATED work item WORK_ITEM. + * See run_file_copy_translated() which generates this work item. + * Implements (struct work_item_dispatch).func. */ +static svn_error_t * +run_file_copy_translated(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg1 = work_item->children->next; + const char *local_abspath, *src_abspath, *dst_abspath; + const char *local_relpath; + svn_subst_eol_style_t style; + const char *eol; + apr_hash_t *keywords; + svn_boolean_t special; + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); + SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->next->data, + arg1->next->len); + SVN_ERR(svn_wc__db_from_relpath(&src_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->next->next->data, + arg1->next->next->len); + SVN_ERR(svn_wc__db_from_relpath(&dst_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__get_translate_info(&style, &eol, + &keywords, + &special, + db, local_abspath, NULL, FALSE, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_subst_copy_and_translate4(src_abspath, dst_abspath, + eol, TRUE /* repair */, + keywords, TRUE /* expand */, + special, + cancel_func, cancel_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__wq_build_file_copy_translated(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + const char *src_abspath, + const char *dst_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_node_kind_t kind; + const char *local_relpath; + + *work_item = svn_skel__make_empty_list(result_pool); + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); + SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); + + /* File must exist */ + SVN_ERR(svn_io_check_path(src_abspath, &kind, result_pool)); + + if (kind == svn_node_none) + return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, + _("'%s' not found"), + svn_dirent_local_style(src_abspath, + scratch_pool)); + + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, dst_abspath, + result_pool, scratch_pool)); + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, src_abspath, + result_pool, scratch_pool)); + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, + local_abspath, result_pool, scratch_pool)); + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + + svn_skel__prepend_str(OP_FILE_COPY_TRANSLATED, *work_item, result_pool); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +/* OP_DIRECTORY_INSTALL */ + +static svn_error_t * +run_dir_install(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg1 = work_item->children->next; + const char *local_relpath; + const char *local_abspath; + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); + SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__ensure_directory(local_abspath, scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__wq_build_dir_install(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool, + apr_pool_t *result_pool) +{ + const char *local_relpath; + + *work_item = svn_skel__make_empty_list(result_pool); + + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, + local_abspath, result_pool, scratch_pool)); + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + + svn_skel__prepend_str(OP_DIRECTORY_INSTALL, *work_item, result_pool); + + return SVN_NO_ERROR; +} + + +/* ------------------------------------------------------------------------ */ + +/* OP_SYNC_FILE_FLAGS */ + +/* Process the OP_SYNC_FILE_FLAGS work item WORK_ITEM. + * See svn_wc__wq_build_sync_file_flags() which generates this work item. + * Implements (struct work_item_dispatch).func. */ +static svn_error_t * +run_sync_file_flags(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg1 = work_item->children->next; + const char *local_relpath; + const char *local_abspath; + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); + SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + return svn_error_trace(svn_wc__sync_flags_with_props(NULL, db, + local_abspath, scratch_pool)); +} + + +svn_error_t * +svn_wc__wq_build_sync_file_flags(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + *work_item = svn_skel__make_empty_list(result_pool); + + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, + local_abspath, result_pool, scratch_pool)); + + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + svn_skel__prepend_str(OP_SYNC_FILE_FLAGS, *work_item, result_pool); + + return SVN_NO_ERROR; +} + + +/* ------------------------------------------------------------------------ */ + +/* OP_PREJ_INSTALL */ + +static svn_error_t * +run_prej_install(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg1 = work_item->children->next; + const char *local_relpath; + const char *local_abspath; + svn_skel_t *conflicts; + const svn_skel_t *prop_conflict_skel; + const char *tmp_prejfile_abspath; + const char *prejfile_abspath; + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); + SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_wc__conflict_read_prop_conflict(&prejfile_abspath, + NULL, NULL, NULL, NULL, + db, local_abspath, conflicts, + scratch_pool, scratch_pool)); + + if (arg1->next != NULL) + prop_conflict_skel = arg1->next; + else + SVN_ERR_MALFUNCTION(); /* ### wc_db can't provide it ... yet. */ + + /* Construct a property reject file in the temporary area. */ + SVN_ERR(svn_wc__create_prejfile(&tmp_prejfile_abspath, + db, local_abspath, + prop_conflict_skel, + scratch_pool, scratch_pool)); + + /* ... and atomically move it into place. */ + SVN_ERR(svn_io_file_rename(tmp_prejfile_abspath, + prejfile_abspath, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__wq_build_prej_install(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *local_relpath; + *work_item = svn_skel__make_empty_list(result_pool); + + /* ### gotta have this, today */ + SVN_ERR_ASSERT(conflict_skel != NULL); + + SVN_ERR(svn_wc__db_to_relpath(&local_relpath, db, local_abspath, + local_abspath, result_pool, scratch_pool)); + + if (conflict_skel != NULL) + svn_skel__prepend(conflict_skel, *work_item); + svn_skel__prepend_str(local_relpath, *work_item, result_pool); + svn_skel__prepend_str(OP_PREJ_INSTALL, *work_item, result_pool); + + return SVN_NO_ERROR; +} + + +/* ------------------------------------------------------------------------ */ + +/* OP_RECORD_FILEINFO */ + + +static svn_error_t * +run_record_fileinfo(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg1 = work_item->children->next; + const char *local_relpath; + const char *local_abspath; + apr_time_t set_time = 0; + + local_relpath = apr_pstrmemdup(scratch_pool, arg1->data, arg1->len); + + SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + if (arg1->next) + { + apr_int64_t val; + + SVN_ERR(svn_skel__parse_int(&val, arg1->next, scratch_pool)); + set_time = (apr_time_t)val; + } + + if (set_time != 0) + { + svn_node_kind_t kind; + svn_boolean_t is_special; + + /* Do not set the timestamp on special files. */ + SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special, + scratch_pool)); + + /* Don't set affected time when local_abspath does not exist or is + a special file */ + if (kind == svn_node_file && !is_special) + SVN_ERR(svn_io_set_file_affected_time(set_time, local_abspath, + scratch_pool)); + + /* Note that we can't use the value we get here for recording as the + filesystem might have a different timestamp granularity */ + } + + + return svn_error_trace(get_and_record_fileinfo(wqb, local_abspath, + TRUE /* ignore_enoent */, + scratch_pool)); +} + +/* ------------------------------------------------------------------------ */ + +/* OP_TMP_SET_TEXT_CONFLICT_MARKERS */ + + +static svn_error_t * +run_set_text_conflict_markers(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg = work_item->children->next; + const char *local_relpath; + const char *local_abspath; + const char *old_abspath = NULL; + const char *new_abspath = NULL; + const char *wrk_abspath = NULL; + + local_relpath = apr_pstrmemdup(scratch_pool, arg->data, arg->len); + SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + arg = arg->next; + local_relpath = arg->len ? apr_pstrmemdup(scratch_pool, arg->data, arg->len) + : NULL; + + if (local_relpath) + { + SVN_ERR(svn_wc__db_from_relpath(&old_abspath, db, wri_abspath, + local_relpath, + scratch_pool, scratch_pool)); + } + + arg = arg->next; + local_relpath = arg->len ? apr_pstrmemdup(scratch_pool, arg->data, arg->len) + : NULL; + if (local_relpath) + { + SVN_ERR(svn_wc__db_from_relpath(&new_abspath, db, wri_abspath, + local_relpath, + scratch_pool, scratch_pool)); + } + + arg = arg->next; + local_relpath = arg->len ? apr_pstrmemdup(scratch_pool, arg->data, arg->len) + : NULL; + + if (local_relpath) + { + SVN_ERR(svn_wc__db_from_relpath(&wrk_abspath, db, wri_abspath, + local_relpath, + scratch_pool, scratch_pool)); + } + + /* Upgrade scenario: We have a workqueue item that describes how to install a + non skel conflict. Fetch all the information we can to create a new style + conflict. */ + /* ### Before format 30 this is/was a common code path as we didn't install + ### the conflict directly in the db. It just calls the wc_db code + ### to set the right fields. */ + + { + /* Check if we should combine with a property conflict... */ + svn_skel_t *conflicts; + + SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + scratch_pool, scratch_pool)); + + if (! conflicts) + { + /* No conflict exists, create a basic skel */ + conflicts = svn_wc__conflict_skel_create(scratch_pool); + + SVN_ERR(svn_wc__conflict_skel_set_op_update(conflicts, NULL, NULL, + scratch_pool, + scratch_pool)); + } + + /* Add the text conflict to the existing onflict */ + SVN_ERR(svn_wc__conflict_skel_add_text_conflict(conflicts, db, + local_abspath, + wrk_abspath, + old_abspath, + new_abspath, + scratch_pool, + scratch_pool)); + + SVN_ERR(svn_wc__db_op_mark_conflict(db, local_abspath, conflicts, + NULL, scratch_pool)); + } + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +/* OP_TMP_SET_PROPERTY_CONFLICT_MARKER */ + +static svn_error_t * +run_set_property_conflict_marker(work_item_baton_t *wqb, + svn_wc__db_t *db, + const svn_skel_t *work_item, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const svn_skel_t *arg = work_item->children->next; + const char *local_relpath; + const char *local_abspath; + const char *prej_abspath = NULL; + + local_relpath = apr_pstrmemdup(scratch_pool, arg->data, arg->len); + + SVN_ERR(svn_wc__db_from_relpath(&local_abspath, db, wri_abspath, + local_relpath, scratch_pool, scratch_pool)); + + + arg = arg->next; + local_relpath = arg->len ? apr_pstrmemdup(scratch_pool, arg->data, arg->len) + : NULL; + + if (local_relpath) + SVN_ERR(svn_wc__db_from_relpath(&prej_abspath, db, wri_abspath, + local_relpath, + scratch_pool, scratch_pool)); + + { + /* Check if we should combine with a text conflict... */ + svn_skel_t *conflicts; + apr_hash_t *prop_names; + + SVN_ERR(svn_wc__db_read_conflict(&conflicts, db, local_abspath, + scratch_pool, scratch_pool)); + + if (! conflicts) + { + /* No conflict exists, create a basic skel */ + conflicts = svn_wc__conflict_skel_create(scratch_pool); + + SVN_ERR(svn_wc__conflict_skel_set_op_update(conflicts, NULL, NULL, + scratch_pool, + scratch_pool)); + } + + prop_names = apr_hash_make(scratch_pool); + SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(conflicts, db, + local_abspath, + prej_abspath, + NULL, NULL, NULL, + prop_names, + scratch_pool, + scratch_pool)); + + SVN_ERR(svn_wc__db_op_mark_conflict(db, local_abspath, conflicts, + NULL, scratch_pool)); + } + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +static const struct work_item_dispatch dispatch_table[] = { + { OP_FILE_COMMIT, run_file_commit }, + { OP_FILE_INSTALL, run_file_install }, + { OP_FILE_REMOVE, run_file_remove }, + { OP_FILE_MOVE, run_file_move }, + { OP_FILE_COPY_TRANSLATED, run_file_copy_translated }, + { OP_SYNC_FILE_FLAGS, run_sync_file_flags }, + { OP_PREJ_INSTALL, run_prej_install }, + { OP_DIRECTORY_REMOVE, run_dir_remove }, + { OP_DIRECTORY_INSTALL, run_dir_install }, + + /* Upgrade steps */ + { OP_POSTUPGRADE, run_postupgrade }, + + /* Legacy workqueue items. No longer created */ + { OP_BASE_REMOVE, run_base_remove }, + { OP_RECORD_FILEINFO, run_record_fileinfo }, + { OP_TMP_SET_TEXT_CONFLICT_MARKERS, run_set_text_conflict_markers }, + { OP_TMP_SET_PROPERTY_CONFLICT_MARKER, run_set_property_conflict_marker }, + + /* Sentinel. */ + { NULL } +}; + +struct work_item_baton_t +{ + apr_pool_t *result_pool; /* Pool to allocate result in */ + + svn_boolean_t used; /* needs reset */ + + apr_hash_t *record_map; /* const char * -> svn_io_dirent2_t map */ +}; + + +static svn_error_t * +dispatch_work_item(work_item_baton_t *wqb, + svn_wc__db_t *db, + const char *wri_abspath, + const svn_skel_t *work_item, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + const struct work_item_dispatch *scan; + + /* Scan the dispatch table for a function to handle this work item. */ + for (scan = &dispatch_table[0]; scan->name != NULL; ++scan) + { + if (svn_skel__matches_atom(work_item->children, scan->name)) + { + +#ifdef SVN_DEBUG_WORK_QUEUE + SVN_DBG(("dispatch: operation='%s'\n", scan->name)); +#endif + SVN_ERR((*scan->func)(wqb, db, work_item, wri_abspath, + cancel_func, cancel_baton, + scratch_pool)); + +#ifdef SVN_RUN_WORK_QUEUE_TWICE +#ifdef SVN_DEBUG_WORK_QUEUE + SVN_DBG(("dispatch: operation='%s'\n", scan->name)); +#endif + /* Being able to run every workqueue item twice is one + requirement for workqueues to be restartable. */ + SVN_ERR((*scan->func)(db, work_item, wri_abspath, + cancel_func, cancel_baton, + scratch_pool)); +#endif + + break; + } + } + + if (scan->name == NULL) + { + /* We should know about ALL possible work items here. If we do not, + then something is wrong. Most likely, some kind of format/code + skew. There is nothing more we can do. Erasing or ignoring this + work item could leave the WC in an even more broken state. + + Contrary to issue #1581, we cannot simply remove work items and + continue, so bail out with an error. */ + return svn_error_createf(SVN_ERR_WC_BAD_ADM_LOG, NULL, + _("Unrecognized work item in the queue")); + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_wc__wq_run(svn_wc__db_t *db, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_uint64_t last_id = 0; + work_item_baton_t wib = { 0 }; + wib.result_pool = svn_pool_create(scratch_pool); + +#ifdef SVN_DEBUG_WORK_QUEUE + SVN_DBG(("wq_run: wri='%s'\n", wri_abspath)); + { + static int count = 0; + const char *count_env_var = getenv("SVN_DEBUG_WORK_QUEUE"); + + if (count_env_var && ++count == atoi(count_env_var)) + return svn_error_create(SVN_ERR_CANCELLED, NULL, "fake cancel"); + } +#endif + + while (TRUE) + { + apr_uint64_t id; + svn_skel_t *work_item; + svn_error_t *err; + + svn_pool_clear(iterpool); + + if (! wib.used) + { + /* Make sure to do this *early* in the loop iteration. There may + be a LAST_ID that needs to be marked as completed, *before* we + start worrying about anything else. */ + SVN_ERR(svn_wc__db_wq_fetch_next(&id, &work_item, db, wri_abspath, + last_id, iterpool, iterpool)); + } + else + { + /* Make sure to do this *early* in the loop iteration. There may + be a LAST_ID that needs to be marked as completed, *before* we + start worrying about anything else. */ + SVN_ERR(svn_wc__db_wq_record_and_fetch_next(&id, &work_item, + db, wri_abspath, + last_id, wib.record_map, + iterpool, + wib.result_pool)); + + svn_pool_clear(wib.result_pool); + wib.record_map = NULL; + wib.used = FALSE; + } + + /* Stop work queue processing, if requested. A future 'svn cleanup' + should be able to continue the processing. Note that we may + have WORK_ITEM, but we'll just skip its processing for now. */ + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + /* If we have a WORK_ITEM, then process the sucker. Otherwise, + we're done. */ + if (work_item == NULL) + break; + + err = dispatch_work_item(&wib, db, wri_abspath, work_item, + cancel_func, cancel_baton, iterpool); + if (err) + { + const char *skel = svn_skel__unparse(work_item, scratch_pool)->data; + + return svn_error_createf(SVN_ERR_WC_BAD_ADM_LOG, err, + _("Failed to run the WC DB work queue " + "associated with '%s', work item %d %s"), + svn_dirent_local_style(wri_abspath, + scratch_pool), + (int)id, skel); + } + + /* The work item finished without error. Mark it completed + in the next loop. */ + last_id = id; + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + + +svn_skel_t * +svn_wc__wq_merge(svn_skel_t *work_item1, + svn_skel_t *work_item2, + apr_pool_t *result_pool) +{ + /* If either argument is NULL, then just return the other. */ + if (work_item1 == NULL) + return work_item2; + if (work_item2 == NULL) + return work_item1; + + /* We have two items. Figure out how to join them. */ + if (SVN_WC__SINGLE_WORK_ITEM(work_item1)) + { + if (SVN_WC__SINGLE_WORK_ITEM(work_item2)) + { + /* Both are singular work items. Construct a list, then put + both work items into it (in the proper order). */ + + svn_skel_t *result = svn_skel__make_empty_list(result_pool); + + svn_skel__prepend(work_item2, result); + svn_skel__prepend(work_item1, result); + return result; + } + + /* WORK_ITEM2 is a list of work items. We can simply shove WORK_ITEM1 + in the front to keep the ordering. */ + svn_skel__prepend(work_item1, work_item2); + return work_item2; + } + /* WORK_ITEM1 is a list of work items. */ + + if (SVN_WC__SINGLE_WORK_ITEM(work_item2)) + { + /* Put WORK_ITEM2 onto the end of the WORK_ITEM1 list. */ + svn_skel__append(work_item1, work_item2); + return work_item1; + } + + /* We have two lists of work items. We need to chain all of the work + items into one big list. We will leave behind the WORK_ITEM2 skel, + as we only want its children. */ + svn_skel__append(work_item1, work_item2->children); + return work_item1; +} + + +static svn_error_t * +get_and_record_fileinfo(work_item_baton_t *wqb, + const char *local_abspath, + svn_boolean_t ignore_enoent, + apr_pool_t *scratch_pool) +{ + const svn_io_dirent2_t *dirent; + + SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, ignore_enoent, + wqb->result_pool, scratch_pool)); + + if (dirent->kind != svn_node_file) + return SVN_NO_ERROR; + + wqb->used = TRUE; + + if (! wqb->record_map) + wqb->record_map = apr_hash_make(wqb->result_pool); + + svn_hash_sets(wqb->record_map, apr_pstrdup(wqb->result_pool, local_abspath), + dirent); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_wc/workqueue.h b/subversion/libsvn_wc/workqueue.h new file mode 100644 index 000000000000..0617a60ed089 --- /dev/null +++ b/subversion/libsvn_wc/workqueue.h @@ -0,0 +1,235 @@ +/* + * workqueue.h : manipulating work queue items + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * + * Greg says: + * + * I think the current items are misdirected + * work items should NOT touch the DB + * the work items should be inserted into WORK_QUEUE by wc_db, + * meaning: workqueue.[ch] should return work items for passing to the wc_db API, + * which installs them during a transaction with the other work, + * and those items should *only* make the on-disk state match what is in the database + * before you rejoined the chan, I was discussing with Bert that I might rejigger the postcommit work, + * in order to do the prop file handling as work items, + * and pass those to db_global_commit for insertion as part of its transaction + * so that once we switch to in-db props, those work items just get deleted, + * (where they're simple things like: move this file to there, or delete that file) + * i.e. workqueue should be seriously dumb + * */ + +#ifndef SVN_WC_WORKQUEUE_H +#define SVN_WC_WORKQUEUE_H + +#include <apr_pools.h> + +#include "svn_types.h" +#include "svn_wc.h" + +#include "wc_db.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Returns TRUE if WI refers to a single work item. Returns FALSE if + WI is a list of work items. WI must not be NULL. + + A work item looks like: (OP_CODE arg1 arg2 ...) + + If we see OP_CODE (an atom) as WI's first child, then this is a + single work item. Otherwise, it is a list of work items. */ +#define SVN_WC__SINGLE_WORK_ITEM(wi) ((wi)->children->is_atom) + + +/* Combine WORK_ITEM1 and WORK_ITEM2 into a single, resulting work item. + + Each of the WORK_ITEM parameters may have one of three values: + + NULL no work item + (OPCODE arg1 arg2 ...) single work item + ((OPCODE ...) (OPCODE ...)) multiple work items + + These will be combined as appropriate, and returned in one of the + above three styles. + + The resulting list will be ordered: WORK_ITEM1 first, then WORK_ITEM2 */ +svn_skel_t * +svn_wc__wq_merge(svn_skel_t *work_item1, + svn_skel_t *work_item2, + apr_pool_t *result_pool); + + +/* For the WCROOT identified by the DB and WRI_ABSPATH pair, run any + work items that may be present in its workqueue. */ +svn_error_t * +svn_wc__wq_run(svn_wc__db_t *db, + const char *wri_abspath, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool); + + +/* Set *WORK_ITEM to a new work item that will install the working + copy file at LOCAL_ABSPATH. If USE_COMMIT_TIMES is TRUE, then the newly + installed file will use the nodes CHANGE_DATE for the file timestamp. + If RECORD_FILEINFO is TRUE, then the resulting RECORDED_TIME and + RECORDED_SIZE will be recorded in the database. + + If SOURCE_ABSPATH is NULL, then the pristine contents will be installed + (with appropriate translation). If SOURCE_ABSPATH is not NULL, then it + specifies a source file for the translation. The file must exist for as + long as *WORK_ITEM exists (and is queued). Typically, it will be a + temporary file, and an OP_FILE_REMOVE will be queued to later remove it. +*/ +svn_error_t * +svn_wc__wq_build_file_install(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + const char *source_abspath, + svn_boolean_t use_commit_times, + svn_boolean_t record_fileinfo, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Set *WORK_ITEM to a new work item that will remove a single + file LOCAL_ABSPATH from the working copy identified by the pair DB, + WRI_ABSPATH. */ +svn_error_t * +svn_wc__wq_build_file_remove(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Set *WORK_ITEM to a new work item that will remove a single + directory or if RECURSIVE is TRUE a directory with all its + descendants. */ +svn_error_t * +svn_wc__wq_build_dir_remove(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + const char *local_abspath, + svn_boolean_t recursive, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Set *WORK_ITEM to a new work item that describes a move of + a file or directory from SRC_ABSPATH to DST_ABSPATH, ready for + storing in the working copy managing DST_ABSPATH. + + Perform temporary allocations in SCRATCH_POOL and *WORK_ITEM in + RESULT_POOL. +*/ +svn_error_t * +svn_wc__wq_build_file_move(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *wri_abspath, + const char *src_abspath, + const char *dst_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Set *WORK_ITEM to a new work item that describes a copy from + SRC_ABSPATH to DST_ABSPATH, while translating the stream using + the information from LOCAL_ABSPATH. */ +svn_error_t * +svn_wc__wq_build_file_copy_translated(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + const char *src_abspath, + const char *dst_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Set *WORK_ITEM to a new work item that will synchronize the + target node's readonly and executable flags with the values defined + by its properties and lock status. */ +svn_error_t * +svn_wc__wq_build_sync_file_flags(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + +/* Set *WORK_ITEM to a new work item that will install a property reject + file for LOCAL_ABSPATH into the working copy. The property conflicts will + be taken from CONFLICT_SKEL. + + ### Caution: Links CONFLICT_SKEL into the *WORK_ITEM, which involves + modifying *CONFLICT_SKEL. + + ### TODO: Make CONFLICT_SKEL 'const' and dup it into RESULT_POOL. + + ### TODO: If CONFLICT_SKEL is NULL, take property conflicts from wc_db + for the given DB/LOCAL_ABSPATH. + */ +svn_error_t * +svn_wc__wq_build_prej_install(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + svn_skel_t *conflict_skel, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Handle the final post-commit step of retranslating and recording the + working copy state of a committed file. + + If PROP_MODS is false, assume that properties are not changed. + + (Property modifications are read when svn_wc__wq_build_file_commit + is called and processed when the working queue is being evaluated) + + Allocate *work_item in RESULT_POOL. Perform temporary allocations + in SCRATCH_POOL. + */ +svn_error_t * +svn_wc__wq_build_file_commit(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t prop_mods, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Set *WORK_ITEM to a new work item that will install the working + copy directory at LOCAL_ABSPATH. */ +svn_error_t * +svn_wc__wq_build_dir_install(svn_skel_t **work_item, + svn_wc__db_t *db, + const char *local_abspath, + apr_pool_t *scratch_pool, + apr_pool_t *result_pool); + +svn_error_t * +svn_wc__wq_build_postupgrade(svn_skel_t **work_item, + apr_pool_t *scratch_pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_WC_WORKQUEUE_H */ |