diff options
author | Peter Wemm <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
---|---|---|
committer | Peter Wemm <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
commit | 32547653cc5376642e1231fb644db99933ac8db4 (patch) | |
tree | 135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_wc | |
download | src-32547653cc5376642e1231fb644db99933ac8db4.tar.gz src-32547653cc5376642e1231fb644db99933ac8db4.zip |
Import trimmed svn-1.8.0-rc3vendor/subversion/subversion-1.8.0-rc3
Notes
Notes:
svn path=/vendor/subversion/dist/; revision=251881
svn path=/vendor/subversion/subversion-1.8.0-rc3/; revision=251882; tag=vendor/subversion/subversion-1.8.0-rc3
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: |