aboutsummaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_wc
diff options
context:
space:
mode:
authorPeter Wemm <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
committerPeter Wemm <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
commit32547653cc5376642e1231fb644db99933ac8db4 (patch)
tree135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_wc
downloadsrc-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')
-rw-r--r--subversion/libsvn_wc/README195
-rw-r--r--subversion/libsvn_wc/adm_crawler.c1239
-rw-r--r--subversion/libsvn_wc/adm_files.c584
-rw-r--r--subversion/libsvn_wc/adm_files.h161
-rw-r--r--subversion/libsvn_wc/adm_ops.c1400
-rw-r--r--subversion/libsvn_wc/ambient_depth_filter_editor.c715
-rw-r--r--subversion/libsvn_wc/cleanup.c231
-rw-r--r--subversion/libsvn_wc/conflicts.c3141
-rw-r--r--subversion/libsvn_wc/conflicts.h443
-rw-r--r--subversion/libsvn_wc/context.c116
-rw-r--r--subversion/libsvn_wc/copy.c1048
-rw-r--r--subversion/libsvn_wc/crop.c361
-rw-r--r--subversion/libsvn_wc/delete.c508
-rw-r--r--subversion/libsvn_wc/deprecated.c4582
-rw-r--r--subversion/libsvn_wc/diff.h144
-rw-r--r--subversion/libsvn_wc/diff_editor.c2747
-rw-r--r--subversion/libsvn_wc/diff_local.c541
-rw-r--r--subversion/libsvn_wc/entries.c2738
-rw-r--r--subversion/libsvn_wc/entries.h164
-rw-r--r--subversion/libsvn_wc/externals.c1686
-rw-r--r--subversion/libsvn_wc/info.c580
-rw-r--r--subversion/libsvn_wc/lock.c1656
-rw-r--r--subversion/libsvn_wc/lock.h91
-rw-r--r--subversion/libsvn_wc/merge.c1424
-rw-r--r--subversion/libsvn_wc/node.c1418
-rw-r--r--subversion/libsvn_wc/old-and-busted.c1340
-rw-r--r--subversion/libsvn_wc/props.c2344
-rw-r--r--subversion/libsvn_wc/props.h154
-rw-r--r--subversion/libsvn_wc/questions.c621
-rw-r--r--subversion/libsvn_wc/relocate.c170
-rw-r--r--subversion/libsvn_wc/revert.c886
-rw-r--r--subversion/libsvn_wc/revision_status.c67
-rw-r--r--subversion/libsvn_wc/status.c3047
-rw-r--r--subversion/libsvn_wc/token-map.h70
-rw-r--r--subversion/libsvn_wc/translate.c452
-rw-r--r--subversion/libsvn_wc/translate.h189
-rw-r--r--subversion/libsvn_wc/tree_conflicts.c513
-rw-r--r--subversion/libsvn_wc/tree_conflicts.h93
-rw-r--r--subversion/libsvn_wc/update_editor.c5486
-rw-r--r--subversion/libsvn_wc/upgrade.c2376
-rw-r--r--subversion/libsvn_wc/util.c636
-rw-r--r--subversion/libsvn_wc/wc-checks.h55
-rw-r--r--subversion/libsvn_wc/wc-checks.sql77
-rw-r--r--subversion/libsvn_wc/wc-metadata.h516
-rw-r--r--subversion/libsvn_wc/wc-metadata.sql951
-rw-r--r--subversion/libsvn_wc/wc-queries.h3100
-rw-r--r--subversion/libsvn_wc/wc-queries.sql1693
-rw-r--r--subversion/libsvn_wc/wc.h808
-rw-r--r--subversion/libsvn_wc/wc_db.c15050
-rw-r--r--subversion/libsvn_wc/wc_db.h3413
-rw-r--r--subversion/libsvn_wc/wc_db_pristine.c925
-rw-r--r--subversion/libsvn_wc/wc_db_private.h458
-rw-r--r--subversion/libsvn_wc/wc_db_update_move.c2631
-rw-r--r--subversion/libsvn_wc/wc_db_util.c228
-rw-r--r--subversion/libsvn_wc/wc_db_wcroot.c900
-rw-r--r--subversion/libsvn_wc/wcroot_anchor.c227
-rw-r--r--subversion/libsvn_wc/workqueue.c1666
-rw-r--r--subversion/libsvn_wc/workqueue.h235
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: